Midtrans-Middleware/docs/sprint_artifacts/story-payment-ux-improvemen...

331 lines
15 KiB
Markdown

# Story 1.1: Prevent Duplicate VA Generation & Improve Feedback
**Status:** Draft
---
## User Story
As a **non-tech-savvy user (ibu-ibu)**,
I want **clear feedback during automatic payment code generation (VA/QR/C-Store) and user-friendly error messages**,
So that **I don't see confusing technical errors and I understand what to do when problems occur across all payment methods (Bank Transfer, GoPay/QRIS, Convenience Store)**.
---
## Acceptance Criteria
### AC #1: Auto-Generation Loading State
**Given** user enters BankTransferPanel with selected bank
**When** VA generation automatically starts (via useEffect on component mount)
**Then** loading spinner is displayed with "Membuat VA…" message
**And** user sees clear feedback that generation is in progress
**And** loading overlay appears with message "Sedang membuat kode pembayaran..."
**And** user cannot interact with page during generation
### AC #2: Prevent Duplicate Generation on Reload/Remount
**Given** VA generation is already in progress or completed for an order+bank combination
**When** user reloads page or component remounts (React StrictMode, navigation back/forward)
**Then** system detects existing in-flight or completed charge via `attemptedChargeKeys` Set
**And** does not create duplicate VA generation request to Midtrans
**And** reuses existing VA code if already generated
**And** shows existing loading state if generation still in progress
### AC #3: User-Friendly Error Messages
**Given** VA generation fails with HTTP 409 (duplicate VA)
**When** error is displayed to user
**Then** message shows "Kode pembayaran Anda sudah dibuat! Klik di sini untuk melihat" (instead of technical "Request Failed 409")
**And** "Lihat Kode Pembayaran" button is displayed
**And** clicking button navigates to payment status page with existing VA
### AC #4: Error Recovery Options
**Given** VA generation fails with any error (404, 500, network error)
**When** error is displayed to user
**Then** "Buat VA" retry button is displayed (currently exists in code line 201-288)
**And** clicking button retries VA generation
**And** error message is in Bahasa Indonesia (not technical English)
**And** user has clear path to recover from error
### AC #5: Loading Overlay Prevents Interaction
**Given** VA generation is in progress
**When** loading overlay is displayed
**Then** user cannot interact with page content (no clicks, no navigation)
**And** overlay shows clear message in Bahasa Indonesia
**And** overlay is responsive on mobile devices
**And** overlay has proper z-index (50+) to stay above all content
---
## Implementation Details
### Tasks / Subtasks
**Phase 1: Understand Existing Implementation** (~30 minutes)
- [ ] Review `BankTransferPanel.tsx` (AC: #1, #2)
- [ ] Understand auto-generation logic in useEffect (lines 35-129)
- [ ] Understand duplicate prevention with `attemptedChargeKeys` Set (line 13)
- [ ] Understand shared promise pattern with `chargeTasks` Map (line 15)
- [ ] Understand retry button logic (lines 201-288)
- [ ] Identify what needs to be improved (AC: #3, #4, #5)
- [ ] Current error handling (lines 110-116, 268-271)
- [ ] Current loading feedback (lines 162-165, 281-286)
- [ ] Missing: Full-screen loading overlay
- [ ] Missing: User-friendly error message mapping
**Phase 2: Create Utility Functions** (~30 minutes)
- [ ] Create `src/lib/errorMessages.ts` (AC: #3, #4)
- [ ] Implement `mapErrorToUserMessage(error: AxiosError): string`
- [ ] Map HTTP 409 → "Kode pembayaran Anda sudah dibuat! Klik di sini untuk melihat"
- [ ] Map HTTP 404 → "Terjadi kesalahan. Silakan coba lagi"
- [ ] Map HTTP 500 → "Terjadi kesalahan server. Silakan coba lagi nanti"
- [ ] Map network error → "Tidak dapat terhubung ke server. Periksa koneksi internet Anda"
- [ ] Map "Gagal membuat VA." → More specific message based on error type
- [ ] Add default fallback message
- [ ] Write unit tests for error mapping
- [ ] Test HTTP 409 mapping
- [ ] Test HTTP 404 mapping
- [ ] Test HTTP 500 mapping
- [ ] Test network error mapping
- [ ] Test default fallback
**Phase 3: Create LoadingOverlay Component** (~1 hour)
- [ ] Create `src/components/LoadingOverlay.tsx` (AC: #1, #5)
- [ ] Implement full-screen overlay with semi-transparent backdrop
- [ ] Add Framer Motion fade-in animation (200ms)
- [ ] Add spinner (reuse existing spinner from line 163 or create new)
- [ ] Add message prop: "Sedang membuat kode pembayaran..."
- [ ] Make responsive for mobile (TailwindCSS)
- [ ] Add z-index: 50 to stay above all content
- [ ] Add accessibility (ARIA labels, role="status", aria-live="polite")
- [ ] Props interface: `{ isLoading: boolean; message: string }`
- [ ] Write component tests for LoadingOverlay
- [ ] Test renders when isLoading=true
- [ ] Test doesn't render when isLoading=false
- [ ] Test displays correct message
- [ ] Test fade-in animation works
- [ ] Test accessibility attributes
**Phase 4: Modify All Payment Panels** (~2 hours)
- [ ] Update error handling in `BankTransferPanel.tsx` (AC: #3, #4)
- [ ] Import `mapErrorToUserMessage` utility
- [ ] Replace line 66: `const msg = ax?.response?.data?.message || ax?.message || 'Gagal membuat VA.'`
- [ ] With: `const msg = mapErrorToUserMessage(ax)`
- [ ] Replace line 112: Same error handling
- [ ] Replace line 234: Same error handling
- [ ] Replace line 270: Same error handling
- [ ] Ensure all 4 error catch blocks use new mapping
- [ ] Add LoadingOverlay integration (AC: #1, #5)
- [ ] Import LoadingOverlay component
- [ ] Add LoadingOverlay to JSX (line 139, before main content)
- [ ] Pass `isLoading={busy}` prop
- [ ] Pass `message="Sedang membuat kode pembayaran..."` prop
- [ ] Enhance error display (AC: #3, #4)
- [ ] Update Alert component usage (line 148-150)
- [ ] Add conditional "Lihat Kode Pembayaran" button for HTTP 409
- [ ] Ensure "Buat VA" retry button (line 201-288) is visible on errors
- [ ] Update error handling in `GoPayPanel.tsx` (AC: #3, #4)
- [ ] Import `mapErrorToUserMessage` utility
- [ ] Replace line 178: `toast.error(\`Gagal membuat QR: ${(e as Error).message}\`)`
- [ ] With: `toast.error(mapErrorToUserMessage(e))`
- [ ] Replace line 207: Same error handling
- [ ] Add LoadingOverlay integration (AC: #1, #5)
- [ ] Import LoadingOverlay component
- [ ] Add LoadingOverlay to JSX (line 55, before main content)
- [ ] Pass `isLoading={busy}` prop
- [ ] Pass `message="Sedang membuat kode QR..."` prop
- [ ] Update error handling in `CStorePanel.tsx` (AC: #3, #4)
- [ ] Import `mapErrorToUserMessage` utility
- [ ] Replace line 39: `toast.error(\`Gagal membuat kode pembayaran: ${(e as Error).message}\`)`
- [ ] With: `toast.error(mapErrorToUserMessage(e))`
- [ ] Replace line 63: Same error handling
- [ ] Add LoadingOverlay integration (AC: #1, #5)
- [ ] Import LoadingOverlay component
- [ ] Add LoadingOverlay to JSX (line 81, before main content)
- [ ] Pass `isLoading={busy}` prop
- [ ] Pass `message="Sedang membuat kode pembayaran..."` prop
- [ ] Verify duplicate prevention still works in all panels (AC: #2)
- [ ] Ensure `attemptedChargeKeys` / `attemptedCStoreKeys` Set logic unchanged
- [ ] Ensure `chargeTasks` / `cstoreTasks` Map logic unchanged
- [ ] Ensure ref-based guards unchanged
**Phase 5: Testing & Refinement** (~1.5 hours)
- [ ] Manual testing on desktop - Bank Transfer (AC: #1, #2, #3, #4, #5)
- [ ] Test auto-generation on panel mount → loading overlay appears
- [ ] Test page reload during generation → no duplicate request
- [ ] Test React StrictMode double-mount → no duplicate request
- [ ] Test HTTP 409 error → user-friendly message + "Lihat Kode" button
- [ ] Test HTTP 404 error → user-friendly message + "Buat VA" retry
- [ ] Test network error → user-friendly message
- [ ] Test loading overlay prevents interaction
- [ ] Test retry button works after error
- [ ] Manual testing on desktop - GoPay/QRIS (AC: #1, #2, #3, #4, #5)
- [ ] Test auto-generation when mode=QRIS → loading overlay appears
- [ ] Test page reload during QR generation → no duplicate request
- [ ] Test mode switch (GoPay ↔ QRIS) → proper generation
- [ ] Test error scenarios → user-friendly messages via toast
- [ ] Test loading overlay prevents interaction
- [ ] Manual testing on desktop - Convenience Store (AC: #1, #2, #3, #4, #5)
- [ ] Test auto-generation on panel mount → loading overlay appears
- [ ] Test page reload during code generation → no duplicate request
- [ ] Test error scenarios → user-friendly messages via toast
- [ ] Test loading overlay prevents interaction
- [ ] Manual testing on mobile devices (all payment methods)
- [ ] Test on Chrome Android (Bank, QRIS, C-Store)
- [ ] Test on Safari iOS (Bank, QRIS, C-Store)
- [ ] Verify loading overlay is full-screen and readable
- [ ] Verify error messages are readable
- [ ] Verify buttons are tappable (44x44px minimum)
- [ ] Test page reload on mobile → no duplicate for all methods
- [ ] Fix any issues found
- [ ] Code review and cleanup
### Technical Summary
**Current Implementation (BankTransferPanel.tsx):**
- **Auto-generation:** VA generation happens automatically via useEffect when `selected` bank changes (line 35-129)
- **Duplicate prevention:** Uses `attemptedChargeKeys` Set to track attempted charges (line 13)
- **Shared promises:** Uses `chargeTasks` Map to share in-flight requests across remounts (line 15)
- **Loading state:** `busy` state variable controls loading spinner (line 21)
- **Error handling:** Catches errors and sets `errorMessage` state (lines 110-116, 268-271)
- **Retry button:** "Buat VA" button appears when `!vaCode || errorMessage` (line 201)
**What Needs to Be Added:**
- **LoadingOverlay component:** Full-screen overlay during VA generation (currently only inline spinner)
- **Error message mapping:** User-friendly Bahasa Indonesia messages (currently technical English)
- **Enhanced error recovery:** Conditional buttons based on error type (409 vs others)
**Key Technical Decisions:**
- **Don't change duplicate prevention logic:** Existing `attemptedChargeKeys` and `chargeTasks` pattern works well
- **Don't change auto-generation trigger:** Keep useEffect pattern (line 35-129)
- **Add overlay on top:** LoadingOverlay wraps existing UI, doesn't replace inline spinner
- **Map errors at catch sites:** Replace error message at all 4 catch blocks
**Files/Modules Involved:**
- **Modified:** `src/features/payments/components/BankTransferPanel.tsx` (main changes)
- **New utility:** `src/lib/errorMessages.ts`
- **New component:** `src/components/LoadingOverlay.tsx`
- **Test files:** Tests for new utility and component
### Project Structure Notes
- **Files to modify:**
- `src/features/payments/components/BankTransferPanel.tsx` (confirmed - Bank VA generation)
- `src/features/payments/components/GoPayPanel.tsx` (confirmed - QRIS/GoPay QR generation)
- `src/features/payments/components/CStorePanel.tsx` (confirmed - Convenience Store code generation)
- **Files to create:**
- `src/lib/errorMessages.ts`
- `src/components/LoadingOverlay.tsx`
- `src/lib/__tests__/errorMessages.test.ts`
- `src/components/__tests__/LoadingOverlay.test.tsx`
- **Expected test locations:**
- `src/lib/__tests__/` - Utility function tests
- `src/components/__tests__/` - Component tests
- `src/features/payments/__tests__/` - Integration tests (optional)
- **Estimated effort:** 5 story points (~2-3 days) - Covers all 3 payment methods
- **Prerequisites:** None - This is the first story, no dependencies
### Key Code References
**Actual Existing Code:**
**BankTransferPanel.tsx (Bank VA):**
- **Auto-generation useEffect:** Lines 35-129
- **Duplicate prevention:** Lines 13 (Set), 15 (Map), 39-77 (check logic), 78-124 (main logic)
- **Error handling:** Lines 64-67 (catch #1), 110-116 (catch #2), 232-236 (catch #3), 268-272 (catch #4)
- **Loading state:** Line 21 (`busy` state), 162-165 (inline spinner), 281-286 (button spinner)
- **Retry button:** Lines 201-288 (manual "Buat VA" button)
- **Error display:** Lines 148-150 (Alert component)
**GoPayPanel.tsx (QRIS/GoPay):**
- **Auto-generation useEffect:** Lines 157-220 (in `GoPayPanel_AutoEffect` component)
- **Duplicate prevention:** Lines 11 (Set), 12 (Map), 163-186 (check logic), 188-215 (main logic)
- **Error handling:** Lines 177-178 (catch #1), 206-208 (catch #2)
- **Loading state:** Line 33 (`busy` state), 94-98 (inline spinner), 140-144 (button spinner)
- **Error display:** Lines 178, 207 (toast.error)
**CStorePanel.tsx (Convenience Store):**
- **Auto-generation useEffect:** Lines 23-72
- **Duplicate prevention:** Lines 12 (Set), 13 (Map), 30-44 (check logic), 46-68 (main logic)
- **Error handling:** Lines 38-39 (catch #1), 62-64 (catch #2)
- **Loading state:** Line 19 (`busy` state), 104-108 (inline spinner)
- **Error display:** Lines 39, 63 (toast.error)
**API Service:**
- `postCharge` function imported from `src/services/api` (line 7)
- Called at lines 90, 250 with payload containing `payment_type`, `transaction_details`, `bank_transfer`
**Important Functions:**
1. `postCharge(payload)` - API call to create VA (lines 90, 250)
2. `run()` async function - Main auto-generation logic (lines 37-125)
3. Button onClick handler - Manual retry logic (lines 205-277)
**Code Patterns to Follow:**
- Functional components with TypeScript
- TailwindCSS utility classes
- Existing spinner pattern (line 163): `<span className="h-3 w-3 animate-spin rounded-full border-2 border-black/40 border-t-transparent" />`
- Existing Alert component (line 148): `<Alert title="...">{message}</Alert>`
- Existing Button component (line 202): `<Button disabled={...} onClick={...}>`
---
## Context References
**Tech-Spec:** [tech-spec.md](../tech-spec.md) - Primary context document containing:
- Brownfield codebase analysis (React 19, TypeScript, Vite, TailwindCSS stack)
- Framework and library details with exact versions
- Existing patterns to follow (functional components, TailwindCSS, Framer Motion)
- Integration points (Midtrans API via `postCharge`, React Router)
- Complete implementation guidance with code examples
- Testing strategy and acceptance criteria
- Deployment and rollback plan
**Problem-Solution Analysis:** [problem-solution-2025-11-25.md](../problem-solution-2025-11-25.md) - Root cause analysis identifying:
- Problem #1: Repeated VA Generation (3-5 second delay, duplicate clicks)
- Root cause: Lack of defensive UX design + no user behavior anticipation
- Solution: Loading overlay + user-friendly errors + duplicate prevention
**Actual Code:** [BankTransferPanel.tsx](../src/features/payments/components/BankTransferPanel.tsx) - Current implementation with:
- Auto-generation via useEffect
- Duplicate prevention with `attemptedChargeKeys` Set
- Shared promises with `chargeTasks` Map
- Inline loading spinner
- Technical error messages (needs improvement)
**Architecture:** Feature-based modular architecture with 16 feature modules
<!-- Additional context XML paths will be added here if story-context workflow is run -->
---
## Dev Agent Record
### Agent Model Used
<!-- Will be populated during dev-story execution -->
### Debug Log References
<!-- Will be populated during dev-story execution -->
### Completion Notes
<!-- Will be populated during dev-story execution -->
### Files Modified
<!-- Will be populated during dev-story execution -->
### Test Results
<!-- Will be populated during dev-story execution -->
---
## Review Notes
<!-- Will be populated during code review -->