Payments Specification
Status: Canonical Last Updated: 2026-05-11 Source:
packages/backend/convex/domains/payments.ts,packages/backend/convex/http/onepay.ts
Related specs
- Tech Stack — External integration patterns
- Data Model — reservations, payments, notificationLogs tables
- Booking Flow — Checkout step integrates with payments
- Notifications — EMAIL_CONFIRMATION, WHATSAPP_CONFIRMATION after payment
- Admin Dashboard — Event payments view in admin
- User Stories — US-B01, US-B02 cover payment flow
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.vnPayment 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 errorPayment Status
| Status | Description | Action |
|---|---|---|
| PENDING | Awaiting payment | User redirected to OnePay |
| PAID | Payment successful | Confirmation sent |
| FAILED | Payment failed | Show error, allow retry |
| CANCELLED | User cancelled | Release seats |
| REFUND_PENDING | Refund requested | Admin processes |
| REFUNDED | Refund completed | Notify customer |
Virtual Account (Bank Transfer)
Flow
- User selects bank transfer
- System generates VA number
- User transfers to VA
- OnePay notifies on receipt
- 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
- Admin opens reservation detail
- Clicks "Cancel & Refund"
- System calls OnePay refund API
- Updates reservation status to REFUNDED
- Sends cancellation email
Pricing Display
All prices in VND. Format with:
// apps/frontend/lib/utils/format.ts
formatCurrency(amount: number): string
// Output: "500.000 đ"