specs
Payments

Payments Specification

Status: Canonical Last Updated: 2026-05-11 Source: packages/backend/convex/domains/payments.ts, packages/backend/convex/http/onepay.ts


Related specs


Payment Flow

┌─────────────────────────────────────────────────────────────┐
│  1. User on checkout page                                  │
│     - Fills customer info                                  │
│     - Clicks "Pay Now"                                    │
├─────────────────────────────────────────────────────────────┤
│  2. Create pending reservation                              │
│     - status: PENDING                                      │
│     - paymentStatus: PENDING                                │
│     - bookingExpiresAt: now + 10 min                        │
├─────────────────────────────────────────────────────────────┤
│  3. Call OnePay API                                        │
│     - Create payment URL with amount, order info            │
│     - Redirect user to OnePay                               │
├─────────────────────────────────────────────────────────────┤
│  4. User completes payment on OnePay                        │
│     - Success → redirect to /booking/[id]/confirmation      │
│     - Fail → redirect to /booking/[id]/checkout?error=...   │
├─────────────────────────────────────────────────────────────┤
│  5. Handle return                                          │
│     - Verify OnePay signature                               │
│     - Update reservation status                            │
│     - Create payment record                                 │
│     - Send confirmation email                              │
└─────────────────────────────────────────────────────────────┘

OnePay Integration

Configuration

// Environment variables (via npx convex env set)
ONEPAY_MERCHANT_ID=your_merchant_id
ONEPAY_ACCESS_CODE=your_access_code
ONEPAY_SECRET_KEY=your_secret_key
ONEPAY_URL=https://mtf.onepay.vn

Payment URL Creation

// packages/backend/convex/lib/onepay/api.ts
createPaymentUrl({
  amount: number,
  orderId: string,
  returnUrl: string,
  transactionType: 'PAY',
})

Return URL Handling

// packages/backend/convex/http/onepayReturn.ts
- Extract VPC response params
- Verify secure hash
- Update reservation paymentStatus
- Redirect to confirmation or error

Payment Status

StatusDescriptionAction
PENDINGAwaiting paymentUser redirected to OnePay
PAIDPayment successfulConfirmation sent
FAILEDPayment failedShow error, allow retry
CANCELLEDUser cancelledRelease seats
REFUND_PENDINGRefund requestedAdmin processes
REFUNDEDRefund completedNotify customer

Virtual Account (Bank Transfer)

Flow

  1. User selects bank transfer
  2. System generates VA number
  3. User transfers to VA
  4. OnePay notifies on receipt
  5. Payment marked PAID

VA Number Display

Shown on checkout for bank transfer option.

Payment Tracking

payments Table

Stores all OnePay transactions:

  • vpcMerchTxnRef: Unique reference
  • vpcTransactionNo: OnePay transaction ID
  • amount: VND amount
  • status: PENDING | SUCCESS | FAILED
  • card: Card type (Visa, Mastercard)
  • cardNum: Last 4 digits

notificationLogs Table

Tracks delivery of confirmations:

  • EMAIL_CONFIRMATION
  • WHATSAPP_CONFIRMATION
  • EMAIL_ADMIN_NEW_BOOKING

Refund Flow

  1. Admin opens reservation detail
  2. Clicks "Cancel & Refund"
  3. System calls OnePay refund API
  4. Updates reservation status to REFUNDED
  5. Sends cancellation email

Pricing Display

All prices in VND. Format with:

// apps/frontend/lib/utils/format.ts
formatCurrency(amount: number): string
// Output: "500.000 đ"