Skip to content

Commerce Domain

Handles the full ticketing purchase lifecycle — from order creation through payment gateway integration (JohorPay) to email confirmation and PDF receipt generation. Supports both member and non-member (guest) purchases, paid and free orders.

Handles:

  • Order creation — member and guest checkout
  • Free order creation — zero-amount tickets
  • Payment gateway — JohorPay integration (FPX / bank transfer)
  • Payment redirect flow — process → gateway → return callback
  • Order management — admin view with pagination and filters
  • PDF receipt generation — downloadable ticket PDF
  • Refund flow (planned)
  • QR code ticket validation (planned — via Zoo API in Tenancy)

Architecture

graph LR
    Customer["Customer Browser"]
    CoreAPI["CoreAPI"]
    JohorPay["JohorPay Gateway"]
    DB["Ticketing DB\n(order_ticket_group, order_ticket_info)"]
    EmailService["Email Service"]
    PDFService["PDF Service"]

    Customer -->|POST /api/orderTicketGroup| CoreAPI
    CoreAPI --> DB
    CoreAPI -->|POST /payment/process| JohorPay
    JohorPay -->|GET /payment/return| CoreAPI
    CoreAPI -->|send confirmation| EmailService
    CoreAPI --> PDFService

Data Model

erDiagram
    OrderTicketGroup {
        uint orderTicketGroupId
        string langChosen
        uint ticketGroupId
        string custId
        string transactionId
        string orderNo
        string transactionStatus
        string bankCurrentStatus
        float totalAmount
        string buyerName
        string buyerEmail
        string admitDate
        bool isEmailSent
    }

    OrderTicketInfo {
        uint orderTicketInfoId
        uint orderTicketGroupId
        string itemId
        float unitPrice
        string itemDesc1
        string itemDesc2
        string variant
        int quantityBought
        string admitDate
        string encryptedId
    }

    OrderTicketLog {
        uint orderTicketLogId
        uint orderTicketGroupId
        string performedBy
        string type
        string title
        string message
        string date
    }

    OrderTicketGroup ||--o{ OrderTicketInfo : "contains"
    OrderTicketGroup ||--o{ OrderTicketLog : "has logs"

Order Status Values

transactionStatus Meaning
INITIATED Order created, payment not yet attempted
PENDING Redirected to gateway, awaiting callback
SUCCESS Payment confirmed
FAILED Payment rejected or expired
FREE Zero-amount order, no payment needed

Purchase Flow

sequenceDiagram
    participant Customer
    participant CoreAPI
    participant JohorPay

    Customer->>CoreAPI: POST /api/orderTicketGroup\n{ ticketGroupId, variants, buyerInfo }
    CoreAPI->>CoreAPI: Validate availability + create order (INITIATED)
    CoreAPI-->>Customer: 200 { orderNo, billId, totalAmount }

    Customer->>CoreAPI: POST /payment/generateToken
    CoreAPI->>JohorPay: Fetch rand_key token
    JohorPay-->>CoreAPI: { token }
    CoreAPI-->>Customer: { token }

    Customer->>CoreAPI: POST /payment/process (form POST)
    CoreAPI->>JohorPay: Forward form + checksum (SHA-512)
    JohorPay-->>Customer: Bank selection page (redirect)

    Customer->>JohorPay: Complete bank transfer
    JohorPay->>CoreAPI: GET /payment/return?...
    CoreAPI->>CoreAPI: Decrypt payload, update order to SUCCESS
    CoreAPI->>CoreAPI: Send confirmation email + PDF
    CoreAPI-->>Customer: Redirect to /payment/success

Endpoints

Orders (/api)

GET /api/orderTicketGroups

List all orders for the authenticated user. Requires auth.

Query params: page, limit, status

Response 200

[
  {
    "orderTicketGroupId": 1,
    "orderNo": "ORD-20260601-001",
    "transactionStatus": "SUCCESS",
    "totalAmount": 50.00,
    "buyerName": "Ahmad Bin Ali",
    "buyerEmail": "ahmad@example.com",
    "admitDate": "2026-07-15",
    "createdAt": "2026-06-01T10:00:00Z"
  }
]


GET /api/orderTicketGroup

Get a single order by ?orderNo=<orderNo> or ?transactionId=<transactionId>.


GET /api/orderNonMemberInquiry

Guest order lookup by ?email=<email>&orderNo=<orderNo>. No auth required.


POST /api/orderTicketGroup

Create a paid order. Requires a valid customer account.

Request

{
  "langChosen": "en",
  "ticketGroupId": 3,
  "custId": "CUST-001",
  "buyerName": "Ahmad Bin Ali",
  "buyerEmail": "ahmad@example.com",
  "admitDate": "2026-07-15",
  "productDesc": "Zoo Negara General Admission",
  "items": [
    {
      "itemId": "VAR-001",
      "variant": "Adult",
      "unitPrice": 25.00,
      "quantityBought": 2,
      "itemDesc1": "Adult Ticket",
      "itemDesc2": "General Admission",
      "itemDesc3": ""
    }
  ]
}

Response 200

{
  "orderNo": "ORD-20260601-001",
  "billId": "BILL-123",
  "productId": "PROD-003",
  "totalAmount": 50.00,
  "msgToken": "<token>",
  "transactionId": "<uuid>"
}


POST /api/orderTicketGroup/free

Create a zero-amount order. Skips payment flow and immediately marks as FREE.

Request — same shape as paid order.

Response 200 — order confirmation with PDF ticket attached.


GET /api/orderTicketGroups/management

Paginated order list for admin. Requires ADMIN, SYSADMIN, or MEMBER role.

Query params: page, limit, query, status, startDate, endDate, ticketGroupId


Payment (/payment)

POST /payment/generateToken

Get a JohorPay rand_key token before initiating a payment.

Response 200

{ "success": true, "token": "<rand_key>" }


POST /payment/bankList

Fetch available FPX banks from JohorPay.

Request

{ "mode": "individual" }

mode values: individual (personal FPX) or corporate (business FPX).

Response 200

{ "success": true, "banks": [{ "bankCode": "MBBEMYKL", "bankName": "Maybank" }] }


POST /payment/process

Form POST that forwards to the JohorPay gateway with a SHA-512 checksum. The response is an HTML auto-submit form that redirects the browser to the bank selection page.

Browser redirect only

This endpoint returns HTML, not JSON. It is designed to be submitted by the browser as a form, not called by JavaScript fetch/XHR.


GET /payment/return

JohorPay callback URL. Decrypts the transaction payload, updates the order status, triggers email + PDF, and redirects the customer to /payment/success or /payment/failure.


GET /payment/decrypt

Developer utility — decrypts a raw JohorPay payload without updating any order. Returns the parsed transaction data.


PDF

GET /pdf/receipt

Generate and download the ticket PDF for a completed order.

Query params: orderNo=<orderNo>

Response 200application/pdf