Skip to content

06 - Frontend System Design

Why This Matters for Glomopay

JD says: "Build and own checkout SDKs" and "Architect scalable frontend systems." They may ask you to design a checkout SDK or dashboard architecture at a high level.


Design 1: Checkout SDK Architecture

Scenario: Build an embeddable checkout SDK that merchants integrate into their sites.

Requirements

  • Merchants embed it with a script tag or npm package
  • Supports multiple payment methods (card, UPI, wallet, netbanking)
  • Must be secure (PCI compliance — card data never touches merchant server)
  • Customizable (colors, logo, payment methods)
  • Works in any framework (React, Vue, vanilla JS)

Architecture

┌─────────────────────────────────────────────────────────┐
│  Merchant Website                                       │
│                                                         │
│  ┌──────────────────────────────────────┐               │
│  │  <iframe> (Checkout SDK)             │               │
│  │                                      │               │
│  │  ┌──────────┐  ┌──────────────────┐  │               │
│  │  │ Payment  │  │ Payment Form     │  │               │
│  │  │ Methods  │  │ (card/upi/etc)   │  │               │
│  │  │ Selector │  │                  │  │               │
│  │  └──────────┘  └──────────────────┘  │               │
│  │                                      │               │
│  │  ┌──────────────────────────────────┐│               │
│  │  │ Order Summary + Pay Button       ││               │
│  │  └──────────────────────────────────┘│               │
│  └──────────────────────────────────────┘               │
│                                                         │
│  <script src="glomopay-sdk.js"></script>                │
│  GlomoPay.open({ orderId, amount, merchantKey })        │
└─────────────────────────────────────────────────────────┘

         │  postMessage (events)

┌─────────────────────────┐
│  Glomopay Backend       │
│  - Payment processing   │
│  - Token management     │
│  - Webhook to merchant  │
└─────────────────────────┘

Key Decisions

1. iframe for security

  • Card data stays in Glomopay's iframe (PCI compliant)
  • Merchant can't access card numbers via JS
  • Communication via postMessage

2. SDK public API

ts
// Merchant-facing API — simple, minimal surface
interface GlomoPayConfig {
  merchantKey: string;
  orderId: string;
  amount: number;
  currency: string;
  customerEmail?: string;
  theme?: { primaryColor: string; logo?: string };
  paymentMethods?: PaymentMethod[];
  onSuccess: (result: PaymentResult) => void;
  onError: (error: PaymentError) => void;
  onClose: () => void;
}

// Usage
GlomoPay.open(config);
GlomoPay.close();

3. Inside the iframe (React app)

src/
  components/
    CheckoutShell.tsx        # Layout + step management
    PaymentMethodList.tsx    # Method selection
    CardForm.tsx             # Card input (tokenized)
    UPIForm.tsx              # UPI input
    OrderSummary.tsx         # Review step
  context/
    CheckoutContext.tsx       # State: step, method, amount
  services/
    PaymentService.ts        # API calls to Glomopay backend
    PostMessageBridge.ts     # Communication with parent window
  types/
    index.ts                 # Shared types

4. postMessage protocol

ts
// SDK → Merchant
type SDKEvent =
  | { type: 'GLOMOPAY_READY' }
  | { type: 'GLOMOPAY_SUCCESS'; payload: PaymentResult }
  | { type: 'GLOMOPAY_ERROR'; payload: PaymentError }
  | { type: 'GLOMOPAY_CLOSE' }
  | { type: 'GLOMOPAY_RESIZE'; height: number };

// Merchant → SDK
type MerchantEvent =
  | { type: 'GLOMOPAY_INIT'; config: GlomoPayConfig }
  | { type: 'GLOMOPAY_CLOSE' };

Design 2: Merchant Dashboard

Scenario: Real-time dashboard showing transactions, settlements, refunds.

Architecture

┌────────────────────────────────────────────────┐
│  Dashboard App (Next.js)                       │
│                                                │
│  ┌──────────┐  ┌───────────────────────────┐   │
│  │ Sidebar  │  │ Main Content              │   │
│  │          │  │                           │   │
│  │ Overview │  │ ┌───────────────────────┐ │   │
│  │ Txns     │  │ │ Filters + Date Range  │ │   │
│  │ Settle   │  │ └───────────────────────┘ │   │
│  │ Refunds  │  │ ┌───────────────────────┐ │   │
│  │ Settings │  │ │ Stats Cards (KPIs)    │ │   │
│  │          │  │ └───────────────────────┘ │   │
│  │          │  │ ┌───────────────────────┐ │   │
│  │          │  │ │ Chart (volume/trend)  │ │   │
│  │          │  │ └───────────────────────┘ │   │
│  │          │  │ ┌───────────────────────┐ │   │
│  │          │  │ │ Transaction Table     │ │   │
│  │          │  │ │ (virtualized, 10k+)   │ │   │
│  │          │  │ └───────────────────────┘ │   │
│  └──────────┘  └───────────────────────────┘   │
└────────────────────────────────────────────────┘

State Architecture

Server State (TanStack Query):
  - transactions (paginated, filterable)
  - settlements
  - dashboard stats
  - user/merchant profile

Client State (Zustand):
  - sidebar collapsed/expanded
  - active filters
  - selected date range
  - table sort/column config

Form State (React Hook Form):
  - refund form
  - settings form
  - filter form

Key Patterns

1. Optimistic updates for refunds

tsx
const refundMutation = useMutation({
  mutationFn: api.createRefund,
  onMutate: async (refundData) => {
    await queryClient.cancelQueries({ queryKey: ['transactions'] });
    const previous = queryClient.getQueryData(['transactions']);

    // Optimistically update
    queryClient.setQueryData(['transactions'], (old) =>
      old.map(tx =>
        tx.id === refundData.transactionId
          ? { ...tx, status: 'refund_pending' }
          : tx
      )
    );

    return { previous };
  },
  onError: (err, data, context) => {
    queryClient.setQueryData(['transactions'], context.previous); // rollback
  },
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['transactions'] });
  },
});

2. Real-time updates via WebSocket

tsx
function useRealtimeTransactions() {
  const queryClient = useQueryClient();

  useEffect(() => {
    const ws = new WebSocket(WS_URL);

    ws.onmessage = (event) => {
      const update = JSON.parse(event.data);

      if (update.type === 'NEW_TRANSACTION') {
        queryClient.setQueryData(['transactions'], (old) => [update.data, ...old]);
      }

      if (update.type === 'STATUS_UPDATE') {
        queryClient.setQueryData(['transactions'], (old) =>
          old.map(tx => tx.id === update.data.id ? { ...tx, ...update.data } : tx)
        );
      }
    };

    return () => ws.close();
  }, [queryClient]);
}

3. Virtualized table for performance

  • 10k+ transactions → virtualize with @tanstack/react-virtual
  • Server-side pagination for initial load
  • Client-side sort/filter for current page

Design 3: Design System (Component Library)

If asked about this:

Structure:
  packages/
    ui/                    # Core components
      src/
        Button/
          Button.tsx
          Button.test.tsx
          Button.stories.tsx  # Storybook
          index.ts
        Input/
        Card/
        Modal/
        ...
      package.json

    tokens/                # Design tokens
      colors.ts
      spacing.ts
      typography.ts

    icons/                 # Icon components
      src/
        generated/         # Auto-generated from SVGs

Key decisions:
  - Tailwind for styling (utility-first, no runtime cost)
  - Compound component pattern for complex components
  - Storybook for documentation and visual testing
  - Changesets for versioning
  - Turbo Repo for monorepo build orchestration

Questions They Might Ask

Q: How would you version a checkout SDK? A: Semantic versioning. Breaking changes in major. SDK loaded via versioned URL (/v1/checkout.js). Support N-1 version for migration period.

Q: How do you handle SDK errors gracefully? A: Never crash the merchant's page. Wrap everything in error boundaries. Failed payment → show error in SDK UI, emit error event to merchant. Network failure → retry with exponential backoff.

Q: How would you handle multiple currencies? A: Intl.NumberFormat for display. Amount always stored in smallest unit (paise/cents). Currency config from backend per merchant. Never do floating point arithmetic on money.

Frontend interview preparation reference.