--- url: >- https://acquirer-api-docs-v4-en.pingpongx.com/en/notes/integrate/non-hosted-apm/index.md description: >- Explain the Non-hosted Local Payment API integration flow for merchants that build their own checkout page and connect e-wallets, online banking, and other local payment methods through API Only. --- PingPong Non-hosted Local Payment is a lightweight, high-security back-end solution designed for cross-border transactions. Through standardized API integration, merchants can connect a broad range of global local payment methods, including e-wallets, online banking, and bank cards, with a single integration path. ## Payment Flow ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#E3F2FD', 'primaryTextColor': '#0D47A1', 'primaryBorderColor': '#1976D2', 'lineColor': '#1565C0', 'secondaryColor': '#BBDEFB', 'tertiaryColor': '#90CAF9', 'background': '#F8FBFF', 'mainBkg': '#E3F2FD', 'secondBkg': '#BBDEFB', 'tertiaryBkg': '#90CAF9', 'actorBkg': '#2196F3', 'actorBorder': '#1976D2', 'actorTextColor': '#FFFFFF', 'actorLineColor': '#1565C0', 'signalColor': '#0D47A1', 'signalTextColor': '#0D47A1', 'noteBkgColor': '#E1F5FE', 'noteTextColor': '#01579B', 'noteBorderColor': '#0288D1', 'loopTextColor': '#0D47A1', 'activationBkgColor': '#B3E5FC', 'activationBorderColor': '#0277BD' } }}%% sequenceDiagram participant Buyer as ๐Ÿ‘ค Shopper participant Client as ๐Ÿ’ป Merchant Front End participant Server as ๐Ÿช Merchant Server participant PP as ๐Ÿ”„ PingPongCheckout participant APM as ๐Ÿงพ Local Payment Channel Buyer->>Client: 1. Open checkout page and choose a local payment method Client->>Server: 2. Submit order info, payment method, and device info Server->>PP: 3. Call Create Order and Pay PP-->>Server: 4. Return code, status, and action Server-->>Client: 5. Return payment continuation parameters Client->>APM: 6. Continue the payment flow according to action Buyer->>APM: 7. Complete login, authorization, or payment confirmation APM-->>Client: 8. Optionally redirect back to payResultUrl APM-->>PP: 9. Return the payment processing result PP-->>Server: 10. Push asynchronous payment notification Server-->>PP: 11. Return HTTP 2xx to acknowledge receipt Server->>PP: 12. Actively query the transaction when needed Server-->>Client: 13. Return merchant order status ``` Flow summary: 1. The shopper chooses a local payment method on the merchant checkout page. 2. The merchant front end submits order information, payment method, and device information to the merchant server. 3. The merchant server calls [Create Order and Pay](/en/notes/checkout/api/uniformly/) to create the payment order. 4. PingPongCheckout returns the synchronous response. If the shopper needs to continue, the response includes `action`. 5. The merchant front end continues the shopper payment flow according to `action`. The following steps use redirect-based local payment methods as the example. 6. After the shopper completes payment, PingPongCheckout pushes the payment result through `notificationUrl`. 7. If the merchant does not receive the notification for a long time, it should actively call [Transaction Query](/en/notes/checkout/api/getOne/) to confirm the final result. ::: warning Note If the shopper returns from the local payment page to `payResultUrl`, it only means the shopper is back on the merchant page. It does not mean the payment has succeeded. Order posting, fulfillment, or entitlement delivery must rely on the server-side asynchronous notification or transaction query result. ::: ## API List Non-hosted local payment typically involves the following APIs and notifications: | Phase | Type | Document | Required | Purpose | |:---|:---|:---|:---:|:---| | Payment Creation | API | [Create Order and Pay](/en/notes/checkout/api/uniformly/) | Required | Create the local payment order and get payment continuation parameters | | Payment Result | Notification | [Payment Notification](/en/notes/notify/payment/notify/) | Required | Receive the final payment result and update the merchant order status | | Payment Result | API | [Transaction Query](/en/notes/checkout/api/getOne/) | Recommended | Actively verify the transaction status when notifications are delayed, not received, or need fallback confirmation | | Refund | API | [Refund Request](/en/notes/checkout/api/modifications/refund/) | As needed | Initiate a refund for a successful local payment transaction | | Refund | Notification | [Refund Notification](/en/notes/notify/refundNotify/) | Recommended | Receive the refund result and update the refund status | | Refund | API | [Refund Query](/en/notes/checkout/api/getRefund/) | Recommended | Actively confirm the refund status when notifications are delayed, not received, or during reconciliation | ## Integration Preparation Before you start development, make sure the following items are ready. | Item | Description | |:---|:---| | Account information | `clientId` and `accId` have been obtained, and the shop status is confirmed to be able to transact | | Payment method | The target local payment method has been enabled, and its supported countries, currencies, amount range, and refund capability have been confirmed | | Signing capability | Request signing has been completed according to the [Signature Guide](/en/notes/guide/sign/), and all request parameters participate in signing | | Signature verification capability | The merchant can verify the `sign` in PingPongCheckout responses and asynchronous notifications | | Notification URL | A public `notificationUrl` is ready to receive asynchronous payment notifications | | Result page URL | `payResultUrl` is ready for redirect-based payment methods to return the shopper to the merchant page | | Order status handling | The merchant system stores `merchantTransactionId`, `requestId`, `transactionId`, and order status, and supports idempotent updates | ::: tip Tip Different local payment methods may require extra fields such as `customer`, `goods`, `billingAddress`, `shippingAddress`, or a specific `device.orderTerminal`. Always follow the target payment method page and the [Create Order and Pay](/en/notes/checkout/api/uniformly/) parameter table. ::: ::: warning Note `notificationUrl` must be a complete public URL. Do not use `localhost`, `127.0.0.1`, or an internal IP, and do not append query parameters to the URL. ::: ## Integration Steps Complete the integration in the following steps: 1. [Step 1: Display available payment methods](#step-1-display-available-payment-methods) 2. [Step 2: Create the payment order](#step-2-create-the-payment-order) 3. [Step 3: Continue the payment flow according to action](#step-3-continue-the-payment-flow-according-to-action) 4. [Step 4: Get the final payment result](#step-4-get-the-final-payment-result) ### Step 1: Display available payment methods Display the payment method name and identifier that you plan to support on the shopper checkout page, so the shopper can choose based on preference and scenario. The payment method list page should be implemented by your own front end. When displaying payment methods, we recommend filtering at least by the following conditions: | Condition | Handling recommendation | |:---|:---| | Trading country | Use the order country or shopper checkout country to match `tradeCountry` | | Currency | Use the currency supported by the payment method. Refer to [Transaction Currency](/en/notes/appendix/transactionCurrency/) for amount precision | | Terminal | Prepare `device.orderTerminal` for Web, WAP, App, and other scenarios | | Payment method type | Write the shopper-selected payment method into `paymentMethod.type` | ### Step 2: Create the payment order Call [Create Order and Pay](/en/notes/checkout/api/uniformly/) to create the payment order. You need to collect the shopper payment method, order information, device information, payment amount, and then submit the payment request from the merchant server. Payment request key parameters: | Parameter | Required | Description | |:---|:---:|:---| | `captureDelayHours` | M | Local payment uses `0`, meaning immediate capture | | `amount` | M | Transaction amount | | `currency` | M | ISO 4217 three-letter transaction currency | | `merchantTransactionId` | M | Merchant order ID, unique order identifier | | `requestId` | M | Payment request ID, globally unique | | `tradeCountry` | M | Trading country, using ISO 3166-1 alpha-2 code | | `shopperIP` | M | Shopper IP | | `paymentMethod.type` | M | Local payment method selected by the shopper | | `goods` | M | Goods information, including at least `goods[].name`, `goods[].unitPrice`, `goods[].number`, and `goods[].imgUrl` | | `device.orderTerminal` | M | Order terminal. `01` mobile browser (WAP), `02` PC browser (WEB), `04` iOS App, `05` Android App | | `notificationUrl` | O | Payment result notification URL, recommended | | `payResultUrl` | C | Merchant result page URL for redirect-based local payment methods | | `merchantUserId` | M | Merchant-side unique user ID | | `customer.email` | C | Shopper email. Required for physical goods merchants, and also required by some payment methods | | `customer.phone` | C | Shopper phone number. Required by some local payment methods. Follow the specific payment method requirements | For full parameter details, see [Create Order and Pay](/en/notes/checkout/api/uniformly/). Payment request example (`bizContent` only): ```json { "captureDelayHours": 0, "amount": "1000", "currency": "THB", "merchantTransactionId": "ORDER_202606100001", "requestId": "REQ_202606100001", "tradeCountry": "TH", "shopperIP": "203.0.113.10", "merchantUserId": "USER_12345", "notificationUrl": "https://www.example.com/checkout/notify", "payResultUrl": "https://www.example.com/checkout/result", "paymentMethod": { "type": "TrueMoney Wallet" }, "customer": { "email": "buyer@example.com", "phone": "66123456789" }, "goods": [ { "name": "Digital product", "description": "Order item", "sku": "SKU_001", "imgUrl": "https://www.example.com/product/SKU_001.png", "unitPrice": "1000", "number": "1", "virtualProduct": "Y" } ], "device": { "orderTerminal": "02" } } ``` ::: tip Tip For readability, the examples in this guide expand `bizContent` as a JSON object. In real requests, process `bizContent` and the signature according to [Create Order and Pay](/en/notes/checkout/api/uniformly/) and the [Signature Guide](/en/notes/guide/sign/). ::: Response key fields: | Parameter | Description | |:---|:---| | `transactionId` | PingPong transaction ID | | `merchantTransactionId` | Merchant order ID | | `requestId` | Payment request ID | | `status` | Transaction status | | `action` | Payment continuation parameters returned when the shopper needs to continue | Response example (`bizContent` only): ```json { "transactionId": "2026061050010001", "merchantTransactionId": "ORDER_202606100001", "requestId": "REQ_202606100001", "amount": "1000.000000", "currency": "THB", "status": "PROCESSING", "paymentMethod": { "type": "TrueMoney Wallet" }, "action": { "type": "PAYMENT_REDIRECT_URL", "paymentRedirectUrl": "https://sandbox-acquirer-static.pingpongx.com/payment/apm.html?token=example" }, "payResultUrl": "https://www.example.com/checkout/result", "transactionTime": "1781080205000", "captureDelayHours": 0 } ``` `status` in the synchronous response may return the following values: | `status` | Meaning | Next action | |:---|:---|:---| | `SUCCESS` | The synchronous response already succeeded | The merchant server can update the order status, but we still recommend using asynchronous notification or query to ensure final consistency | | `FAILED` | Payment failed | Ask the shopper to choose another payment method or retry | | `PROCESSING` | Payment is in progress | If `action` is returned, continue with Step 3 and wait for the asynchronous notification or actively query the transaction | ### Step 3: Continue the payment flow according to action When `status` in the synchronous response is `PROCESSING` and `action` is returned, the front end needs to continue the payment flow according to `action`. The following explanation uses redirect-based local payment methods where `action.type=PAYMENT_REDIRECT_URL`. | `action.type` | Key field | Front-end handling | |:---|:---|:---| | `PAYMENT_REDIRECT_URL` | `action.paymentRedirectUrl` | Redirect the shopper to this URL and let the shopper complete payment on the target payment page | ::: warning Payment continuation notes - `action.paymentRedirectUrl` is the complete payment continuation link returned by PingPongCheckout. The merchant front end should use the original value returned by the server directly and should not append extra parameters or replace the domain or path. - We recommend opening the payment continuation link in a top-level page, system browser, or system-level web container. If you must use `WKWebView` or `WebView`, complete real-device verification in advance. ::: After the merchant server receives the continuation link from PingPong, pass the URL to the front end and let the merchant front end redirect the shopper to the payment method page. The following examples show how the merchant front end loads the payment continuation link:
::: tabs @tab Web ```js function redirectOnWeb(action) { if (action?.type !== 'PAYMENT_REDIRECT_URL') return; const paymentWindow = window.open(action.paymentRedirectUrl, '_blank'); if (!paymentWindow) { window.location.assign(action.paymentRedirectUrl); } } ``` @tab WAP ```js function redirectOnWap(action) { if (action?.type !== 'PAYMENT_REDIRECT_URL') return; window.location.href = action.paymentRedirectUrl; } ``` @tab App (iOS) ```objc // Objective-C example: NSURL *url = [NSURL URLWithString:paymentRedirectUrl]; if (url) { [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:^(BOOL success) { if (!success) { // Ask the shopper to retry or use another payment method } }]; } // Swift example: guard let url = URL(string: paymentRedirectUrl) else { return } UIApplication.shared.open(url, options: [:]) { success in if !success { // Ask the shopper to retry or use another payment method } } ``` @tab App (Android) ```java // Java example: try { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(paymentRedirectUrl)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } catch (Exception e) { e.printStackTrace(); } // Kotlin example: try { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(paymentRedirectUrl)) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(intent) } catch (e: Exception) { e.printStackTrace() } ``` :::
### Step 4: Get the final payment result For local payments, we recommend confirming the final result through server-side asynchronous notification first. If the notification is delayed, not received, notification handling fails, or the merchant needs to verify the result immediately after the shopper returns, you can actively call transaction query. For the full transaction query integration solution, see [After Payment > Transaction Query](#transaction-query). If you use the asynchronous notification flow, the merchant server should first return HTTP `2xx` to acknowledge receipt, and then update the order according to the notification status. Payment notification example: ```json { "accId": "2023042011040310224447", "clientId": "2023042011040310224", "code": "000000", "description": "Transaction succeeded", "sign": "{{Sign}}", "signType": "SHA256", "bizContent": { "amount": "1000.000000", "currency": "THB", "transactionId": "2026061050010001", "merchantTransactionId": "ORDER_202606100001", "requestId": "REQ_202606100001", "notifyType": "RECHARGE", "paymentMethod": { "type": "TrueMoney Wallet" }, "status": "SUCCESS", "transactionTime": "1781080205000", "transactionEndingTime": "1781080265000", "captureDelayHours": 0 } } ``` Notification key fields: | Parameter | Description | Handling recommendation | |:---|:---|:---| | `sign` / `signType` | Notification signature and signature type | Verify the signature before processing business logic | | `notifyType` | Notification type. Payment notification is always `RECHARGE` | Used to identify the event type | | `transactionId` | PingPong transaction ID | Save it for later query and reconciliation | | `merchantTransactionId` | Merchant order ID | Used to locate the merchant order | | `requestId` | Payment request ID | Used to locate the current payment request | | `amount` / `currency` | Transaction amount and currency | Must be checked against the merchant order amount and currency | | `status` | Payment result status | Update the merchant order according to the status | Recommended handling for `status` in the notification: | `status` | Handling recommendation | |:---|:---| | `SUCCESS` | Payment succeeded. The merchant can fulfill the order or grant entitlements | | `FAILED` | Payment failed. Keep the order unpaid or failed and allow the shopper to retry | | `CANCEL` | Payment was cancelled or rejected by risk control. Do not treat the order as successful | Notification response example: ```http HTTP/1.1 200 OK Content-Type: text/plain; charset=UTF-8 Content-Length: 2 OK ``` ## After Payment ### Transaction Query When the shopper returns from the payment page to the merchant result page, the asynchronous notification is delayed, notification handling fails, or the merchant needs to run a compensation task, you can call [Transaction Query](/en/notes/checkout/api/getOne/) to get the latest transaction status. Transaction query request example (`bizContent` only): ```json { "merchantTransactionId": "ORDER_202606100001", "requestId": "REQ_202606100001" } ``` Transaction query response example (`bizContent` only): ```json { "transactionId": "2026061050010001", "merchantTransactionId": "ORDER_202606100001", "requestId": "REQ_202606100001", "amount": "1000.000000", "currency": "THB", "status": "SUCCESS", "paymentMethod": { "type": "TrueMoney Wallet" }, "transactionTime": "1781080205000", "transactionEndingTime": "1781080265000" } ``` Query response key fields: | Parameter | Handling recommendation | |:---|:---| | `status` | The merchant order status should follow this field | | `transactionId` | Save the PingPong transaction ID for later refund, reconciliation, or query | | `amount` / `currency` | Check against the merchant order amount and currency | | `transactionEndingTime` | Final transaction time, which can be used as the order completion time | Recommended handling for transaction query statuses: | `status` | Handling recommendation | |:---|:---| | `SUCCESS` | Payment succeeded and the order can be completed | | `FAILED` | Payment failed. The shopper may be allowed to retry | | `PROCESSING` | Payment is still in progress. Continue waiting for the asynchronous notification or query again later | | `CANCEL` | Payment was cancelled or rejected by risk control. Do not treat it as successful | | `CLOSED` | The order has been closed and can no longer be paid | ### Refund Refund capability varies by payment method, including whether refunds are supported, the refund window, whether partial refunds are supported, and whether multiple refunds are supported. Refer to [Payment Methods](/en/notes/paymentMethods/overview/) or the target payment method page to confirm the exact refund capability. After a payment succeeds, if the shopper requests a refund, the merchant cancels a paid order, or part or all of the funds need to be returned, you can call [Refund Request](/en/notes/checkout/api/modifications/refund/) for the original transaction. PingPongCheckout supports the following refund capabilities: - Full refund - Partial refund - Multiple refunds, as long as the cumulative refund amount does not exceed the refundable amount of the original transaction Refer to [Refund](/en/notes/onlinePayment/modify/Refund/) for the refund integration solution. For request parameters, response fields, and handling rules, see [Refund Request](/en/notes/checkout/api/modifications/refund/). To confirm the refund result, see [Refund Query](/en/notes/checkout/api/getRefund/). ### Reconciliation To understand statement generation rules and settlement cycles, refer to [Settlement Cycle and Reconciliation Statement](/en/notes/reconciliation/settlementCycle/). If you need statement download capability, refer to [SFTP Service Application](/en/notes/reconciliation/transactionStatementDownload/).