Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

  1. Overview
  2. Architecture Diagram
  3. Payment Methods
  4. API Endpoints
  5. Process Flows
  6. Xendit API Calls from Backend
  7. Webhook Processing
  8. Reservation Payload Changes
  9. Frontend Responsibilities
  10. Database Schema
  11. FPX Bank Codes
  12. Key Files Reference

Overview

The Xendit integration supports three payment types for Malaysia:

Payment TypeXendit TypeHow It Works
FPX (B2C)DIRECT_DEBITUser selects Malaysian bank → redirected to bank portal → pays → redirected back
FPX B2BDIRECT_DEBITSame as FPX but for business banking (higher limits up to RM 1,000,000)
Touch n’ GoEWALLETUser 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

#MethodXendit URLPurposeService
1POSThttps://api.xendit.co/payment_requestsCreate FPX/TnG payment requestXendit::PaymentRequestService
2POSThttps://api.xendit.co/sessionsCreate card payment session (Session JS)Xendit::PaymentService
3GEThttps://api.xendit.co/payment_requests/{id}Fetch payment request details (3DS auth URL)Xendit::PaymentService.fetch_payment_request

FE → BE API Calls

#MethodBE EndpointPurpose
1POST/api/v5/reservationsCreate reservation (auto-creates Xendit payment request inline)
2POST/api/v5/xendit_payment_requestsCreate standalone payment request (alternative flow)
3POST/api/v5/xendit_payment_sessionsCreate card payment session (credit card only)
4POST/api/v5/xendit_voucher_payment_sessionsCreate voucher payment session (gift cards)

Xendit → BE (Webhooks)

#MethodBE EndpointPurpose
1POST/api/webhook/xenditReceive 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" }        │                               │

FE sends payment_method_type + channel_code alongside 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

EventPayment TypeAction
payment.succeededFPX / TnGUpdate charge → Mark reservation paid → Firebase {status: paid}
payment.failedFPX / TnGFirebase {status: payment_failed}
invoice.paidFPX / TnG (legacy Invoice API)Same as payment.succeeded
payment.captureCredit CardUpdate charge → Mark reservation paid
payment_session.completedCredit CardUpdate charge → Mark reservation paid
payment_session.failedCredit CardFirebase {status: payment_failed}
payment_session.expiredCredit CardFirebase {status: payment_failed}
payment_session.pendingCredit CardLog only (informational)
payment_session.createdCredit CardLog 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:

ParamTypeRequiredDescription
payment_method_typeStringYes"fpx" (B2C and B2B) or "touchngo". Use a _FPX_BUSINESS channel_code for B2B.
channel_codeStringFPX onlyBank code e.g. "CIMB_FPX". Not needed for Touch n’ Go
payment_request_idStringNoOnly if using Flow A (standalone). Omit for auto-create

Note: payment_method_type can also be sent as omise_payment_type or payment_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 type
  • data.channel_code — from Externals::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_config endpoint 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 via derive_charge_price().

3. Get Redirect URL from Firebase

After the reservation is created:

  1. Read the reservation response (meta.payment_request_id confirms Xendit was used)
  2. Listen to Firebase at reservations/{reservation_id}
  3. Read the authorization_url field — 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:

StatusMeaning
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_requests separately — 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

ColumnTypeDescription
idbigintPrimary key
payment_request_idstringXendit payment request ID (e.g., pr-xxx)
amount_centsintegerAmount in cents/subunits
currencystringCurrency code (e.g., MYR)
statusstringPayment status
channel_codestringBank/method code (e.g., CIMB_FPX, TOUCHNGO)
customer_idstringXendit customer ID
paid_atdatetimeWhen payment was confirmed
reservation_idbigintFK to reservations
voucher_transaction_idbigintFK to voucher_transactions
ticket_transaction_idbigintFK to ticket_transactions

Success statuses: SUCCEEDED, COMPLETED

externals_xendit_customers

ColumnTypeDescription
idbigintPrimary key
customer_idstringXendit customer ID
reservation_idbigintFK to reservations
voucher_transaction_idbigintFK to voucher_transactions

users (new column)

ColumnTypeDescription
xendit_customer_id_mystringXendit Malaysia customer ID

FPX Bank Codes

B2C (Personal Banking)

Channel CodeBank
AFFIN_FPXAffin Bank
AGRO_FPXAgroBank
ALLIANCE_FPXAlliance Bank
AMBANK_FPXAmBank
BOC_FPXBank of China
BSN_FPXBank Simpanan Nasional
CIMB_FPXCIMB Bank
HLB_FPXHong Leong Bank
HSBC_FPXHSBC Bank
ISLAM_FPXBank Islam
KFH_FPXKuwait Finance House
MAYB2E_FPXMaybank2E
MAYB2U_FPXMaybank2u
MUAMALAT_FPXBank Muamalat
OCBC_FPXOCBC Bank
PUBLIC_FPXPublic Bank
RAKYAT_FPXBank Rakyat
RHB_FPXRHB Bank
SCH_FPXStandard Chartered
UOB_FPXUOB Bank

B2B (Business Banking)

Same banks with _BUSINESS suffix (e.g., CIMB_FPX_BUSINESS), plus:

  • BNP_FPX_BUSINESS — BNP Paribas
  • CITIBANK_FPX_BUSINESS — Citibank
  • DEUTSCHE_FPX_BUSINESS — Deutsche Bank

Key Files Reference

Services

FilePurpose
app/services/xendit/payment_request_service.rbCreates FPX/TnG payment requests via Xendit API
app/services/xendit/payment_service.rbCreates card payment sessions + fetches payment requests
app/services/xendit/webhook_handler_service.rbProcesses all Xendit webhook events
app/services/xendit/charge_updater_service.rbUpdates charge/customer DB records from webhooks
app/services/xendit/card_saver_service.rbSaves card info (card payments only, not FPX/TnG)
app/services/reservation_service/create.rbReservation creation with auto Xendit payment request

Controllers

FilePurpose
app/controllers/api/v5/xendit_payment_requests_controller.rbStandalone FPX/TnG payment request endpoint
app/controllers/api/v5/xendit_payment_sessions_controller.rbCard payment session endpoint
app/controllers/api/v5/xendit_voucher_payment_sessions_controller.rbVoucher payment session endpoint
app/controllers/api/webhooks_controller.rbXendit webhook handler
app/controllers/api/v5/concerns/tmp_reservation.rbAuto-create payment request in create_v2 flow

Models

FilePurpose
app/models/externals/xendit/charge.rbXendit charge records
app/models/externals/xendit/customer.rbXendit customer records

Workers

FilePurpose
app/workers/mark_transaction_as_paid_worker.rbSidekiq job to mark reservation as paid

Config

FilePurpose
config/routes.rbRoute definitions (lines 2759-2761, 3073)

Return URLs

URLWhen
{web_host}/restaurants/{slug}/payment-success/{hash}After successful payment
{sub_domain}/payment-failedAfter failed payment