Xendit FPX / Touch n’ Go Integration Documentation
Malaysia-only payment methods — FPX (online banking) and Touch n’ Go (eWallet). Currency is always MYR. Country is always MY.
Table of Contents
- Overview
- Architecture Diagram
- Payment Methods
- API Endpoints
- Process Flows
- Xendit API Calls from Backend
- Webhook Processing
- Reservation Payload Changes
- Frontend Responsibilities
- Database Schema
- FPX Bank Codes
- Key Files Reference
Overview
The Xendit integration supports three payment types for Malaysia:
| Payment Type | Xendit Type | How It Works |
|---|---|---|
| FPX (B2C) | DIRECT_DEBIT | User selects Malaysian bank → redirected to bank portal → pays → redirected back |
| FPX B2B | DIRECT_DEBIT | Same as FPX but for business banking (higher limits up to RM 1,000,000) |
| Touch n’ Go | EWALLET | User redirected to TnG eWallet → approves payment → redirected back |
All three are redirect-based: the backend creates a payment request with Xendit, gets a redirect URL, and the frontend sends the user to that URL.
Architecture Diagram
┌──────────────────────────────────────────────────────────────────────────────┐
│ FRONTEND (FE) │
│ │
│ 1. User selects FPX/TnG payment ──► 2. POST /api/v5/reservations │
│ + picks bank (for FPX) (with payment_method_type, │
│ channel_code, etc.) │
│ │
│ 3. Receive reservation response ◄── { meta.payment_request_id, │
│ with redirect_url (Firebase) authorization_url (Firebase) } │
│ │
│ 4. Redirect user to bank/TnG ──► External bank portal / TnG app │
│ │
│ 5. User completes payment ──► Redirected to payment_success_url │
│ or fails or payment_failed_url │
│ │
│ 6. Listen to Firebase for Firebase: reservations/{id} │
│ payment confirmation { status: 'paid' } │
└──────────────────────────────────────────────────────────────────────────────┘
│ ▲
│ POST /api/v5/reservations │ Firebase update
▼ │
┌──────────────────────────────────────────────────────────────────────────────┐
│ BACKEND (BE) │
│ │
│ ReservationsController (create_v2) │
│ │ │
│ ├─► auto_create_xendit_payment_request_if_needed() │
│ │ │ │
│ │ ├─► Xendit::PaymentRequestService.create_from_reservation_params() │
│ │ │ │ │
│ │ │ ├─► derive_charge_price() — calculates amount from packages │
│ │ │ │ (includes voucher deductions via active_vouchers) │
│ │ │ │ │
│ │ │ └─► POST https://api.xendit.co/payment_requests ◄── Xendit │
│ │ │ (returns payment_request_id + redirect_url) API #1 │
│ │ │ │
│ │ └─► Sets params[:payment_request_id] + params[:xendit_redirect_url] │
│ │ │
│ ├─► ReservationService::Create.execute │
│ │ │ │
│ │ ├─► Creates reservation record │
│ │ ├─► Creates Externals::Xendit::Charge record (PENDING) │
│ │ ├─► Sets reservation.cc_provider = :xendit │
│ │ ├─► Skips card save (no card for FPX/TnG) │
│ │ └─► Updates Firebase with { authorization_url: redirect_url } │
│ │ │
│ └─► Returns reservation JSON + meta.payment_request_id │
│ │
│ ─ ─ ─ ─ ─ ─ ─ LATER (async via webhook) ─ ─ ─ ─ ─ ─ ─ │
│ │
│ WebhooksController#xendit ◄── POST /api/webhook/xendit (from Xendit) │
│ │ │
│ ├─► Xendit::WebhookHandlerService.process │
│ │ │ │
│ │ ├─► 'payment.succeeded' event │
│ │ │ ├─► Xendit::ChargeUpdaterService — updates charge to SUCCEEDED│
│ │ │ └─► MarkTransactionAsPaidWorker.perform_async │
│ │ │ └─► MarkReservationAsPaidService — marks as paid │
│ │ │ └─► Firebase update { status: 'paid' } │
│ │ │ │
│ │ └─► 'payment.failed' event │
│ │ └─► Firebase update { status: 'payment_failed' } │
│ │ │
│ └─► Returns HTTP 200 OK to Xendit │
└──────────────────────────────────────────────────────────────────────────────┘
Payment Methods
FPX (Financial Process Exchange)
- Type:
DIRECT_DEBIT payment_method_type:"fpx"(used for both B2C and B2B)- Requires
channel_code: Yes — B2C: e.g.,"CIMB_FPX","MAYB2U_FPX"; B2B: e.g.,"CIMB_FPX_BUSINESS"(channel codes ending in_FPX_BUSINESS) - Limit: B2C up to RM 30,000 / B2B up to RM 1,000,000 per transaction
- Reusability:
ONE_TIME_USE
Touch n’ Go
- Type:
EWALLET payment_method_type:"touchngo"- Requires
channel_code: No (auto-set to"TOUCHNGO") - Reusability:
ONE_TIME_USE
API Endpoints
BE → Xendit API Calls
| # | Method | Xendit URL | Purpose | Service |
|---|---|---|---|---|
| 1 | POST | https://api.xendit.co/payment_requests | Create FPX/TnG payment request | Xendit::PaymentRequestService |
| 2 | POST | https://api.xendit.co/sessions | Create card payment session (Session JS) | Xendit::PaymentService |
| 3 | GET | https://api.xendit.co/payment_requests/{id} | Fetch payment request details (3DS auth URL) | Xendit::PaymentService.fetch_payment_request |
FE → BE API Calls
| # | Method | BE Endpoint | Purpose |
|---|---|---|---|
| 1 | POST | /api/v5/reservations | Create reservation (auto-creates Xendit payment request inline) |
| 2 | POST | /api/v5/xendit_payment_requests | Create standalone payment request (alternative flow) |
| 3 | POST | /api/v5/xendit_payment_sessions | Create card payment session (credit card only) |
| 4 | POST | /api/v5/xendit_voucher_payment_sessions | Create voucher payment session (gift cards) |
Xendit → BE (Webhooks)
| # | Method | BE Endpoint | Purpose |
|---|---|---|---|
| 1 | POST | /api/webhook/xendit | Receive payment status webhooks |
Process Flows
Flow A: Standalone Payment Request (2-step)
FE creates payment request separately, then creates reservation with the ID.
FE BE Xendit
│ │ │
│ 1. POST /api/v5/xendit_payment_requests │
│ { │ │
│ tmp_reservation_id, │ │
│ payment_method_type, │ │
│ channel_code, │ │
│ amount, email │ │
│ } │ │
│ ─────────────────────────► │ │
│ │ 2. POST /payment_requests │
│ │ ─────────────────────────► │
│ │ │
│ │ ◄───────────────────────── │
│ │ { id: "pr-xxx", │
│ │ actions: [{url_type:"WEB", │
│ │ url:"https://..."}] } │
│ ◄───────────────────────── │ │
│ { payment_request_id, │ │
│ redirect_url } │ │
│ │ │
│ 3. POST /api/v5/reservations │ │
│ { ..., │ │
│ payment_request_id: │ │
│ "pr-xxx" │ │
│ } │ │
│ ─────────────────────────► │ │
│ │ Creates reservation + │
│ ◄───────────────────────── │ Xendit::Charge record │
│ { reservation } │ │
│ │ │
│ 4. Redirect user to │ │
│ redirect_url │ │
│ ─────────────────────────────────────────────────────────► │
│ │ │
│ (user pays at bank/TnG) │ │
│ │ │
│ 5. Xendit redirects user │ │
│ to success/failure URL │ │
│ ◄──────────────────────────────────────────────────────── │
│ │ │
│ │ 6. POST /api/webhook/xendit │
│ │ ◄───────────────────────── │
│ │ { event:"payment.succeeded", │
│ │ data:{...} } │
│ │ │
│ │ → Update charge status │
│ │ → Enqueue paid worker │
│ │ → Firebase: {status:"paid"} │
│ │ │
│ 7. FE reads Firebase │ ◄─── Firebase update │
│ { status: "paid" } │ │
Flow B: Auto-Create During Reservation (1-step — recommended)
FE sends
payment_method_type+channel_codealongside reservation params. BE auto-creates the payment request.
FE BE Xendit
│ │ │
│ 1. POST /api/v5/reservations │ │
│ { │ │
│ reservation: {...}, │ │
│ packages: [...], │ │
│ payment_method_type: │ │
│ "fpx", │ │
│ channel_code: │ │
│ "CIMB_FPX", │ │
│ guest_user: { │ │
│ email, name, phone │ │
│ } │ │
│ } │ │
│ ─────────────────────────► │ │
│ │ │
│ │ auto_create_xendit_payment │
│ │ _request_if_needed() │
│ │ │ │
│ │ ├─ derive_charge_price() │
│ │ │ (calc from packages + │
│ │ │ voucher deductions) │
│ │ │ │
│ │ └─ POST /payment_requests │
│ │ ─────────────────────────► │
│ │ │
│ │ ◄───────────────────────── │
│ │ { id:"pr-xxx", │
│ │ redirect_url:"https://..." │
│ │ status:"REQUIRES_ACTION" } │
│ │ │
│ │ Creates reservation │
│ │ Creates Xendit::Charge │
│ │ Firebase: { │
│ │ authorization_url: │
│ │ "https://..." │
│ │ } │
│ │ │
│ ◄───────────────────────── │ │
│ { │ │
│ success: true, │ │
│ data: { reservation }, │ │
│ meta: { │ │
│ payment_request_id: │ │
│ "pr-xxx" │ │
│ } │ │
│ } │ │
│ │ │
│ 2. FE reads Firebase for │ │
│ authorization_url │ │
│ │ │
│ 3. Redirect user to │ │
│ authorization_url │ │
│ ─────────────────────────────────────────────────────────► │
│ │ │
│ (user pays at bank/TnG) │ │
│ │ │
│ 4. Xendit redirects user to │ │
│ success/failure URL │ │
│ ◄──────────────────────────────────────────────────────── │
│ │ │
│ │ 5. POST /api/webhook/xendit │
│ │ ◄───────────────────────── │
│ │ { event:"payment.succeeded"} │
│ │ │
│ │ → ChargeUpdaterService │
│ │ → MarkTransactionAsPaidWorker│
│ │ → Firebase: {status:"paid"} │
│ │ │
│ 6. FE reads Firebase │ │
│ { status: "paid" } │ │
Flow C: Xendit Card Payment via Session JS (credit card)
This is for credit card payments, NOT FPX/TnG. Included for completeness.
FE BE Xendit
│ │ │
│ 1. POST /api/v5/xendit_payment_sessions │
│ { │ │
│ tmp_reservation_id, │ │
│ first_name, email, │ │
│ mobile_number, amount │ │
│ } │ │
│ ─────────────────────────► │ │
│ │ 2. POST /sessions │
│ │ ─────────────────────────► │
│ │ ◄───────────────────────── │
│ │ { session_id, ... } │
│ ◄───────────────────────── │ │
│ { session_id } │ │
│ │ │
│ 3. FE uses Xendit Session JS │ │
│ SDK to tokenize card │ │
│ with session_id ────────────────────────────► │
│ │ │
│ 4. POST /api/v5/reservations │ │
│ { ..., │ │
│ payment_request_id: │ │
│ "pr-xxx" (from SDK) │ │
│ } │ │
│ ─────────────────────────► │ │
│ │ 5. GET /payment_requests/ │
│ │ {payment_request_id} │
│ │ ─────────────────────────► │
│ │ ◄───────────────────────── │
│ │ { authorization_url (3DS) } │
│ │ │
│ │ Firebase: {authorization_url}│
│ │ │
│ 6. Redirect to 3DS page │ │
│ 7. Webhook → mark as paid │ │
Xendit API Calls from Backend
API Call #1: Create Payment Request (FPX / TnG)
Service: Xendit::PaymentRequestService#create
POST https://api.xendit.co/payment_requests
Authorization: Basic {base64(XENDIT_SECRET_KEY:)}
Content-Type: application/json
Request Body (FPX example):
{
"reference_id": "{reservation_id}-{timestamp}",
"amount": 336.00,
"currency": "MYR",
"country": "MY",
"payment_method": {
"type": "DIRECT_DEBIT",
"reusability": "ONE_TIME_USE",
"direct_debit": {
"channel_code": "CIMB_FPX",
"channel_properties": {
"success_return_url": "https://web.hungryhub.com/restaurants/{slug}/payment-success/{hash}",
"failure_return_url": "https://hungryhub.com/payment-failed"
}
}
},
"customer": {
"reference_id": "{user_id}-{reservation_id}-{timestamp}",
"type": "INDIVIDUAL",
"email": "user@example.com",
"mobile_number": "+60123456789",
"individual_detail": {
"given_names": "John"
}
},
"metadata": {
"reservation_id": "12345"
}
}
Request Body (Touch n’ Go example):
{
"reference_id": "{reservation_id}-{timestamp}",
"amount": 336.00,
"currency": "MYR",
"country": "MY",
"payment_method": {
"type": "EWALLET",
"reusability": "ONE_TIME_USE",
"ewallet": {
"channel_code": "TOUCHNGO",
"channel_properties": {
"success_return_url": "https://web.hungryhub.com/restaurants/{slug}/payment-success/{hash}",
"failure_return_url": "https://hungryhub.com/payment-failed"
}
}
},
"customer": {
"reference_id": "{user_id}-{reservation_id}-{timestamp}",
"type": "INDIVIDUAL",
"email": "user@example.com",
"individual_detail": {
"given_names": "John"
}
},
"metadata": {
"reservation_id": "12345"
}
}
Response (both FPX and TnG):
{
"id": "pr-f37f95cb-a682-459b-bb12-9a44d8047797",
"reference_id": "12345-1700000000",
"status": "REQUIRES_ACTION",
"amount": 336.00,
"currency": "MYR",
"actions": [
{
"url_type": "WEB",
"url": "https://checkout.xendit.co/web/xxxxxxxx"
},
{
"url_type": "MOBILE",
"url": "https://checkout.xendit.co/mobile/xxxxxxxx"
}
]
}
The BE extracts the url from the action with url_type: "WEB" as the redirect_url.
API Call #2: Create Payment Session (Cards only)
Service: Xendit::PaymentService#create_session
POST https://api.xendit.co/sessions
Authorization: Basic {base64(XENDIT_SECRET_KEY:)}
Content-Type: application/json
Request Body:
{
"reference_id": "reservation-{id}",
"session_type": "PAY",
"currency": "MYR",
"amount": 336.00,
"mode": "CARDS_SESSION_JS",
"country": "MY",
"customer": {
"reference_id": "{user_id}-reservation-{id}-{timestamp}",
"type": "INDIVIDUAL",
"email": "user@example.com",
"mobile_number": "+60123456789",
"individual_detail": {
"given_names": "John",
"surname": "Doe"
}
},
"cards_session_js": {
"success_return_url": "https://web.hungryhub.com/restaurants/{slug}/payment-success/{hash}",
"failure_return_url": "https://hungryhub.com/payment-failed"
}
}
API Call #3: Fetch Payment Request (3DS / status check)
Service: Xendit::PaymentService.fetch_payment_request
GET https://api.xendit.co/payment_requests/{payment_request_id}
Authorization: Basic {base64(XENDIT_SECRET_KEY:)}
Content-Type: application/json
Used after card payment to fetch the 3DS authorization_url from the actions array.
Not used for FPX/TnG — the redirect URL is already available from the create response.
Webhook Processing
Webhook Endpoint
POST /api/webhook/xendit
Controller: Api::WebhooksController#xendit
Service: Xendit::WebhookHandlerService
Webhook Events
| Event | Payment Type | Action |
|---|---|---|
payment.succeeded | FPX / TnG | Update charge → Mark reservation paid → Firebase {status: paid} |
payment.failed | FPX / TnG | Firebase {status: payment_failed} |
invoice.paid | FPX / TnG (legacy Invoice API) | Same as payment.succeeded |
payment.capture | Credit Card | Update charge → Mark reservation paid |
payment_session.completed | Credit Card | Update charge → Mark reservation paid |
payment_session.failed | Credit Card | Firebase {status: payment_failed} |
payment_session.expired | Credit Card | Firebase {status: payment_failed} |
payment_session.pending | Credit Card | Log only (informational) |
payment_session.created | Credit Card | Log only (informational) |
Webhook Payload Example (payment.succeeded)
{
"event": "payment.succeeded",
"business_id": "...",
"created": "2025-01-15T10:30:00Z",
"data": {
"id": "pmt-xxx",
"payment_request_id": "pr-f37f95cb-a682-459b-bb12-9a44d8047797",
"reference_id": "12345-1700000000",
"status": "SUCCEEDED",
"amount": 336.00,
"currency": "MYR",
"customer_id": "cust-xxx",
"payment_method": {
"type": "DIRECT_DEBIT",
"channel_code": "CIMB_FPX"
}
}
}
Webhook Processing Chain
WebhooksController#xendit
└─► Xendit::WebhookHandlerService#process
├─► Xendit::ChargeUpdaterService#update
│ ├─ Find/create Externals::Xendit::Charge by payment_request_id
│ ├─ Update status, amount_cents, currency, paid_at, channel_code
│ ├─ Find/create Externals::Xendit::Customer
│ └─ Update User.xendit_customer_id_my
│
└─► MarkTransactionAsPaidWorker.perform_async (Sidekiq :critical queue)
└─► MarkReservationAsPaidService#execute
└─► Reservation marked paid + Firebase updated
Reservation Payload Changes
New Params Added to POST /api/v5/reservations
For FPX/TnG, the FE sends these additional params alongside the standard reservation params:
| Param | Type | Required | Description |
|---|---|---|---|
payment_method_type | String | Yes | "fpx" (B2C and B2B) or "touchngo". Use a _FPX_BUSINESS channel_code for B2B. |
channel_code | String | FPX only | Bank code e.g. "CIMB_FPX". Not needed for Touch n’ Go |
payment_request_id | String | No | Only if using Flow A (standalone). Omit for auto-create |
Note:
payment_method_typecan also be sent asomise_payment_typeorpayment_type(all three are accepted for backward compatibility).
Reservation Create Response Changes
Standard response — no structural changes. FPX/TnG adds these:
{
"success": true,
"data": {
"id": 12345,
"status": "waiting_for_payment",
"payment_type": "fpx",
"channel_code": "CIMB_FPX",
...
},
"message": "Reservation created",
"meta": {
"payment_request_id": "pr-f37f95cb-a682-459b-bb12-9a44d8047797",
"misc": { ... }
}
}
Key fields:
data.payment_type— the resolved payment provider typedata.channel_code— fromExternals::Xendit::Charge.channel_code(bank code)meta.payment_request_id— the Xendit payment request ID
Firebase Record Update
After reservation creation, the backend pushes to Firebase at reservations/{id}:
{
"status": "waiting_for_payment",
"authorization_url": "https://checkout.xendit.co/web/xxxxxxxx",
"facebook_event_id": "...",
"delivery_status": "Driver::FINDING_DRIVER"
}
The authorization_url is the redirect URL the FE uses to send the user to the bank/TnG portal.
Frontend Responsibilities
What FE Needs to Do
1. Payment Method Selection UI
- Show FPX option with a bank selector dropdown (20 B2C banks + 20 B2B banks)
- Show Touch n’ Go option (no bank selection needed)
- Get the bank list from the BE
app_configendpoint or hardcode from FPX Bank Codes
2. Create Reservation with Payment Params
Send a POST /api/v5/reservations with these extra fields:
FPX Example:
{
"reservation": { "...standard fields..." },
"packages": [ "...packages..." ],
"payment_method_type": "fpx",
"channel_code": "CIMB_FPX",
"guest_user": {
"email": "user@example.com",
"name": "John",
"phone": "+60123456789"
}
}
Touch n’ Go Example:
{
"reservation": { "...standard fields..." },
"packages": [ "...packages..." ],
"payment_method_type": "touchngo",
"guest_user": {
"email": "user@example.com",
"name": "John",
"phone": "+60123456789"
}
}
No need to send
amount— the BE calculates it from the packages and vouchers viaderive_charge_price().
3. Get Redirect URL from Firebase
After the reservation is created:
- Read the reservation response (
meta.payment_request_idconfirms Xendit was used) - Listen to Firebase at
reservations/{reservation_id} - Read the
authorization_urlfield — this is the redirect URL
4. Redirect User to Payment Portal
- Web:
window.location.href = authorization_url - Mobile: Open in-app browser or system browser to
authorization_url
The user will see:
- FPX: The selected bank’s internet banking login page
- Touch n’ Go: The TnG eWallet approval page
5. Handle Return URLs
After payment, Xendit redirects the user to:
- Success:
https://web.hungryhub.com/restaurants/{slug}/payment-success/{hash} - Failure:
https://hungryhub.com/payment-failed
The FE should:
- On the success page: Show a “processing” state and listen to Firebase for
status: "paid" - On the failure page: Show an error message and offer retry
6. Listen to Firebase for Final Status
Firebase at reservations/{id} will be updated:
| Status | Meaning |
|---|---|
status: "paid" | Payment succeeded, reservation confirmed |
status: "payment_failed" | Payment failed or expired |
7. Handle Edge Cases
- User closes bank page without completing: Payment will expire, webhook sends
payment.failed - Slow webhook: User may land on success page before webhook arrives. Show “processing” spinner and poll Firebase
- Network error during redirect: User can check reservation status page
What FE Does NOT Need to Do
- Do NOT calculate the payment amount — BE derives it from packages
- Do NOT call
/api/v5/xendit_payment_requestsseparately — the auto-create flow handles it - Do NOT handle webhook verification — BE handles all webhook processing
- Do NOT create Xendit customers — BE creates them from webhook data
Database Schema
externals_xendit_charges
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
payment_request_id | string | Xendit payment request ID (e.g., pr-xxx) |
amount_cents | integer | Amount in cents/subunits |
currency | string | Currency code (e.g., MYR) |
status | string | Payment status |
channel_code | string | Bank/method code (e.g., CIMB_FPX, TOUCHNGO) |
customer_id | string | Xendit customer ID |
paid_at | datetime | When payment was confirmed |
reservation_id | bigint | FK to reservations |
voucher_transaction_id | bigint | FK to voucher_transactions |
ticket_transaction_id | bigint | FK to ticket_transactions |
Success statuses: SUCCEEDED, COMPLETED
externals_xendit_customers
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
customer_id | string | Xendit customer ID |
reservation_id | bigint | FK to reservations |
voucher_transaction_id | bigint | FK to voucher_transactions |
users (new column)
| Column | Type | Description |
|---|---|---|
xendit_customer_id_my | string | Xendit Malaysia customer ID |
FPX Bank Codes
B2C (Personal Banking)
| Channel Code | Bank |
|---|---|
AFFIN_FPX | Affin Bank |
AGRO_FPX | AgroBank |
ALLIANCE_FPX | Alliance Bank |
AMBANK_FPX | AmBank |
BOC_FPX | Bank of China |
BSN_FPX | Bank Simpanan Nasional |
CIMB_FPX | CIMB Bank |
HLB_FPX | Hong Leong Bank |
HSBC_FPX | HSBC Bank |
ISLAM_FPX | Bank Islam |
KFH_FPX | Kuwait Finance House |
MAYB2E_FPX | Maybank2E |
MAYB2U_FPX | Maybank2u |
MUAMALAT_FPX | Bank Muamalat |
OCBC_FPX | OCBC Bank |
PUBLIC_FPX | Public Bank |
RAKYAT_FPX | Bank Rakyat |
RHB_FPX | RHB Bank |
SCH_FPX | Standard Chartered |
UOB_FPX | UOB Bank |
B2B (Business Banking)
Same banks with _BUSINESS suffix (e.g., CIMB_FPX_BUSINESS), plus:
BNP_FPX_BUSINESS— BNP ParibasCITIBANK_FPX_BUSINESS— CitibankDEUTSCHE_FPX_BUSINESS— Deutsche Bank
Key Files Reference
Services
| File | Purpose |
|---|---|
app/services/xendit/payment_request_service.rb | Creates FPX/TnG payment requests via Xendit API |
app/services/xendit/payment_service.rb | Creates card payment sessions + fetches payment requests |
app/services/xendit/webhook_handler_service.rb | Processes all Xendit webhook events |
app/services/xendit/charge_updater_service.rb | Updates charge/customer DB records from webhooks |
app/services/xendit/card_saver_service.rb | Saves card info (card payments only, not FPX/TnG) |
app/services/reservation_service/create.rb | Reservation creation with auto Xendit payment request |
Controllers
| File | Purpose |
|---|---|
app/controllers/api/v5/xendit_payment_requests_controller.rb | Standalone FPX/TnG payment request endpoint |
app/controllers/api/v5/xendit_payment_sessions_controller.rb | Card payment session endpoint |
app/controllers/api/v5/xendit_voucher_payment_sessions_controller.rb | Voucher payment session endpoint |
app/controllers/api/webhooks_controller.rb | Xendit webhook handler |
app/controllers/api/v5/concerns/tmp_reservation.rb | Auto-create payment request in create_v2 flow |
Models
| File | Purpose |
|---|---|
app/models/externals/xendit/charge.rb | Xendit charge records |
app/models/externals/xendit/customer.rb | Xendit customer records |
Workers
| File | Purpose |
|---|---|
app/workers/mark_transaction_as_paid_worker.rb | Sidekiq job to mark reservation as paid |
Config
| File | Purpose |
|---|---|
config/routes.rb | Route definitions (lines 2759-2761, 3073) |
Return URLs
| URL | When |
|---|---|
{web_host}/restaurants/{slug}/payment-success/{hash} | After successful payment |
{sub_domain}/payment-failed | After failed payment |