# Midtrans CIFO Frontend Architecture Document

## Change Log
| Date       | Version | Description                            | Author |
|------------|---------|----------------------------------------|--------|
| 2025-11-07 | v1      | Initial frontend architecture scaffolding | Architect (Winston) |

## 1. Template and Framework Selection

Context & Decision
- Basis dokumen: `docs/prd.md` (UI multi-metode pembayaran: VA, QRIS, Kartu) dan belum ada dokumen arsitektur utama (`docs/architecture.md`).
- Lingkungan proyek: Laragon (PHP) untuk backend; frontend akan diatur sebagai aplikasi SPA terpisah yang berkomunikasi via REST.
- Starter pilihan: Vite + React + TypeScript.

Rationale
- Vite + React memberikan setup cepat, build cepat, dan ekosistem luas. TypeScript membantu kualitas kode meski QA dilakukan manual (type-safety, DX lebih baik).
- React Router memadai untuk halaman Checkout, Status, dan Riwayat; TanStack Query menangani data fetching, status, cache, dan retry/polling untuk status transaksi.
- Tailwind CSS mempercepat UI dengan gaya konsisten, aksesibilitas lebih terukur, dan footprint tooling rendah.
- Tidak memilih Next.js karena kebutuhan SSR tidak kritikal untuk halaman checkout sederhana, dan menghindari kompleksitas deploy.

Konsekuensi
- Manual QA only: tidak ada pipeline test otomatis; struktur proyek dan standar coding harus tegas untuk menghindari regressi.
- Build/deploy dilakukan manual; dokumentasi command harus jelas (dev/build). Observability di UI via logging ringan & event tracking internal.

## 2. Frontend Tech Stack (Draft untuk Validasi)

| Category            | Technology              | Version | Purpose                                        | Rationale                                                                 |
|---------------------|-------------------------|---------|------------------------------------------------|---------------------------------------------------------------------------|
| Framework           | React                   | ^18     | SPA untuk checkout dan status transaksi        | Ekosistem luas, composable UI, dukungan community yang kuat               |
| UI Library          | Tailwind CSS            | ^3      | Styling utilitas cepat                         | Cepat, konsisten, mudah diadopsi, cocok untuk MVP                         |
| State Management    | TanStack Query          | ^5      | Server state (fetch/poll/retry/cache)          | Ideal untuk status transaksi & polling; minimal boilerplate               |
| Routing             | React Router            | ^6      | Navigasi halaman                               | Pola rute sederhana, proteksi rute mudah                                  |
| Build Tool          | Vite                    | ^5      | Dev/build tooling                              | Startup cepat, konfigurasi sederhana                                      |
| Styling             | Tailwind + CSS Modules  | ^3      | Styling kombinasi utilitas + modular           | Fleksibel; module untuk komponen kompleks                                 |
| Testing             | Manual QA               | N/A     | Pengujian manual                               | Sesuai kebijakan proyek (tanpa CI/CD); template test dapat ditambahkan    |
| Component Library   | Headless UI / Shadcn    | latest  | Komponen aksesibel minimal                     | Mempercepat pembuatan form/overlay; tetap fleksibel                       |
| Form Handling       | React Hook Form         | ^7      | Validasi & kontrol form                        | Ringan, ergonomis, integrasi baik dengan TS                               |
| Animation           | Framer Motion           | ^11     | Mikro-animasi & transisi                       | Peningkatan UX tanpa kompleksitas berlebih                                |
| Dev Tools           | ESLint + Prettier       | latest  | Konsistensi & kualitas kode                    | Membantu kualitas saat QA manual; cegah kesalahan umum                    |

Rationale Tambahan
- TanStack Query mempermudah implementasi polling status transaksi (fallback ketika webhook belum tiba) dan retry terukur sesuai PRD.
- React Hook Form memudahkan validasi input kartu (tokenisasi) dan interaksi form minimal.
- Headless UI/Shadcn memberi komponen aksesibel untuk dialog/menus tanpa mengikat ke desain tertentu.

Elicit Options (1–9)
1. Proceed ke bagian berikutnya (Project Structure)
2. Risk & Challenge Analysis untuk pilihan stack
3. Structural Analysis (komponen vs halaman vs services)
4. Devil’s Advocate (tantangan stack ini untuk manual QA)
5. Multi-Persona (PO/SM/Dev/QA) feedback cepat
6. Creative Exploration (alternatif: Vue/Nuxt atau Angular)
7. 2025 Techniques (SSR-lite/Partial Hydration pertimbangan)
8. Process Control (tandai asumsi dan batasan eksplisit)
9. Reflective Review (cek keselarasan dengan PRD Epics 1–3)

Select 1–9 or just type your question/feedback:

## 7. Routing

Tujuan: mendefinisikan peta rute, alur navigasi Checkout → Status → Riwayat, serta praktik React Router v6 (lazy loading, error boundaries, guard ringan).

### 7.1 Route Map

- `/checkout` — halaman utama untuk memilih metode (VA, QRIS/GoPay, Card, Cstore) dan memulai charge.
- `/payments/:orderId/status` — halaman status transaksi real‑time (polling/sse) untuk semua metode.
- `/history` — riwayat transaksi (read-only, filter/sort sederhana).
- Opsional (Card 3DS): `/checkout/card/challenge` — halaman penanganan challenge bila diperlukan; atau gunakan `redirect_url` eksternal.
- Fallback: `*` — NotFound.

### 7.2 Konfigurasi React Router (v6)

```tsx
// src/app/router.tsx
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom';
import { AppLayout } from './AppLayout';
import { CheckoutPage } from '../pages/CheckoutPage';
import { PaymentStatusPage } from '../pages/PaymentStatusPage';
import { PaymentHistoryPage } from '../pages/PaymentHistoryPage';
import { NotFoundPage } from '../pages/NotFoundPage';

const router = createBrowserRouter([
  {
    path: '/',
    element: <AppLayout />,
    errorElement: <div role="alert">Terjadi kesalahan. Coba muat ulang.</div>,
    children: [
      { index: true, element: <Navigate to="/checkout" replace /> },
      { path: 'checkout', element: <CheckoutPage /> },
      { path: 'payments/:orderId/status', element: <PaymentStatusPage /> },
      { path: 'history', element: <PaymentHistoryPage /> },
      { path: '*', element: <NotFoundPage /> },
    ],
  },
]);

export function AppRouter() {
  return <RouterProvider router={router} />;
}
```

Catatan:
- Gunakan lazy loading untuk halaman berat (mis. `PaymentHistoryPage`) guna mempercepat initial load checkout.
- Tambahkan `<ScrollRestoration />` di layout bila perlu.

### 7.3 Alur Navigasi

- Dari `/checkout`, setelah `charge` sukses (menerima `order_id`), arahkan ke `/payments/:orderId/status`.
- Simpan `payment_type` di `location.state` atau query (contoh: `?type=gopay`) bila diperlukan untuk UI hint.
- Terminal state:
  - `settlement`: tampilkan success dan CTA kembali ke `/history`.
  - `expire`/`cancel`/`deny`: tampilkan informasi dan CTA ulang di `/checkout`.
- Card (3DS): bila response berisi `redirect_url`, buka di jendela/tab baru atau navigasi ke `/checkout/card/challenge` yang menangani proses lalu kembali ke `/payments/:orderId/status`.

Contoh helper navigasi:

```ts
// src/features/payments/lib/navigation.ts
import { useNavigate } from 'react-router-dom';

export function usePaymentNavigation() {
  const navigate = useNavigate();
  return {
    toStatus(orderId: string, paymentType?: string) {
      navigate(`/payments/${orderId}/status`, {
        state: paymentType ? { paymentType } : undefined,
        replace: true,
      });
    },
    toHistory() {
      navigate('/history');
    },
    toCheckout() {
      navigate('/checkout');
    },
  };
}
```

### 7.4 Guards & Recovery

- Validasi akses langsung ke `/payments/:orderId/status`:
  - Query `getPaymentStatus(orderId)` — jika 404/invalid, tampilkan NotFound atau ajak kembali ke `/checkout`.
- Lindungi `/history` dengan batasan sederhana (misal hanya read-only; tidak memerlukan auth jika PRD tidak mensyaratkan).
- Toleransi refresh: halaman status dapat dibuka ulang langsung selama `order_id` valid.

### 7.5 Status Updates (Polling / Push)

- Default: polling 3 detik menggunakan TanStack Query seperti di bagian State Management.
- Opsi 2025: SSE/WebSocket untuk push status agar real‑time dan hemat jaringan; fallback ke polling saat koneksi tidak stabil.

### 7.6 Aksesibilitas & Analytics

- Announce perubahan status dengan ARIA live region (mis. `role="status"`, `aria-live="polite"`).
- Catat event navigasi (route change) beserta `X-Correlation-Id` untuk tracing ringan di UI.

---

Elicit Options (Routing → Next)
1. Lanjut ke Styling (Tailwind arsitektur dan utility patterns)
2. Tambah Testing (rencana manual QA dan template kasus uji)
3. Environment Config (env vars & build/dev commands)
4. Developer Standards (lint, format, commit hygiene)
5. Error UX polish (banner, retry, deep link handling)
6. Performance (lazy routes, code-splitting, prefetch)

## 8. Styling

Tujuan: menetapkan arsitektur styling yang konsisten dan dapat dipelihara menggunakan Tailwind CSS (utilitas) dan CSS Modules (gaya terisolasi), termasuk varian komponen, responsif, dark mode, dan a11y visual.

### 8.1 Setup Tailwind (config + globals)

Konfigurasi dasar Tailwind dengan dark mode berbasis class dan plugin formulir/typografi.

```ts
// tailwind.config.ts
import type { Config } from 'tailwindcss';

export default {
  content: ['./index.html', './src/**/*.{ts,tsx}'],
  darkMode: 'class',
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#fef2f2',
          100: '#fee2e2',
          200: '#fecaca',
          300: '#fca5a5',
          400: '#f87171',
          500: '#ef4444',
          600: '#dc2626', // merah utama
          700: '#b91c1c',
          800: '#991b1b',
          900: '#7f1d1d',
        },
      },
      boxShadow: {
        focus: '0 0 0 3px rgba(220,38,38,0.45)',
      },
    },
  },
  plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')],
} satisfies Config;
```

Global stylesheet minimal: reset, font, dan token CSS (opsional) untuk warna.

```css
/* src/styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --radius: 8px;
}

@layer base {
  html, body, #root { height: 100%; }
  body { @apply antialiased bg-white text-black; }
  .dark body { @apply bg-black text-white; }
  a { @apply text-brand-600 hover:text-brand-700; }
}

@layer utilities {
  .focus-ring { @apply outline-none ring-2 ring-brand-600 ring-offset-2 ring-offset-white dark:ring-offset-black; }
  .card { @apply rounded-[var(--radius)] border border-black/10 bg-white shadow-sm dark:bg-black dark:border-white/20; }
}
```

### 8.2 Pola Utilitas & Komposisi

- Gunakan utilitas untuk layout: `flex`, `grid`, `gap`, `container`, `max-w-*`, `space-*`.
- Terapkan skala spacing/typography konsisten: `text-sm/base/lg`, `p-2/3/4`, `gap-2/3/4`.
- Komposisi class dinamis via `clsx`/`cn` helper.

```ts
// src/lib/cn.ts
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: Array<string | Record<string, boolean> | undefined>) {
  return twMerge(clsx(inputs));
}
```

Contoh penggunaan:

```tsx
// src/components/Section.tsx
import { cn } from '../lib/cn';
export function Section(props: { title: string; className?: string; children: React.ReactNode }) {
  return (
    <section className={cn('mb-6', props.className)}>
      <h2 className="text-lg font-semibold mb-2">{props.title}</h2>
      {props.children}
    </section>
  );
}
```

### 8.3 Varian Komponen dengan CVA (opsional)

Gunakan `class-variance-authority` untuk varian yang dapat dikontrol tanpa membengkakkan class string.

```ts
// src/components/ui/button.tsx
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '../../lib/cn';

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:focus-ring disabled:opacity-50 disabled:pointer-events-none',
  {
    variants: {
      variant: {
        primary: 'bg-brand-600 text-white hover:bg-brand-700',
        secondary: 'bg-white text-black hover:bg-white/90 dark:bg-black dark:text-white dark:hover:bg-black/90',
        outline: 'border border-black text-black hover:bg-black/5 dark:border-white dark:text-white dark:hover:bg-white/10',
      },
      size: { sm: 'h-8 px-3', md: 'h-10 px-4', lg: 'h-11 px-6' },
    },
    defaultVariants: { variant: 'primary', size: 'md' },
  }
);

export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {}

export function Button({ className, variant, size, ...props }: ButtonProps) {
  return <button className={cn(buttonVariants({ variant, size }), className)} {...props} />;
}
```

### 8.4 CSS Modules untuk Gaya Terkapsulasi

Gunakan CSS Modules untuk komponen kompleks yang membutuhkan selector stateful atau animasi khusus sambil memanfaatkan `@apply` Tailwind.

```css
/* src/components/alert/Alert.module.css */
.root { @apply card p-4 flex gap-3; }
.icon { @apply text-brand-600; }
.title { @apply font-semibold; }
.desc { @apply text-sm text-slate-600 dark:text-slate-300; }
```

```tsx
// src/components/alert/Alert.tsx
import s from './Alert.module.css';
export function Alert({ title, children }: { title: string; children?: React.ReactNode }) {
  return (
    <div className={s.root} role="status" aria-live="polite">
      <div className={s.icon}>⚑</div>
      <div>
        <div className={s.title}>{title}</div>
        {children ? <div className={s.desc}>{children}</div> : null}
      </div>
    </div>
  );
}
```

### 8.5 Responsif & Dark Mode

- Breakpoint standar: `sm`, `md`, `lg`, `xl`, `2xl`; gunakan progressif (mobile-first) dan hindari override berlebihan.
- Dark mode via class `dark` di tag `html`/`body`; toggling dilakukan oleh provider tema UI (lihat State Management bagian UI-store).
- Gambar/ikon: pilih versi gelap/terang atau gunakan `opacity`/`mix-blend` untuk adaptasi.

### 8.6 Aksesibilitas Visual

- Fokus terlihat: gunakan `.focus-ring` dan `focus-visible` agar keyboard navigation jelas.
- Kontras warna memenuhi WCAG AA; warna brand 600+ untuk tombol utama, teks tetap terbaca di dark mode.
- ARIA untuk komponen interaktif (dialog, toast, status) sesuai rekomendasi Headless UI/Shadcn.

### 8.7 Performansi & Maintainability

- Hindari class string sangat panjang—ekstrak ke komponen kecil atau helper varian.
- Gunakan `prettier-plugin-tailwindcss` untuk pengurutan class otomatis (opsional, non-blocking).
- Pastikan purge/content paths tepat agar ukuran CSS hasil build tetap kecil.

---

Elicit Options (Styling → Next)
1. Testing (kasus uji manual dan template skenario)
2. Environment Config (env vars, build/dev command)
3. Developer Standards (lint/format/commit hygiene)
4. Error UX polish (banner, retry, deep link)
5. Performance (code-splitting, prefetch assets)
6. Theming tokens (CSS vars dan skala desain)

## 6. API Integration

Tujuan: mendefinisikan batas frontend–backend, kontrak endpoint REST, tipe respons yang relevan untuk UI, dan layanan frontend untuk VA (Bank Transfer), Card (3DS), E-Wallet/QRIS (GoPay), serta Over the Counter (cstore). Sesuai kebijakan keamanan, semua `charge` dilakukan di backend (Server Key).

### 6.1 Backend Endpoints (Kontrak)

- `POST /api/payments/charge`
  - Body (umum):
    - `payment_type`: `'bank_transfer' | 'permata' | 'echannel' | 'credit_card' | 'gopay' | 'cstore'`
    - `transaction_details`: `{ gross_amount: number, order_id?: string }`
    - Parameter metode:
      - Bank Transfer (VA): `{ bank_transfer: { bank: 'bca' | 'bni' | 'bri' | 'cimb' } }`
      - Permata VA: `payment_type: 'permata'` (tanpa `bank_transfer` body)
      - Mandiri Bill Payment (E-Channel): `payment_type: 'echannel'`, `echannel: { bill_info1?: string, bill_info2?: string }`
      - Card: `{ credit_card: { token_id: string, authentication: true } }` (token diambil di frontend via 3DS script)
      - E-Wallet/QRIS: `{ gopay: { enable_callback?: boolean, callback_url?: string } }`
      - Cstore: `{ cstore: { store: 'alfamart' | 'indomaret', message?: string, ... } }`
  - Response (disederhanakan untuk UI):
    - Bank Transfer: `{ order_id, payment_type:'bank_transfer', bank, va_number, status:'pending', expiry_time }` (backend menormalkan `va_numbers` menjadi satu `va_number`)
    - Permata VA: `{ order_id, payment_type:'permata', va_number, status:'pending', expiry_time }` (backend menormalkan `permata_va_number` → `va_number`)
    - Mandiri E-Channel: `{ order_id, payment_type:'echannel', bill_key, biller_code, status:'pending', expiry_time }`
    - Card: `{ order_id, payment_type:'credit_card', status:'pending' | 'challenge' | 'capture' | 'settlement', redirect_url? }`
    - QRIS/GoPay: `{ order_id, payment_type:'gopay', qr_url, deeplink_url?, expiry_time, status:'pending' }`
    - Cstore: `{ order_id, payment_type:'cstore', payment_code, store, status:'pending', expiry_time }`
- `GET /api/payments/:orderId/status`
  - Response: `{ order_id, payment_type, status: 'pending' | 'settlement' | 'deny' | 'cancel' | 'expire', transaction_time?, gross_amount?, fraud_status? }`
- `POST /api/payments/:orderId/cancel` (opsional; sebelum settlement)
- `POST /api/payments/:orderId/refund` (opsional; admin flow)
- `POST /api/payments/webhook` (backend only) — UI tidak memanggil; status dipush ke DB, lalu UI membaca via endpoint `status`.

Catatan keamanan:
- Server Key hanya di backend; frontend memakai Client Key untuk 3DS script.
- Backend memvalidasi `order_id` untuk idempotensi; frontend boleh mengirim atau backend dapat membuatkan.
- Header standar: `X-Correlation-Id` untuk tracing.

### 6.2 Frontend Services (contracts)

`services/apiClient.ts` — klien HTTP berbasis Axios (atau fetch) dengan base URL dari `VITE_API_BASE_URL`.

```ts
// src/features/payments/services/apiClient.ts
import axios from 'axios';

const apiBase = import.meta.env.VITE_API_BASE_URL;

export const apiClient = axios.create({ baseURL: apiBase });

apiClient.interceptors.request.use((config) => {
  config.headers = {
    ...config.headers,
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'X-Correlation-Id': crypto?.randomUUID?.() ?? String(Date.now()),
  };
  return config;
});
```

`services/paymentsService.ts` — fungsi-fungsi terpisah per metode.

```ts
// src/features/payments/services/paymentsService.ts
import { apiClient } from './apiClient';

export type PaymentMethod = 'bank_transfer' | 'credit_card' | 'gopay' | 'cstore';
export type PaymentStatus = 'pending' | 'challenge' | 'capture' | 'settlement' | 'deny' | 'cancel' | 'expire';

export async function chargeVA(bank: 'bca' | 'bni' | 'bri' | 'cimb') {
  const res = await apiClient.post('/payments/charge', {
    payment_type: 'bank_transfer',
    bank_transfer: { bank },
  });
  return res.data as {
    order_id: string;
    payment_type: 'bank_transfer';
    bank: string;
    va_number: string;
    status: PaymentStatus;
    expiry_time?: string;
  };
}

export async function chargePermataVA() {
  const res = await apiClient.post('/payments/charge', {
    payment_type: 'permata',
  });
  return res.data as {
    order_id: string;
    payment_type: 'permata';
    va_number: string; // normalized from permata_va_number
    status: PaymentStatus;
    expiry_time?: string;
  };
}

export async function chargeMandiriEChannel(billInfo?: { bill_info1?: string; bill_info2?: string }) {
  const res = await apiClient.post('/payments/charge', {
    payment_type: 'echannel',
    echannel: billInfo ?? {},
  });
  return res.data as {
    order_id: string;
    payment_type: 'echannel';
    bill_key: string;
    biller_code: string;
    status: PaymentStatus;
    expiry_time?: string;
  };
}

export async function chargeCard(tokenId: string, authentication = true) {
  const res = await apiClient.post('/payments/charge', {
    payment_type: 'credit_card',
    credit_card: { token_id: tokenId, authentication },
  });
  return res.data as {
    order_id: string;
    payment_type: 'credit_card';
    status: PaymentStatus;
    redirect_url?: string;
  };
}

export async function chargeQRIS(options?: { enable_callback?: boolean; callback_url?: string }) {
  const res = await apiClient.post('/payments/charge', {
    payment_type: 'gopay',
    gopay: options ?? {},
  });
  return res.data as {
    order_id: string;
    payment_type: 'gopay';
    qr_url: string;
    deeplink_url?: string;
    status: PaymentStatus;
    expiry_time?: string;
  };
}

export async function chargeCstore(store: 'alfamart' | 'indomaret', message?: string) {
  const res = await apiClient.post('/payments/charge', {
    payment_type: 'cstore',
    cstore: { store, message },
  });
  return res.data as {
    order_id: string;
    payment_type: 'cstore';
    payment_code: string;
    store: string;
    status: PaymentStatus;
    expiry_time?: string;
  };
}

export async function getPaymentStatus(orderId: string) {
  const res = await apiClient.get(`/payments/${orderId}/status`);
  return res.data as {
    order_id: string;
    payment_type: PaymentMethod;
    status: PaymentStatus;
    transaction_time?: string;
    gross_amount?: number;
    fraud_status?: string;
  };
}
```

### 6.3 Env Vars & Script Loader (Card 3DS)

- Frontend `.env.example`:
  - `VITE_API_BASE_URL=https://localhost:8000/api`
  - `VITE_MIDTRANS_CLIENT_KEY=YOUR_CLIENT_KEY`
  - `VITE_MIDTRANS_ENV=sandbox` (atau `production`)
- Komponen loader script 3DS:

```tsx
// src/components/ScriptLoader.tsx
import { useEffect } from 'react';

export function Midtrans3DSLoader() {
  useEffect(() => {
    const script = document.createElement('script');
    script.id = 'midtrans-script';
    script.type = 'text/javascript';
    script.src = 'https://api.midtrans.com/v2/assets/js/midtrans-new-3ds.min.js';
    script.setAttribute('data-environment', import.meta.env.VITE_MIDTRANS_ENV);
    script.setAttribute('data-client-key', import.meta.env.VITE_MIDTRANS_CLIENT_KEY);
    document.body.appendChild(script);
    return () => {
      document.body.removeChild(script);
    };
  }, []);
  return null;
}
```

Penggunaan: Render `Midtrans3DSLoader` di `CheckoutPage` atau `App` saat flow kartu diperlukan.

### 6.4 Error Contract & Mapping

- Standar error dari backend:
  - 400: `VALIDATION_ERROR` (contoh: kartu gagal Luhn, parameter salah)
  - 401/403: `AUTH_ERROR`
  - 409: `ORDER_CONFLICT` (duplikasi `order_id`)
  - 5xx: `SERVER_ERROR`
- UI menampilkan pesan ramah di `ErrorBanner` dan menyediakan CTA `Retry`, `Back`, `Contact Support` sesuai konteks.
- Logging ringan di `lib/logger.ts` mencatat `X-Correlation-Id` untuk pelacakan.

### 6.5 Alur per Metode (Ringkas)

- VA:
  - `chargeVA(bank)` → tampilkan `va_number` + instruksi; arahkan ke `PaymentStatus`; polling hingga `settlement/expire`.
- Card (3DS):
  - Muat 3DS script → `MidtransNew3ds.getCardToken(...)` → kirim `token_id` ke backend via `chargeCard(token)` → arahkan ke `PaymentStatus`; status bisa `challenge` sebelum `settlement`.
- QRIS/GoPay:
  - `chargeQRIS()` → tampilkan `qr_url` (atau deeplink untuk mobile) + countdown; arahkan ke `PaymentStatus`; polling status.
- Cstore:
  - `chargeCstore(store)` → tampilkan `payment_code` + instruksi; arahkan ke `PaymentStatus`; tunggu konfirmasi.

### 6.6 Boundary & Keamanan

- Tidak ada Server Key di frontend; semua request ke Midtrans dari backend.
- Client Key hanya untuk 3DS tokenization; token_id berlaku satu transaksi.
- Hindari menampilkan data sensitif; UI menyimpan minimal (status & indikator UX).

### 6.7 Schema Compatibility Notes (Midtrans Core API)

Tujuan: memastikan kontrak UI selaras dengan skema resmi Midtrans dan menjelaskan pemetaan/normalisasi yang dilakukan backend agar UI tetap sederhana.

- Bank Transfer (VA)
  - Request resmi: `payment_type: "bank_transfer"`, `transaction_details.{order_id,gross_amount}`, `bank_transfer.{bank}` untuk BCA/BNI/BRI/CIMB [0].
  - Response variasi: `va_numbers: [{ bank, va_number }]` (multi-bank), disederhanakan backend menjadi satu `va_number` untuk UI.
  - Permata: gunakan `payment_type: "permata"` (bukan `bank_transfer`); response field `permata_va_number` dinormalisasi menjadi `va_number` [0].
  - Mandiri Bill Payment (E-Channel): gunakan `payment_type: "echannel"` dengan `echannel.{bill_info1,bill_info2}`; response berisi `bill_key` dan `biller_code` [0].

- Card (3DS)
  - Frontend mengambil `token_id` via `midtrans-new-3ds` script (Client Key) [1].
  - Backend Charge: `payment_type: "credit_card"`, `credit_card.{ token_id, authentication }` [1].
  - Response dapat mengandung `redirect_url` (untuk challenge) dan `transaction_status` seperti `pending`, `challenge`, `capture`/`settlement`; UI menambahkan dukungan `capture` [1].

- E-Wallet/QRIS (GoPay)
  - Request: `payment_type: "gopay"`; optional `gopay.{ enable_callback, callback_url }` [2].
  - Response: `actions` (QR URL untuk desktop, deeplink untuk mobile) dan `transaction_status: pending`; backend menormalkan ke `qr_url` dan `deeplink_url` untuk konsumsi UI [2].

- Over the Counter (cstore)
  - Request: `payment_type: "cstore"`, `cstore.{ store, message, alfamart_free_text_1..3? }` [3].
  - Response: `payment_code`, `store`, `transaction_status: pending` [3].

- Field umum yang dapat di-pass-through untuk debugging: `status_code`, `status_message`, `transaction_id`, `order_id`, `gross_amount`, `payment_type`, `transaction_time`, `fraud_status`.

Catatan implementasi:
- Backend bertindak sebagai adapter yang menormalisasi variasi field antar metode/bank agar UI konsumsi konsisten.
- Jika diperlukan strictness penuh, UI dapat memakai nama field resmi (`transaction_status`, `va_numbers`, `actions`) dan merender sesuai variasi.

---

Elicit Options (1–9)
1. Proceed ke Routing (navigasi Checkout → Status → Riwayat)
2. Risk & Challenge (kontrak API dan dampak QA manual)
3. Structural Analysis (abstraksi services dan tipe respons)
4. Devil’s Advocate (apakah satu endpoint `charge` terlalu generik?)
5. Multi-Persona (PO/SM/Dev/QA sudut pandang)
6. Creative Exploration (layer adapter untuk future payment methods)
7. 2025 Techniques (SSE/WebSocket untuk status push)
8. Process Control (idempotensi & correlation id praktik)
9. Reflective Review (keselarasan dengan PRD epics & Midtrans docs)

Select 1–9 or just type your question/feedback:

## 5. State Management

Tujuan: memisahkan dengan jelas server-state (status transaksi, hasil charge) dan UI-state (pilihan metode, dialog), menerapkan polling yang aman, dan konsisten dengan PRD (webhook + fallback polling).

### 5.1 Server-State dengan TanStack Query

- Query Client Defaults (di `providers/QueryProvider.tsx`):
  - `refetchOnWindowFocus: false` (hindari refetch tak terduga saat QA manual).
  - `retry(failCount, error)`: maksimal 3 kali untuk error jaringan/transient; mapping error 4xx jadi non-retry.
  - `staleTime`: 3–5 detik untuk status transaksi; 0 untuk operasi lain.
- Query Keys Standar:
  - `['payments','status', orderId]` untuk status transaksi.
  - `['payments','details', orderId]` untuk detail transaksi.
  - Mutations: `['payments','charge', method]` untuk pembuatan transaksi.
- Invalidation & Lifecycle:
  - Setelah `charge` sukses, arahkan ke `PaymentStatus` dan mulai polling `status`.
  - Hentikan polling saat mencapai terminal state (`settlement`, `deny`, `cancel`, `expire`).
  - Invalidate status jika webhook backend mengubah status (opsional: gunakan indikator versi/timestamp dari backend).

Contoh hook status dengan polling kondisional:

```ts
// features/payments/hooks/usePaymentStatus.ts
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '../services/apiClient';

const TERMINAL = new Set(['settlement', 'deny', 'cancel', 'expire']);

export function usePaymentStatus(orderId: string) {
  return useQuery({
    queryKey: ['payments', 'status', orderId],
    queryFn: async () => {
      const res = await apiClient.get(`/payments/${orderId}/status`);
      return res.data; // { status: 'pending' | 'settlement' | ... }
    },
    refetchInterval: (data) => {
      const status = data?.status;
      return status && TERMINAL.has(status) ? false : 3000; // 3s polling
    },
    retry: (failCount, error: any) => {
      const is4xx = error?.response?.status && String(error.response.status).startsWith('4');
      if (is4xx) return false; // user error, jangan retry
      return failCount < 3;
    },
    refetchOnWindowFocus: false,
  });
}
```

Catatan:
- Status mengikuti pola Midtrans (contoh umum: `pending`, `settlement`, `deny`, `cancel`, `expire`).
- Untuk kartu, status dapat melalui tahapan otentikasi (3DS) sebelum menjadi `settlement`.

### 5.2 UI-State dengan Store Tipis

Ruang lingkup UI-state: pilihan metode, visibilitas modal/dialog, notifikasi/UI flags (misal QR countdown). Simpan terpisah dari server-state.

Contoh store sederhana berbasis Context + Reducer:

```ts
// features/payments/store/uiStore.ts
import React, { createContext, useContext, useReducer } from 'react';

type PaymentMethod = 'VA' | 'QRIS' | 'Card' | 'Cstore' | null;

type UIState = {
  selectedMethod: PaymentMethod;
  isModalOpen: boolean;
  toast: { type: 'info' | 'error' | 'success'; message: string } | null;
};

type Action =
  | { type: 'SET_METHOD'; method: PaymentMethod }
  | { type: 'OPEN_MODAL' }
  | { type: 'CLOSE_MODAL' }
  | { type: 'SHOW_TOAST'; toast: UIState['toast'] }
  | { type: 'CLEAR_TOAST' };

const initialState: UIState = {
  selectedMethod: null,
  isModalOpen: false,
  toast: null,
};

function reducer(state: UIState, action: Action): UIState {
  switch (action.type) {
    case 'SET_METHOD':
      return { ...state, selectedMethod: action.method };
    case 'OPEN_MODAL':
      return { ...state, isModalOpen: true };
    case 'CLOSE_MODAL':
      return { ...state, isModalOpen: false };
    case 'SHOW_TOAST':
      return { ...state, toast: action.toast };
    case 'CLEAR_TOAST':
      return { ...state, toast: null };
    default:
      return state;
  }
}

const UIContext = createContext<{ state: UIState; dispatch: React.Dispatch<Action> } | undefined>(
  undefined,
);

export function UIProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return <UIContext.Provider value={{ state, dispatch }}>{children}</UIContext.Provider>;
}

export function useUI() {
  const ctx = useContext(UIContext);
  if (!ctx) throw new Error('useUI must be used within UIProvider');
  return ctx;
}
```

Prinsip:
- UI-state tidak menyimpan data transaksi dari server; gunakan Query untuk itu.
- Reset UI-state saat berganti rute (contoh: dari Checkout ke PaymentStatus).
- Hindari global state berlebihan; scope-kan per feature bila perlu.

### 5.3 Error Handling & Logging

- Mapping error backend ke pesan user-friendly di `features/payments/components/ErrorBanner.tsx`.
- Log teknis (correlation id, status) di `lib/logger.ts`; hindari menyimpan data sensitif.
- Tampilkan CTA standar: `Retry`, `Back`, atau `Contact Support` sesuai konteks.

### 5.4 Kontrak API & Mutations

- Semua `charge` dipanggil via backend (`/api/payments/charge`) dengan `payment_type` dan parameter metode (VA/QRIS/Card/Cstore). Server Key tetap di backend.
- Untuk kartu: frontend mengambil `token_id` via script 3DS, mengirim ke backend untuk `charge` dengan `authentication: true` bila 3DS diaktifkan.
- Mutations mengembalikan `order_id` atau detail awal; UI lalu menavigasi ke `PaymentStatus` dan memulai polling.

---

Elicit Options (1–9)
1. Proceed ke API Integration (kontrak endpoints & services)
2. Risk & Challenge (ketahanan polling dan dampak UX)
3. Structural Analysis (batas UI-state vs server-state)
4. Devil’s Advocate (apakah store terpisah perlu diganti Context-only?)
5. Multi-Persona (PO/SM/Dev/QA sudut pandang)
6. Creative Exploration (push vs polling: SSE/WebSocket pros/cons)
7. 2025 Techniques (background sync & offline cache relevansi)
8. Process Control (standar error mapping & logging ringan)
9. Reflective Review (keselarasan dengan PRD status & rekonsiliasi)

Select 1–9 or just type your question/feedback:

## 4. Component Standards

Tujuan: menyelaraskan pola pembuatan komponen agar konsisten, mudah dirawat, dan dapat ditinjau dengan QA manual.

### 4.1 Component Template (TypeScript + A11y + Tailwind)

Gunakan fungsi komponen dengan props bertipe jelas, aksesibilitas dasar, dan kelas Tailwind terstandar.

```tsx
// components/Button.tsx
import React from 'react';

export type ButtonProps = {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
  children: React.ReactNode;
  'aria-label'?: string;
};

export const Button: React.FC<ButtonProps> = ({
  variant = 'primary',
  size = 'md',
  disabled = false,
  onClick,
  children,
  ...aria
}) => {
  const base =
    'inline-flex items-center justify-center rounded-md font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors';
  const sizes: Record<NonNullable<ButtonProps['size']>, string> = {
    sm: 'px-2.5 py-1.5 text-xs',
    md: 'px-3 py-2 text-sm',
    lg: 'px-4 py-2.5 text-base',
  };
  const variants: Record<NonNullable<ButtonProps['variant']>, string> = {
    primary:
      'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500 disabled:bg-blue-400',
    secondary:
      'bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-400 disabled:bg-gray-200',
    danger:
      'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500 disabled:bg-red-400',
  };

  return (
    <button
      type="button"
      className={`${base} ${sizes[size]} ${variants[variant]}`}
      disabled={disabled}
      onClick={onClick}
      {...aria}
    >
      {children}
    </button>
  );
};

export default Button;
```

Contoh komponen formulir ringan (gunakan React Hook Form di page yang memakai form):

```tsx
// components/FormField.tsx
import React from 'react';

export type FormFieldProps = {
  label: string;
  name: string;
  type?: 'text' | 'number';
  value: string | number;
  onChange: (value: string) => void;
  error?: string;
  required?: boolean;
};

export const FormField: React.FC<FormFieldProps> = ({
  label,
  name,
  type = 'text',
  value,
  onChange,
  error,
  required,
}) => {
  const id = `field-${name}`;
  return (
    <div className="space-y-1">
      <label htmlFor={id} className="text-sm font-medium text-gray-700">
        {label} {required && <span className="text-red-600">*</span>}
      </label>
      <input
        id={id}
        name={name}
        type={type}
        className="block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-blue-500"
        value={String(value)}
        onChange={(e) => onChange(e.target.value)}
        aria-invalid={Boolean(error)}
        aria-describedby={error ? `${id}-error` : undefined}
      />
      {error && (
        <p id={`${id}-error`} className="text-xs text-red-600">
          {error}
        </p>
      )}
    </div>
  );
};
```

Prinsip umum:
- Props selalu bertipe eksplisit; hindari `any`.
- Aksesibilitas: gunakan `aria-*`, `htmlFor`, dan label jelas.
- Tailwind untuk layout/warna; gunakan CSS Modules untuk gaya spesifik komponen bila perlu.
- Komponen presentasional tidak mengakses API langsung; logika data di layer `services/` atau hooks.

### 4.2 Penamaan & Konvensi

- Components: PascalCase untuk nama file dan komponen (`PaymentSummary.tsx`).
- Hooks: `useCamelCase` untuk nama hook (`usePaymentStatus.ts`). Hanya named exports.
- Services: file camelCase berakhiran `Service` bila berkelas atau fungsi di satu file (`paymentsService.ts`). Named exports.
- Stores (state): `paymentsStore.ts`, `uiStore.ts` dengan named exports.
- Types: nama tipe PascalCase (`Payment`, `PaymentStatus`). File `types/payment.ts` (lowercase singular).
- Pages: PascalCase folder, file `index.tsx` per page; komponen utama bernama sama dengan folder.
- Routes: `routes.tsx` sebagai pusat deklarasi rute.
- CSS Modules: `Component.module.css` untuk gaya spesifik; global di `styles/`.
- Icons/assets: kebab-case (`credit-card.svg`), impor dari `assets/icons`.
- Test (opsional): `Component.test.tsx` berdekatan dengan file sumber; karena QA manual, gunakan hanya untuk unit kecil yang berisiko.

Anti-pattern yang dihindari:
- Komponen melakukan fetch langsung ke Midtrans; gunakan `services/paymentsService` atau hooks.
- State global tanpa kontrol (gunakan TanStack Query untuk server-state, store terpisah untuk UI-state).
- Default export untuk hooks/services (prefer named export untuk tree-shaking dan konsistensi).

Rasional:
- Template tipe + a11y mempercepat review QA manual dan mengurangi regresi UI.
- Konvensi penamaan konsisten mempermudah navigasi struktur feature-first yang selaras dengan PRD.
- Pemisahan presentational vs data logic mendukung keterujian manual dan perawatan.

Elicit Options (1–9)
1. Proceed ke State Management (server-state vs UI-state)
2. Risk & Challenge (konsekuensi konvensi terhadap kecepatan dev)
3. Structural Analysis (komponen presentasional vs container)
4. Devil’s Advocate (apakah Tailwind + CSS Modules berlebih?)
5. Multi-Persona (PO/SM/Dev/QA sudut pandang)
6. Creative Exploration (atomic design vs feature-first)
7. 2025 Techniques (server components/partial hydration relevansi)
8. Process Control (standarisasi lint/format tanpa CI)
9. Reflective Review (cek keselarasan dengan PRD Epics 1–3)

Select 1–9 or just type your question/feedback:

## 3. Project Structure

Struktur direktori yang diusulkan (Vite + React + TS) untuk memenuhi PRD (Checkout, VA/QRIS/Kartu, Status, Riwayat), TanStack Query (polling/retry), dan Tailwind CSS.

```
frontend/
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
├── .env.example
├── tailwind.config.js
├── postcss.config.js
├── src/
│   ├── main.tsx
│   ├── app/
│   │   ├── App.tsx
│   │   ├── routes.tsx
│   │   └── providers/
│   │       ├── QueryProvider.tsx
│   │       └── ThemeProvider.tsx
│   ├── pages/
│   │   ├── Checkout/
│   │   │   ├── CheckoutPage.tsx
│   │   │   └── components/
│   │   │       ├── MethodSelector.tsx
│   │   │       └── SummaryPanel.tsx
│   │   ├── PaymentStatus/
│   │   │   └── PaymentStatusPage.tsx
│   │   └── TransactionHistory/
│   │       └── TransactionHistoryPage.tsx
│   ├── features/
│   │   └── payments/
│   │       ├── VA/
│   │       │   ├── VAInstructions.tsx
│   │       │   └── VAInfoCard.tsx
│   │       ├── QRIS/
│   │       │   ├── QRISCanvas.tsx
│   │       │   └── QRISCountdown.tsx
│   │       ├── Card/
│   │       │   ├── CardForm.tsx
│   │       │   └── ThreeDSFlow.tsx
│   │       ├── components/
│   │       │   ├── ErrorBanner.tsx
│   │       │   ├── LoadingSkeleton.tsx
│   │       │   └── StatusBadge.tsx
│   │       ├── hooks/
│   │       │   ├── usePaymentStatus.ts
│   │       │   └── useCopyToClipboard.ts
│   │       ├── services/
│   │       │   ├── apiClient.ts
│   │       │   └── paymentsService.ts
│   │       ├── types/
│   │       │   └── payment.ts
│   │       └── store/
│   │           └── uiStore.ts
│   ├── components/
│   │   ├── Button.tsx
│   │   ├── Modal.tsx
│   │   └── FormField.tsx
│   ├── lib/
│   │   ├── logger.ts
│   │   └── accessibility.ts
│   ├── styles/
│   │   ├── index.css
│   │   └── themes.css
│   ├── assets/
│   │   └── icons/
│   ├── utils/
│   │   ├── formatting.ts
│   │   └── validation.ts
│   └── config/
│       └── env.ts
└── README.md
```

Rationale
- Memetakan PRD Epics ke struktur: `pages/Checkout` (pemilihan metode & create transaksi), `pages/PaymentStatus` (status real-time), `pages/TransactionHistory` (riwayat pengguna).
- `features/payments` mengelompokkan komponen spesifik per-metode (VA/QRIS/Kartu), hooks (status/polling/copy), services (API client + payment service) agar mudah di-maintain.
- TanStack Query di `providers/QueryProvider.tsx` untuk cache/polling/retry terpusat; `services/apiClient.ts` untuk interceptors (auth, error mapping) dan konfigurasi `VITE_API_BASE_URL`.
- Tailwind + CSS Modules: utilitas cepat + modularitas untuk komponen kompleks; `styles/themes.css` untuk variabel global.
- Observability ringan di `lib/logger.ts` (correlation id pada event UI) dan komponen `ErrorBanner/StatusBadge` konsisten.
- `.env.example` mendokumentasikan variabel `VITE_...` (frontend) terpisah dari secret backend; `config/env.ts` membaca dan memvalidasi.

Opsi Elicitasi (1–9)
1. Proceed ke Component Standards (template dan konvensi)
2. Structural Analysis (cek keseimbangan pages vs features)
3. Risk & Challenge (kompleksitas maintainability dan QA manual)
4. Devil’s Advocate (apakah struktur terlalu granular?)
5. Multi-Persona (PO/SM/Dev/QA sudut pandang)
6. Creative Exploration (varian struktur: feature-first vs route-first)
7. 2025 Techniques (module federation/partial hydration relevansi)
8. Process Control (tandai asumsi & batasan eksplisit)
9. Reflective Review (keselarasan dengan PRD & arsitektur backend)

Select 1–9 or just type your question/feedback: