# FAQs
Source: https://sdk.qfapi.com/integration/FAQs
**Q1. In the provided test account credentials, which field identifies the partner and which identifies the merchant?**
> A1. If you are an agent providing payment services to merchants, `X-QF-APPCODE` and `ClientKey` identify the **partner**, while `MCHID` identifies the **merchant**.\
> If you are a direct merchant, then `X-QF-APPCODE` and `ClientKey` identify the **merchant**, and `MCHID` is typically not provided.
**Q2. Can I use the given test or production account in a different country?**
> A2. No. Both test and production accounts are **country-specific** and cannot be used outside their designated region.
**Q3. How do I test a transaction?**
> A3. You can use a real wallet (e.g., Alipay or WeChat Pay) to test transactions in the test environment, which simulates the production setup. Contact our technical support if you need assistance.
**`Q4. I received response code 1143 or 1145. What should I do?`**
> A4. Continue polling the transaction status using the enquiry API.\
> If your business process requires binary logic (success/failure), you may treat the transaction as **failed**, and later issue a refund if a successful asynchronous notification is received.
**Q5. The payment method I want to integrate is not listed in the documentation. What should I do?**
> A5. Use the [Common Payment Parameters](/integration/api-reference/request-format) and refer to the **Notes** section at the end of the [Payment Types](/integration/api-reference/paytypes) page for any special instructions.
**Q6. Can I refund a transaction made a few days ago?**
> A6. Refunds are only allowed if the **same-day unsettled transaction amount** is **equal to or greater** than the refund amount.
**Q7. Will funds be transferred to my bank account when testing in the sandbox environment?**
> A7. No. Transactions in the sandbox environment are **not settled**. Please remember to **immediately refund** test transactions after they are created.
**Q8. Can I use my overseas Alipay wallet to make a payment?**
> A8. No. Only Alipay wallets that are **real-name verified Mainland China accounts** can be used for cross-border transactions.
# API Usage Guidelines
Source: https://sdk.qfapi.com/integration/api-reference/api-usage-guidelines
## Overview
To ensure fair usage and maintain optimal performance of our platform, we have implemented an API rate limit policy. This policy outlines the limits on API requests and the appropriate handling of any rate limit violations.
## Rate Limit
* **Limit**: Each merchant is allowed a maximum of **100 requests per second (RPS)** and **400 requests per minute**.
* **Response on Exceeding Limit**: If a merchant exceeds this limit, the API will respond with a **HTTP 429 Too Many Requests** status code.
## Usage Guidelines
To help you make the most of our API and to avoid hitting the rate limit, please consider the following best practices:
1. **Batch Requests**: Where applicable, use batch processing to minimize the number of individual requests.
2. **Efficient Data Retrieval**: Optimize your queries to reduce the frequency of requests. Utilize filtering and pagination to retrieve only the necessary data.
3. **Caching Responses**: Implement caching strategies to store responses temporarily and avoid repeated requests for the same data.
4. **Monitor Usage**: Keep track of your API usage to ensure you stay within the set limits. Implement logging to analyze your request patterns.
## Error Handling
In the event of a rate limit violation, the following practices should be followed:
1. **Handle HTTP 429 Response**:
* Implement logic in your application to gracefully handle the HTTP 429 response by:
* Pausing further requests for a specified duration (e.g., retry after a few seconds).
* Logging the error for monitoring and alerting purposes.
2. **Exponential Backoff**:
* When retrying after receiving a 429 response, implement an exponential backoff strategy to gradually increase the wait time between retries.
## Spike Traffic Management
If you anticipate a spike in traffic, such as during a promotional event, please contact our technical support team:
* **Technical Support Email**: [technical.support@qfpay.com](mailto:technical.support@qfpay.com)
By reaching out in advance, we can assist you in managing your traffic and ensure uninterrupted service during peak times.
# Supported Currencies
Source: https://sdk.qfapi.com/integration/api-reference/currencies
This section lists the currency codes accepted by the QFPay API. The values passed in the `txcurrcd` parameter should match one of the supported currency codes listed below.
All codes follow the standard ISO 4217 format (3-letter uppercase).
| Currency Code | Description |
| ------------- | -------------------- |
| AED | Arab Emirates Dirham |
| CNY | Chinese Yuan |
| EUR | Euro |
| HKD | Hong Kong Dollar |
| IDR | Indonesian Rupiah |
| JPY | Japanese Yen |
| MMK | Myanmar Kyat |
| MYR | Malaysian Ringgit |
| SGD | Singapore Dollar |
| THB | Thai Baht |
| USD | United States Dollar |
| CAD | Canadian Dollar |
| AUD | Australian Dollar |
Some payment methods may only support HKD. Always verify with your integration manager before initiating non-HKD transactions.
# Payment Types
Source: https://sdk.qfapi.com/integration/api-reference/paytypes
The `pay_type` parameter specifies which payment method is used to process a transaction. Each value represents a distinct payment channel, such as Alipay, WeChat Pay, or a card scheme. The correct code must be passed in the request body when calling `/trade/v1/payment` or similar endpoints.
This value directly affects how the transaction is routed, and whether specific UI or authorisation is required (e.g., scanning a QR code, invoking a native wallet, etc.).
### Where to use
Include `pay_type` in your payment request payload. For example:
```json theme={null}
{
"pay_type": "800101",
"txamt": "1000",
...
}
```
Not all `pay_type` values are enabled for every merchant. Please refer to your onboarding configuration or contact [technical.support@qfpay.com](mailto:technical.support@qfpay.com) for clarification.
## Supported Payment Types
| Code | Description |
| ------ | ----------------------------------------------------------------------------------------------- |
| 800008 | Consumer Present QR Code Mode (CPM) for WeChat, Alipay, UNIONPAY Quick Pass, PayMe |
| 800101 | Alipay Merchant Presented QR Code Payment in store (MPM) (Overseas Merchants) |
| 800108 | Alipay Consumer Presented QR Code Payment (CPM) (Overseas & HK Merchants) |
| 801101 | Alipay Online WEB (in browser Chrome etc.) Payment (Overseas Merchants) |
| 801107 | Alipay Online WAP (in mobile browser Chrome etc.) Payment (Overseas Merchants) |
| 801110 | Alipay in-APP Payments (Overseas Merchants) |
| 801501 | Alipay Merchant Presented QR Code (MPM) Payment (HK Merchants) |
| 801510 | Alipay In-App Payment (HK Merchants) |
| 801512 | Alipay Online WAP Payment (HK Merchants) |
| 801514 | Alipay Online WEB Payment (HK Merchants) |
| 800201 | WeChat Merchant Presented QR Code Payment (MPM) (Overseas & HK Merchants) |
| 800208 | WeChat Consumer Presented QR Code Payment (CPM) (Overseas & HK Merchants) |
| 800207 | WeChat JSAPI Payment - WeChat Official Account Payment (in Wechat App)(Overseas & HK Merchants) |
| 800212 | WeChat H5 Payment (In mobile browser) |
| 800210 | WeChat in-APP Payment (Overseas & HK Merchants) |
| 800213 | WeChat Mini-Program Payment (Overseas & HK Merchants) |
| 805801 | PayMe Merchant Presented QR Code Payment in store (MPM) (HK Merchants) |
| 805808 | PayMe Consumer Presented QR Code Payment (CPM) (HK Merchants) |
| 805814 | PayMe Online WEB (in browser Chrome etc.) Payment (HK Merchants) |
| 805812 | PayMe Online WAP (in mobile browser Chrome etc.) Payment (HK Merchants) |
| 800701 | UNIONPAY Quick Pass Merchant Presented QR Code Payment (MPM) |
| 800708 | UNIONPAY Quick Pass Consumer Presented QR Code Payment (CPM) |
| 800712 | UNIONPAY WAP Payment (HK Merchants) |
| 800714 | UNIONPAY PC-Web Payment (HK Merchants) |
| 802001 | FPS Merchant Presented QR Code Payment (MPM) (HK Merchants) |
| 802010 | FPS Merchant Presented APP Payment |
| 803701 | Octopus dynamic QRC Payment - Merchant Present Mode (MPM) (HK Merchants) |
| 803712 | Octopus WAP Payment (HK Merchants) |
| 803714 | Octopus PC-Web Payment (HK Merchants) |
| 802801 | Visa / Mastercard Online Payments |
| 802808 | Visa / Mastercard Offline Payments |
| 806527 | ApplePay Online Payments |
| 806708 | UnionPay Card Offline Payments |
| 806808 | American Express Card Offline Payments |
## Special Remarks
* **801101**: Transaction amount must be **greater than 1 HKD**.
# Request Format
Source: https://sdk.qfapi.com/integration/api-reference/request-format
### HTTP Request
`POST ../trade/v1/payment`
Listed below are the most common parameters for the payment endpoint. Please refer to the payment scenario applicable to you for additional parameters.
***
### Common Payment Request Parameters
| Attribute | Mandatory | Type | Description |
| -------------- | ------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `txamt` | Yes | Int(11) | Amount of the transaction. Unit in cents (i.e. 100 = \$1). Suggest value > 200 to avoid risk control. |
| `txcurrcd` | Yes | String(3) | Transaction currency. View the [Currencies](/integration/api-reference/currencies) table for a complete list of available currencies |
| `pay_type` | Yes | String(6) | Please refer to the section [Payment Types](/integration/api-reference/paytypes) for a complete list of payment types |
| `out_trade_no` | Yes | String(128) | External transaction number / Merchant platform transaction number: This parameter must be unique for each payment and refund request under the same merchant account in the system. |
| `txdtm` | Yes | String(20) | Transaction time format: YYYY-MM-DD hh:mm:ss |
| `auth_code` | Yes (CPM only) | String(128) | Specifies the authorisation code for scanning a barcode/QR Code. Each `auth_code` can be used only once and expires within one day. For testing CPM with Alipay or WeChat Pay, the `auth_code` can be extracted from the consumer wallet or scanned from the barcode manually. |
| `expired_time` | No (MPM only) | String(3) | QRC expiration time in minutes. Default: 30 mins. Minimum: 5 mins, Maximum: 120 mins. Available for: 800201, 800101, 801512, 801501, 801107, 801101, 801010, 801510 |
| `goods_name` | No | String(64) | Goods name or item description. Cannot exceed 20 alphanumeric characters or contain special characters. Must be UTF-8 encoded if in Chinese. Required for App payments. |
| `mchid` | No | String(16) | May or may not be given to merchant. **`If mchid is assigned`**, it is mandatory to provide it in API requests. **`If mchid is not assigned`**, it must not be included in the request. |
| `udid` | No | String(40) | Unique device ID. Used for internal transaction tracking. |
| `notify_url` | No | String(256) | URL to receive asynchronous notifications once payment is completed. |
***
### Signature Requirement
For instructions on how to generate a valid signature, refer to [Signature Generation](/integration/preparation/authentication-and-signature).
### Request Description
| Field | Description |
| ------------ | -------------------------------------------------- |
| Character | `UTF-8` |
| Method | **POST/ GET** (Depends on individual API function) |
| Content-type | `application/x-www-form-urlencoded` |
### Required Parameter Settings in HTTP Header to Request the API
| Field | Mandatory | Description |
| --------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-QF-APPCODE` | Yes | App code assigned to the merchant |
| `X-QF-SIGN` | Yes | Signature generated according to the signature formulation method described above |
| `X-QF-SIGNTYPE` | No | Signature algorithm used to generate the signature. If SHA256 is used, the developer must pass the value as `SHA256`. The default value is `MD5` in case this field is not passed to the API. |
# Response Format
Source: https://sdk.qfapi.com/integration/api-reference/response-format
### API Response Format
All QFPay APIs return JSON-formatted responses. A standard successful response will follow the structure below:
```json theme={null}
{
"respcd": "0000",
"respmsg": "success",
"data": {
"txamt": "100",
"out_trade_no": "20231101000001",
"txcurrcd": "HKD",
"pay_type": "800101",
"txdtm": "2023-11-01 10:00:00"
}
}
```
### Field Description
| Field | Type | Description |
| --------- | ---------- | ------------------------------------------------------------------ |
| `respcd` | String(4) | Return code. `"0000"` means success. Other codes indicate failure. |
| `respmsg` | String(64) | Message description of the `respcd` value. |
| `data` | Object | Contains payment transaction data. See details below. |
#### Signature Verification
In critical integrations, developers are encouraged to verify the response signature (if present in headers) to ensure data integrity.
The response may also include the `X-QF-SIGN` and `X-QF-SIGNTYPE` headers, which can be validated by:
1. Extracting the `data` fields in the same sorted order.
2. Concatenating them into a string with format: `key1=value1&key2=value2&...`
3. Appending the client key.
4. Generating the MD5 hash and comparing it to the received signature.
For the full logic to generate and verify the signature, refer to [Signature Generation](/integration/preparation/authentication-and-signature).
# Transaction Status Codes
Source: https://sdk.qfapi.com/integration/api-reference/status-codes
Standard QFPay API response codes and recommended integration handling.
# Transaction Status Codes
The table below lists common `respcd` values returned by QFPay APIs.
`respcd = 0000` indicates the API request was processed successfully.
The actual transaction result should be determined using business fields such as `txstatus` or by calling the Transaction Enquiry API.
***
# Successful Response
| Code | Description |
| ---- | ---------------------- |
| 0000 | Transaction successful |
***
# Request & Signature Errors
| Code | Description |
| ---- | --------------------------------- |
| 1102 | Duplicate request |
| 1103 | Request format error |
| 1104 | Request parameter error |
| 1108 | Signature error |
| 2011 | Transaction serial number repeats |
**Integration Guidance**
* Validate required parameters
* Ensure correct signature algorithm and sorting
* Ensure `out_trade_no` is unique per merchant
***
# Device & Store Status Errors
| Code | Description |
| ---- | ----------------------------------------------- |
| 1105 | Device not activated |
| 1106 | Invalid device |
| 1107 | Device not allowed |
| 1241 | Store does not exist or status incorrect |
| 1242 | Store configuration error |
| 1243 | Store disabled |
| 1251 | Store configuration issue (under investigation) |
**Integration Guidance**
* Verify merchant onboarding configuration
* Confirm device/store activation status
* Contact technical support if persistent
***
# Order State Conflicts
| Code | Description |
| ---- | ---------------------------- |
| 1125 | Transaction already refunded |
| 1142 | Order already closed |
| 1181 | Order expired |
| 1260 | Order already paid |
| 1261 | Order not paid |
| 1262 | Order already refunded |
| 1263 | Order already cancelled |
| 1264 | Order already closed |
| 1266 | Transaction amount mismatch |
| 1267 | Order information mismatch |
| 1268 | Order does not exist |
**Integration Guidance**
* Always query transaction status before retrying
* Avoid resubmitting payment requests blindly
* Ensure idempotent handling in your system
***
# Processing / Pending States
| Code | Description |
| ---- | ---------------------------------------------------------------- |
| 1143 | Order not paid yet or customer entering password |
| 1145 | Transaction processing |
| 1298 | Network delay — if payment may have been completed, do not retry |
**Integration Guidance**
* Do not immediately retry
* Implement delayed polling via Transaction Enquiry API
* Prevent duplicate payment submission
***
# Payment Channel Errors
| Code | Description |
| ---- | --------------------------------------- |
| 1147 | WeChat Pay transaction error |
| 1201 | Insufficient balance |
| 1202 | Invalid or expired payment code |
| 1203 | Merchant account error |
| 1204 | Bank error |
| 1205 | Transaction failed |
| 1212 | UnionPay overseas payment code required |
| 1297 | Banking system busy |
| 2005 | Customer payment code expired |
**Integration Guidance**
* Allow customer to retry payment
* Suggest alternative payment method if applicable
***
# Refund Restrictions
| Code | Description |
| ---- | ------------------------------------------------ |
| 1150 | T0 settlement does not support cancellation |
| 1155 | Refund request denied |
| 1265 | Refund not allowed during restricted time window |
| 1269 | Insufficient unsettled balance |
| 1270 | Currency does not support partial refund |
| 1271 | Transaction does not support partial refund |
| 1272 | Refund amount exceeds refundable limit |
**Integration Guidance**
* Verify original transaction status before refund
* Check refundable balance
* Confirm payment channel refund rules
***
# System / Risk / Network Errors
| Code | Description |
| ---- | --------------------------------------------- |
| 1100 | System under maintenance |
| 1101 | Reversal error |
| 1136 | Transaction not found or not operable |
| 1250 | Transaction forbidden |
| 1252 | System error during request |
| 1254 | System exception |
| 1294 | Transaction prohibited by bank (risk control) |
| 1295 | Network congestion |
| 1296 | Network congestion, retry later |
**Integration Guidance**
* Retry only when appropriate (e.g. 1295, 1296)
* Do not automatically retry risk-related errors (e.g. 1294)
* Log full request and response for troubleshooting
***
# Recommended Handling Strategy
1. Implement idempotency using `out_trade_no`
2. Query transaction status before retrying
3. Apply retry logic only for safe retry codes
4. Log all error responses for audit and reconciliation
# Physical Store / Restaurant
Source: https://sdk.qfapi.com/integration/business-scenarios/physical-store
Recommended QFPay integrations for physical stores, restaurants, cafés, and other in-person payment environments.
This guide helps merchants choose the appropriate QFPay APIs when accepting payments in physical stores, restaurants, cafés, supermarkets, or any other in-person payment environment.
Many real-world deployments require a combination of multiple QFPay APIs rather than a single integration.
## Suitable For
This integration scenario is recommended for merchants operating:
* Restaurants
* Cafés
* Retail stores
* Supermarkets
* Convenience stores
* Service providers
* Any business with face-to-face payments
## Typical Setup
A typical merchant environment may include:
* A cashier system or ECR
* A QFPay POS terminal
* Customer-facing QR codes for ordering or payment
* A merchant backend for order management and reconciliation
## Recommended Integration
| Payment Experience | Recommended Integration | Description |
| -------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Cashier collects payment using a POS terminal | [POS API](/integration/in-store/pos-api/ecr) | The cashier system sends the payment amount to the QFPay POS terminal. Customers can pay using cards, digital wallets, or other supported payment methods. |
| Customer scans a table QR code and pays on their mobile device | H5 Payment / WAP Payment | Customers place an order and complete payment through a mobile web payment page. |
| Customer scans a QR code displayed by the merchant | [Merchant Present Mode (MPM)](/integration/in-store/MPM) | Generate a dynamic payment QR code for customer wallet payments. |
| Merchant scans the customer's wallet barcode | [Consumer Present Mode (CPM)](/integration/in-store/CPM) | Scan customer wallet barcodes for quick payment collection. |
## Example Solution
### Restaurant Table QR Ordering
A common restaurant scenario where customers scan a QR code at their table to browse the menu, place orders, and complete payment from their mobile device.
1. Customer scans the table QR code.
2. The merchant's H5 ordering page opens.
3. Customer places an order.
4. Merchant backend creates a payment using the H5 / WAP Payment API.
5. Customer completes payment through the selected wallet or payment method.
6. Merchant backend receives the payment result through [**Asynchronous Notifications**](/integration/common-api/async-notifications).
7. If necessary, the backend verifies the payment result using [**Transaction Enquiry**](/integration/common-api/transaction-enquiry).
8. The order status is updated and food preparation begins.
## Best Practices
Always implement[Transaction Enquiry](/integration/common-api/transaction-enquiry) as a fallback mechanism when confirming payment results.
Although [**Asynchronous Notifications**](/integration/common-api/async-notifications) should be the primary method for receiving payment results, network issues or customer interruptions may prevent notifications from being received immediately.
QFPay recommends:
* Use [**Asynchronous Notifications**](/integration/common-api/async-notifications) as the primary payment callback.
* Use [**Transaction Enquiry**](/integration/common-api/transaction-enquiry) whenever the payment result is uncertain.
* Do not rely solely on browser redirects or customer actions to determine payment success.
This approach helps prevent orders from remaining in an unknown payment state.
Keep payment processing, order fulfilment, and reconciliation as separate backend processes to improve system reliability and simplify exception handling.
## Related Documentation
Integrate cashier systems with QFPay POS terminals.
Connect ECR systems to QFPay POS terminals.
Mobile browser payment for QR ordering and self-service payment.
Generate dynamic QR codes for customer wallet payments.
Scan customer wallet barcodes for payment collection.
Query payment status to confirm transaction results.
Receive real-time payment notifications from QFPay.
## Next Steps
Once you have identified the appropriate integration methods for your business, continue with the corresponding API documentation to begin implementation.
# Self-Service Retail Machine
Source: https://sdk.qfapi.com/integration/business-scenarios/self-service-retail
Recommended QFPay integrations for unattended machines, vending kiosks, ticketing terminals, and other self-service retail environments.
This guide helps merchants choose the appropriate QFPay APIs when integrating unattended machines, vending kiosks, ticketing terminals, smart lockers, and other self-service retail solutions.
Many self-service deployments require a combination of multiple QFPay APIs rather than a single integration.
## Suitable For
This integration scenario is recommended for merchants operating:
* Vending machines
* Self-service kiosks
* Smart lockers
* Ticketing machines
* Parking payment terminals
* Self-checkout terminals
* Other unattended retail devices
## Typical Setup
A typical self-service environment may include:
* A customer-facing display
* A QR code scanner (optional)
* A connected or embedded QFPay POS terminal
* A merchant backend for order creation and reconciliation
## Recommended Integration
| Payment Experience | Recommended Integration | Description |
| ------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Customer scans a QR code displayed on the machine | [Merchant Present Mode (MPM)](/integration/in-store/MPM) | Generate a dynamic payment QR code for wallet payments and display it on the machine screen. |
| Customer pays using a bank card or Octopus | [POS API](/integration/in-store/pos-api/ecr) | Trigger payment collection on a connected QFPay POS terminal. |
| Customer presents a wallet barcode for scanning | [Consumer Present Mode (CPM)](/integration/in-store/CPM)*(Optional)* | If the machine is equipped with a barcode scanner, wallet barcode payments can be supported. |
## Example Solution
### Self-Service Payment Flow
A common self-service payment scenario where customers purchase products or services directly from an unattended machine.
1. Customer selects products or services.
2. The merchant backend creates an order and calculates the payable amount.
3. For wallet payments, call[Merchant Present Mode (MPM)](/integration/in-store/MPM)to generate a dynamic payment QR code.
4. For card or Octopus payments, call [**POS API**](/integration/in-store/pos-api/ecr) to initiate payment on the connected QFPay POS terminal.
5. The merchant backend receives the payment result through [**Asynchronous Notifications**](/integration/common-api/async-notifications).
6. If necessary, verify the payment result using [**Transaction Enquiry**.](/integration/common-api/transaction-enquiry)
7. Dispense goods or provide the requested service only after payment has been confirmed successfully.
## Best Practices
Always confirm the payment result before dispensing goods or providing services.
QFPay recommends:
* Use [Asynchronous Notifications](/integration/common-api/async-notifications) as the primary mechanism for receiving payment results.
* Use [**Transaction Enquiry**](/integration/common-api/transaction-enquiry) whenever the payment status is uncertain.
* Never rely solely on customer actions or terminal responses to determine payment success.
* Release goods only after the transaction has been confirmed successfully.
This helps prevent revenue loss caused by network interruptions, delayed notifications, or uncertain payment states.
Design your machine to handle payment failures, timeouts, and communication interruptions gracefully. Customers should always receive clear status updates, and unfinished transactions should be recoverable by the merchant backend.
## Related Documentation
Generate dynamic QR codes for customer wallet payments.
Trigger card and Octopus payments on QFPay POS terminals.
Scan customer wallet barcodes for payment collection.
Connect merchant systems with QFPay POS terminals.
Query transaction status to confirm payment results.
Receive real-time payment notifications from QFPay.
## Next Steps
Once you have identified the appropriate integration methods for your self-service solution, continue with the corresponding API documentation to begin implementation.
# Asynchronous Notifications
Source: https://sdk.qfapi.com/integration/common-api/async-notifications
Learn how to handle QFPay's payment and refund asynchronous notifications.
To configure your notification URL, please send your endpoint address along with your merchant and store information to `technical.support@qfpay.com`.
QFPay supports asynchronous notifications for:
* Payments (`"notify_type": "payment"`)
* Refunds (`"notify_type": "refund"`)
These notifications allow merchants to receive real-time updates on transaction results. Since parameters may expand in future versions, your integration should be able to gracefully handle unknown fields.
***
## Overview
When a payment or refund is completed, QFPay will POST a JSON-formatted notification to the merchant-defined callback URL.
It is strongly recommended to **verify** the status of a transaction using the [Transaction Enquiry API](/integration/common-api/transaction-enquiry) in addition to handling the callback.
***
## Notification Rules
1. Only **successful** payments and refunds will trigger a notification.
2. Please register your notification endpoint via email to `technical.support@qfpay.com`. Our team will configure it for you.
3. Merchants must validate the notification using the **signature verification** process below. After successful verification, return:
* HTTP Status Code: `200 OK`
* Response Body: `SUCCESS`
4. If the expected response is not received, QFPay will retry the callback at the following intervals:
* 2 minutes → 10 minutes → 10 minutes → 60 minutes → 2 hours → 6 hours → 15 hours
* Retry stops after receiving `200 OK` and `SUCCESS`
5. One `app_code` + `client_key` pair can only be bound to **one notification URL**. Agents should use a shared endpoint for sub-merchants.
6. **Method:** `POST`\
**Content-Type:** `application/json`\
**Allowed Ports:** `80` and `443` only (for security)
***
## Signature Verification
The verification process differs from regular API requests.
### Steps
1. Extract the value from the `X-QF-SIGN` header.
2. Concatenate the raw request body (JSON string) **+** your `client_key`.
3. Generate an MD5 hash of this combined string.
4. If the hash matches the `X-QF-SIGN` value, the message is valid. Return `200 OK` with body `SUCCESS`.
***
## Signature Example
```python Python theme={null}
import hashlib
import json
# Client Credentials
client_key = "3ABB1BFFE2E0497BB9270978B0BXXXXX"
# Raw Content Data
data = {
"status": "1",
"pay_type": "800101",
"sysdtm": "2020-06-15 10:32:58",
"paydtm": "2020-06-15 10:33:35",
"goods_name": "",
"txcurrcd": "HKD",
"txdtm": "2020-06-15 10:32:58",
"mchid": "O37MRh6Qq5",
"txamt": "10",
"exchange_rate": "",
"chnlsn2": "",
"out_trade_no": "9G3ZIWTG1R3IVSC2AH2O5EGKJQ7I72QO",
"syssn": "20200615000200020000641807",
"cash_fee_type": "",
"cancel": "0",
"respcd": "0000",
"goods_info": "",
"cash_fee": "0",
"notify_type": "payment",
"chnlsn": "2020061522001453561406303428",
"cardcd": "2088032341453564"
}
combine_str = (json.dumps(data) + client_key).encode()
signature = hashlib.md5(combine_str).hexdigest().upper()
print(signature)
```
**Sample Signature Output:**
```text theme={null}
A4021A3B1EBBB0F05451EF94E9EXXXXX
```
***
## Notification Response Example
```json theme={null}
{
"status": "1",
"pay_type": "800101",
"sysdtm": "2020-05-14 12:32:56",
"paydtm": "2020-05-14 12:33:56",
"goods_name": "",
"txcurrcd": "HKD",
"txdtm": "2020-05-14 12:32:56",
"mchid": "lkbqahlRYj",
"txamt": "10",
"exchange_rate": "",
"chnlsn2": "",
"out_trade_no": "YEPE7WTW46NVU30JW5N90H7DHD94N56B",
"syssn": "20200514000300020093755455",
"cash_fee_type": "",
"cancel": "0",
"respcd": "0000",
"goods_info": "",
"cash_fee": "0",
"notify_type": "payment",
"chnlsn": "2020051422001453561444935817",
"cardcd": "2088032341453564"
}
```
***
## Response Field Reference
| Field | Required | Type | Description |
| ---------------------- | -------- | ------ | ------------------------------------------------------------------------------ |
| `status` | Yes | String | `1` = Payment Successful |
| `notify_type` | Yes | String | `payment` or `refund` |
| `pay_type` | Yes | String | QFPay payment code (see [Payment Types](/integration/api-reference/paytypes)) |
| `syssn` | Yes | String | QFPay Transaction Number |
| `out_trade_no` | Yes | String | Merchant Order Number |
| `txamt` | Yes | String | Amount in cents. Suggest value > 200 |
| `txcurrcd` | Yes | String | Transaction currency (see [Currencies](/integration/api-reference/currencies)) |
| `txdtm` | Yes | String | Merchant-side transaction time |
| `sysdtm` | Yes | String | System transaction time (used for settlement cutoff) |
| `paydtm` | Yes | String | Payment time |
| `cancel` | Yes | String | Cancellation status (see definitions below) |
| `respcd` | Yes | String | Always `0000` in async notifications |
| `mchid` | No | String | QFPay Merchant ID |
| `goods_name` | No | String | Product name |
| `goods_info` | No | String | Product description |
| `exchange_rate` | No | String | Currency exchange rate |
| `chnlsn` | No | String | Channel transaction number |
| `chnlsn2` | No | String | Additional channel reference |
| `cardcd` | No | String | Card number |
| `cash_fee` | No | String | Actual paid amount |
| `cash_fee_type` | No | String | Currency of `cash_fee` |
| `cash_refund_fee` | No | String | Actual refund amount |
| `cash_refund_fee_type` | No | String | Refund currency |
***
## Cancel Field Definitions
| Value | Meaning |
| ----- | -------------------------------- |
| 0 | Not Cancelled |
| 1 | **CPM:** Reversed or Refunded |
| 2 | **MPM:** Cancelled |
| 3 | Refunded |
| 4 | Preauth order completed (Alipay) |
| 5 | Partially Refunded |
***
## Notification IP Addresses
Ensure your server allows POST requests from:
* `13.228.112.115`
* `18.138.115.47`
* `18.166.202.92`
# Refund API Guide
Source: https://sdk.qfapi.com/integration/common-api/refund
API guide for processing refunds through QFPay's system.
Only transactions with the return code `0000` (transaction successful) can be refunded.
For credit card payments, QFPay performs **automatic capture**. If a refund is submitted on the **same day**, it will be treated as a **void** and handled by this same refund API.
## API Endpoint
**Endpoint** : `/trade/v1/refund`\
**Method** : `POST`
### Request Headers
```http theme={null}
Content-Type: application/x-www-form-urlencoded
X-QF-APPCODE:
X-QF-SIGN:
```
***
## Request Parameters
| Parameter | Mandatory | Type | Description |
| -------------- | ----------- | ----------- | ----------------------------------------------------------------------------- |
| `syssn` | Yes | String(128) | QFPay transaction number of the **original** transaction to be refunded |
| `out_trade_no` | Yes | String(128) | Unique refund transaction ID (must not repeat across refund requests) |
| `txamt` | Yes | Int(11) | Refund amount in cents (e.g. 100 = \$1). Suggest > 200 to avoid risk control. |
| `txdtm` | Yes | String(20) | Refund request time. Format: `YYYY-MM-DD hh:mm:ss` |
| `mchid` | Conditional | String(16) | Merchant ID. Required only if one is issued. |
| `udid` | No | String(40) | Unique transaction device ID |
***
## Response Parameters
| Parameter | Type | Description |
| ---------------------- | ----------- | ------------------------------------------------------------ |
| `syssn` | String(40) | New refund transaction ID |
| `orig_syssn` | String(128) | Original transaction ID |
| `txamt` | Int(11) | Refunded amount in cents |
| `sysdtm` | String(20) | Refund system time (`YYYY-MM-DD hh:mm:ss`) |
| `respcd` | String(4) | `0000` = success, `1143`/`1145` = processing, other = failed |
| `resperr` | String(128) | Response message |
| `cash_fee` | String | Actual amount paid by user (after discounts) |
| `cash_fee_type` | String | Payment currency (e.g. CNY) |
| `cash_refund_fee` | String | Actual refunded amount |
| `cash_refund_fee_type` | String | Refund currency (e.g. CNY) |
***
## Sample HTTP Body
```http theme={null}
txamt=10&syssn=20191227000200020061752831&out_trade_no=12345678&txdtm=2019-12-27 10:39:39&mchid=ZaMVg*****
```
***
## SDK Code Examples
```python Python theme={null}
import urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error, urllib.parse, hashlib
import requests
from hashids import Hashids
import datetime
import string
import random
# Enter Client Credentials
environment = 'https://test-openapi-hk.qfapi.com'
app_code = 'D5589D2A1F2E42A9A60C37**********'
client_key = '0E32A59A8B454940A2FF39**********'
# Create parameter values for data payload
current_time = datetime.datetime.now().replace(microsecond=0)
random_string = ''.join(random.choices(string.ascii_uppercase + string.digits, k=32))
# Create signature
def make_req_sign(data, key):
keys = list(data.keys())
keys.sort()
p = []
for k in keys:
v = data[k]
p.append('%s=%s'%(k,v))
unsign_str = ('&'.join(p) + key).encode("utf-8")
s = hashlib.md5(unsign_str).hexdigest()
return s.upper()
# Body payload
txamt = '10' # Partial or full refund amount
syssn = '20191227000200020061752831' # Original transaction number
out_trade_no = random_string
txdtm = current_time
key = client_key
mchid = 'ZaMVg*****'
data = {'mchid': mchid, 'txamt': txamt, 'syssn': syssn, 'out_trade_no': out_trade_no, 'txdtm': txdtm}
r = requests.post(
environment + "/trade/v1/refund",
data=data,
headers={'X-QF-APPCODE': app_code, 'X-QF-SIGN': make_req_sign(data, key)}
)
print(r.json())
```
```java Java theme={null}
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class Refund {
public static void main(String args[]){
String appcode="D5589D2A1F2E42A9A60C37**********";
String key="0E32A59A8B454940A2FF39**********";
String mchid="ZaMVg*****"; // Only Agents must provide the mchid
String out_trade_no= "22333444455555";
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String txdtm=df.format(new Date());
String txamt="15";
String syssn="20191227000300020061662295";
Map unsortMap = new HashMap<>();
unsortMap.put("mchid", mchid);
unsortMap.put("txamt", txamt);
unsortMap.put("syssn", syssn);
unsortMap.put("out_trade_no", out_trade_no);
unsortMap.put("txdtm", txdtm);
String data=QFPayUtils.getDataString(unsortMap);
System.out.println("Data:\n"+data+key);
String md5Sum=QFPayUtils.getMd5Value(data+key);
System.out.println("Md5 Value:\n"+md5Sum);
String url="https://test-openapi-hk.qfapi.com";
String resp= Requests.sendPostRequest(url+"/trade/v1/refund", data, appcode,key);
System.out.println(resp);
}
}
```
```javascript Node.js theme={null}
// Enter Client Credentials
const environment = 'https://test-openapi-hk.qfapi.com'
const app_code = 'D5589D2A1F2E42A9A60C37**********'
const client_key = '0E32A59A8B454940A2FF3***********'
// Generate Timestamp
var dateTime = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')
console.log(dateTime)
// Body Payload
var tradenumber = String(Math.round(Math.random() * 1000000000))
console.log(tradenumber)
var payload = {
'syssn': '20191231000300020063521806',
'txamt': '10',
'out_trade_no': tradenumber,
'txdtm': dateTime,
'mchid': 'ZaMVg*****'
};
// Signature Generation
const ordered = {};
Object.keys(payload).sort().forEach(function(key) {
ordered[key] = payload[key]
});
var str = [];
for (var p in ordered) {
if (ordered.hasOwnProperty(p)) {
str.push(p + "=" + ordered[p]);
}
}
var string = str.join("&") + client_key;
const crypto = require('crypto')
var hashed = crypto.createHash('md5').update(string).digest('hex')
var request = require("request");
request({
uri: environment + "/trade/v1/refund",
headers: {
'X-QF-APPCODE': app_code,
'X-QF-SIGN': hashed
},
method: "POST",
form: payload,
}, function(error, response, body) {
console.log(body);
});
```
```php PHP theme={null}
urlencode($syssn),
'out_trade_no' => urlencode(GetRandStr(20)),
'txamt' => urlencode(2200),
'txdtm' => $now_time
);
ksort($fields);
foreach($fields as $key=>$value) {
$fields_string .= $key.'='.$value.'&' ;
}
$fields_string = substr($fields_string , 0 , strlen($fields_string) - 1);
$sign = strtoupper(md5($fields_string . $app_key));
$header = array();
$header[] = 'X-QF-APPCODE: ' . $app_code;
$header[] = 'X-QF-SIGN: ' . $sign;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url . $api_type);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
$output = curl_exec($ch);
curl_close($ch);
$final_data = json_decode($output, true);
print_r($final_data);
ob_end_flush();
?>
```
***
## Sample JSON Response
```json theme={null}
{
"orig_syssn": "20191227000200020061752831",
"sysdtm": "2019-12-27 11:11:23",
"paydtm": "2019-12-27 11:11:26",
"txdtm": "2019-12-27 11:10:38",
"udid": "qiantai2",
"txcurrcd": "EUR",
"txamt": "10",
"resperr": "success",
"respmsg": "",
"out_trade_no": "RGNOEIVU9JZLNP9GGYXWXCW7OEMI720F",
"syssn": "20191227000300020061652643",
"respcd": "0000",
"chnlsn": "2019122722001411461404119764",
"cardcd": ""
}
```
***
## Notes
* Ensure refund amount does not exceed the original transaction value.
* Some wallets may not support partial refunds.
* Refund time limits vary by channel. Contact QFPay support for details.
* For failed refunds (`respcd` not `0000`), retry logic or query via [Transaction Enquiry](/integration/common-api/transaction-enquiry) is advised.
# Reversal API Guide
Source: https://sdk.qfapi.com/integration/common-api/reversal
Use the Reversal API to void incomplete QR code transactions or void authorised Credit Card/Apple Pay transactions. If a QR code transaction has already succeeded, please use the Refund API instead.
This page explains how to use the **Reversal API** to void a transaction. Please note that the behavior of a reversal depends entirely on the **payment method (QR Code vs. Credit Card)**:
1. **QR Code payment flows**: Can only void transactions that have **not yet been successfully completed**. If the original transaction has already succeeded (`respcd=0000`), you must use the [Refund API](/integration/common-api/refund) instead.
2. **Credit Card / Apple Pay flows**: Used to **void an authorised** card transaction. This cancels the transaction directly.
For QR code payments like Alipay and WeChat Pay, a **reversal** is not a **refund**. It can only be initiated if the **original transaction has not been completed successfully**.
***
## Supported Scenarios
The `/trade/v1/reversal` API currently supports the following payment scenarios. Review the specific behavior below based on your integration type.
### 1. QR Code Payment Scenarios (Incomplete Transactions Only)
Typically used when a merchant has created an order but the consumer has not completed payment (e.g., consumer scanned the QR code but abandoned the payment, or a consumer-presented code is pending password confirmation).
* **Alipay MPM (Merchant Presented Mode)**
* `800101`:Alipay Merchant Presented QR Code Payment in store (MPM) (Overseas Merchants)
* `801501`:Alipay Merchant Presented QR Code (MPM) Payment (HK Merchants)
* **WeChat Pay MPM (Merchant Presented Mode)**
* `800201`:WeChat Merchant Presented QR Code Payment (MPM) (Overseas & HK Merchants)
* **Alipay CPM (Consumer Presented Mode)**
* `800108`:Alipay Consumer Presented QR Code Payment (CPM) (Overseas & HK Merchants)
### 2. Online Credit Card / Apple Pay Scenarios (Authorised Transactions)
* **Online Credit Card Payments**
* `802801`: Visa/Mastercard
* **Apple Pay**
* `806527`: Apple Pay
* **Behavior Description**: In these card-based scenarios, executing a reversal acts as a **Void** on the authorised transaction, canceling the authorisation in real time.
> For cancellations involving other wallets, please refer to the [Close API](#reversal-vs-close) or contact QFPay Support for integration guidance.
***
## API Endpoint
* **Endpoint**: `/trade/v1/reversal`
* **Method**: `POST`
A successful reversal will return `respcd=0000`.
For QR code flows, if the transaction has already completed successfully (`respcd=0000` in the payment response), it **cannot** be reversed. Instead, refer to the [Refund API](/integration/common-api/refund).
***
## Request Parameters
| Parameter | Type | Required | Description |
| -------------- | ----------- | -------- | ------------------------------------------------------------------ |
| `mchid` | String(16) | No | QFPay Merchant ID. Required only for agents. |
| `syssn` | String(40) | Yes\* | QFPay transaction number |
| `out_trade_no` | String(128) | Yes\* | Merchant transaction number |
| `txamt` | Int(11) | Yes | Transaction amount in cents (e.g. 100 = \$1). Suggest value > 200. |
| `txdtm` | String(20) | Yes | Original transaction time. Format: `YYYY-MM-DD hh:mm:ss` |
| `udid` | String(40) | No | Unique terminal ID (used for traceability) |
> \*Either `syssn` or `out_trade_no` must be provided.
***
## Response Parameters
| Parameter | Type | Description |
| ------------ | --------- | --------------------------------------------------------------------- |
| `syssn` | String | New QFPay transaction number for the reversal |
| `orig_syssn` | String | QFPay transaction number of the original (to-be-reversed) transaction |
| `txamt` | Int | Reversed amount (in cents) |
| `txcurrcd` | String(3) | Currency code, e.g. HKD |
| `txdtm` | String | Transaction time |
| `sysdtm` | String | QFPay system time of the reversal |
| `chnlsn` | String | Payment channel transaction number (may be empty if not processed) |
| `respcd` | String(4) | Response code (`0000` = success, others = failure or in progress) |
| `resperr` | String | Result message |
| `respmsg` | String | Additional description (if any) |
***
## Code Examples
```python Python theme={null}
import urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error, urllib.parse, hashlib
import requests
from hashids import Hashids
import datetime
import string
import random
# Enter Client Credentials
environment = 'https://test-openapi-hk.qfapi.com'
app_code = '3F504C39125E4886AB4741**********'
client_key = '5744993FBC034DBBB995FA**********'
# Create parameter values for data payload
current_time = datetime.datetime.now().replace(microsecond=0)
random_string = ''.join(random.choices(string.ascii_uppercase + string.digits, k=32))
print(current_time)
# Create signature
def make_req_sign(data, key):
keys = list(data.keys())
keys.sort()
p = []
for k in keys:
v = data[k]
p.append('%s=%s'%(k,v))
unsign_str = ('&'.join(p) + key).encode("utf-8")
print(unsign_str)
s = hashlib.md5(unsign_str).hexdigest()
return s.upper()
# Body payload
txamt = '2500' #In USD,EUR,etc. Cent. Suggest value > 200 to avoid risk control
out_trade_no = '4MDGEJ7L496LAAU1V1HBY9HMOGWZWLXQ'
syssn = '20200305066100020000977812'
txdtm = '2020-03-05 16:50:30'
mchid = 'MNxMp11FV35qQN'
key = client_key
#data ={'txamt': txamt, 'out_trade_no': out_trade_no, 'syssn': syssn, 'txdtm': txdtm, 'udid': udid, 'mchid': mchid}
data ={'txamt': txamt, 'out_trade_no': out_trade_no, 'txdtm': txdtm, 'mchid': mchid}
r = requests.post(environment+"/trade/v1/reversal",data=data,headers={'X-QF-APPCODE':app_code,'X-QF-SIGN':make_req_sign(data, key)})
print(r.json())
```
```java Java theme={null}
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class Refund {
public static void main(String args[]){
String appcode="3F504C39125E4886AB4741**********";
String key="5744993FBC034DBBB995FA**********";
String mchid="MNxMp11FV35qQN"; // Only Agents must provide the mchid
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date=df.format(new Date());
String txdtm="2020-03-05 16:50:30";
String txamt="2500";
String syssn="20200305066100020000977812"; //only syssn or out_trade_no must be provided
String out_trade_no="4MDGEJ7L496LAAU1V1HBY9HMOGWZWLXQ"; //only syssn or out_trade_no must be provided
Map unsortMap = new HashMap<>();
unsortMap.put("mchid", mchid);
unsortMap.put("txamt", txamt);
unsortMap.put("syssn", syssn);
unsortMap.put("out_trade_no", out_trade_no);
unsortMap.put("txdtm", txdtm);
String data=QFPayUtils.getDataString(unsortMap);
System.out.println("Data:\n"+data+key);
String md5Sum=QFPayUtils.getMd5Value(data+key);
System.out.println("Md5 Value:\n"+md5Sum);
//如果是国内钱台,网址是:https://test-openapi-hk.qfapi.com.
String url="https://test-openapi-hk.qfapi.com";
String resp= Requests.sendPostRequest(url+"/trade/v1/reversal", data, appcode,key);
System.out.println(resp);
}
}
```
```javascript Javascript theme={null}
// Enter Client Credentials
environment = 'https://test-openapi-hk.qfapi.com'
app_code = '3F504C39125E4886AB4741**********'
client_key = '5744993FBC034DBBB995FA**********'
// Generate Timestamp
var dateTime = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')
console.log(dateTime)
// Body Payload
const key = client_key
var tradenumber = String(Math.round(Math.random() * 1000000000))
console.log(tradenumber)
var payload = {
'txamt': '2500',
'out_trade_no': '4MDGEJ7L496LAAU1V1HBY9HMOGWZWLXQ', //only syssn or out_trade_no must be provided
'syssn': '20200305066100020000977812', //only syssn or out_trade_no must be provided
'txdtm': '2020-03-05 16:50:30',
'mchid': 'MNxMp11FV35qQN'
};
// Signature Generation
const ordered = {};
Object.keys(payload).sort().forEach(function(key) {
ordered[key] = payload[key] });
console.log(ordered)
var str = [];
for (var p in ordered)
if (ordered.hasOwnProperty(p)) {
str.push((p) + "=" + (ordered[p]));
}
var string = str.join("&")+client_key;
console.log(string)
const crypto = require('crypto')
var hashed = crypto.createHash('md5').update(string).digest('hex')
console.log(hashed)
// API Request
var request = require("request");
request({
uri: environment+"/trade/v1/reversal",
headers: {
'X-QF-APPCODE': app_code,
'X-QF-SIGN': hashed
},
method: "POST",
form: payload,
},
function(error, response, body) {
console.log(body);
});
```
```php PHP theme={null}
urlencode($mchid),
'syssn' => urlencode($syssn),
//'out_trade_no' => urlencode($out_trade_no),
'txcurrcd' => urlencode('HKD'),
'txamt' => urlencode(2200),
'txdtm' => date('2020-03-05 16:50:30'),
);
ksort($fields); //Sort parameters in ascending order from A to Z
print_r($fields);
foreach($fields as $key=>$value) {
$fields_string .= $key.'='.$value.'&' ;
}
$fields_string = substr($fields_string , 0 , strlen($fields_string) - 1);
$sign = strtoupper(md5($fields_string . $app_key));
//// Header ////
$header = array();
$header[] = 'X-QF-APPCODE: ' . $app_code;
$header[] = 'X-QF-SIGN: ' . $sign;
//Post Data
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url . $api_type);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
$output = curl_exec($ch);
curl_close($ch);
$final_data = json_decode($output, true);
print_r($final_data);
ob_end_flush();
?>
```
***
## Sample Response
```json JSON theme={null}
{
"orig_syssn": "20200305066100020000977813",
"syssn": "20200305066100020000977814",
"txamt": "2500",
"txcurrcd": "EUR",
"txdtm": "2020-03-05 16:50:30",
"sysdtm": "2020-03-05 16:54:38",
"chnlsn": "",
"respcd": "0000",
"resperr": "success",
"respmsg": ""
}
```
***
## Additional Notes
* A reversal **does not guarantee** that the user has not been charged. Always verify using [Transaction Enquiry](/integration/common-api/transaction-enquiry).
* Reversal for QR Code payment is intended for **real-time failures** only. Do not use it to refund successful transactions.
* If `respcd=1143` or `1145`, the reversal is in progress. You should poll the [Transaction Enquiry](/integration/common-api/transaction-enquiry) endpoint until the status is confirmed.
***
> The above command returns JSON structured like this:
```json JSON theme={null}
{
"surcharge_fee": "0",
"resperr": "success",
"txdtm": "2020-03-05 16:50:30",
"syssn": "20200305066100020000977814",
"sysdtm": "2020-03-05 16:54:38",
"txcurrcd": "EUR",
"respmsg": "",
"chnlsn2": "",
"cardcd": "",
"udid": "qiantai2",
"txamt": "2500",
"orig_syssn": "20200305066100020000977813",
"surcharge_rate": "0",
"respcd": "0000",
"chnlsn": ""
}
```
## Reversal vs Close
Some wallets (e.g. WeChat Pay MPM) support the `/trade/v1/close` endpoint instead of reversal.
### Supported payment flows and PayTypes
WeChat Pay CPM (Consumer Presented Mode)
* `800008`:Consumer Present QR Code Mode (CPM) for WeChat
* `800208`:WeChat Consumer Presented QR Code Payment (CPM) (Overseas & HK Merchants)
* `801008`:WeChat Pay HK Consumer Presented QR Code Payment (CPM) (Direct Settlement, HK Merchants)
**Method** : `GET`
\*If you would like to use this endpoint on a wallet other than Alipay & Wechat Pay please contact us for instructions.
### Request Parameters
| Attribute | Mandatory | Type | Description |
| -------------- | --------- | ----------- | ----------------------------------------------------------------------------------------------------- |
| `mchid` | No | String(16) | Merchant ID allocated by QFPay |
| `syssn` | Yes\* | String(40) | QFPay transaction number, returned by the system once payment is completed |
| `out_trade_no` | Yes\* | String(128) | External transaction number |
| `txamt` | Yes | Int(11) | Amount of the transaction. Unit in cents (i.e. 100 = \$1). Suggest value > 200 to avoid risk control. |
| `txdtm` | Yes | String(20) | Transaction time format: YYYY-MM-DD hh:mm:ss |
| `udid` | No | String(40) | Unique transaction device ID. Is displayed on the merchant portal. |
\*Either the `syssn` or `out_trade_no` must be provided.
### Response Parameters
| Attribute | Type | Description |
| -------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `orig_syssn` | String(40) | Refers to the original QFPay transaction number |
| `syssn` | String(40) | QFPay transaction number of the cancel/ reversal |
| `out_trade_no` | String(128) | External transaction number |
| `txamt` | Int(11) | Amount of the transaction. Unit in cents (i.e. 100 = \$1) |
| `txcurrcd` | String(3) | Transaction currency. View the [Currencies](/integration/api-reference/currencies) table for a complete list of available currencies |
| `txdtm` | String(20) | Transaction time. Format: YYYY-MM-DD hh:mm:ss |
| `sysdtm` | String(20) | System transaction time. Format: YYYY-MM-DD hh:mm:ss This parameter value is used as the cut-off time for settlements. |
| `chnlsn` | String | Transaction number from payment channel (wallet side) |
| `respcd` | String(4) | Response code 0000 = Reversal/ cancel successul 1143/1145 = Reversal/ cancel in progress others = Reversal/ cancel failed |
| `resperr` | String(128) | Result description |
| `respmsg` | String(128) | Information description |
# Transaction Enquiry
Source: https://sdk.qfapi.com/integration/common-api/transaction-enquiry
Query the status of payments, refunds, and cancellations via QFPay's transaction enquiry API.
After making a payment, refund, or cancellation request, merchants can use the transaction enquiry API to retrieve the latest transaction status.
This API supports querying by:
* `syssn` (QFPay transaction number)
* `out_trade_no` (merchant-side order number)
* `start_time` / `end_time` (time-based filtering)
If querying a refund transaction, the response will also return `origssn`, which refers to the original transaction’s `syssn`.
***
## HTTP Request
* **Endpoint**: `/trade/v1/query`
* **Method**: `POST`
### Request Headers
```http HTTP theme={null}
Content-Type: application/x-www-form-urlencoded
X-QF-APPCODE:
X-QF-SIGN:
```
***
## Request Parameters
Full details are documented in [Common API Request Format](/integration/api-reference/request-format). Key parameters include:
| Parameter | Type | Required | Description |
| -------------- | ----------- | ----------- | ---------------------------------------------------------------- |
| `mchid` | String(16) | Conditional | Required if assigned to the merchant. |
| `syssn` | String(128) | No | QFPay transaction number(s), comma-separated. |
| `out_trade_no` | String(128) | No | Merchant order number(s), comma-separated. |
| `pay_type` | String(6) | No | Payment type(s), comma-separated. |
| `respcd` | String(4) | No | Filter by specific response code (e.g. `0000`). |
| `start_time` | String(20) | No | Format: `YYYY-MM-DD hh:mm:ss`. Required for cross-month queries. |
| `end_time` | String(20) | No | Format: `YYYY-MM-DD hh:mm:ss`. Required for cross-month queries. |
| `page` | Integer | No | Default = 1. |
| `page_size` | Integer | No | Default = 10. Max = 100. |
For cross-month queries, `start_time` and `end_time` are required.
***
## Example Request Body
```http Form Example theme={null}
mchid=&syssn=&start_time=2022-12-01 00:00:00&end_time=2022-12-01 23:59:59
```
***
## Sample Code (Multi-language)
```python Python theme={null}
import hashlib
import requests
environment = 'https://test-openapi-hk.qfapi.com'
app_code = 'D5589D2A1F2E42A9A60C37**********'
client_key = '0E32A59A8B454940A2FF39**********'
def make_req_sign(data, key):
keys = sorted(data.keys())
parts = []
for k in keys:
parts.append(f"{k}={data[k]}")
unsign_str = ("&".join(parts) + key).encode("utf-8")
return hashlib.md5(unsign_str).hexdigest().upper()
data = {
'mchid': 'ZaMVg*****',
'syssn': '20191227000200020061752831'
}
sign = make_req_sign(data, client_key)
r = requests.post(
environment + "/trade/v1/query",
data=data,
headers={
'X-QF-APPCODE': app_code,
'X-QF-SIGN': sign
}
)
print(sign)
print(r.json())
```
```java Java theme={null}
import java.util.HashMap;
import java.util.Map;
public class Enquiry {
public static void main(String[] args) {
String appcode = "D5589D2A1F2E42A9A60C37**********";
String key = "0E32A59A8B454940A2FF39*********";
String mchid = "ZaMVg*****";
String syssn = "20191227000300020061662295";
Map params = new HashMap<>();
params.put("mchid", mchid);
params.put("syssn", syssn);
String data = QFPayUtils.getDataString(params);
String md5Sum = QFPayUtils.getMd5Value(data + key);
String url = "https://test-openapi-hk.qfapi.com";
String resp = Requests.sendPostRequest(url + "/trade/v1/query", data, appcode, md5Sum);
System.out.println(resp);
}
}
```
```javascript Node.js theme={null}
const crypto = require('crypto');
const request = require('request');
const environment = 'https://test-openapi-hk.qfapi.com';
const app_code = 'D5589D2A1F2E42A9A60C37**********';
const client_key = '0E32A59A8B454940A2FF39**********';
const payload = {
syssn: '20191231000300020063521806',
start_time: '2019-12-27 00:00:00',
end_time: '2019-12-31 23:59:59',
mchid: 'ZaMVg*****'
};
const ordered = {};
Object.keys(payload).sort().forEach(k => ordered[k] = payload[k]);
const str = Object.keys(ordered)
.map(k => `${k}=${ordered[k]}`)
.join('&') + client_key;
const sign = crypto.createHash('md5')
.update(str)
.digest('hex')
.toUpperCase();
request({
uri: environment + "/trade/v1/query",
headers: { 'X-QF-APPCODE': app_code, 'X-QF-SIGN': sign },
method: "POST",
form: payload
}, (error, response, body) => {
console.log(body);
});
```
```php PHP theme={null}
'20200311066100020000977841'
);
ksort($fields);
$fields_string = '';
foreach ($fields as $k => $v) {
$fields_string .= $k.'='.urlencode($v).'&';
}
$fields_string = rtrim($fields_string, '&');
$sign = strtoupper(md5($fields_string . $app_key));
$header = array(
'X-QF-APPCODE: ' . $app_code,
'X-QF-SIGN: ' . $sign
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url . $api_type);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
$output = curl_exec($ch);
curl_close($ch);
echo $output;
?>
```
***
## Sample Response
```json JSON theme={null}
{
"respcd": "0000",
"resperr": "Request successful",
"data": [
{
"syssn": "20230423000200020088888888",
"out_trade_no": "YOUR_ORDER_001",
"txamt": "100",
"txcurrcd": "HKD",
"respcd": "0000",
"errmsg": "success",
"pay_type": "801107",
"note": "reference txn note",
"order_type": "payment",
"txdtm": "2023-04-23 12:00:00",
"sysdtm": "2023-04-23 12:00:03",
"cancel": "0",
"cash_fee": "100",
"cash_fee_type": "HKD"
}
]
}
```
# Transaction Notes API
Source: https://sdk.qfapi.com/integration/common-api/transaction-note
Add custom remarks to a completed transaction. Remarks will show in the Merchant Portal.
This endpoint is only available to merchants who **do not** pass in the `mchid`.
Merchants can use this API to attach remarks to a completed transaction. These remarks will be visible in the Merchant Portal and transaction reports.
***
## API Endpoint
**URL**: `/trade/v1/add_note`\
**Method**: `POST`\
**Content-Type**: `application/x-www-form-urlencoded`
***
## Request Parameters
| Attribute | Mandatory | Type | Description |
| --------- | --------- | ----------- | ---------------------------------------------------- |
| `code` | Yes | String(32) | Merchant app code provided by QFPay |
| `syssn` | Yes | String(40) | QFPay transaction number (from original transaction) |
| `note` | Yes | String(200) | Remark content to be added (max 200 characters) |
***
## Request Sample
```http theme={null}
POST /trade/v1/add_note
Content-Type: application/x-www-form-urlencoded
X-QF-APPCODE: D5589D2A1F2E42A9A60C37**********
X-QF-SIGN: 6FB43AC29175B4602FF95F8332028F19
code=A6A49A6******DFE94EA95032¬e=add_note&syssn=20190722000200020081075691
```
## Response Parameters
| Attribute | Type | Description |
| ------------ | ----------- | ------------------------------------------------ |
| `resperr` | String(128) | Transaction result message |
| `respmsg` | String(128) | Error message |
| `respcd` | String(4) | Return code (`0000` = Interface call successful) |
| `data.syssn` | String(40) | QFPay transaction number |
## Response Sample
```json theme={null}
{
"resperr": "Success",
"respcd": "0000",
"respmsg": "",
"data": {
"syssn": "20190722000200020081084545"
}
}
```
# Customs Declaration API
Source: https://sdk.qfapi.com/integration/customs-declaration
Submit cross-border transaction information to customs authorities for Alipay and WeChat payments.
# Customs Declaration API
The Customs Declaration API allows merchants to report cross-border eCommerce transactions to customs authorities via Alipay or WeChat.
This process is required in certain jurisdictions to:
* Comply with cross-border trade regulations
* Enable customs clearance
* Prevent shipment delays
* Ensure payment and goods release alignment
Customs declaration must be performed **after a successful payment**.
***
## When to Use
Use this API when:
* The payment is cross-border
* Customs reporting is legally required
* The payment was completed successfully (`respcd = 0000`)
* The transaction involves Alipay or WeChat
The QFPay Transaction ID (`syssn`) used here refers to the **payment ID**, not a refund ID.
***
# 1. Push Customs Declaration
Submit declaration data to Alipay or WeChat.
***
## Endpoint
§§§http HTTP POST /custom/v1/declare §§§
***
## Request Parameters
| Parameter | Required | Type | Description |
| ------------------ | -------- | ---------- | --------------------------------------------- |
| `trade_type` | Yes | String(8) | Payment platform: `weixin` or `alipay` |
| `syssn` | Yes | String(32) | QFPay Transaction ID (Payment ID) |
| `customs` | Yes | String(20) | Customs bureau code (e.g. `SHANGHAI_ZS`) |
| `mch_customs_no` | Yes | String(20) | Merchant customs registration number |
| `action_type` | No | String | WeChat only: `ADD` (new) or `MODIFY` (update) |
| `mch_customs_name` | No | String | Alipay merchant record name |
| `out_request_no` | No | String(32) | Unique request ID (Alipay only) |
| `amount` | No | String | Declaration amount (Alipay only, e.g. `2.00`) |
***
## Sub-Order Fields (Split or Modified Orders)
Required only when the original payment is split into multiple customs declarations.
| Parameter | Conditional | Type | Description |
| --------------- | ------------------ | ---------- | ----------------------------------- |
| `sub_order_no` | Yes (split orders) | String(64) | Sub-order identifier |
| `fee_type` | Yes (WeChat only) | String(8) | Must be `CNY` |
| `order_fee` | Yes (WeChat only) | String | Total sub-order amount in CNY cents |
| `product_fee` | Yes | String | Product amount in CNY cents |
| `transport_fee` | Yes | String | Shipping amount in CNY cents |
***
## Response Parameters
| Parameter | Description |
| ---------------------------- | --------------------------------------------------------------------- |
| `syssn` | QFPay Transaction ID (Payment ID) |
| `respcd` | `0000` = Success `1143` / `1145` = Pending Others = Failure |
| `resperr` | Error message |
| `respmsg` | Additional info |
| `verify_department` | Customs authority handling the declaration |
| `verify_department_trade_id` | Customs-assigned trade ID |
***
## Response Behaviour
* `0000` → Declaration successfully accepted.
* `1143` / `1145` → Declaration processing.
* Other codes → Failed. Merchant should correct and retry.
Customs declaration processing may not be instantaneous.
***
# 2. Query Customs Declaration
Check the current status of a declaration.
***
## Endpoint
§§§http HTTP POST /custom/v1/query GET /custom/v1/query §§§
***
## Request Parameters
| Parameter | Required | Description |
| -------------- | -------- | ------------------------------------- |
| `trade_type` | Yes | `weixin` or `alipay` |
| `customs` | Yes | Customs bureau code |
| `syssn` | Yes | QFPay Transaction ID (Payment ID) |
| `sub_order_no` | No | Required for split-order declarations |
***
## Response Fields
| Parameter | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `syssn` | QFPay Transaction ID |
| `respcd` | Result code |
| `resperr` | Error reason |
| `respmsg` | Additional info |
| `data` | Array of sub-declaration records including: - `sub_order_no` - `verify_department` - `verify_department_trade_id` - `resperr` - `errmsg` |
***
# 3. Repush Customs Declaration
Use when a previous declaration was not successfully received by customs.
***
## Endpoint
§§§http HTTP POST /custom/v1/redeclare §§§
***
## Request Parameters
| Parameter | Required | Description |
| ---------------- | -------- | ------------------------------------- |
| `trade_type` | Yes | `weixin` or `alipay` |
| `customs` | Yes | Customs bureau code |
| `syssn` | Yes | QFPay Transaction ID (Payment ID) |
| `mch_customs_no` | Yes | Merchant customs registration number |
| `sub_order_no` | No | Required for split-order declarations |
***
## Integration Notes
Customs declaration must only be submitted after a successful payment (`respcd = 0000`). Submitting before payment confirmation may cause rejection.
* Use the **payment ID (`syssn`)**, not refund ID.
* Store `verify_department_trade_id` for reconciliation.
* For WeChat, split declarations must ensure `order_fee = product_fee + transport_fee`.
* For Alipay, ensure `mch_customs_name` matches your registered customs record.
* If `1143` / `1145` is returned, query status instead of immediately retrying.
* Customs declaration rules vary by jurisdiction. Confirm compliance requirements before go-live.
***
For a complete list of return codes, refer to [Transaction Status Codes](/integration/api-reference/status-codes).
# Consumer Present Mode (CPM)
Source: https://sdk.qfapi.com/integration/in-store/CPM
API guide for Consumer Present Mode (CPM) in-store barcode payments.
Consumer Present Mode (CPM) is used for **in-person barcode payments**. The customer presents a dynamic barcode from their wallet app, and the merchant scans it to initiate payment.
This mode is designed exclusively for **offline, face-to-face transactions** (e.g. retail stores, restaurants, physical POS terminals).
***
## When to Use CPM
Use CPM when:
* The customer is physically present
* The customer displays a wallet barcode
* The merchant scans the barcode using a POS device or scanner
* Immediate payment confirmation is required
***
## API Endpoint
**Endpoint** : `/trade/v1/payment` **Method** : `POST`
***
## Supported PayTypes
| PayType | Description |
| ------- | --------------------------------------------- |
| 800008 | WeChat, Alipay, PayMe, Unionpay Quickpass CPM |
| 800108 | Alipay CPM (Overseas & HK Merchants) |
| 800208 | WeChat CPM (Overseas & HK Merchants) |
| 805808 | PayMe CPM (HK Merchants) |
| 800708 | UNIONPAY Quick Pass CPM |
Refer to [Signature Generation](/integration/preparation/authentication-and-signature) for request signing instructions.
***
## Request Parameters
| Field | Required | Type | Description |
| ------------------------------------------------------------------------------------------------ | -------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| [Common Parameters](/integration/api-reference/request-format#common-payment-request-parameters) | — | — | Includes `mchid`, `txamt`, `txdtm`, etc. |
| `auth_code` | Yes (CPM only) | String(128) | Authorisation code scanned from the customer's wallet barcode. This value is unique per transaction and must be captured in real-time. |
***
## Example Request
```http HTTP theme={null}
POST /trade/v1/payment HTTP/1.1
Content-Type: application/x-www-form-urlencoded
X-QF-APPCODE: A6A49A66B4C********94EA95032
X-QF-SIGN: 3b020a6349646684ebeeb0ec2cd3d1fb
auth_code=13485790*******88557&goods_name=qfpay&mchid=R1zQrTdJnn&out_trade_no=Native201907221520536a25477909&pay_type=800208&txamt=10&txcurrcd=HKD&txdtm=2019-07-22 15:20:54&udid=AA
```
***
## Example Response
```json JSON theme={null}
{
"pay_type": "800108",
"sysdtm": "2019-07-22 15:20:54",
"paydtm": "2019-07-22 15:20:56",
"txdtm": "2019-07-22 15:20:54",
"udid": "AA",
"txcurrcd": "EUR",
"txamt": 10,
"resperr": "交易成功",
"respmsg": "OK",
"out_trade_no": "201907221520536a25477909",
"syssn": "20190722000300020081074842",
"respcd": "0000",
"chnlsn": "4200000384201907223585006133"
}
```
***
## Response Behaviour
`respcd` indicates transaction result:
* `0000` → Payment successful
* `1143` or `1145` → Transaction is still processing
When `1143` or `1145` is returned:
* The customer may still be entering their password
* The wallet provider may still be confirming the payment
* The transaction is **not final**
Merchants should call the [Transaction Enquiry API](/integration/common-api/transaction-enquiry) to retrieve the final status.
***
## Integration Notes
* CPM is synchronous but may return a **processing state** (`1143` / `1145`).
* Always treat `1143` / `1145` as pending, not failure.
* Do not immediately retry a payment if processing is returned.
* Use **Transaction Enquiry** to confirm final status.
* Store the QFPay Transaction ID (`syssn`) for reconciliation.
* Implement idempotency using your `out_trade_no`.
***
## Response Parameters
| Field | Type | Description |
| -------------- | ------ | ------------------------------------------------------ |
| `syssn` | String | QFPay Transaction ID (Payment ID for this transaction) |
| `out_trade_no` | String | Merchant Order Number |
| `respcd` | String | Return Code (`0000` = success) |
| `sysdtm` | String | QFPay system processing time |
| `txdtm` | String | Merchant transaction time |
| `paydtm` | String | Actual payment completion time |
| `chnlsn` | String | Wallet-side transaction reference |
# Merchant Present Mode (MPM)
Source: https://sdk.qfapi.com/integration/in-store/MPM
API guide for Merchant Present Mode (MPM) dynamic QR payments.
## MPM API Request
**Endpoint** : `/trade/v1/payment`\
**Method** : `POST`
***
### Supported PayTypes
| PayType | Description |
| ------- | -------------------------------------- |
| 800101 | Alipay MPM (Overseas Merchants) |
| 801501 | Alipay MPM (HK Merchants) |
| 800201 | WeChat MPM (Overseas & HK Merchants) |
| 805801 | PayMe MPM (HK Merchants) |
| 800701 | UNIONPAY Quick Pass MPM |
| 802001 | FPS MPM (HK Merchants) |
| 803701 | Octopus Dynamic QRC MPM (HK Merchants) |
***
### Scenario
The merchant generates a dynamic QR code based on Alipay or WeChat Pay protocol and displays it for the customer to scan. Customers complete the payment by scanning the QR code using their wallets.
This applies to:
* Offline POS environments
* Online browser-initiated flows
Refer to [Signature Generation](/integration/preparation/authentication-and-signature) for signing instructions.
***
## Example Request
```http HTTP theme={null}
POST /trade/v1/payment HTTP/1.1
Content-Type: application/x-www-form-urlencoded
X-QF-APPCODE: A6A49A66B4C********94EA95032
X-QF-SIGN: 3b020a6349646684ebeeb0ec2cd3d1fb
expired_time=10&goods_name=qfpay&limit_pay=no_credit&mchid=R1zQrTdJnn&out_trade_no=Native20190722145741431794b8d1&pay_type=800201&txamt=20&txcurrcd=HKD&txdtm=2019-07-22 14:57:42&udid=AA
```
***
## Example Response
```json JSON theme={null}
{
"surcharge_fee": 0,
"qrcode": "https://qr.alipay.com/bax03190uxd47wbekffy6033",
"pay_type": "800101",
"surcharge_rate": 0,
"resperr": "success",
"txdtm": "2020-04-23 11:09:24",
"out_trade_no": "364ZK6BAJGYHMU3TUX0X7MGIGQL4O8KI",
"syssn": "20200423066200020000976054",
"sysdtm": "2020-04-23 11:09:27",
"txcurrcd": "EUR",
"respmsg": "",
"chnlsn2": "",
"cardcd": "",
"udid": "qiantai2",
"txamt": "1",
"respcd": "0000",
"chnlsn": ""
}
```
***
## Request Parameters
| Field | Required | Type | Description |
| -------------------------------------------------------------- | -------- | ---------- | ------------------------------------------------------------------------ |
| [Common Parameters](/integration/api-reference/request-format) | — | — | Common fields like `mchid`, `txamt`, etc. |
| `pay_tag` | No | String(16) | Wallet specifier. Default: `ALIPAYHK`. Mainland Alipay: `ALIPAYCN` |
| `expired_time` | No | String(3) | QR code expiry in minutes (5–120). Default is 30. Only for WeChat/Alipay |
| `limit_pay` | No | String | Use `no_credit` to disable credit cards. Applicable to CN wallets only |
***
## Response Parameters
| Field | Type | Description |
| --------------------------------------------------------------- | ----------- | ------------------------------------------ |
| `qrcode` | String(512) | Generated QR code URL |
| [Common Parameters](/integration/api-reference/response-format) | — | Common fields like `respcd`, `syssn`, etc. |
# App Call App Android SDK
Source: https://sdk.qfapi.com/integration/in-store/pos-api/app-call-app
Integrates the QFPay HaoJin Android SDK to enable App-to-App payments, refunds, queries, pre-authorisation, and card settlement.
## Latest Version
**version 2.3.4.jar**
The SDK supports configurable scan modes:
* `CollectionReq.SCAN_TYPE_SCAN` — Scan customer QR code
* `CollectionReq.SCAN_TYPE_QRCODE` — Display QR code for customer to scan
### Example
```java theme={null}
CollectionReq req = new CollectionReq(Long.parseLong(money));
req.setScan_type(scan_type);
```
To force QR code display mode (e.g. static QR for customer scanning), use `SCAN_TYPE_QRCODE`.
The default camera used is the rear camera.
***
## Introduction
HaoJin is a mobile application that provides integrated payment collection for merchants.
This SDK enables third-party apps to invoke HaoJin for payment operations.
Supported capabilities:
1. Payment, refund, transaction query, and transaction details
2. Transaction summary and channel configuration retrieval
3. Card transaction query, cancellation, and adjustment
***
## Installation & Setup
### Required Permission
Add to **AndroidManifest.xml**:
```xml theme={null}
```
HaoJin App must be installed on the device.
***
### Integrate JAR
Download and place the SDK in `/libs`, then import in `build.gradle`.
[Download Latest SDK](https://github.com/QFPay/docs/raw/refs/heads/docusaurus-developer-center/static/files/qfpay_haojin_api_2.3.6.zip)
***
### Set Target App ID
```java theme={null}
Config.setTargetAppId("in.haojin.nearbymerchant.oversea");
```
***
### Proguard Rules
Add to `proguard-rules.pro`:
```proguard theme={null}
-dontnote com.qfpay.haojin.model.**
-keep class com.qfpay.haojin.model.** {*;}
```
***
## API Usage
### Create Trade API Instance
```java theme={null}
ITradeAPI mTradeApi = TradeApiFactory.createTradeApi(XXXActivity, this);
```
***
## Collection (Payment)
```java theme={null}
CollectionReq collectionReq = new CollectionReq(100);
collectionReq.setScan_type(CollectionReq.SCAN_TYPE_SCAN);
collectionReq.setOut_trade_no("EXT202312345");
collectionReq.setWait_card_timeout(120);
collectionReq.setPay_method("card_payment");
collectionReq.setCamera_id(0);
int ret = mTradeApi.doTrade(collectionReq);
```
### Parse Result
```java theme={null}
CollectionResp collectionResp =
(CollectionResp) mTradeApi.parseResponse(requestCode, resultCode, data);
if (collectionResp == null) return;
if (collectionResp.isSuccess()) {
Transaction transaction = collectionResp.getPayResult();
} else {
Log.e(TAG, collectionResp.getErrorMsg());
}
```
The returned response may be `null`. Always perform a null check.
***
## Refund
```java theme={null}
RefundReq refundReq = new RefundReq(qfOrderId);
int ret = mTradeApi.doTrade(refundReq);
```
### Parse
```java theme={null}
RefundResp refundResp =
(RefundResp) mTradeApi.parseResponse(requestCode, resultCode, data);
if (refundResp != null && refundResp.isSuccess()) {
Transaction transaction = refundResp.getRefundResult();
}
```
***
## Query Transactions
### Multiple Transactions
```java theme={null}
GetTransListReq req = new GetTransListReq();
req.setChannels(selectedChannel);
req.setTypes(selectedType);
req.setMonth(month);
req.setStartTime(startTime);
req.setEndTime(endTime);
req.setPageSize(pageSize);
req.setPageNum(pageNum);
int ret = mTradeApi.doTrade(req);
```
• Supported types: payment, refund • Time format: yyyy-MM-dd HH:mm:ss • Month format: yyyyMM • Page index starts from 1 • Time range overrides monthly query
***
### Transaction Detail
```java theme={null}
GetTransReq req = new GetTransReq(qfOrderId);
req.setOut_trade_no("EXT20230123");
int ret = mTradeApi.doTrade(req);
```
***
## Transaction Summary
```java theme={null}
CheckTradeSumReq req = new CheckTradeSumReq();
int ret = mTradeApi.doTrade(req);
```
***
## User Configuration
```java theme={null}
GetUserConfigReq req = new GetUserConfigReq();
int ret = getTradeApi().doTrade(req);
```
If user configuration cannot be retrieved, it may indicate an authorisation issue. Contact technical support.
***
## Pre-authorisation
Suitable for scenarios such as hotel check-ins or equipment rental where funds are reserved and later captured or cancelled.
### Deduct
```java theme={null}
PreAuthTransDeductReq req = new PreAuthTransDeductReq(transId);
int ret = mTradeApi.doTrade(req);
```
### Cancel
```java theme={null}
PreAuthTransCancelReq req = new PreAuthTransCancelReq(transId);
int ret = mTradeApi.doTrade(req);
```
### List
```java theme={null}
PreAuthTransListReq req = new PreAuthTransListReq(10, 1);
int ret = mTradeApi.doTrade(req);
```
### Detail
```java theme={null}
PreAuthTransDetailReq req = new PreAuthTransDetailReq(transId);
int ret = mTradeApi.doTrade(req);
```
***
## Card Operations
### Card Refund
```java theme={null}
CardRefundReq req = new CardRefundReq(qfOrderId);
int ret = mTradeApi.doTrade(req);
```
### Card Settlement
```java theme={null}
CardSettleReq req = new CardSettleReq();
int ret = mTradeApi.doTrade(req);
```
***
## Version History
### v2.3.4
* Added scan type configuration
### v2.3.3
* Added `getOut_trade_no` and `getCardscheme` methods
### v2.3.2
* External order number support
* Configurable card swipe timeout
### v2.3.1
* Payment method selection support
* Camera selection support
# ECR Integration Technical Specification
Source: https://sdk.qfapi.com/integration/in-store/pos-api/ecr
Integration protocol and encryption details for connecting POS systems with QFPay ECR.
**Supported Terminal Models**
* **LANDI A8 / A8S** — All payment methods supported
* **PAX A920** — QR code payments only
***
## 1. POS-KEY
POS-KEY is a secret key used to encrypt transaction data. It is generated via the Haojin App.
Encryption is **enabled by default**. Merchants may toggle encryption on/off or regenerate the POS-KEY from the Merchant Portal (MMS). After changes, the POS device must refresh via the Haojin App.
### Refresh POS-KEY
Haojin App → **My → Settings → POS-Key → Generate**
### Check POS-KEY
Merchant Portal → **Settings → Device Settings → POS Key Mgmt**
***
## 2. Encryption
* Algorithm: **AES**
* Key: POS-KEY
* IV: `qfpay202306_hjsh`
* Output: Base64 encoded
***
## 3. Request Payload Format
| Attribute | Mandatory | Type | Description |
| ------------------- | --------- | ------- | -------------------------------------------- |
| `amt` | Yes | Double | Transaction amount (e.g. 10.1 = HKD \$10.10) |
| `func_type` | Yes | Integer | Instruction code |
| `channel` | Yes | String | Wallet/payment method |
| `out_trade_no` | No | String | Merchant reference ID |
| `camera_id` | No | Integer | 0 = back camera (default), 1 = front camera |
| `payment_timeout` | No | Integer | Payment timeout (seconds) |
| `wait_card_timeout` | No | Integer | Card input timeout (default 120s) |
***
### 3.1 Payment
**QR Mode**
* MPM / CPM auto-selected based on last usage
**Camera**
* `0` back (default)
* `1` front
**Timeout**
* Card: time to tap/swipe
* Wallet/QR: time to confirm
* PayMe max timeout: **120 seconds**
**Scan Type**
* `QRCODE_PAY`
* `SCAN_PAY`
**Return to Home**
* `0` stay on result screen (default)
* `1` return to home after transaction
```json theme={null}
{
"content": {
"amt": 100,
"camera_id": 0,
"channel": "card_payment",
"func_type": 1001,
"moveToBack": 1,
"out_trade_no": "456799999999",
"wait_card_timeout": 120,
"scan_type": "SCAN_PAY"
},
"digest": "76b9186077cdc2bc5d78ae921309811d"
}
```
***
### 3.2 Refund / Void
| Attribute | Mandatory | Type | Description |
| ------------------- | --------- | ------- | ------------------------ |
| `orderId` | Yes | String | QFPay transaction ID |
| `refund_amount` | No | String | Partial refund supported |
| `allow_modify_flag` | No | Integer | 0 fixed, 1 allow change |
For Visa, Mastercard, UnionPay Card, and Amex, same-day refunds must be **full amount**.
```json theme={null}
{
"content": {
"allow_modify_flag": 1,
"func_type": 1002,
"orderId": "order_id",
"refund_amount": "0.05",
"moveToBack": 1
},
"digest": "9C8E9FB05C7C24B6CA04EBFA1263EF41"
}
```
***
### 3.3 Print Receipt
```json theme={null}
{
"content": {"orderId": "12345678","func_type": 3001},
"digest":"79fd145311d54d03e4e685d50f15dd7f"
}
```
***
### 3.4 Print Summary
```json theme={null}
{
"content": {"func_type": 3002},
"digest":"79fd145311d54d03e4e685d50f15dd7f"
}
```
***
### 3.5 Transaction Inquiry
```json theme={null}
{
"content": {"orderId": "1234567890","func_type": 4001},
"digest":"99CE8BF9C7304AC964522D10F51660B4"
}
```
***
### 3.6 Cancel Request
```json theme={null}
{
"content": {"func_type": 5001},
"digest": "99CE8BF9C7304AC964522D10F51660B4"
}
```
***
## 4. Signature Generation
```javascript theme={null}
// original payload
content={"amt":100,"channel":"card_payment","func_type":1001,"out_trade_no":"456799999999"}
// sort keys alphabetically
format_content={amt=100,channel='card_payment',func_type=1001,out_trade_no='456799999999'}
// generate digest
digest=md5(format_content + pos_key)
```
If encryption is enabled:
1. Encrypt `content` using AES
2. Generate digest from encrypted payload
***
## 5. Field Definitions
### func\_type
| Value | Description |
| ----- | ------------- |
| 1001 | Payment |
| 1002 | Refund |
| 3001 | Print receipt |
| 3002 | Print summary |
| 4001 | Inquiry |
| 5001 | Cancel |
***
### channel (Payment Method)
| Value | Description |
| -------------- | ---------------- |
| card\_payment | Credit card |
| wx | WeChat Pay |
| alipay | Alipay |
| payme | PayMe |
| union | UnionPay |
| fps | FPS |
| octopus | Octopus |
| unionpay\_card | UnionPay Card |
| amex\_card | American Express |
***
## 6. Response Format
```json theme={null}
{"respcd":"6000","data":"{...}","respmsg":"OK","resperr":""}
```
### Response Codes
| Code | Meaning |
| ---- | ----------------- |
| 6000 | Success |
| 6001 | Cancelled |
| 6002 | Error |
| 4003 | Denied |
| 5001 | Decryption failed |
***
## 7. Communication Methods
### USB
Connect POS to register via USB. Payload must be AES encrypted and parsed according to protocol.
### HTTP / HTTPS
* Default port: `9001`
* POST JSON payload
* AES encrypted
Endpoints:
| Operation | Path |
| ------------- | ---------------------------- |
| Trade | `/api/pos/trade` |
| Refund | `/api/pos/cancel` |
| Print receipt | `/api/pos/print_receipt` |
| Print summary | `/api/pos/transaction_info` |
| Inquiry | `/api/pos/query_transaction` |
For HTTPS setup, please refer to the [ECR Integration HTTPS](/integration/in-store/pos-api/ecr-https) documentation.
### TCP
* Default port: `9002`
* Socket communication
* AES encrypted
***
## 8. USB Protocol Overview
Start indicator: `0x2f6e` Payload max: **65536 bytes** Recommended: ≤ **1024 bytes**
`0x2f6e` represents the ASCII string "/n" — not a newline.
***
## 9. AES Encryption
* AES-128 CBC
* PKCS5 padding
* 16-byte key
* Required for request & response
***
## 10. Serial Port Settings
| Setting | Value |
| ------------ | ----- |
| Baud rate | 9600 |
| Stop bit | 1 |
| Parity | 0 |
| Data bits | 8 |
| Flow control | Off |
***
## 11. USB-Serial Chip Support
| Chip | Supported |
| ---------- | --------- |
| PL2303 HXD | Yes |
| CH340 | No |
| FT232 | No |
Chip support applies only to USB communication mode. HTTP / HTTPS and TCP integrations are unaffected.
# HTTPS Setup for ECR Integration
Source: https://sdk.qfapi.com/integration/in-store/pos-api/ecr-https
Guide to configure secure HTTPS communication between POS and client device for ECR integration.
This guide explains how to configure **HTTPS-based ECR (Electronic Cash Register) integration** between:
* QFPay POS (Haojin App)
* Client device (e.g. cashier web app)
* Merchant backend server (optional for async notification)
***
## Integration Overview
The HTTPS ECR flow requires configuration across three components:
1. **Merchant Portal (MMS)** → Enable HTTPS mode
2. **POS Device (Haojin App)** → Sync and verify HTTPS status
3. **Client Device** → Install certificate and send HTTPS requests
4. *(Optional)* Merchant backend → Receive async notifications
***
## 1. Enable HTTPS in Merchant Portal (MMS)
Go to: 👉 [https://merchant.qfpay.global/main/devicePosMSetting](https://merchant.qfpay.global/main/devicePosMSetting)
Path: `Settings` → `Device Setting` → Search `POS Key List`
* Enable **"HTTPS"**
* Save configuration
***
## 2. Verify HTTPS Mode on POS (Haojin App)
After enabling HTTPS:
* Restart or re-login Haojin App
* Configuration will automatically sync to the POS
Check status:
`Settings` → `ECR Status`
Ensure:
* HTTPS mode is **enabled**
* Certificate is **auto-generated**
Minimum supported version: **4.34.12**
***
## 3. Configure Client Device (Cashier / Web App)
The client device must trust the POS HTTPS certificate.
### Install CA Certificate
Download and install: [ca3.crt](http://cdn.qfpay.com.hk/haojin/ca3.crt)
Verification fingerprints:
$$
SHA-256=9C 0F A4 04 C5 9F D7 A2 7E A3 1F 6B 96 F4 74 01 1B 3F 86 8A A0 84 61 47 4D 75 69 ED 63 0D 32 C3
SHA-1=A7 4C AD CE D7 21 AD CB 77 C8 F6 47 6C 19 E6 C6 4E 0E 6A 61
$$
### Installation Notes
* **Android**: Install under trusted credentials
* **Windows**: `Control Panel → Certificates → Trusted Root Certification Authorities`
***
## 4. Configure Merchant Backend (Async Notification)
For transaction status updates, configure your backend to receive notifications.
### Setup
* Contact QFPay support to register your webhook: 👉 [technical.support@qfpay.com](mailto:technical.support@qfpay.com)
* Provide:
* Merchant ID
* Store ID
* Notification URL
Refer to: 👉 /docs/online-shop/integration-by-use-case/preauth/#async-notification
***
## Configuration Checklist
Before going live, confirm all items:
* POS HTTPS enabled in MMS
* Haojin App shows HTTPS **enabled + certificate generated**
* Client device installed CA certificate correctly
* Certificate fingerprint matches `ca3.crt`
* Client app connects using correct:
* POS IP address
* Secret / credentials
* Webhook / notification URL configured in QFPay
***
## Example: Odoo Integration
Reference: 👉 [https://www.odoo.com/documentation/19.0/applications/sales/point\_of\_sale/payment\_methods/terminals/qfpay.html](https://www.odoo.com/documentation/19.0/applications/sales/point_of_sale/payment_methods/terminals/qfpay.html)
Example:
Merchant domain:
```text theme={null}
shop-abc.odoo.com
```
Notification URL:
```text theme={null}
https://shop-abc.odoo.com/qfpay/notify
```
***
## Troubleshooting
### Connectivity Issues
1. Check POS → `ECR Status` page (ensure HTTPS service is running)
2. Test endpoint via browser or `curl`
3. Restart POS application
4. Try another POS device
5. Check network restrictions:
* Wi-Fi isolation
* Router firewall rules
***
### Fixed IP Address Issues
If connection is unstable:
1. Configure router DHCP settings
2. Bind POS MAC address to a fixed IP
3. Ensure client uses the same IP consistently
***
## Common Pitfalls
* Missing CA certificate → HTTPS request will fail
* Wrong POS IP → connection timeout
* HTTPS enabled in MMS but not synced to POS → restart required
* Webhook not configured → merchant cannot receive transaction updates
***
## Summary
To successfully enable HTTPS ECR:
* Enable in MMS
* Verify on POS
* Install certificate on client
* (Optional) Configure webhook
Once completed, the client device can securely communicate with POS via HTTPS.
# Hosted Checkout Page
Source: https://sdk.qfapi.com/integration/online-shop/checkout-integration/checkout
Integrate QFPay’s hosted checkout to accept multiple payment methods using a secure prebuilt interface.
QFPay provides a prebuilt, secure checkout interface that allows merchants to accept multiple payment methods via simple redirection.
This solution:
* Requires minimal development effort
* Supports both desktop and mobile browsers
* Supports multi-language UI
* Is maintained and secured by QFPay
***
## UI Preview
The checkout page is fully responsive and adapts automatically to different screen sizes.
***
# Customisation Options
Merchants can customise selected aspects of the hosted checkout page to better match their brand identity and business requirements. Customisations are configured by QFPay Technical Support.
The following customisations are currently supported:
### 1. Payment Methods
* Configure which payment methods or wallets are displayed
* Set the default display order of payment methods
#### 2. Branding & Visual Appearance
* Browser favicon (tab icon)
* Page logo
* Header background colour
* "Pay Now" button colour
* Selected page styling (such as brand colours and visual elements)
#### 3. Content & User Experience
* Payment instructions or informational messages (for example, payment guidance, customer service information, or policy notices)
* Return button text and redirect behaviour (such as the destination and label of the **Back** button)
Any UI components not explicitly listed above are currently not customisable.
***
## How to Request Customisation
To request checkout customisation, please provide the following information to technical support:
* `app_code`
* `client_key`
* Detailed customisation requirements
* Brand assets (logo files, colour codes, etc.)
Contact:
[technical.support@qfpay.com](mailto:technical.support@qfpay.com)
***
## Static Asset Requirements
Please ensure all submitted branding assets follow the specifications below:
### PC Logo
* Height: 40px
* No horizontal padding (no blank space on left or right)
### WAP Logo (Mobile)
* Height: 25px
* No horizontal padding
### Favicon
* Size: 15 × 15 px
PNG format with transparent background is recommended.
***
# Environment Notice
If test transactions are performed in the Live Testing environment, please refund them immediately to avoid reconciliation discrepancies.
For environment details, refer to:
[Environment Documentation](/integration/preparation/environments)
***
# Process Flow
1. Customer proceeds to checkout on the merchant website
2. Merchant redirects the customer to the QFPay hosted checkout page
3. Customer selects a payment method and completes payment
4. Customer is redirected back to the merchant website
***
# API Request
## Endpoint
```text TEXT theme={null}
https:///checkstand/#/?
```
## Method
```text TEXT theme={null}
GET
```
***
# Request Parameters
| Parameter | Type | Required | Description |
| ----------------------- | ----------- | -------- | --------------------------------------- |
| `appcode` | String(64) | Yes | Store identifier issued by QFPay |
| `sign_type` | String | Yes | `SHA256` (recommended) or `MD5` |
| `sign` | String | Yes | Generated signature |
| `paysource` | String | Yes | Must end with `_checkout` |
| `txamt` | Int | Yes | Amount in cents (e.g. 1099 = HKD 10.99) |
| `txcurrcd` | String(3) | Yes | Currency code (e.g. HKD) |
| `out_trade_no` | String(128) | Yes | Unique merchant transaction ID |
| `txdtm` | String | Yes | Timestamp (`YYYY-MM-DD HH:mm:ss`) |
| `return_url` | String | Yes | Redirect URL after successful payment |
| `failed_url` | String | Yes | Redirect URL after failed payment |
| `notify_url` | String | Yes | Asynchronous notification URL |
| `mchntid` | String(16) | No | Required in agent scenarios |
| `goods_name` | String(64) | No | Product name |
| `udid` | String(40) | No | Device identifier |
| `expired_time` | String | No | QR expiry time (5–120 minutes) |
| `checkout_expired_time` | String | No | Checkout session expiry |
| `limit_pay` | String | No | `no_credit` disables credit cards |
| `lang` | String | No | `zh-hk`, `zh-cn`, `en` |
| `cancel_url` | String | No | Redirect URL if user cancels |
***
# Creating a Checkout Order
Each `out_trade_no` must be unique to prevent duplicate transactions.
Generate the signature using your `client_key`, then redirect the customer to the constructed checkout URL.
***
# Example: Signature & Redirect
```javascript JavaScript theme={null}
const params = {
appcode: "YOUR_APPCODE",
goods_name: "checkout_product",
out_trade_no: "TXN1234567890",
paysource: "remotepay_checkout",
return_url: "https://merchant.com/success",
failed_url: "https://merchant.com/failed",
notify_url: "https://merchant.com/notify",
sign_type: "SHA256",
txamt: "1000",
txcurrcd: "HKD",
txdtm: "2025-01-01 12:00:00"
};
const apiKey = "YOUR_CLIENT_KEY";
const sorted = Object.keys(params)
.sort()
.map(k => `${k}=${params[k]}`)
.join("&");
const sign = sha256(sorted + apiKey);
const url = `https://test-openapi-hk.qfapi.com/checkstand/#/?${sorted}&sign=${sign}`;
window.location.href = url;
```
***
# Important Notes
* Always generate signatures on your server in production.
* Never expose your `client_key` in frontend code.
* Ensure your `notify_url` accepts POST callbacks.
* It is recommended to verify final transaction status using the Transaction Enquiry API.
# Element SDK
Source: https://sdk.qfapi.com/integration/online-shop/checkout-integration/payment-element
Build custom checkout flows using QFPay’s prebuilt payment UI components.
QFPay’s **Element SDK** enables merchants to build customised checkout experiences using secure, prebuilt payment components hosted by QFPay.
It provides flexible front-end integration while keeping payment processing secure and compliant.
The Element SDK supports standard payments as well as saving a customer's payment method for supported future token-based payment scenarios.
This guide explains how to integrate the Element SDK into your website or application.
***
## Integration Flow

### Flow Summary
1. Create a **Payment Intent** via QFPay API.
2. Initialise the SDK using `QFpay.config()`.
3. Render the payment UI.
4. Collect customer payment details.
5. Initiate the payment using `payment.pay()` or `payment.walletPay()`.
6. Confirm the payment using `confirmPayment()` or `confirmWalletPayment()`.
7. If `customer_id` is provided, QFPay also generates a `token_id` for the saved payment method.
Saving a payment method is optional.
If `customer_id` is not provided, the payment is processed as a standard payment and no payment token is generated.
***
## Supported Payment Methods
The Element SDK supports:
* Alipay (Mainland China & Hong Kong)
* WeChat Pay
* UnionPay / QuickPass
* FPS
* PayMe
* Visa / Mastercard
* Apple Pay
* Visa / Mastercard Pre-authorisation
***
# Integration Steps
## Step 1: Load SDK
Include the SDK script in your page.
```html theme={null}
{/* Sandbox */}
{/* Live Testing */}
{/* Production */}
```
***
## Step 2: Initialise SDK
Initialise the SDK and configure the required region and environment.
```js theme={null}
const qfpay = QFpay.config({
region: 'hk',
env: 'prod'
});
```
| Parameter | Required | Values | Description |
| --------- | -------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `region` | No | `hk` \| `qa` | Region selection. `hk` represents the Hong Kong production environment and `qa` represents the sandbox environment. |
| `env` | No | `prod` \| `test` \| `qa` | Environment selection. `prod` is production, `test` is live testing, and `qa` is sandbox. |
Returns the global `qfpay` object for subsequent SDK operations.
***
## Step 3: Create Payment Intent (Backend)
**Endpoint:** `/payment_element/v1/create_payment_intent`
**Method:** `POST`
### Headers
| Header | Required | Description |
| -------------- | -------- | ----------------- |
| `X-QF-APPCODE` | Yes | Merchant App Code |
| `X-QF-SIGN` | Yes | Request signature |
### Request Parameters
| Parameter | Required | Description |
| -------------- | -------- | --------------------------------------------- |
| `txamt` | Yes | Payment amount in cents (recommended > 200). |
| `txcurrcd` | No | Currency, e.g. `HKD`. |
| `pay_type` | Yes | Payment type code. |
| `out_trade_no` | Yes | Merchant order number. |
| `mchid` | No | Merchant ID (required for agent setups). |
| `return_url` | No | Redirect URL after successful payment. |
| `failed_url` | No | Redirect URL after failed payment. |
| `notify_url` | No | Webhook URL for payment result notifications. |
### Response Example
```json theme={null}
{
"respcd": "0000",
"payment_intent": "38aec7ce...",
"intent_expiry": "2026-01-01 12:00:00"
}
```
***
## Step 4: Retrieve Payment Intent (Frontend)
After your backend creates the Payment Intent, retrieve and validate it from the frontend.
```js theme={null}
const qfpay = QFpay.config();
const result = qfpay.retrievePaymentIntent();
if (result.code === '0000') {
console.log('Payment intent is valid');
} else {
console.error('Invalid or expired payment intent');
}
```
***
## Step 5: Configure Appearance (Optional)
Initialise the UI element handler for rendering the card form or multi-payment interface.
You can provide an `appearance` object to customise the styling and displayed billing address fields.
```js theme={null}
const appearance = {
theme: 'night',
variables: {
fontFamily: 'cursive',
fontWeight: '400',
colorText: 'black',
sizeFontSubTitle: 'inherit',
colourBackground: '#fff',
colourPrimary: '#ced4da',
colourComponentText: 'purple',
sizeComponentText: '15px',
colourErrorMessage: '#da5d4a',
sizeErrorMessage: 'inherit',
colorPaymentButton: '#000000',
colorPaymentButtonText: '#FFFFFF',
colorQRCodeTopPromptContent: '#000000',
colorQRCodeBottomPromptContent: '#000000',
fontWeightQRCodeTopPrompt: '900',
fontWeightQRCodeBottomPrompt: '300'
},
billingAddressDisplay: {
city: true,
address1: true,
address2: true
}
};
const elements = qfpay.element(appearance);
```
***
## Step 6: Render Payment UI
### Enhanced Payment Interface (Wallet + Card)
Use `elements.createEnhance()` to render an enhanced payment interface that supports multiple payment methods.
```js theme={null}
const elements = qfpay.element(appearance);
elements.createEnhance({
selector: '#container',
email: true,
tab: true,
element: 'payment',
lang: 'en'
});
```
### Card Payment Form Only
Use `elements.create()` to render a card-only payment form.
```js theme={null}
elements.create('#container');
```
***
## Step 7: Initiate Payment
### Card Payment
Use `payment.pay()` to collect card payment details and initiate the payment.
```js theme={null}
payment.pay({
goods_name: 'Premium Product',
paysource: 'payment_element'
}, intentParams.payment_intent);
```
### Multi-Payment Interface
Use `payment.walletPay()` to initialise the multi-payment interface.
```js theme={null}
payment.walletPay({
lang: 'en',
goods_info: 'Premium membership',
goods_name: 'VIP Access',
paysource: 'payment_element_checkout',
out_trade_no: intentParams.out_trade_no,
txamt: intentParams.txamt,
txcurrcd: intentParams.txcurrcd,
support_pay_type: [
'Alipay',
'AlipayHK',
'WeChat',
'FPS',
'UnionPay',
'PayMe',
'VisaMasterCardPayment',
'ApplePay',
'VisaMasterCardPreAuth'
]
}, intentParams.payment_intent);
```
After calling `walletPay()`, render the payment interface by calling:
```js theme={null}
elements.createWallet({
selector: '#container'
});
```
* `txamt`, `txcurrcd`, and `out_trade_no` should preferably be generated together with the Payment Intent by your backend and returned to the frontend.
* If `support_pay_type` is omitted, the SDK displays all payment methods enabled for the merchant.
* `walletPay()` does not return the final transaction result. Complete the payment flow by calling `qfpay.confirmWalletPayment()`.
***
## Optional: Generate a Payment Token (Card Payments Only)
Payment token generation is currently supported **only for card payments** initiated using `payment.pay()`.
Payment tokens are **not** generated for wallet payment methods.
The Element SDK supports saving a customer's payment method during a successful payment.
To request payment token creation, provide `customer_id` when calling `payment.pay()` or `payment.walletPay()`.
When `customer_id` is provided and the payment is completed successfully, QFPay generates a payment token and returns a `token_id` associated with the customer.
Saving a payment method is optional.
If `customer_id` is not provided, the payment will be processed normally and **no `token_id` will be generated**.
***
### Token Creation Flow
1. Create a Payment Intent.
2. Render the payment interface using the Element SDK.
3. The customer enters their payment details.
4. Initiate a card payment using `payment.pay()` with `customer_id`.
5. The customer completes the payment.
6. QFPay generates a payment token.
7. A `token_id` is returned.
8. Store the `token_id` securely in your backend.
***
### Token Parameters
The following parameters are available when initiating a **card payment** using `payment.pay()`.
| Parameter | Required | Description |
| ----------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| `customer_id` | Yes (for token creation) | Merchant-defined customer identifier. When provided, QFPay generates a payment token after a successful payment. |
| `token_expiry` | No | Expiry date of the generated payment token. |
| `token_reason` | No | Merchant-defined reason for creating the payment token. |
| `token_reference` | No | Merchant-defined reference for reconciliation or internal tracking. |
***
### Example - Card Payment
```js theme={null}
payment.pay({
goods_name: 'Premium Product',
paysource: 'payment_element',
customer_id: 'CUST123456',
token_expiry: '2026-01-01',
token_reason: 'Save Card',
token_reference: 'INV-0987'
}, intentParams.payment_intent);
```
***
### Payment Result
When a **card payment** is completed successfully and `customer_id` is provided, QFPay generates a payment token and returns a `token_id`.
The returned `token_id` uniquely identifies the customer's saved payment method.
Store the returned `token_id` securely in your backend.
Do not rely solely on browser storage or frontend state for future use.
***
### Token Confirmation
Although `confirmPayment()` and `confirmWalletPayment()` may return a `token_id`, QFPay recommends using the **Token Creation Notification** as the authoritative confirmation that the payment token has been successfully created.
Merchants should persist the `token_id` using the asynchronous notification whenever possible.
The Token Creation Notification uses a **dedicated notification endpoint**, which is different from the standard payment notification endpoint.
To enable Token Creation Notifications, please contact QFPay Technical Support and provide your notification URL together with your Merchant ID and Store ID.
Email: [**technical.support@qfpay.com**](mailto:technical.support@qfpay.com)
Use the synchronous SDK response to provide immediate feedback to the customer.
Use the Token Creation Notification as the final source of truth for persisting the generated `token_id`.
Do not store raw card details collected through the Element SDK.
Store only the QFPay-issued `token_id`.
***
## Step 8: Confirm Payment
After the customer completes the payment, confirm the payment result using the corresponding SDK method.
### Card / Apple Pay
```js theme={null}
const response = qfpay.confirmPayment({
return_url: 'https://example.com'
});
```
### Wallet Payments
```js theme={null}
const response = qfpay.confirmWalletPayment({
return_url: 'https://example.com'
});
```
| Code | Meaning |
| ------ | ------------------- |
| `0000` | Success |
| `1111` | Apple Pay cancelled |
| other | Failed |
When payment token creation is requested, `confirmPayment()` may also return the generated `token_id`.
Token generation is currently supported only for card payments.
***
## Step 9: Query Transaction
If the final payment result cannot be reliably determined from the client-side response, query the transaction status using the inquiry API.
```js theme={null}
payment.inquiry({
out_trade_no: intentParams.out_trade_no
}, intentParams.payment_intent);
```
Use Transaction Enquiry as a fallback mechanism when payment status is uncertain due to network interruptions, browser closure, or other unexpected scenarios.
***
## Important Notes
If `lang` is not specified, the SDK automatically uses the browser language.
Do not place the Element container inside a `
Payment Intent expiry:
* Production: 2 years
* Sandbox: 7 days
Always generate signatures and create Payment Intents from your backend.
Never expose your AppKey or any secret credentials in frontend code.
Do not rely solely on the frontend SDK response to determine the final transaction result.
Implement **Asynchronous Notifications** and **Transaction Enquiry** to reliably confirm payment status before fulfilling an order.
# Alipay In-App Payments
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-payment-type/alipay/alipay-in-app
Integration guide for Alipay in-app payments using AlipayHK or AlipayCN SDK.
This guide explains how to integrate Alipay In-App payments using the Alipay SDK (AlipayHK or AlipayCN). This payment method is used within a native app environment and provides a seamless user experience.
## SDK Download
To begin integration, download the official SDK packages below:
* [Alipay Overseas SDK Documentation](https://global.alipay.com/docs/ac/app/client_integration)
* [AlipayHK SDK Download](https://global.alipay.com/docs/ac/app_hk/download)
* [AlipayHK SDK Trigger Guide](https://global.alipay.com/docs/ac/hkapi/securitypay_pay)
***
## HTTP Request
**Endpoint** : `/trade/v1/payment`\
**Method** : `POST`
| PayType | Description |
| -------- | -------------------------------- |
| `801110` | Alipay In-App (Settle By Alipay) |
| `801510` | Alipay In-App (Settle By QFPay) |
## Required Parameters
| Parameter | Required | Type | Description |
| -------------- | -------- | ----------- | ----------------------------------------------- |
| `txamt` | Yes | Int | Transaction amount in cents. Example: 100 = \$1 |
| `txcurrcd` | Yes | String(3) | Currency code (e.g. HKD) |
| `pay_type` | Yes | String(6) | Use 801110 for Alipay CN, 801510 for AlipayHK |
| `out_trade_no` | Yes | String(128) | Unique merchant transaction ID |
| `txdtm` | Yes | String(20) | Format: `YYYY-MM-DD hh:mm:ss` |
| `goods_name` | Yes | String | Product name |
| `return_url` | Yes | String | Redirect URL after payment |
| `seller_id` | No | String | Alipay seller account |
| `mchid` | No | String(16) | QFPay assigned Merchant ID |
See [Common Payment Parameters](/integration/api-reference/request-format) for shared fields.
***
## Optional Parameters
| Parameter Name | Parameter Key | Required | Type | Description |
| ------------------- | -------------- | ------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------- |
| Product Description | `goods_info` | No | String | Required by Alipay. Special characters are not allowed. |
| Payment Tag | `pay_tag` | No | String(16) | Default: ALIPAYHK For Alipay Mainland use: ALIPAYCN |
| Order Expiry Time | `expired_time` | No (Main-scan only) | String(3) | Expiration time in minutes. Default is 30 minutes. Min: 5, Max: 120. Applicable to WeChat Pay and Alipay |
## Sample Request (Form Payload)
```plaintext theme={null}
txamt=1
&txcurrcd=HKD
&pay_type=801510
&out_trade_no=052711570017898
&txdtm=2021-05-27 11:57:00
&goods_name=goods_name
&goods_info=goods_info
&mchid=nDB64h9qJ1An
&trade_name=trade_name
&goods_detail=goods_detail
&return_url=https://www.qfpay.global/
&pay_tag=ALIPAYHK
&seller_id=testoverseas9191@alipay.com
```
## Response Fields (pay\_params)
These fields are returned by QFPay and should be passed into the Alipay SDK.
| Parameter Key | Sub Parameter Key | Description |
| -------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------- |
| `pay_params` | `partner` | Partner ID |
| | `seller_id` | Unique Alipay user ID of the receiving account |
| | `subject` | Product title / trade title / order title |
| | `body` | Detailed description of the transaction. For multiple items, concatenate descriptions into this field |
| | `total_fee` | Total order amount |
| | `notify_url` | Notification callback URL |
| | `service` | Service name |
| | `cardcd` | Card number |
| | `payment_type` | Payment type |
| | `_input_charset` | Character encoding format |
| | `it_b_pay` | Custom timeout parameter |
| | `return_url` | Redirect URL after payment |
| | `payment_inst` | Payment institution |
| | `currency` | Currency code |
| | `product_code` | Product code |
| | `sign` | RSA signature (Required) |
| | `sign_type` | Signature type |
| | `secondary_merchant_id` | Secondary merchant ID |
| | `secondary_merchant_name` | Secondary merchant name |
| | `secondary_merchant_industry` | Secondary merchant industry |
| `chnlsn` | | Channel transaction number |
| Common Response Parameters | — | — |
## Sample QFPay Response
```json theme={null}
{
"pay_type": "801510",
"sysdtm": "2021-05-27 11:57:02",
"paydtm": "2021-05-27 11:57:02",
"udid": "qiantai2",
"txcurrcd": "HKD",
"txdtm": "2021-05-27 11:57:00",
"txamt": "1",
"resperr": "交易成功",
"respmsg": "",
"out_trade_no": "052711570017898",
"syssn": "20210527154100020004180921",
"pay_params": {
"body": "goods_info",
"forex_biz": "FP",
"seller_id": "2088231067382451",
"secondary_merchant_id": "1000007081",
"service": "mobile.securitypay.pay",
"payment_inst": "ALIPAYHK",
"it_b_pay": "30m",
"secondary_merchant_name": "IFlare Hong Kong Limited (external) - online",
"_input_charset": "UTF-8",
"sign": "iU1yXUnsCK7rJAu0DoN61arVexbIfo3GLR5jr3QzjkZ29INSPhcA4e%2F2%2BdPrsf5huzQAkxVKP0CTfvaGPMYqNkxmhoaJWUH0ZhgYDgKugMvtweBvRqOX2W0h3A%2F%2FIdJuxeyOAuh7bHiuazSB3ZH%2BEQwRGP%2Bkk8Jpha930gHwPtw%3D",
"currency": "HKD",
"out_trade_no": "20210527154100020004180921",
"payment_type": "1",
"total_fee": 0.01,
"sign_type": "RSA",
"notify_url": "https://test-o2-hk.qfapi.com/trade/alipay_hk/v1/notify",
"partner": "2088231067382451",
"secondary_merchant_industry": "5941",
"product_code": "NEW_WAP_OVERSEAS_SELLER",
"return_url": "https://www.qfpay.global/",
"subject": "goods_name"
},
"respcd": "0000",
"chnlsn": "",
"cardcd": ""
}
```
## Using Alipay SDK
After receiving the `pay_params` in the response, construct the `orderInfo` string required by the SDK in the following format:
1. Join all fields in the format: `key="value"`
2. Sort keys in ascending order (alphabetical)
3. Concatenate with `&`
4. Place `sign` and `sign_type` at the end
### Sample
```plaintext theme={null}
_input_charset="UTF-8"&body="goods_info"¤cy="HKD"&forex_biz="FP"&it_b_pay="30m"¬ify_url="https://test-o2-hk.qfapi.com/trade/alipay_hk/v1/notify"&out_trade_no="20210527154100020004180921"&partner="2088231067382451"&payment_inst="ALIPAYHK"&payment_type="1"&product_code="NEW_WAP_OVERSEAS_SELLER"&return_url="https://www.qfpay.global/"&secondary_merchant_id="1000007081"&secondary_merchant_industry="5941"&secondary_merchant_name="IFlare Hong Kong Limited (external) - online"&seller_id="2088231067382451"&service="mobile.securitypay.pay"&subject="goods_name"&total_fee="0.01"&sign="iU1yXUnsCK7rJAu0DoN61arVexbIfo3GLR5jr3QzjkZ29INSPhcA4e%2F2%2BdPrsf5huzQAkxVKP0CTfvaGPMYqNkxmhoaJWUH0ZhgYDgKugMvtweBvRqOX2W0h3A%2F%2FIdJuxeyOAuh7bHiuazSB3ZH%2BEQwRGP%2Bkk8Jpha930gHwPtw%3D"&sign_type="RSA"
```
Make sure you:
* Follow the correct SDK version and region (HK vs CN)
* Use the exact `key` names and `values` returned in `pay_params`
* Keep your sign logic consistent with Alipay SDK format
# Alipay WAP / H5 Payments
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-payment-type/alipay/alipay-wap-h5-payments
Integration guide for Alipay Cross-Border (801107), AlipayHK (801512), and Alipay Service Window H5 (800107).
## Use Cases
* Payment initiated via **mobile browsers** (not inside native apps)
* Suitable for mobile websites, WebApps, or flows where users open links in Safari/Chrome
* Supported wallets:
* Alipay Cross-Border (`801107`)
* AlipayHK (`801512`)
* Alipay Service Window H5 (`800107`) — requires user authorisation (`openid`)
Social apps (e.g. WeChat, Facebook Messenger) may block redirecting to external wallet apps.\
For better conversion, guide users to open payment links in **Safari, Chrome, or the system browser**.
***
## HTTP Request
**Endpoint**: `POST /trade/v1/payment`
### PayType Reference
| PayType | Wallet | Description |
| -------: | ------------------------ | ---------------------------------------------------------------- |
| `801107` | Alipay Cross-Border | International WAP/H5 payment |
| `801512` | AlipayHK | Hong Kong WAP payments |
| `800107` | Alipay Service Window H5 | Alipay Mini Program, Requires user authorisation code (`openid`) |
***
## Request Parameters
For the full list of shared parameters, see:\
[Common Request Parameters](/integration/api-reference/request-format)
Below are **Alipay WAP / H5 specific or commonly required** fields:
| Parameter | Required | Description |
| -------------- | ----------- | ---------------------------------------------------------- |
| `pay_type` | Yes | One of `801107`, `801512`, `800107` |
| `return_url` | Recommended | Redirect after payment completion (browser flow) |
| `notify_url` | Recommended | Backend webhook for payment result |
| `goods_name` | Recommended | Required by some wallets; keep ≤20 chars |
| `openid` | Conditional | Required for `800107` only (Alipay Service Window H5) |
| `expired_time` | Optional | QR / link expiry in minutes (5–120, if supported) |
| `limit_pay` | Optional | Mainland restrictions only (if applicable) |
| `mchid` | Conditional | Include **only if provided by QFPay** for your store setup |
Never hardcode or assume `mchid` is always required.\
Include it only if QFPay has provided it for your store configuration.
***
## Sample Request
```python Python theme={null}
# coding=utf-8
import hashlib
import requests
import datetime
environment = "https://test-openapi-hk.qfapi.com"
app_code = "YOUR_APP_CODE"
client_key = "YOUR_CLIENT_KEY"
def make_req_sign(data, key):
keys = sorted(list(data.keys()))
parts = [f"{k}={data[k]}" for k in keys]
sign_str = ("&".join(parts) + key).encode("utf-8")
return hashlib.md5(sign_str).hexdigest().upper()
txdtm = datetime.datetime.now().replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S")
data = {
"txamt": "10",
"txcurrcd": "HKD",
"pay_type": "801107",
"out_trade_no": "01234567890123",
"txdtm": txdtm,
"goods_name": "test1",
"return_url": "https://merchant.example/success",
"notify_url": "https://merchant.example/notify"
# "mchid": "ZaMVg*****" # include only if provided by QFPay
# "openid": "xxx" # required for pay_type=800107 only
}
headers = {
"X-QF-APPCODE": app_code,
"X-QF-SIGN": make_req_sign(data, client_key)
}
resp = requests.post(environment + "/trade/v1/payment", data=data, headers=headers)
print(resp.json())
```
```java Java theme={null}
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class AlipayWapExample {
public static void main(String[] args) {
String appcode = "YOUR_APP_CODE";
String key = "YOUR_CLIENT_KEY";
String txdtm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
Map params = new HashMap<>();
params.put("txamt", "10");
params.put("txcurrcd", "HKD");
params.put("pay_type", "801107");
params.put("out_trade_no", "01234567890123");
params.put("txdtm", txdtm);
params.put("goods_name", "test1");
params.put("return_url", "https://merchant.example/success");
params.put("notify_url", "https://merchant.example/notify");
// params.put("mchid", "ZaMVg*****"); // include only if provided by QFPay
// params.put("openid", "xxx"); // required for pay_type=800107 only
String data = QFPayUtils.getDataString(params);
String sign = QFPayUtils.getMd5Value(data + key);
String url = "https://test-openapi-hk.qfapi.com";
String resp = Requests.sendPostRequest(url + "/trade/v1/payment", data, appcode, sign);
System.out.println(resp);
}
}
```
```javascript JavaScript theme={null}
const crypto = require("crypto");
const request = require("request");
const environment = "https://test-openapi-hk.qfapi.com";
const app_code = "YOUR_APP_CODE";
const client_key = "YOUR_CLIENT_KEY";
const txdtm = new Date().toISOString().replace("T", " ").replace(/\..+/, "");
const payload = {
txamt: "10",
txcurrcd: "HKD",
pay_type: "801107",
out_trade_no: "01234567890123",
txdtm,
goods_name: "test1",
return_url: "https://merchant.example/success",
notify_url: "https://merchant.example/notify"
// mchid: "ZaMVg*****", // include only if provided by QFPay
// openid: "xxx" // required for pay_type=800107 only
};
const ordered = Object.keys(payload).sort().map(k => `${k}=${payload[k]}`).join("&");
const sign = crypto.createHash("md5").update(ordered + client_key).digest("hex").toUpperCase();
request({
uri: environment + "/trade/v1/payment",
headers: {
"X-QF-APPCODE": app_code,
"X-QF-SIGN": sign
},
method: "POST",
form: payload
}, function (error, response, body) {
console.log(body);
});
```
```php PHP theme={null}
$v) { $pairs[] = $k . '=' . $v; }
$str = implode('&', $pairs) . $key;
return strtoupper(md5($str));
}
$url = "https://test-openapi-hk.qfapi.com/trade/v1/payment";
$app_code = "YOUR_APP_CODE";
$app_key = "YOUR_CLIENT_KEY";
$fields = array(
"txamt" => "10",
"txcurrcd" => "HKD",
"pay_type" => "801107",
"out_trade_no" => "01234567890123",
"txdtm" => date("Y-m-d H:i:s"),
"goods_name" => "test1",
"return_url" => "https://merchant.example/success",
"notify_url" => "https://merchant.example/notify"
// "mchid" => "ZaMVg*****", // include only if provided by QFPay
// "openid" => "xxx" // required for pay_type=800107 only
);
$sign = buildSign($fields, $app_key);
$headers = array(
"X-QF-APPCODE: " . $app_code,
"X-QF-SIGN: " . $sign
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($fields));
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$output = curl_exec($ch);
curl_close($ch);
echo $output;
?>
```
***
## Response Parameters
Refer to shared fields here:\
[Common Response Parameters](/integration/api-reference/response-format)
***
## Sample Response
```json theme={null}
{
"sysdtm": "2020-04-13 11:32:03",
"paydtm": "2020-04-13 11:32:03",
"txcurrcd": "HKD",
"respmsg": "",
"pay_type": "801107",
"udid": "qiantai2",
"txdtm": "2020-04-13 11:32:03",
"txamt": "300",
"resperr": "success",
"out_trade_no": "BUFB3PT9ZDUWEUAE4ATD21JKNHVEIIPV",
"syssn": "20200413000200020087171988",
"respcd": "0000",
"pay_url": "https://globalmapi.alipay.com/gateway.do?...",
"chnlsn": ""
}
```
***
## Asynchronous Notification
After payment completion, QFPay will notify your backend via `notify_url`.
* Notification format: [Asynchronous Notifications](/integration/common-api/async-notifications)
* Verify final status via API: [Transaction Enquiry](/integration/common-api/transaction-enquiry)
Never rely solely on front-end redirection. Always verify the final result via backend notification and signature validation.
***
## Security Considerations
* Follow signature rules: [Signature Generation](/integration/preparation/authentication-and-signature)
* Never expose secret keys in front-end code
* For errors: [Status Codes](/integration/api-reference/status-codes)
***
## Additional References
* Official: Alipay H5 Authorisation Flow (Service Window)
* Official: Alipay Cashier / WAP Documentation
# Alipay Web Payments
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-payment-type/alipay/alipay-web-payments
Integration guide for Alipay Web payments (Hong Kong + Overseas).
This guide describes how to integrate **Alipay Web Payments** for both Hong Kong and overseas merchants.
**AlipayHK does not support the native Alipay checkout page.**\
For AlipayHK, merchants typically request a QR code / payment URL and embed it in the merchant page (e.g. as a QR code image or via an `iframe`).
***
## Overview
Alipay Web Payments allow customers to complete a purchase by scanning a QR code (or opening a hosted payment URL). After payment confirmation, funds are deducted from the customer’s wallet and the result is returned to the merchant system.
* Mainland China users pay in **CNY**; QFPay settles to merchants in the configured settlement currency.
* Hong Kong users pay in **HKD**.
If `return_url` is provided, the customer is redirected back to the merchant website after payment.
***
## HTTP Request
**Endpoint**
`POST /trade/v1/payment`
***
## Supported Pay Types
| PayType | Description |
| ------: | ------------------------------------- |
| 801101 | Alipay Web Payment (Settle By Alipay) |
| 801514 | Alipay Web Payment (Settle By QFPay) |
***
## Request Parameters
| Field | Type | Required | Description |
| -------------- | ----------- | -------- | -------------------------------------------------------------------------------------- |
| `txamt` | Int(11) | Yes | Amount in cents. Suggested > 200 to reduce risk control triggers. |
| `txcurrcd` | String(3) | Yes | Currency (e.g. `HKD`, `CNY`). See [Currencies](/integration/api-reference/currencies). |
| `pay_type` | String(6) | Yes | PayType (see table above). |
| `out_trade_no` | String(128) | Yes | Unique merchant order ID. |
| `txdtm` | String(20) | Yes | Timestamp `YYYY-MM-DD HH:mm:ss`. |
| `expired_time` | String(3) | No | QR expiry (minutes). 5–120. (MPM only) |
| `goods_name` | String(64) | No | Product name (≤20 chars). Use UTF-8 if Chinese. |
| `mchid` | String(16) | No | Required if provided by QFPay for your store setup. |
| `udid` | String(40) | No | Device ID for reporting. |
| `return_url` | String(512) | No | Redirect URL after successful payment. |
***
## Optional Parameters
| Payment Tag | `pay_tag` | No | String(16) | Default: ALIPAYHK For Alipay Mainland use: ALIPAYCN |
| :---------- | :-------- | :- | :--------- | :------------------------------------------------------- |
***
## Response Parameters
| Field | Type | Description |
| -------------- | ----------- | --------------------------------------------------------------------------- |
| `respcd` | String(4) | Return code. `0000` = success; `1143/1145` = retry needed; others = failed. |
| `resperr` | String(128) | Error details (if any). |
| `respmsg` | String(128) | General response message. |
| `syssn` | String(40) | QFPay transaction ID. |
| `out_trade_no` | String(128) | Merchant order ID. |
| `txamt` | Int(11) | Amount (cents). |
| `txdtm` | String(20) | Request timestamp. |
| `sysdtm` | String(20) | Server timestamp (used for settlement cutoff). |
| `pay_type` | String(6) | PayType used. |
| `pay_url` | String(512) | Payment URL (render as QR code or embed in `iframe`). |
***
## Code Examples
Use the language tabs below. Replace the placeholders with your own credentials and values.
```python Python theme={null}
# coding=utf-8
import hashlib
import requests
import datetime
environment = "https://test-openapi-hk.qfapi.com"
app_code = "YOUR_APP_CODE"
client_key = "YOUR_CLIENT_KEY"
def make_req_sign(data, key):
keys = sorted(list(data.keys()))
parts = []
for k in keys:
parts.append(f"{k}={data[k]}")
sign_str = ("&".join(parts) + key).encode("utf-8")
return hashlib.md5(sign_str).hexdigest().upper()
current_time = datetime.datetime.now().replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S")
data = {
"txamt": "10",
"txcurrcd": "HKD",
"pay_type": "801101",
"out_trade_no": "01234567890123",
"txdtm": current_time,
# "mchid": "ZaMVg*****" # include only if provided by QFPay
}
headers = {
"X-QF-APPCODE": app_code,
"X-QF-SIGN": make_req_sign(data, client_key),
}
r = requests.post(environment + "/trade/v1/payment", data=data, headers=headers)
print(r.json())
```
```java Java theme={null}
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class TestMain {
public static void main(String[] args) {
String appcode = "YOUR_APP_CODE";
String key = "YOUR_CLIENT_KEY";
String pay_type = "801101";
String out_trade_no = "01234567890123";
String txdtm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
Map params = new HashMap<>();
params.put("pay_type", pay_type);
params.put("out_trade_no", out_trade_no);
params.put("txdtm", txdtm);
params.put("txamt", "10");
params.put("txcurrcd", "HKD");
// params.put("mchid", "ZaMVg*****"); // include only if provided by QFPay
String data = QFPayUtils.getDataString(params);
String md5 = QFPayUtils.getMd5Value(data + key);
String url = "https://test-openapi-hk.qfapi.com";
String resp = Requests.sendPostRequest(url + "/trade/v1/payment", data, appcode, md5);
System.out.println(resp);
}
}
```
```javascript JavaScript theme={null}
const crypto = require("crypto");
const request = require("request");
const environment = "https://test-openapi-hk.qfapi.com";
const app_code = "YOUR_APP_CODE";
const client_key = "YOUR_CLIENT_KEY";
const txdtm = new Date().toISOString().replace("T", " ").replace(/\..+/, "");
const out_trade_no = String(Math.round(Math.random() * 1000000000));
const payload = {
txamt: "10",
txcurrcd: "HKD",
pay_type: "801101",
out_trade_no,
txdtm
// mchid: "ZaMVg*****" // include only if provided by QFPay
};
const ordered = Object.keys(payload).sort().map(k => `${k}=${payload[k]}`).join("&");
const signString = ordered + client_key;
const sign = crypto.createHash("md5").update(signString).digest("hex").toUpperCase();
request({
uri: environment + "/trade/v1/payment",
headers: {
"X-QF-APPCODE": app_code,
"X-QF-SIGN": sign
},
method: "POST",
form: payload
}, function (error, response, body) {
console.log(body);
});
```
```php PHP theme={null}
'801101',
'out_trade_no' => randStr(20),
'txcurrcd' => 'HKD',
'txamt' => '2200',
'txdtm' => date("Y-m-d H:i:s")
// 'mchid' => 'ZaMVg*****' // include only if provided by QFPay
);
ksort($fields);
$pairs = array();
foreach($fields as $k => $v) { $pairs[] = $k . '=' . $v; }
$fields_string = implode('&', $pairs);
$sign = strtoupper(md5($fields_string . $app_key));
$header = array(
'X-QF-APPCODE: ' . $app_code,
'X-QF-SIGN: ' . $sign
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url . $api);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
$output = curl_exec($ch);
curl_close($ch);
echo $output;
?>
```
***
## Sample Response
```json theme={null}
{
"sysdtm": "2020-04-13 10:30:34",
"paydtm": "2020-04-13 10:30:34",
"txcurrcd": "HKD",
"respmsg": "",
"pay_type": "801101",
"udid": "qiantai2",
"txdtm": "2020-04-13 10:30:34",
"txamt": "300",
"resperr": "success",
"out_trade_no": "4K35N374II7UJJ8RGIAE45O2CVHGHFF0",
"syssn": "20200413000300020087033882",
"respcd": "0000",
"pay_url": "https://globalmapi.alipay.com/gateway.do?...",
"chnlsn": ""
}
```
***
## Notes
Render the QR code or `iframe` using `pay_url`.
Do not reuse `out_trade_no` across payments or refunds.
If `respcd` is `1143` or `1145`, retry or query the transaction result using `/trade/v1/query`.
# Apple Pay Support
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-payment-type/applepay
## Introduction
QFPay Checkout and Payment Element SDK both support Apple Pay as an embedded payment option. Merchants must first enable this payment method in the QFPay backend before customers can use it.
Apple Pay helps improve authorisation rates and streamlines the checkout experience on Apple-supported devices.
***
## Supported Devices
Apple Pay is available on:
* iPhone
* iPad
* Mac (via Safari)
* Third-party browsers on Windows and other platforms (via QR Code mode)
Apple Pay is **not supported on Android devices**.\
If a customer selects Apple Pay on an unsupported device, the Apple Pay button will be hidden automatically.
***
## QR Code Payment Mode
If the customer is using a device that does not support Apple Pay natively—such as a Windows desktop—a **QR code** will be displayed automatically.
The customer can scan this QR code using a supported iPhone or iPad (iOS version ≥ 18) to complete the payment securely.
This feature is **not available** if the customer's Apple Store region or device language is set to mainland China.
# FPS App-to-App Integration
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-payment-type/fps/fps-app-call-app
Integration guide for FPS App-to-App and H5-to-App payments (Android & iOS)
This guide explains how a merchant mobile app or H5 page can launch supported bank apps to complete **FPS App-to-App payments**.
QFPay provides payment parameters via API. Your application then opens the user’s banking app using platform-specific methods. After payment, the result is returned via callback or backend verification.
Android and iOS platforms are supported.
***
## Integration Overview
Choose the flow that matches your application architecture:
| Platform | Flow Type | Usage Scenario | Launch Method |
| -------- | ---------- | --------------------------------- | -------------------------- |
| Android | Native App | App directly triggers FPS payment | `Intent` to bank app |
| Android | H5 to App | WebView triggers FPS payment | JSBridge + `Intent` |
| iOS | Native App | App directly triggers FPS payment | `UIActivityViewController` |
| iOS | H5 to App | WKWebView triggers FPS payment | WKWebView + JSBridge |
For all flows:
1. Retrieve payment parameters via API
2. Launch the FPS bank app
3. Receive callback or verify payment status
***
## 1. Retrieve Payment Parameters
**Endpoint**
`POST /trade/v1/payment`
**PayType:** `802010`
### Request Parameters
| Parameter | Required | Type | Description |
| -------------- | -------- | ------ | ------------------------------------------------------------------ |
| `pay_type` | Yes | String | Fixed value `802010` |
| `txamt` | Yes | Int | Amount in cents (100 = HKD 1.00) |
| `txdtm` | Yes | String | Format: `YYYY-MM-DD hh:mm:ss` |
| `out_trade_no` | Yes | String | Unique merchant order reference |
| … | — | — | See [Common Parameters](/integration/api-reference/request-format) |
If your FPS integration is via **HSBC direct channel**, you must obtain a **dedicated SSL certificate** matching your merchant legal name.
This is required for:
• secure callbacks (Universal Links / HTTPS redirects) • HSBC certification & production onboarding
See the FPS e-Cert setup guide for details.
### Response Parameters
| Name | Description |
| ---------------------- | -------------------------------------- |
| Common Response Fields | See response format documentation |
| `pay_params` | URL used to launch the FPS payment app |
***
## 2. Android FPS Payment Flow
### 2.1 Native App-to-App Flow
This flow launches the banking app directly using an Android Intent.
1. Retrieve `pay_params` from API
2. Launch bank app using Intent
3. Receive result via `onActivityResult`
4. Verify final status via backend
> Android Example
```java theme={null}
int REQUEST_CODE = 100;
String payUrl = "PAYMENT_URL_FROM_API";
Intent intent = new Intent("hk.com.hkicl");
intent.putExtra("url", payUrl);
startActivityForResult(intent, REQUEST_CODE);
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE) {
if (resultCode == RESULT_OK) {
// Payment initiated
} else if (resultCode == RESULT_CANCELED) {
// User cancelled or failed
}
}
}
```
***
### 2.2 Android H5-to-App (WebView)
Used when an H5 page inside WebView launches the FPS app.
**Flow**
1. Enable JavaScript in WebView
2. Register JS bridge
3. H5 sends payment URL to Android
4. Android launches bank app
5. Return result to H5
6. Verify payment via backend
H5 example:
```javascript theme={null}
AndroidBridge.handleMessage(JSON.stringify({
url: "PAYMENT_URL_FROM_API"
}));
```
***
## 3. iOS FPS Payment Flow
### 3.1 Native App-to-App Flow
iOS uses **UIActivityViewController** and App Extensions to launch bank apps.
**Flow**
1. Retrieve payment URL
2. Launch bank app via extension
3. User completes payment
4. App receives Universal Link callback
5. Verify status via backend
#### Key Steps
• Create `NSExtensionItem` • Package payment URL & callback URL • Use type identifier: `hk.com.hkicl` • Present `UIActivityViewController`
```swift theme={null}
let paymentPayload = [
"URL": paymentURL,
"callback": "https://your-domain.com/callback"
]
let itemProvider = NSItemProvider(
item: paymentPayload as NSDictionary,
typeIdentifier: "hk.com.hkicl"
)
let extensionItem = NSExtensionItem()
extensionItem.attachments = [itemProvider]
let activityVC = UIActivityViewController(
activityItems: [extensionItem],
applicationActivities: nil
)
present(activityVC, animated: true)
```
***
### 3.2 iOS H5-to-App via WKWebView
Used when FPS payment is triggered from an H5 page.
**Flow**
1. H5 retrieves payment URL
2. H5 calls native app via JSBridge
3. Native launches bank app
4. Native returns result to H5
5. Backend verifies final payment
Bridge call example:
```javascript theme={null}
window.webkit.messageHandlers.NativeBridge.postMessage({
action: "FPSH5CallApp",
params: { paymentRequestURL: "PAYMENT_URL_FROM_API" },
callbackId: "callback_123"
});
```
***
## Error Handling & Fallbacks
### Result Codes
| Code | Meaning |
| ---------------- | --------------------------------- |
| RESULT\_OK | Payment app launched successfully |
| RESULT\_CANCELED | Cancelled, failed, or timeout |
Always verify payment using your backend order query.
***
### If FPS App Is Not Installed
If no supported bank app is available:
• show an error message • provide fallback QR payment • allow retry
***
### Callback Failure
If callback or redirect fails:
• inform user payment is verifying • re-query backend after 3–5 seconds • display loading or retry option
***
## Demo Downloads
| Platform | Flow |
| -------- | ----------------------------------------------------------------------------- |
| Android | [Native & WebView](https://cdn.qfpay.com.hk/docs_attachment/HsbcFPS.zip) |
| iOS | [Native (Obj-C)](https://cdn.qfpay.com.hk/docs_attachment/FPSElement.zip) |
| iOS | [Native (Swift)](https://cdn.qfpay.com.hk/docs_attachment/FPSElementDemo.zip) |
| iOS | [JSBridge](https://cdn.qfpay.com.hk/docs_attachment/fps-bridge.zip) |
# FPS e-Cert Certificate Application and Setup Guide
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-payment-type/fps/fps-ecert-setup
Guide for applying and configuring SSL certificates for FPS App-to-App integration, particularly for HSBC merchants.
This guide is required if you are an FPS merchant using App-to-App payment, especially for HSBC merchants.
***
## When Do You Need an e-Cert?
If you are integrating FPS App-to-App using a **merchant-specific Universal Link**, an e-Cert (server certificate) is required to secure the domain.
This applies to banks such as **HSBC**, which operate in direct integration mode and require domain validation and organisation name matching.
***
## Important Notes for FPS App-to-App Certificate
The **merchant organisation name** (subject field in the X.509 certificate) **must exactly match** the payee name registered in the FPS Addressing Service.
This requirement comes from FPS Technical Specification 6.9.2.
Payment apps MUST validate that the organisation name in the merchant certificate matches the payee name returned from the FPS addressing service.
The **certificate domain name** is assigned and configured by QFPay based on the integration context. Merchants do not select this domain themselves.
Each **distinct domain** requires a **separate e-Cert application** (e.g. `fps.payment.example-shop.com`), which incurs additional cost and processing time.
Merchants must also add the following **CNAME record** in their DNS configuration:
```
Host: fps.merchant.com
Type: CNAME
Value: hk.qfapi.com
```
***
## FPS e-Cert Application Overview
| Step | Description |
| ---- | --------------------------------------------------------------------------------- |
| 1 | Complete application form **CPos 798F** |
| 2 | Submit the form in person at any Hongkong Post office |
| 3 | Present authorised ID and pay the subscription fee |
| 4 | Receive PIN envelope for CSR submission |
| 5 | Generate and submit CSR via the designated e-Cert portal |
| 6 | Wait \~10 working days for approval and domain/email validation |
| 7 | Upon approval, submit CSR to issue certificate |
| 8 | Download and install the e-Cert on your HTTPS server |
| 9 | Send **certificate and private key** to QFPay Technical Support for configuration |
***
## CSR Generation Requirements
Before submitting the certificate application, generate a **Certificate Signing Request (CSR)**.
Example OpenSSL command:
```
openssl req -new -SHA256 -newkey rsa:2048 -nodes \
-keyout .key \
-out .csr \
-subj "/C=HK/ST=HongKong/L=HongKong/O=/OU=/CN="
```
### Parameter Breakdown
| Field | Description |
| ------------------ | -------------------------------------------- |
| `-newkey rsa:2048` | Generate a new RSA key pair (2048-bit) |
| `-nodes` | Skip password encryption for the private key |
| `-keyout` | Path to save the private key |
| `-out` | Path to save the CSR |
| `-subj` | Subject fields included in the certificate |
**Notes**
* `O=` (organisation name) **must exactly match** the FPS payee name registered in the FPS Addressing Service.
* `CN=` (common name) is the domain name configured by QFPay.
* Leave `OU=` empty if no department is specified.
***
## Documents Required
* Completed CPos Form 798F
* Business Registration (BR) copy
* Company Incorporation (CI) copy
* Domain ownership proof (invoice, DNS screenshot, or domain email confirmation)
***
## Post-Issuance Responsibilities
Hongkong Post will send **expiration reminders** to the registered email **30 days and 14 days before expiry**.\
Merchants are responsible for timely renewal and notifying QFPay.
After certificate issuance:
* Send the certificate (`.cer` / `.crt`) and private key (`.key`) to QFPay Technical Support.
* QFPay will complete backend setup for the FPS payment endpoint.
***
## FPS Specification Reference
**Section 6.9.2 — Certificate Validation Logic**
The payment app (e.g. HSBC app) validates that the **Organisation Name (O)** in the X.509 certificate matches the FPS payee name.
The comparison is:
* Case-insensitive
* Whitespace-insensitive
If the values do not match, the payment will be rejected.
***
## Resources
* [e-Cert Form 798F (Apply for e-Cert Server)](https://www.ecert.gov.hk/product/ecert/apply/certapply.html#t4)
* [e-Cert Application Flowchart (PDF)](https://www.ecert.gov.hk/product/ecert/apply/img/e-Cert_\(S\)_Flow.pdf)
***
# Visa / Mastercard Online Payments
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-payment-type/visa-master-online-payment
This page provides integration guidance for online credit card payments using Visa and Mastercard. Our solution currently supports credit card payments in the [Hong Kong environment](/integration/preparation/environments), covering all major card issuers.
***
## Integration Methods
Merchants can choose from two integration options:
1. [**QFPay Checkout Services**](/integration/online-shop/checkout-integration/checkout)\
A hosted payment page solution—ideal for merchants who prefer minimal PCI scope and faster setup.
2. [**QFPay Payment Element SDK**](/integration/online-shop/checkout-integration/payment-element)\
A client-side SDK that embeds payment input fields directly into your website, offering full UX control and 3DS support.
***
## Asynchronous Notification
QFPay will send an asynchronous notification to your backend to confirm the transaction result.
Refer to [Asynchronous Notification](/integration/common-api/async-notifications) for details on the notification format and signature verification.
> Example Notification Payload:
```json theme={null}
{
"cardtp": "5",
"cancel": "0",
"pay_type": "802801",
"order_type": "payment",
"clisn": "054256",
"txdtm": "2021-12-08 07:04:15",
"out_trade_no": "354267281",
"syssn": "20211208180500020000001637",
"sysdtm": "2021-12-08 15:04:16",
"paydtm": "2021-12-08 15:06:51",
"txcurrcd": "HKD",
"udid": "qiantai2",
"userid": "1130000355",
"txamt": "1",
"respcd": "0000",
"errmsg": "success"
}
```
Always validate the notification using the provided signature, and never trust the notification result blindly.
You can use the [**Transaction Enquiry**](/integration/common-api/transaction-enquiry) API as a fallback to confirm transaction status when:
* Callback is delayed
* Signature verification fails
* Merchant server missed the notification
***
## Test Cards
The following test cards are available in the **Sandbox** environment. Use them to simulate different transaction results, including 3D Secure (3DS) flows.
| Card Brand | Card Number | Simulation Result |
| ---------- | ------------------- | --------------------------------- |
| Mastercard | 5200 0000 0000 1096 | Successful payment |
| Visa | 4000 0000 0000 1091 | Successful payment |
| Mastercard | 5200 0000 0000 1005 | Successful (3DS frictionless) |
| Visa | 4000 0000 0000 1000 | Successful (3DS frictionless) |
| Mastercard | 5200 0000 0000 1120 | Failed (during verification) |
| Visa | 4000 0000 0000 1125 | Failed (during verification) |
| Mastercard | 5200 0000 0000 1013 | Failed (3DS frictionless failure) |
| Visa | 4000 0000 0000 1018 | Failed (3DS frictionless failure) |
If you're unsure which integration method fits your use case (Checkout vs Element SDK), refer to your onboarding document or contact QFPay support.
# WeChat In-App Payments (Native App)
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-payment-type/wechat/wechat-in-app-payments
This document explains how to integrate WeChat In-App payments using the official SDK within iOS or Android apps.
## Prerequisites
To use WeChat In-App payments, merchants must:
* Register on the **WeChat Open Platform**
* Create an App and obtain an **AppID**
* Complete WeChat approval and onboarding
More details:\
[WeChat Official In-App Payment Guide](https://pay.weixin.qq.com/wiki/doc/api/wxpay/en/pay/In-AppPay/chapter6_2.shtml#menu1)
***
## Real-name Verification (Optional)
Merchants may enable **real-name verification**.
* Applies to Mainland China users only
* The WeChat Wallet identity must match submitted details
* Users can still pay without linking a bank card
* Availability depends on merchant configuration and PayType support
***
## SDK Downloads
Download the official SDK:
[WeChat SDK Download Page](https://developers.weixin.qq.com/doc/oplatform/Downloads/iOS_Resource.html)
***
## API Request
### HTTP Request
* **Method:** POST
* **Endpoint:** `/trade/v1/payment`
* **PayType:** `800210`
Confirm the PayType with QFPay if your account uses a different code for WeChat In-App payments.
***
### Request Parameters
| Field | Param | Required | Type | Description |
| ----------------- | -------------- | -------- | --------- | ------------------------------------------------------------------------- |
| Merchant ID | `mchid` | No | String | Store-level merchant ID (if provided) |
| External Order ID | `out_trade_no` | Yes | String | Unique transaction reference |
| Amount | `txamt` | Yes | Int | Amount in cents (recommended > 200 to avoid risk flags) |
| Currency | `txcurrcd` | Yes | String(3) | Currency code. See [Currency List](/integration/api-reference/currencies) |
| RMB Tag | `rmb_tag` | No | String(1) | Use with `txcurrcd=CNY` to indicate RMB |
| Transaction Time | `txdtm` | Yes | String | `YYYY-MM-DD hh:mm:ss` |
| Device ID | `udid` | No | String | Mobile device identifier |
| Return URL | `return_url` | No | String | Required for some channels |
| Real-name Info | `extend_info` | No | Object | Required only for Mainland China real-name flow |
| … | | | | See **Common Parameters** for shared request fields |
***
### Real-Name Verification Format (`extend_info`)
Submit real-name verification information only when required for Mainland China flows.
```json theme={null}
{
"user_creid": "430067798868676871",
"user_truename": "\\u5c0f\\u6797"
}
```
* `user_creid` — Mainland China ID number
* `user_truename` — payer’s real name (Unicode or Chinese characters)
***
### Sample Request
```json theme={null}
{
"goods_info": "test_app",
"goods_name": "qfpay",
"out_trade_no": "ORDER_10001",
"pay_type": "800210",
"txamt": "10",
"txcurrcd": "HKD",
"txdtm": "2019-09-13 04:53:03",
"udid": "DEVICE123"
}
```
***
## API Response
### Response Parameters
| Param | Type | Description |
| -------------- | ------ | -------------------------- |
| `syssn` | String | QFPay transaction ID |
| `out_trade_no` | String | Merchant order reference |
| `txdtm` | String | Request time |
| `txamt` | Int | Amount |
| `sysdtm` | String | Processing time |
| `respcd` | String | `0000` = success |
| `respmsg` | String | Response message |
| `resperr` | String | Error description |
| `cardcd` | String | Card number (if available) |
| `txcurrcd` | String | Currency |
| `pay_params` | Object | Parameters for WeChat SDK |
***
### Sample Response
```json theme={null}
{
"respcd": "0000",
"respmsg": "",
"pay_params": {
"appid": "wx3c6896fa9b351f2a",
"partnerid": "316525492",
"prepayid": "wx131253044253463a81dc336e1254149882",
"package": "Sign=WXPay",
"noncestr": "7786db42d9a245c2b1cfc717ac59376e",
"timestamp": 1568350384,
"sign": "[sign string]"
}
}
```
***
## Calling the WeChat SDK
After receiving `pay_params`, pass the values into the WeChat SDK.
[Official Documentation](https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12\&index=2)
### Required Parameter Mapping
* `appid`
* `partnerid`
* `prepayid`
* `package`
* `noncestr`
* `timestamp`
* `sign`
***
## Summary
* Designed for **native mobile apps only**
* Requires WeChat Open Platform registration & AppID approval
* Payment flow is handled by the official WeChat SDK
* Always confirm final status using the [Transaction Enquiry API](/integration/common-api/transaction-enquiry)
# WeChat JSAPI Payment (Official Account)
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-payment-type/wechat/wechat-jsapi-payment
Integrate WeChat JSAPI payments within the WeChat in-app browser using OAuth authorization and JSAPI payment invocation.
This payment method must be initiated **inside the WeChat in-app browser**.\
It will NOT work in external browsers such as Chrome or Safari.
***
## Overview
WeChat JSAPI enables payments inside an Official Account web page viewed within WeChat.
This method is ideal for:
* Official Account H5 stores
* In-app service flows
* QR campaigns opening inside WeChat
> For merchants in Canada, refer to\
> [Alipay Web Payments](/integration/online-shop/integration-by-payment-type/alipay/alipay-web-payments)\
> where `pay_type = 800207`.
***
## Integration Methods
### Method 1 — Merchant’s Own Official Account
Merchants use their verified WeChat Official Account.
**Requirements**
1. Official Account verified & linked to QFPay
2. Obtain user `openid`
3. Domain added to JSAPI whitelist
Resources:
* [https://developers.weixin.qq.com/doc/offiaccount/en/Getting\_Started/Overview.html](https://developers.weixin.qq.com/doc/offiaccount/en/Getting_Started/Overview.html)
* [https://pay.weixin.qq.com/wiki/doc/api/jsapi.php](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php)
***
### Method 2 — QFPay Official Account (Indirect Settlement)
For merchants without a verified account, QFPay provides an Official Account.
***
## Payment Flow Overview
1. User opens payment page inside WeChat
2. Retrieve OAuth code
3. Exchange code → obtain `openid`
4. Submit payment request
5. Redirect to WeChat JSAPI payment module
6. Verify payment result via backend
***
## Step 1 — Obtain WeChat OAuth Code
**Request**
GET `/tool/v1/get_weixin_oauth_code`
This endpoint must be opened inside WeChat.\
`app_code` and `sign` must be sent as **query parameters**, not headers.
### Parameters
| Name | Param | Required | Description |
| ------------ | -------------- | -------- | ------------------------------- |
| App Code | `app_code` | Yes | Provided by QFPay |
| Redirect URL | `redirect_uri` | Yes | URL after authorization |
| Merchant ID | `mchid` | No | Provided if applicable |
| Signature | `sign` | Yes | MD5 signature using client\_key |
```python theme={null}
def make_req_sign(data, key):
keys = sorted(data.keys())
raw = "&".join(f"{k}={data[k]}" for k in keys) + key
return hashlib.md5(raw.encode()).hexdigest().upper()
```
**Response**
```json theme={null}
{
"redirect": "https://yourdomain.com/callback?code=011xxxxx"
}
```
Redirect the user to the returned URL.
***
## Step 2 — Exchange Code for `openid`
**Request**
GET `/tool/v1/get_weixin_openid`
### Parameters
| Parameter | Required | Description |
| --------- | -------- | --------------------------- |
| `code` | Yes | OAuth code from Step 1 |
| `mchid` | No | Required for some merchants |
Headers required:
* `X-QF-APPCODE`
* `X-QF-SIGN`
```python theme={null}
def get_open_id(code):
params = {"code": code}
headers = {
"X-QF-APPCODE": app_code,
"X-QF-SIGN": make_req_sign(params, client_key)
}
r = requests.get(environment + "/tool/v1/get_weixin_openid",
params=params, headers=headers)
return r.json().get("openid")
```
**Response**
```json theme={null}
{
"respcd": "0000",
"openid": "oo3Lss8d0hLOuyTuSJMVwLTk68JE"
}
```
A new OAuth code and openid must be obtained for **each payment attempt**.\
Do not cache or reuse.
***
## Step 3 — Submit Payment Request
**Endpoint**
POST `/trade/v1/payment`
**PayType:** `800207` (WeChat JSAPI)
### Required Fields
| Field | Required | Description |
| ----------------- | -------- | -------------------------------------------- |
| `sub_openid` | Yes | openid from Step 2 |
| Common Parameters | Yes | amount, currency, timestamp, order no |
| `limit_pay` | No | restrict payment methods |
| `extend_info` | No | real-name verification (Mainland China only) |
Refer to:
* [Common Parameters](/integration/api-reference/request-format)
* [PayType Reference](/integration/api-reference/paytypes)
```python theme={null}
def create_payment(openid):
data = {
"txamt": "100",
"txcurrcd": "HKD",
"pay_type": "800207",
"out_trade_no": "ORDER12345",
"txdtm": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"sub_openid": openid
}
headers = {
"X-QF-APPCODE": app_code,
"X-QF-SIGN": make_req_sign(data, client_key)
}
r = requests.post(environment + "/trade/v1/payment",
params=data, headers=headers)
return r.json()["pay_params"]
```
***
### pay\_params Response
| Field | Description |
| ----------- | ---------------------- |
| `appId` | Official Account AppID |
| `timeStamp` | timestamp |
| `nonceStr` | random string |
| `package` | prepay package |
| `signType` | signature method |
| `paySign` | payment signature |
***
## Step 4 — Redirect to WeChat Payment Module
Redirect the user’s browser to:
`GET https://o2-hk.qfapi.com/q/direct`
### Required Parameters
| Parameter | Description |
| -------------- | --------------------- |
| `mchntnm` | Merchant display name |
| `txamt` | Amount |
| `currency` | Currency |
| `redirect_url` | URL after payment |
| `package` | from pay\_params |
| `timeStamp` | from pay\_params |
| `signType` | from pay\_params |
| `paySign` | from pay\_params |
| `appId` | from pay\_params |
| `nonceStr` | from pay\_params |
This request is sent directly from the user’s browser.\
No authentication headers are required.
***
## Additional Notes
* JSAPI flow must be executed sequentially
* Do not reuse OAuth codes or openid
* If payment result is delayed, verify via\
[Transaction Enquiry API](/integration/common-api/transaction-enquiry)
* Real-name verification is optional and depends on merchant configuration
# WeChat Mini Programs
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-payment-type/wechat/wechat-mini-programs
## HTTP Request
**Endpoint** : `/trade/v1/payment` **Method** : `POST` **PayType** : `800213`
**Step 1:** WeChat real name authentification Before the payment function within WeChat can be used the business personnel must authenticate themselves on the official WeChat platform.
**Step 2:** Get openid Once real name authentication has been completed, the openid parameter is obtained through the small program of the real name of the merchant. The specific acquisition method is described in the [Wechat documentation](https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html).
**Step 3:** Send payment request Initiate a payment request with the parameters below.
Optionally merchants can activate real-name authentication with WeChat. Currently real-name identification is only available for Mainland Chinese citizens and include a person's real name and national ID card number. In case identification is provided the payer's wallet information like a connected bank card must be identical with the data provided by merchants. If customers did not yet bind their WeChat account to a bank card the payment will go through regardless.
```plaintext theme={null}
For code instructions select Node.js with the tabs above.
```
```javascript theme={null}
qfPayOpenAPI: function () {
let app_code = 'YOUR_APP_CODE';
let client_key = 'YOUR_CLIENT_KEY';
let environment = 'https://test-openapi-hk.qfapi.com/trade/v1/payment';
let openid = this.data.openid;
let amount = this.data.amount * 100;
let random_number = String(Math.round(Math.random() * 1000000000));
let datetime = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
let payload = {
txamt: 100,
txcurrcd: 'SGD',
pay_type: '800213',
out_trade_no: '0123456789',
txdtm: '2020-07-03 03:14:29',
sub_openid: 'oS80_5dxekECAOlVBeQFk34q123s'
};
var ordered = {};
Object.keys(payload).sort().forEach(function(key) {
ordered[key] = payload[key]
});
console.log(ordered)
var str = [];
for (var p in ordered) {
if (ordered.hasOwnProperty(p)) {
str.push(p + "=" + ordered[p]);
}
}
var string = str.join("&") + client_key;
console.log(string)
var signature = utilMd5.hexMD5(string).toUpperCase()
console.log(signature)
wx.request({
url: environment,
data: payload,
method: 'POST',
header: {
'X-QF-APPCODE': app_code,
'X-QF-SIGN': signature,
'content-Type': 'application/x-www-form-urlencoded'
},
success: (res) => {
if (res.statusCode == 200) {
console.log(res)
console.log(res.data)
this.weChatPayment(res);
}
},
fail: (err) => {
console.log(err);
},
complete: (res) => {
wx.hideLoading();
console.log("API request completed")
}
})
},
```
## Request Parameters
| Attribute | Mandatory | Type | Description |
| :------------------------ | :-------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Common request parameters | — | — | Please refer to the [Common Parameters](/integration/api-reference/request-format) for more details |
| `sub_openid` | Yes | String(128) | |
| `expired_time` | No | String(3) | QRC expiration time in unit minutes. The default QRC expiration time for WeChat Mini Programs is 30 minutes. The parameter can manually be adjusted to a minimum of 5 minutes, and up to a maximum of 120 minutes. |
| `limit_pay` | No | String | The parameter value is specified as `no_credit`, and credit card payment is prohibited. This setting is only valid for mainland China. |
| `extend_info` | No | Object | Real name customer identification. This parameter is currently only available for Mainland Chinese citizens and needs to be explicitly activated with WeChat for the selected [PayType](/integration/api-reference/paytypes). The consumer's **national ID card number** is contained in the parameter `user_creid` and the payer's **real name** in encoded form or written in Chinese characters must be provided in `user_truename`. An example looks like this; `extend_info = '{"user_creid":"430067798868676871","user_truename":"\\\\u5c0f\\\\u6797"}'` |
| | | | |
## Response Parameters
| Attribute | Secondary Attribute | Type | Description |
| :------------------------- | :------------------ | :---------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Common response parameters | — | — | Please refer to the [Common Parameters](/integration/api-reference/response-format) for more details |
| `pay_params` | `appId` | String(16) | Public WMP ID, after the developer registers the Mini Program with the WeChat, the appId can be obtained. |
| — | `timeStamp` | String(32) | Current time |
| — | `nonceStr` | String(32) | Random string, no longer than 32 bits |
| — | `package` | String(128) | Order details extension string. The value of the prepay\_id parameter returned by the unified interface is in the format of prepay\_id=\*\* |
| — | `signType` | String(32) | Signature type, default is MD5 |
| — | `paySign` | String(64) | Signature |
| `txcurrcd` | | | Transaction currency. View the [Currencies](/integration/api-reference/currencies) table for a complete list of available currencies |
**Step 4:** Evoke the payment module
For code instructions please refer to the tab below.
```javascript theme={null}
weChatPayment: function(res) {
wx.requestPayment(
{
'timeStamp': '1593746074',
'nonceStr': '69d8a67fe34e44ca9bb2a20dd299cc58',
'package': 'prepay_id=wx03111434674853b80d25e0911996417600',
'signType': 'MD5',
'paySign': 'B0AECE676746F2A310CB06F27641E809',
'success': function(res){},
'fail': function(res){},
'complete': function(res){}
})
},
```
Obtain the `pay_params` parameter, and then provide payment details accordingly. For more details, please refer to the [Wechat documentation](https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7\&index=5).
## WeChat Mini Program Boilerplate
To get started quickly, download the [QFPay WeChat Mini Program Boilerplate](https://cdn.qfpay.com.hk/docs_attachment/qfpay_mini_program_payments_boilerplate.zip) and get access to the MD5 hash algorithm.
### Setup Instructions
1. Sign up with QFPay and we bind your WeChat appid to your API credentials.
2. Visit the WeChat MP portal at [https://mp.weixin.qq.com](https://mp.weixin.qq.com) and whitelist our environment for incoming server traffic: 开发 -> 开发设置 -> 服务器域名 -> request合法域名: e.g. `https://test-openapi-hk.qfapi.com`
3. Copy and paste the files from the zip file to your local harddrive and setup a cloudfunction environment.
4. Obtain the user openid with the cloudfunction "getUserOpenID" and run the API calls accroding to the code.
# WeChat H5 Payment (Third-Party Browser)
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-payment-type/wechat/wechat-pay-h5
This guide helps merchants integrate WeChat H5 payment for non-WeChat browser environments, including scene configuration and redirect handling.
***
## HTTP Request
`POST /trade/v1/payment`\
`pay_type: 800212` (WeChat H5 Payment)
***
## Request Parameters
| Field Name | Field Code | Required | Type | Description |
| --------------------- | ------------- | -------- | ------ | --------------------------------------------------------------- |
| Common Payment Params | — | Yes | — | See [Request Format](/integration/api-reference/request-format) |
| Extended Info | `extend_info` | Yes | Object | See structure below |
### `extend_info` Structure
```js theme={null}
extend_info: {
scene_info: {
h5_info: {
type: "Wap", // Scene type (fixed value)
wap_url: "https://qfpay.global/h5/pay", // Mobile site URL
wap_name: "qfpay" // Mobile site name
}
},
spbill_create_ip: "192.168.1.10" // User's real IP address
}
```
For IP acquisition details, see [WeChat Docs](https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_5)
### Extended Info Parameters
| Field Code | Subfield Code | Sub-subfield Code | Required | Type | Description |
| ------------------ | ------------- | ----------------- | -------- | ------ | ---------------------- |
| `scene_info` | | | Yes | Object | Scene information |
| | `h5_info` | | Yes | Object | H5 scene configuration |
| | | `type` | Yes | String | Fixed value: `Wap` |
| | | `wap_url` | Yes | String | Mobile website URL |
| | | `wap_name` | Yes | String | Mobile website name |
| `spbill_create_ip` | | | Yes | String | Customer’s real IP |
***
## Response Parameters
| Field Code | Subfield Code | Type | Field Name | Description |
| ---------------------- | ------------- | ------ | ----------- | ----------------------------------------------------------------- |
| Common Response Params | — | — | — | See [Response Format](/integration/api-reference/response-format) |
| Payment URL | `pay_url` | String | Payment URL | URL to redirect the user to complete payment |
### Example `pay_url`
```text theme={null}
https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20161110163838f231619da20804912345&package=1037687096
```
### Appended with `redirect_url`
```text theme={null}
https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20161110163838f231619da20804912345&package=1037687096&redirect_url=https%3A%2F%2Fwww.wechatpay.com.cn
```
***
## Additional Notes
* Suitable for mobile websites, app-embedded browsers, and external mobile browsers.
* ❗ **Not supported within the WeChat in-app browser.**
* `wap_url` and `wap_name` must match the actual mobile site information or WeChat may reject the request.
* The IP address submitted **must be the real client IP** to avoid risk rejection by WeChat.
# WeChat QR Code Payment
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-payment-type/wechat/wechat-web-qr-payment
This guide helps merchants integrate WeChat online QR Code payments, including QR generation, verification, and timeout settings.
## Overview
WeChat QR Code payment allows customers to use **WeChat’s Scan feature** to scan a QR code generated by the merchant system and complete payment.
### Typical flow
* Merchant generates a payment QR code
* Customer scans using WeChat
* WeChat performs security verification
* Payment completes in the wallet
### Common scenarios
* Desktop checkout pages
* POS display screens
* Self-service kiosks
***
## Real-name Verification (Optional)
Real-name verification is currently available only for **Mainland China citizens**.
If enabled, the merchant must submit:
* payer’s legal name
* Chinese national ID number
Rules:
* Wallet information must match provided identity data
* Payment still succeeds if the customer has not linked a bank card
* Enforcement depends on merchant configuration
***
## Create Payment Request
### HTTP Request
* **Method:** POST
* **Endpoint:** `/trade/v1/payment`
* **PayType:** `800201` (WeChat QR Payment)
***
## Sample Code
All examples follow the same logic. Choose the language matching your backend.
```python theme={null}
import hashlib, requests, datetime
environment = "https://test-openapi-hk.qfapi.com"
app_code = "YOUR_APP_CODE"
client_key = "YOUR_CLIENT_KEY"
def sign(data, key):
s = "&".join(f"{k}={data[k]}" for k in sorted(data))
return hashlib.md5((s + key).encode()).hexdigest().upper()
payload = {
"txamt": "300",
"txcurrcd": "HKD",
"pay_type": "800201",
"out_trade_no": "ORDER123456",
"txdtm": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
headers = {
"X-QF-APPCODE": app_code,
"X-QF-SIGN": sign(payload, client_key)
}
r = requests.post(environment + "/trade/v1/payment", data=payload, headers=headers)
print(r.json())
```
```javascript theme={null}
const crypto = require("crypto");
const request = require("request");
const payload = {
txamt: "300",
txcurrcd: "HKD",
pay_type: "800201",
out_trade_no: "ORDER123456",
txdtm: "2026-01-01 12:00:00"
};
const key = "YOUR_CLIENT_KEY";
const signString = Object.keys(payload).sort().map(k => `${k}=${payload[k]}`).join("&") + key;
const sign = crypto.createHash("md5").update(signString).digest("hex").toUpperCase();
request.post({
url: "https://test-openapi-hk.qfapi.com/trade/v1/payment",
form: payload,
headers: {
"X-QF-APPCODE": "YOUR_APP_CODE",
"X-QF-SIGN": sign
}
}, (err, res, body) => console.log(body));
```
```php theme={null}
"300",
"txcurrcd" => "HKD",
"pay_type" => "800201",
"out_trade_no" => "ORDER123456",
"txdtm" => date("Y-m-d H:i:s")
];
$headers = [
"X-QF-APPCODE: YOUR_APP_CODE",
"X-QF-SIGN: " . sign($payload, "YOUR_CLIENT_KEY")
];
$ch = curl_init("https://test-openapi-hk.qfapi.com/trade/v1/payment");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch);
curl_close($ch);
?>
```
***
## API Response
On success, the API returns a **QR code URL**.
Render the `qrcode` value into an image for customers to scan.
```json theme={null}
{
"respcd": "0000",
"resperr": "success",
"qrcode": "weixin://wxpay/bizpayurl?pr=ABC123",
"syssn": "20260101000100020012345678",
"out_trade_no": "ORDER123456"
}
```
***
## Request Parameters
| Parameter | Required | Description |
| -------------- | -------- | --------------------------- |
| `txamt` | Yes | Amount in cents |
| `txcurrcd` | Yes | Currency code |
| `pay_type` | Yes | Must be `800201` |
| `out_trade_no` | Yes | Unique order ID |
| `txdtm` | Yes | Timestamp |
| `expired_time` | No | QR expiry (5–120 min) |
| `goods_name` | No | Product name |
| `mchid` | No | Required for agent setups |
| `extend_info` | No | Real-name verification data |
***
## Response Fields
| Field | Description |
| -------------- | -------------------- |
| `qrcode` | QR code URL |
| `respcd` | `0000` = success |
| `resperr` | status message |
| `syssn` | QFPay transaction ID |
| `out_trade_no` | merchant order ID |
If `respcd` = **1143** or **1145**, the transaction is pending.\
Call the Transaction Enquiry API to confirm final status.
***
## Summary
* Designed for desktop & POS QR display
* Convert returned `qrcode` to a scannable image
* Real-name verification is optional
* Always implement order enquiry fallback
***
# Card BIN Promotions
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-use-case/card-bin-promotions
QFPay Checkout supports **Card BIN Promotions** for online card payments.\
This feature allows merchants to offer discounts or promotional campaigns to specific cardholders by matching eligible Card BIN ranges.
Card BIN promotions are commonly used for:
* Bank-issued card campaigns
* Visa / Mastercard promotions
* Merchant-funded discounts
* Limited-time issuer offers
* Loyalty and co-branded campaigns
## How Card BIN Promotions Work
A Card BIN Promotion works by matching:
1. A merchant-defined `promotion_tag`
2. Eligible Card BIN ranges
3. Supported payment methods
4. Active promotion period
Only transactions meeting all conditions will qualify for the promotion.
## Key Concepts
| Term | Description |
| ------------------------ | ---------------------------------------------------------------------------------- |
| Card BIN | The first 6–8 digits of a payment card, identifying the issuing bank and card type |
| `promotion_tag` | Unique identifier used to associate a transaction with a promotion campaign |
| Promotion Period | Active start and end time for the campaign |
| Eligible Payment Methods | Payment methods allowed for the promotion (e.g. Visa, Mastercard) |
## Supported Scenarios
Examples of supported campaigns:
* Visa cardholders receive 10% discount
* Mastercard users receive cashback
* Specific issuing bank promotions
* Merchant seasonal campaigns
* Co-branded partner promotions
## Prerequisites
Before activating a Card BIN Promotion, merchants must provide the following information to QFPay for pre-registration.
### Required Information
| Item | Description |
| -------------------------------- | --------------------------------------------------- |
| Promotion Period | Promotion start and end date/time |
| Card BIN List | List of eligible Card BIN ranges |
| Promotion Tag | Unique campaign identifier |
| Historical Promotion Information | (Optional) previous campaign information for review |
## Example Promotion Configuration
| Item | Example |
| ---------------- | ------------------------ |
| Promotion Period | 2025-06-01 to 2025-06-30 |
| Payment Methods | Visa, Mastercard |
| Eligible BINs | 412345, 512345, 622345 |
| Promotion Tag | `PROMO12345` |
## API Integration
To identify a transaction as part of a promotion campaign, include the `promotion_tag` in the payment request.
### Example Request
```text theme={null}
{"promotion_tag": "PROMO12345"}
```
***
## Request Parameters
| Parameter | Type | Required | Description |
| --------------- | ------ | -------- | ---------------------------------------------------------- |
| `promotion_tag` | String | Yes | Promotion identifier provided during campaign registration |
***
## Promotion Matching Behaviour
A transaction qualifies for the promotion only if:
* `promotion_tag` is valid
* Promotion period is active
* Card BIN matches eligible BIN list
* Payment method is supported
If any condition fails, the user may receive an eligibility error message during the payment process.
***
## Testing & Verification
Before launching the promotion:
### Test Transactions
Perform test payments using:
* Eligible Card BINs
* Non-eligible Card BINs
Verify that:
* Eligible cards successfully trigger the promotion
* Non-eligible cards do not receive promotional benefits
***
## Error Handling
Ensure your integration properly handles invalid promotion scenarios.
### Common Scenarios
| Scenario | Description |
| -------------------------- | -------------------------------------- |
| Invalid `promotion_tag` | Promotion identifier does not exist |
| BIN Not Eligible | Card BIN does not match campaign rules |
| Promotion Expired | Promotion is outside active period |
| Unsupported Payment Method | Wallet type is not supported |
***
## Webhook & Reporting
Merchants may use:
* QFPay merchant portal
* Transaction reports
* Merchant backend logs
to monitor promotion performance and validate eligible transactions.
It is recommended to store the `promotion_tag` in your internal order system for reconciliation purposes.
***
## Best Practices
Use unique `promotion_tag` values for each campaign to simplify reconciliation and reporting.
Test both successful and non-successful matching scenarios before production launch.
Card BIN promotions must be pre-registered with QFPay before use.
Transactions with unregistered promotion tags may not qualify for promotional pricing or issuer offers.
If multiple promotions are active simultaneously, ensure the correct `promotion_tag` is sent for the intended campaign.
***
## Launch Process
1. Submit campaign details to QFPay
2. Complete integration testing
3. Receive confirmation from QFPay
4. Launch campaign during approved promotion period
5. Monitor transaction behaviour and promotion performance
***
## Post-Promotion Review
After the campaign ends, merchants are encouraged to:
* Review transaction performance
* Analyze conversion and usage
* Evaluate eligible BIN performance
* Share feedback for future campaigns
***
## Support
For setup assistance or campaign registration, please contact:
[technical.support@qfpay.com](mailto:technical.support@qfpay.com)
# In-App Payment (Native App Integration)
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-use-case/in-app-payment
This page explains how to integrate e-wallet payment functions within a native App environment, supporting various mainstream wallets and standard payment flows.
Integrating e-wallet payment functions into native iOS or Android apps allows users to complete payments seamlessly within the app, without being redirected to external browsers.
***
## Supported Scenarios
In-App payments are suitable for:
* Native apps with built-in shopping or payment features
* Invoking wallet payment modules via SDKs (e.g. WeChat, Alipay)
* Letting users initiate payment inside the app and redirect to a wallet to complete payment
***
## Common Wallets and Transaction Types
Refer to the following table for `pay_type` values and related documentation:
| Transaction Type (`pay_type`) | Wallet | Description |
| ----------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `800210` | WeChat | [WeChat In-App Payment](/integration/online-shop/integration-by-payment-type/wechat/wechat-in-app-payments) (via SDK) |
| `801510` | AlipayHK | [Alipay Hong Kong In-App Payment](/integration/online-shop/integration-by-payment-type/alipay/alipay-in-app) (via URL Scheme or SDK) |
| `805810` | PayMe (HK) | PayMe In-App Payment |
| `800710` | UnionPay | UnionPay In-App Payment |
***
## Common Integration Methods
* **SDK Mode**: The merchant app uses a third-party SDK to call wallet APIs
* **URL Scheme / Deep Link**: Merchant generates a payment link and redirects to the wallet App
* **Redirect Back to App**: After payment is completed, the wallet redirects back to the merchant app (requires setting a return URL or deep link binding)
***
## General API Flow
Most In-App payment types use the `POST /trade/v1/payment` endpoint to initiate payment requests. The response will include `pay_params` or a `pay_url`, which is then passed to the SDK or used to trigger a redirect.
Refer to the documentation of each wallet for specific request and response formats.
***
## Important Notes
* Each wallet has different requirements — check if App payment permission needs to be pre-approved (e.g. WeChat SDK AppID, Alipay Merchant Setup)
* Always test redirect and return flow thoroughly to ensure the app receives the payment result properly
* It's recommended to use the Transaction Enquiry API to confirm the final payment status, especially if the app is closed before receiving the callback
***
## Related Resources
* [Transaction Enquiry API](/integration/common-api/transaction-enquiry)
* [Payment Status Code Reference](/integration/api-reference/status-codes)
* [Developer FAQ](/integration/FAQs)
# Online Pre-authorisation API
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-use-case/preauth
This document explains how to use QFPay's pre-authorisation APIs to freeze funds, capture payments, unfreeze unused amounts, and issue refunds.
Pre-authorisation allows merchants to **freeze a specific amount of customer funds** without immediately charging. The merchant can then **capture (charge)** the full or partial amount within a set period or **unfreeze** the remaining amount if not used.
***
## When to Use Pre-authorisation
Pre-authorisation is especially useful in scenarios where the final charge amount is not confirmed upfront or the service is provided later:
* **Hotel bookings**: Freeze a deposit at check-in, charge final amount at checkout
* **Car or equipment rental**: Freeze a guarantee or holding amount at pickup
* **E-commerce with delayed shipment**: Reserve funds on order, charge on dispatch
* **Subscriptions or deferred services**: Verify funding ability before providing service
* **High-risk or variable pricing cases**: Avoid payment failures and fraud
***
## Valid Capture Period
Depending on your industry and channel, different maximum capture periods are supported:
| Industry Type | Max Capture Window |
| ----------------- | ------------------ |
| General merchants | Up to **7 days** |
| Hotels / Rentals | Up to **28 days** |
Actual capture duration is subject to your agreement with QFPay and the payment channel’s capability. Please confirm before go-live.
***
## Key APIs
Please first familiarise yourself with:
* [API Usage Guidelines](/integration/api-reference/api-usage-guidelines)
* [API credentials](/integration/preparation/credentials-and-merchant-structure)
* [Test vs Production environment](/integration/preparation/environments)
* [Signature generation method](/integration/preparation/authentication-and-signature)
* [Common error codes and resolution](/integration/api-reference/status-codes)
Other relevant APIs:
* [Transaction Enquiry](/integration/common-api/transaction-enquiry)
* [Refund API](/integration/common-api/refund)
***
## Pre-authorisation Flow

***
## Step 1: Create Pre-authorised Transaction
This step is handled via **Payment Element SDK** and involves freezing the customer's funds.
No actual deduction happens at this stage.
Please refer to the [Payment Element Integration Guide](/integration/online-shop/checkout-integration/payment-element) for front-end SDK usage.
***
## Step 2: Capture (Deduct) Funds
Within the valid capture window, merchants can **charge** part or all of the frozen amount.
Multiple partial captures are allowed, but the **total cannot exceed the authorised amount**.
**Endpoint**: `POST /trade/v1/authtrade`
### Headers
| Header | Required | Description |
| -------------- | -------- | ------------- |
| `X-QF-APPCODE` | Yes | Your App Code |
| `X-QF-SIGN` | Yes | API Signature |
### Request Parameters
| Parameter | Required | Description |
| ---------- | -------- | ---------------------------------------- |
| `txamt` | Yes | Amount to capture (suggested > 200) |
| `txcurrcd` | No | Currency code |
| `mchid` | No | Merchant ID (for specific channels only) |
| `syssn` | Yes | Original pre-auth system order number |
### Example Response
```json theme={null}
{
"sysdtm": "2024-02-26 15:04:12",
"paydtm": "2024-02-26 15:04:12",
"udid": "qiantai2",
"txcurrcd": "HKD",
"txdtm": "2024-02-26 07:04:11",
"txamt": "500",
"resperr": "交易成功",
"respmsg": "Capture received",
"out_trade_no": "",
"syssn": "20240226180500020000014116",
"orig_syssn": "20240226180500020000014079",
"respcd": "0000",
"chnlsn": "",
"cardcd": ""
}
```
***
## Unfreeze Remaining Funds
Unused funds from a pre-authorised transaction can be **unfrozen** and returned to the customer.
* Only the *un-captured* amount can be unfrozen.
* **Unfreeze can only be done ONCE**.
* After unfreeze, no further capture can be made.
**Endpoint**: `POST /trade/v1/unfreeze`
### Request Parameters
| Parameter | Required | Description |
| -------------- | -------- | ------------------------------------- |
| `txamt` | Yes | Amount to unfreeze |
| `txdtm` | Yes | Timestamp of the unfreeze |
| `syssn` | Yes | Original pre-auth system order number |
| `out_trade_no` | Yes | Original merchant order number |
| `mchid` | No | Merchant ID (if applicable) |
### Example Response
```json theme={null}
{
"sysdtm": "2024-02-26 17:17:05",
"paydtm": "2024-02-26 17:17:06",
"udid": "qiantai2",
"txcurrcd": "HKD",
"txdtm": "2024-02-26 09:17:05",
"txamt": "2000",
"resperr": "交易成功",
"respmsg": "Void received",
"out_trade_no": "",
"syssn": "20240226180500020000014222",
"orig_syssn": "20240226180500020000014220",
"respcd": "0000",
"chnlsn": "",
"cardcd": ""
}
```
***
## Refund for Captured Payments
If the customer has already been charged (via capture), a refund can be initiated using the [Refund API](/integration/common-api/refund).
Use the `syssn` from the **capture step** (not the original pre-authorisation) when issuing the refund.
***
## Async Notification
All major actions trigger standard webhook notifications:
| Operation | `notify_type` value |
| --------- | ------------------- |
| Capture | `payment` |
| Unfreeze | `unfreeze` |
| Refund | `refund` |
### Notification Sample
```json theme={null}
{
"status": "1",
"pay_type": "800101",
"sysdtm": "2020-05-14 12:32:56",
"paydtm": "2020-05-14 12:33:56",
"goods_name": "",
"txcurrcd": "HKD",
"txdtm": "2020-05-14 12:32:56",
"mchid": "",
"txamt": "10",
"exchange_rate": "",
"chnlsn2": "",
"out_trade_no": "YEPE7WTW46NVU30JW5N90H7DHD94N56B",
"syssn": "20200514000300020093755455",
"cash_fee_type": "",
"cancel": "0",
"respcd": "0000",
"goods_info": "",
"cash_fee": "0",
"notify_type": "payment",
"chnlsn": "",
"cardcd": ""
}
```
***
## Best Practices & Warnings
If you do **not** perform capture within the allowed time, the frozen funds will **automatically unfreeze**. Failure to capture may result in lost revenue if the customer revokes authorisation or their account balance changes.
Capture, unfreeze, and refund operations can be performed via:
* API calls
* QFPay merchant portal
***
# Subscription API Reference
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-use-case/recurring-payments/recurring-api-reference
This page lists the API reference for creating, updating, and managing recurring (subscription) payment resources.
This page documents the API endpoints for recurring products and subscriptions. Before using these APIs, create and confirm a saved payment method in [Saved Payment Methods](/integration/online-shop/integration-by-use-case/saved-payment-methods/overview).
***
## Prerequisite: Saved customer and token
Create and manage the `customer_id` and `token_id` in [Saved Payment Methods](/integration/online-shop/integration-by-use-case/saved-payment-methods/overview). A confirmed `customer_id` and `token_id` are required before you create a subscription.
***
## 1. Create Product
**POST** `/product/v1/create`
Creates a recurring billing product.
### Request Parameters
| Parameter | Type | Required | Description |
| ---------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name` | String | Yes | Product name |
| `type` | String | Yes | Must be `recurring` |
| `description` | String | No | Product description |
| `txamt` | Int | Yes | Amount in cents (100 = \$1) |
| `txcurrcd` | String | Yes | Currency (e.g. HKD) |
| `interval` | String | Yes | Billing cycle: `weekly`, `monthly`, `yearly` |
| `interval_count` | Int | Yes | NO. of interval units (e.g. 1 month = 1) Weekly: weekly + 1 Monthly: monthly + 1 Quarterly: monthly + 3 Semi-annually: monthly + 6 Annually: yearly + 1 (or monthly + 12) |
| `usage_type` | String | Yes | Must be `licensed` |
In the sandbox environment, `minutes` and `hours` can be used in the `interval` field for testing purposes.
### Response
Returns generated `product_id`.
***
## 6. Update Product
**POST** `/product/v1/update`
Updates product metadata.
### Request Parameters
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | -------------------- |
| `product_id` | String | Yes | Product ID to update |
| `name` | String | No | New name |
| `description` | String | No | New description |
### Response
Returns number of affected rows.
***
## 7. Query Products
**POST** `/product/v1/query`
Search for products.
### Request Parameters
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | ----------------------- |
| `product_id` | String | No | Product ID |
| `name` | String | No | Product name |
| `description` | String | No | Description |
| `txcurrcd` | String | No | Currency |
| `interval` | String | No | `monthly` / `yearly` |
| `page` | Int | No | Default = 1 |
| `page_size` | Int | No | Default = 10, max = 100 |
### Response
List of matching products.
***
## 8. Delete Product
**POST** `/product/v1/delete`
Deletes a product (must not be linked to active subscriptions).
### Request Parameters
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | -------------------- |
| `product_id` | String | Yes | Product ID to delete |
### Response
Returns deleted product ID and row count.
***
## 9. Create Subscription
You must create `customer_id`, `product_id`, and `token_id` before calling the subscription API.
**POST** `/subscription/v1/create`
Creates a recurring subscription.
### Request Parameters
| Parameter | Type | Required | Description |
| ---------------------- | ------ | -------- | ----------------------------------------- |
| `customer_id` | String | Yes | Linked customer ID |
| `token_id` | String | Yes | Linked payment token |
| `products` | List | Yes | Product ID and quantity |
| `total_billing_cycles` | Int | No | Total billing periods (null = indefinite) |
| `start_time` | String | No | ISO timestamp to start subscription |
### Products Object
| Attribute | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------- |
| `product_id` | String | Yes | Unique product identifier |
| `quantity` | Int | No | Default = 1 |
### Example Request
```json theme={null}
{
"products": [
{
"product_id": "prod_54c3772d******9a54b236e09ec74f",
"quantity": 1
}
],
"customer_id": "cust_aaf6aae94******982c54c9cae5ba32",
"token_id": "tk_a99892fd*********d3417d168a18bb",
"total_billing_cycles": 2,
"start_time": "2020-05-14 12:32:56"
}
```
### Response (data field)
| Attribute | Type | Description |
| ----------------- | ------ | ----------------------------------- |
| `subscription_id` | String | Unique subscription identifier |
| `state` | String | `ACTIVE`, `INCOMPLETE`, `COMPLETED` |
***
## 10. Update Subscription
**POST** `/subscription/v1/update`
### Request Parameters
| Attribute | Type | Required | Description |
| ---------------------- | ------ | -------- | ----------------------- |
| `subscription_id` | String | Yes | Unique subscription ID |
| `total_billing_cycles` | Int | No | Null = indefinite |
| `start_time` | String | No | First billing time |
| `token_id` | String | No | Payment token |
| `products` | Object | No | List of product objects |
### Response (data field)
| Attribute | Type | Description |
| ----------------- | ------ | ------------------------- |
| `subscription_id` | String | Subscription ID |
| `rowAffected` | Int | Number of records updated |
***
## 11. Query Subscription
**POST** `/subscription/v1/query`
### Request Parameters
| Attribute | Type | Required | Description |
| ----------------- | ------ | -------- | --------------------------- |
| `page` | Int | No | Default = 1 |
| `page_size` | Int | No | Default = 10, max = 100 |
| `subscription_id` | String | No | Subscription ID |
| `customer_id` | String | No | Customer ID |
| `state` | String | No | e.g. `INCOMPLETE`, `ACTIVE` |
### Sample Response
```json theme={null}
{
"resperr": "success",
"respcd": "0000",
"respmsg": "success",
"data": [
{
"subscription_id": "sub_e120378de*******da066f690da75",
"customer_id": "cust_5ba1539f*******c9bda11d12c854e36",
"token_id": "tk_9ac510017*******69b614e8f7ee",
"state": "ACTIVE",
"total_billing_cycles": 3,
"completed_iteration": 1,
"last_billing_time": "2024-11-21T11:12:06Z",
"next_billing_time": "2024-11-21T11:13:06Z",
"products": [
{ "product_id": "prod_8efecd0bd******b9aa1ec5ec01", "quantity": 1 }
]
}
]
}
```
***
## 12. Cancel Subscription
**POST** `/subscription/v1/cancel`
### Request Parameters
| Attribute | Type | Required | Description |
| ----------------- | ------ | -------- | --------------- |
| `subscription_id` | String | Yes | Subscription ID |
***
## 13. Query Subscription Orders
**POST** `/subscription/billing_order/v1/list`
### Request Parameters
| Attribute | Type | Required | Description |
| ----------------- | ------ | -------- | ----------------------- |
| `subscription_id` | String | Yes | Subscription ID |
| `page` | Int | No | Default = 1 |
| `page_size` | Int | No | Default = 10, max = 100 |
### Response (data field)
| Attribute | Type | Description |
| ----------------------- | ------ | ------------------------------------------ |
| `subscription_order_id` | String | Format: `sub_ord_{subscription_id}_{0001}` |
| `subscription_id` | String | Parent subscription |
| `trigger_by` | String | `auto` or `manual` |
| `sequence_no` | Int | Billing iteration number |
***
## 14. Manual Charge Subscription
**POST** `/subscription/v1/charge`
### Request Parameters
| Attribute | Type | Required | Description |
| ----------------------- | ------ | -------- | ----------------------- |
| `subscription_id` | String | Yes | Subscription ID |
| `subscription_order_id` | String | No | Specific order to retry |
This API is only valid for subscriptions with failed billing and state `UNPAID`, `INCOMPLETE`, or `PAST_DUE`.
* If manual charge occurs **before** next billing time → subscription remains `ACTIVE`
* If manual charge occurs **after** next billing time → subscription will be cancelled
* If manual charge fails → subscription state remains unchanged
***
## Query a saved payment token
**POST** `/token/v1/query`
Query token details, including the token expiry date. Use `customer_id` and `token_id` when you need a specific saved payment method. For the saved-payment-method flow and notification handling, see [Customer & Token Setup](/integration/online-shop/integration-by-use-case/saved-payment-methods/customer-token-setup).
***
# Integration Guide for Recurring Payments
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-use-case/recurring-payments/recurring-integration-guide
Developer guide for integrating recurring payment features, including setup flow, data model, and best practices.
This guide covers the subscription-specific part of a recurring payment integration. Create and confirm the customer payment token first, then use it to create a subscription.
## Prerequisites
Before creating a recurring payment, complete the following in [Saved Payment Methods](/integration/online-shop/integration-by-use-case/saved-payment-methods/overview):
1. Create a customer and store the `customer_id`.
2. Generate a payment token with the Element SDK.
3. Confirm and store the `token_id` from the Token Creation Notification.
You also need a QFPay merchant account with recurring payments enabled, API credentials, and an HTTPS web application.
## Understanding the billing flow
The saved payment method and the subscription are separate records:
* The saved payment method contains the reusable `customer_id` and `token_id`.
* The subscription defines the billing product, frequency, and lifecycle.
Depending on your business logic, the initial card payment used to create the token can be either a verification charge or the first subscription charge.
## Integration steps
### Step 1: Create a product
Define the recurring product and billing cycle.
**POST** `/product/v1/create`
```json theme={null}
{
"name": "Premium Plan",
"type": "recurring",
"description": "Monthly premium subscription",
"txamt": 10000,
"txcurrcd": "HKD",
"interval": "monthly",
"interval_count": 1,
"usage_type": "licensed"
}
```
Store the returned `product_id`.
### Step 2: Create the subscription
Create the subscription with the saved customer, token, and recurring product.
**POST** `/subscription/v1/create`
```json theme={null}
{
"products": [
{
"product_id": "prod_xyz789",
"quantity": 1
}
],
"customer_id": "cust_abc123",
"token_id": "tok_def456",
"start_time": "2024-01-01 00:00:00"
}
```
The response returns a `subscription_id` and its current state.
### Step 3: Handle subscription notifications
Implement the [Subscription Webhook](/integration/online-shop/integration-by-use-case/recurring-payments/recurring-webhook) to receive subscription state changes and recurring billing results.
Token Creation Notifications are documented separately in [Saved Payment Methods](/integration/online-shop/integration-by-use-case/saved-payment-methods/token-notification).
## Testing
Test the complete flow in sandbox: create the saved payment method, confirm the token notification, create a product, create the subscription, and verify webhook handling.
## Best practices
1. Validate and process webhooks idempotently.
2. Store `customer_id`, `token_id`, `product_id`, and `subscription_id` in your backend.
3. Handle failed billing and customer notifications according to your retry policy.
4. Test cancellation, token replacement, and failed-payment paths before production.
## Next steps
* [Subscription API Reference](/integration/online-shop/integration-by-use-case/recurring-payments/recurring-api-reference)
* [Subscription Webhook](/integration/online-shop/integration-by-use-case/recurring-payments/recurring-webhook)
* [Saved Payment Methods](/integration/online-shop/integration-by-use-case/saved-payment-methods/overview)
# Recurring Payment Overview
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-use-case/recurring-payments/recurring-overview
Overview of QFPay's recurring payment system, including use cases, system architecture, and key components involved in subscription billing.
Recurring payments (also known as subscription payments) allow merchants to automatically charge customers on a regular schedule using a previously saved payment token. This flow is ideal for memberships, SaaS billing, utility payments, and any services billed over time.
***
## What is a Subscription?
A subscription is an agreement to charge a customer at fixed intervals (e.g. every month) for a fixed or variable amount. In QFPay, this is represented by a `subscription` object that references:
* A `customer_id`: the end user being charged
* A `token_id`: the saved card or payment method
* A `product_id`: defining the billing frequency and amount
***
## Key Concepts & Objects
| Object | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `customer_id` | A user profile saved for recurring billing (e.g. email, phone) |
| `token_id` | A stored payment credential obtained from the cardholder via [Payment Element](/integration/online-shop/checkout-integration/payment-element) |
| `product_id` | Defines billing amount, interval (monthly, yearly), and currency |
| `subscription_id` | The subscription record combining the above, controlling the billing lifecycle |
***
## End-to-End Subscription Lifecycle
### Step 1: Prepare the Saved Payment Method
Create the customer and payment token in [Saved Payment Methods](/integration/online-shop/integration-by-use-case/saved-payment-methods/overview). Store the confirmed `customer_id` and `token_id`.
### Step 2: Create a Product
Use `/product/v1/create` to define billing frequency and amount (for example, HKD 100 every month).
### Step 3: Create the Subscription
Use `/subscription/v1/create` with `customer_id`, `product_id`, and `token_id`.
### Step 4: Automatic or Manual Billing
QFPay charges the customer on the schedule. Failed payments can be retried manually via `/subscription/v1/charge`.
***
## Subscription States
| State | Description |
| ------------ | ------------------------------------------------- |
| `ACTIVE` | Subscription is live and billing as scheduled |
| `INCOMPLETE` | Initial setup failed or token is invalid |
| `PAST_DUE` | One or more billing attempts failed |
| `CANCELLED` | Subscription was cancelled by merchant or expired |
| `COMPLETED` | Subscription ended after completing all cycles |
To receive updates, implement [webhook notifications](/integration/online-shop/integration-by-use-case/recurring-payments/recurring-webhook).
***
## Example Use Cases
* **Gym Memberships**: Charge HKD 300 monthly
* **SaaS Subscriptions**: Charge HKD 98 monthly or HKD 999 yearly
* **Equipment Rentals**: Time-limited recurring payments with retries
***
## FAQ
### Can I update the payment token?
Yes. You can update the `token_id` on an active subscription via `/subscription/v1/update`.
### Can I pause a subscription?
Pausing is not currently supported. Cancel the subscription and create a new one when resuming.
### Can I retry failed charges?
Yes. Use `/subscription/v1/charge` with the failed `subscription_order_id`.
***
## Next Steps
* [API Reference](/integration/online-shop/integration-by-use-case/recurring-payments/recurring-api-reference)
* [Webhook Guide](/integration/online-shop/integration-by-use-case/recurring-payments/recurring-webhook)
# Subscription Webhook (Recurring Payment Notifications)
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-use-case/recurring-payments/recurring-webhook
This document describes the webhook notification system for recurring payments (subscriptions), including token creation, subscription status changes, and billing result notifications.
This page describes asynchronous notifications for the subscription system:
* Subscription state changes
* Recurring billing results
Token Creation Notifications are independent of subscriptions. See [Token Notification](/integration/online-shop/integration-by-use-case/saved-payment-methods/token-notification).
## How to configure the notification URL
To set your webhook endpoint, email your webhook URL, merchant ID, and store ID to [technical.support@qfpay.com](mailto:technical.support@qfpay.com).
## Subscription status change
Sent when a subscription changes state.
| Field | Description |
| ----------------- | -------------------------------------------------------------- |
| `notify_type` | Notification type: `subscription`. |
| `subscription_id` | Subscription identifier. |
| `state` | Current state, such as `ACTIVE`, `COMPLETED`, or `INCOMPLETE`. |
| `sysdtm` | System timestamp. |
```json theme={null}
{
"state": "COMPLETED",
"sysdtm": "2024-04-24 15:19:39",
"notify_type": "subscription",
"subscription_id": "sub_e51bb914919*****f6b0fe36d"
}
```
## Subscription payment result
Sent when a recurring billing attempt succeeds or fails.
| Field | Description |
| ----------------------- | ------------------------------------------- |
| `notify_type` | Value: `subscription_payment`. |
| `subscription_id` | Subscription ID. |
| `subscription_order_id` | Format: `sub_ord_{subscription_id}_{0001}`. |
| `respcd` | Response code. `0000` indicates success. |
| `respmsg` | Response message. |
| `syssn` | System transaction ID. |
| `txdtm` | Transaction timestamp. |
| `txamt` | Transaction amount. |
| `txcurrcd` | Currency. |
| `customer_id` | Customer ID. |
| `product_id` | Product ID or IDs. |
| `cardcd` | Masked card number. |
| `card_scheme` | Card scheme, when present. |
| `current_iteration` | Billing iteration number. |
```json theme={null}
{
"txcurrcd": "HKD",
"reason": "AUTHORISED",
"subscription_order_id": "sub_ord_a360f06eb*****ad6aff24c3a",
"product_id": "prod_8c838c17ddb043b9***11f1a85c30",
"txdtm": "2024-04-24 15:19:37",
"txamt": "300",
"card_scheme": "VISA_DEBIT-SSL",
"syssn": "20240424180500020000015704",
"respcd": "0000",
"subscription_id": "sub_e51bb914919***31d800f6b0fe36d",
"customer_id": "cust_a9c0bcf2717f4***786a10e5f8f2",
"notify_type": "subscription_payment",
"current_iteration": "1"
}
```
## Retry logic and idempotency
Make your webhook receiver idempotent. The same notification can be delivered more than once if delivery fails.
Return HTTP 200 after successful processing. If your server does not respond with HTTP 200, QFPay retries delivery with exponential backoff. The maximum retry count depends on internal configuration.
# Customer & Token Setup
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-use-case/saved-payment-methods/customer-token-setup
Create a customer and generate a reusable payment token with the Element SDK.
This guide covers the reusable setup required before using a saved payment method in recurring payments or another supported future-payment flow.
## 1. Create a customer
Create a customer record before collecting the payment method.
**POST** `/customer/v1/create`
| Parameter | Type | Required | Description |
| ----------------- | ------------- | -------- | ------------------------------- |
| `name` | String | No | Customer name. |
| `phone` | String | No | Contact number. |
| `email` | String | No | Email address. |
| `billing_address` | String (JSON) | No | JSON string of billing address. |
The response returns a `customer_id`. Store it in your backend and use it when initiating the Element SDK card payment.
## 2. Create a Payment Intent
Create a Payment Intent from your backend, then follow the [Element SDK](/integration/online-shop/checkout-integration/payment-element) integration steps to render the card-payment form.
Create Payment Intents and request signatures only from your backend. Never expose merchant secrets in browser code.
## 3. Request token creation
Token generation is supported only for card payments initiated with `payment.pay()`. Include the `customer_id` and optional token fields in that call.
```js theme={null}
payment.pay({
goods_name: 'Premium Product',
paysource: 'payment_element',
customer_id: 'cust_aaf6aae94...',
token_expiry: '2026-01-01',
token_reason: 'Save Card',
token_reference: 'customer-123'
}, intentParams.payment_intent);
```
| Parameter | Required | Description |
| ----------------- | ---------------------- | -------------------------------------------------------------- |
| `customer_id` | Yes for token creation | QFPay customer identifier to associate with the payment token. |
| `token_expiry` | No | Requested expiry date for the generated token. |
| `token_reason` | No | Reason for creating the token. |
| `token_reference` | No | Merchant reference for reconciliation. |
## 4. Confirm the card payment
Complete the payment with `qfpay.confirmPayment()`. A successful response can include a `token_id`.
Do not treat the browser response as the final source of truth. Use the [Token Creation Notification](/integration/online-shop/integration-by-use-case/saved-payment-methods/token-notification) to confirm and persist the token.
## Manage customer records
Use these endpoints to manage the customer record associated with a saved payment method.
| Action | Endpoint | Purpose |
| --------------- | -------------------------- | ----------------------------------------------------------- |
| Update customer | `POST /customer/v1/update` | Update a customer's name, phone, email, or billing address. |
| Query customers | `POST /customer/v1/query` | Search customer records by customer ID or contact details. |
| Delete customer | `POST /customer/v1/delete` | Delete a customer record. This action is irreversible. |
## Query a saved token
**POST** `/token/v1/query`
Query by `customer_id` and `token_id` to retrieve the saved token's details and expiry date. You can also filter by `reason`, `reference`, and token creation or expiry dates.
## Next steps
* [Token Notification](/integration/online-shop/integration-by-use-case/saved-payment-methods/token-notification)
* [Recurring Payments](/integration/online-shop/integration-by-use-case/recurring-payments/recurring-overview)
# Saved Payment Methods Overview
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-use-case/saved-payment-methods/overview
Create and manage reusable customer and payment-token records for future payments.
Saved Payment Methods lets you securely associate a customer with a QFPay payment token for a supported future card-payment flow. The resulting `customer_id` and `token_id` can be reused by recurring payments and other supported customer-initiated payment scenarios.
## When to use this
Use Saved Payment Methods when you need to collect a cardholder's payment details once, store the QFPay-issued token, and use that token in an approved future-payment flow.
Payment token generation is currently supported for card payments made through the [Element SDK](/integration/online-shop/checkout-integration/payment-element). Wallet payment methods do not generate payment tokens.
## How it works
1. Create a customer record and store the returned `customer_id`.
2. Collect card details through the Element SDK and provide the `customer_id` when initiating the card payment.
3. After successful payment and authentication, QFPay creates a `token_id`.
4. Confirm and persist the token from the Token Creation Notification.
5. Use the saved `customer_id` and `token_id` in a supported future-payment flow.
## What to store
| Value | Purpose |
| ----------------- | ----------------------------------------------------------------- |
| `customer_id` | Identifies the customer record in QFPay. |
| `token_id` | Identifies the saved payment method. |
| `token_expiry` | Indicates when the payment token expires, when provided by QFPay. |
| `token_reference` | Your reconciliation or internal tracking reference, if supplied. |
Store only QFPay-issued identifiers and masked card information. Never store raw card details in your systems.
## Next steps
* [Customer & Token Setup](/integration/online-shop/integration-by-use-case/saved-payment-methods/customer-token-setup)
* [Token Notification](/integration/online-shop/integration-by-use-case/saved-payment-methods/token-notification)
* [Element SDK](/integration/online-shop/checkout-integration/payment-element)
# Token Notification
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-use-case/saved-payment-methods/token-notification
Handle asynchronous notifications that confirm payment-token creation.
QFPay sends a Token Creation Notification after it creates a payment token through the Element SDK. Use this notification as the authoritative confirmation that a `token_id` is available for future use.
## Configure the notification URL
To enable Token Creation Notifications, email your notification URL, merchant ID, and store ID to [technical.support@qfpay.com](mailto:technical.support@qfpay.com).
The Token Creation Notification uses a dedicated endpoint. It is different from the standard payment notification endpoint.
## Notification fields
| Field | Description |
| ------------------- | ---------------------------------------- |
| `userid` | QFPay Store ID. |
| `notify_type` | Notification type: `payment_token`. |
| `event` | One of `NEW`, `MATCH`, or `CONFLICT`. |
| `tokenid` | Generated payment-token ID. |
| `token_expiry_date` | Token expiry date. |
| `cardcd` | Masked card number. |
| `card_scheme` | Card scheme, such as VISA or MASTERCARD. |
| `respcd` | Response code. `0000` indicates success. |
| `respmsg` | Response message. |
| `sysdtm` | System timestamp. |
| `customer_id` | Linked customer ID, if available. |
| `token_reason` | Reason for tokenisation. |
| `token_reference` | Merchant token reference. |
## Example
```json theme={null}
{
"respmsg": "success",
"card_scheme": "ECMC_DEBIT",
"cardcd": "5200****1096",
"tokenid": "tk_6a699aae75094caeb066f****988daa32de",
"respcd": "0000",
"token_expiry_date": "2024-04-30 00:00:00",
"sysdtm": "2024-04-29 15:37:17",
"notify_type": "payment_token",
"event": "CONFLICT"
}
```
## Handling guidance
* Verify the notification according to your QFPay integration requirements.
* Persist the `tokenid` only after a successful notification.
* Make processing idempotent so duplicate deliveries do not create duplicate records.
* Return HTTP 200 after successful processing.
## Next steps
Use the confirmed `token_id` with the saved `customer_id` in [Recurring Payments](/integration/online-shop/integration-by-use-case/recurring-payments/recurring-overview) or another supported future-payment flow.
# WAP Payment (Mobile Browser)
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-use-case/wap-payment
This document explains how to initiate WAP (H5) payments using different wallet types through QFPay from mobile browsers like Chrome or Safari.
WAP (H5) payment allows merchants to trigger wallet payment flows from mobile browsers such as Chrome or Safari.
For WAP/H5 payments, merchants should guide users to open the payment link in a mobile browser such as Chrome, Safari, or Edge. Due to restrictions in in-app browsers (for example WhatsApp, Facebook Messenger, or WeChat), QFPay cannot guarantee that these apps can invoke external wallet apps correctly.
Example: Alipay cannot be automatically opened inside the WeChat in-app browser. This is a browser restriction beyond QFPay’s control.
***
## HTTP Request
`POST /trade/v1/payment`
You can find the corresponding `pay_type` for each wallet in the table below:
| PayType | Description |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `800212` | WeChat H5 Payment — see [WeChat H5 Payment](/integration/online-shop/integration-by-payment-type/wechat/wechat-pay-h5) |
| `801512` | AlipayHK WAP Payment — see [Alipay H5 Payment](/integration/online-shop/integration-by-payment-type/alipay/alipay-wap-h5-payments) |
| `800712` | UnionPay WAP Payment |
| `805812` | PayMe WAP Payment |
***
## Request Parameters
| Name | Parameter | Required | Type | Description |
| ------------------ | -------------- | -------- | ----------- | -------------------------------------------------------------------------------------------------- |
| Transaction Amount | `txamt` | Yes | Int(11) | Amount in smallest unit (e.g. 100 = \$1). Recommended to be > 200 to reduce risk control failures. |
| Currency | `txcurrcd` | Yes | String(3) | Transaction currency. See [Currency List](/integration/api-reference/currencies). |
| Payment Type | `pay_type` | Yes | String(6) | The wallet payment type code (e.g. PayMe WAP = `805812`). |
| Order Number | `out_trade_no` | Yes | String(128) | Unique order number per merchant account across all payment/refund requests. |
| Transaction Time | `txdtm` | Yes | String(20) | Format: `YYYY-MM-DD hh:mm:ss` |
| Product Name | `goods_name` | No | String(64) | Product name/identifier. |
| QFPay Merchant ID | `mchid` | No | String(16) | Assigned by QFPay. Required for some merchants depending on backend configuration. |
| Device ID | `udid` | No | String(40) | Unique device ID shown in the merchant dashboard. |
| Redirect URL | `return_url` | No | String(255) | URL the user is redirected to after payment completes. |
| Notification URL | `notify_url` | No | String(255) | URL to receive asynchronous notifications after payment. |
***
## Response Parameters
| Name | Parameter | Type | Description |
| ------------------ | -------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Payment Type | `pay_type` | String(6) | Wallet payment type code. |
| System Time | `sysdtm` | String(20) | `YYYY-MM-DD hh:mm:ss`. Used as settlement cutoff. |
| Transaction Time | `txdtm` | String(20) | As sent in request. |
| Response Message | `resperr` | String(128) | Description or status message. |
| Amount | `txamt` | Int(11) | Transaction amount. |
| Debug Info | `respmsg` | String(128) | Internal response/debug message. |
| External Order No. | `out_trade_no` | String(128) | Returned for reference. |
| QFPay Order No. | `syssn` | String(40) | QFPay system-generated order number. |
| Response Code | `respcd` | String(4) | `0000` = success, `1143/1145` = pending/retry with status check; others = failure. See [Status Codes](/integration/api-reference/status-codes). |
| Payment URL | `pay_url` | String(512) | Redirect URL (mobile browser), or QR-code display URL (PC browser). |
Always confirm final payment status on your backend (for example via asynchronous notification and/or Transaction Enquiry), especially when `respcd` is not `0000`.
***
## Summary
* Suitable for mobile browser environments (not recommended for in-app browsers inside social apps).
* Set `return_url` and `notify_url` if you need user redirection and/or backend confirmation.
* Use `pay_url` to redirect users (mobile) or display a QR code (desktop).
* If `respcd` is not `0000`, use asynchronous notification and/or Transaction Enquiry to confirm the final result.
# Web Payment
Source: https://sdk.qfapi.com/integration/online-shop/integration-by-use-case/web-payment
This document explains how to integrate Web-based QR code payments across different wallets including WeChat Pay, Alipay, UnionPay, and PayMe. Suitable for desktop and mobile web checkout flows.
## Overview
Web payments allow customers to complete purchases directly from a merchant website. Users scan a QR code, confirm the payment amount, and complete payment using their selected wallet. After payment, the `return_url` will redirect the user to a predefined merchant page.
The selected wallet will deduct the amount in HKD from the customer, and QFPay will settle the same HKD amount to the merchant.
***
## HTTP Request
`POST /trade/v1/payment`
Supported `pay_type` values:
| PayType | Description |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `800201` | WeChat Web QR Payment (HK merchants) – see [WeChat Web Payment](/integration/online-shop/integration-by-payment-type/wechat/wechat-web-qr-payment) |
| `801514` | Alipay Web Payment (HK merchants) – see [Alipay Web Payment](/integration/online-shop/integration-by-payment-type/alipay/alipay-web-payments) |
| `800714` | UnionPay Web Payment (Cloud QuickPass for HK merchants) |
| `805814` | PayMe Web Payment (HK merchants) |
***
## Request Parameters
| Parameter Name | Parameter Code | Required | Type | Description |
| ----------------- | -------------- | -------- | ----------- | --------------------------------------------------------------------------------------------------------- |
| Order Amount | `txamt` | Yes | Int(11) | Integer amount in smallest currency unit. E.g. 100 = \$1. Recommended to use >200 to avoid risk triggers. |
| Currency | `txcurrcd` | Yes | String(3) | Transaction currency. See [Supported Currencies](/integration/api-reference/currencies). |
| Payment Type | `pay_type` | Yes | String(6) | e.g. `805814` for PayMe. |
| External Order ID | `out_trade_no` | Yes | String(128) | Unique transaction ID within the merchant system. Must be unique per transaction/refund. |
| Transaction Time | `txdtm` | Yes | String(20) | Format: `yyyy-MM-dd HH:mm:ss` |
| Expiry Time | `expired_time` | No | String(3) | QR code expiry in minutes. Default is 30, range: 5–120. (Applicable for static QR payments) |
| Product Name | `goods_name` | No | String(64) | Optional label shown on payment page. If using Chinese, encode in UTF-8. Required for App payments. |
| Sub-Merchant ID | `mchid` | No | String(16) | Assigned by QFPay. Use this when operating as a sub-merchant. |
| Device ID | `udid` | No | String(40) | Optional device identifier shown in backend. |
| Return URL | `return_url` | No | String(512) | User will be redirected here after payment completion. |
| | | | | |
***
## Response Parameters
| Parameter Name | Parameter Code | Type | Description |
| ----------------- | -------------- | ----------- | ------------------------------------------------------------------------------------------------------- |
| Payment Type | `pay_type` | String(6) | e.g. `805814` for PayMe |
| System Time | `sysdtm` | String(20) | Format: `YYYY-MM-DD hh:mm:ss`. Used as settlement cut-off. |
| Transaction Time | `txdtm` | String(20) | Format: `YYYY-MM-DD hh:mm:ss` |
| Response Message | `resperr` | String(128) | Text description of the result |
| Order Amount | `txamt` | Int(11) | Transaction amount |
| Debug Info | `respmsg` | String(128) | Optional technical message for debugging |
| External Order ID | `out_trade_no` | String(128) | Matches request value |
| QFPay Order ID | `syssn` | String(40) | QFPay-generated transaction ID |
| Response Code | `respcd` | String(4) | `0000` = success, `1143/1145` = pending. See [Response Codes](/integration/api-reference/status-codes). |
| Payment URL | `pay_url` | String(512) | The generated payment URL. Can be used to generate QR code on desktop UI. |
***
# Authentication & Signature
Source: https://sdk.qfapi.com/integration/preparation/authentication-and-signature
Generate request signatures to authenticate and secure QFPay API calls.
All API requests must include a digital signature to ensure authenticity and data integrity.
Unless otherwise specified, include the signature in the HTTP header:
```
X-QF-SIGN:
```
***
## How signature generation works
1. Collect request parameters
2. Sort parameters by name
3. Append your `client_key`
4. Hash the final string
5. Send the signature in the request header
***
## Step 1: Sort parameters
Sort all request parameters by **parameter name** in **ASCII ascending order**.
Example:
| Parameter | Value |
| --------- | ---------- |
| mchid | ZaMVg12345 |
| txamt | 100 |
| txcurrcd | HKD |
Sorted result:
```
mchid=ZaMVg12345&txamt=100&txcurrcd=HKD
```
***
## Step 2: Append your client key
Append your secret `client_key` to the end of the string.
If:
```
client_key = abcd1234
```
Result:
```
mchid=ZaMVg12345&txamt=100&txcurrcd=HKDabcd1234
```
***
## Step 3: Hash the string
Hash the final string.
**SHA256 is recommended.**\
**MD5 may be required by certain channels — follow the API specification.**
Example:
```
SHA256("mchid=ZaMVg12345&txamt=100&txcurrcd=HKDabcd1234")
```
***
## Step 4: Add signature to header
Include the hash result in the request header:
```
X-QF-SIGN:
```
***
## Important Rules
* Sort parameters alphabetically by name.
* Do **not** include empty or null parameters.
* Do **not** include the signature itself in the signing string.
* Parameter names and values are case-sensitive.
* Use UTF-8 encoding when building the string.
* Do not include spaces, line breaks, or extra characters.
Include `mchid` in the signature only if it is part of the request parameters.
***
## Example (Node.js)
```javascript theme={null}
const crypto = require("crypto");
const payload = {
txamt: "10",
txcurrcd: "HKD"
};
const key = "client_key_here";
const ordered = Object.keys(payload)
.sort()
.map(k => `${k}=${payload[k]}`)
.join("&");
const signature = crypto
.createHash("md5")
.update(ordered + key)
.digest("hex")
.toUpperCase();
console.log(signature);
```
***
# Before You Begin
Source: https://sdk.qfapi.com/integration/preparation/before-you-begin
Requirements and prerequisites for integrating with QFPay OpenAPI.
Welcome to the QFPay OpenAPI.
Before integrating, ensure you have the required credentials and access to the appropriate environment.
This documentation includes code examples in **Python, Java, Node.js, and PHP**, which you can adapt for your integration.
If you want to quickly test payment requests, you can use our preconfigured Postman collection:
[Download Postman Collection](/files/qfpay_openapi_payment_request.postman_collection.json)
***
## What you need before integrating
Before making API requests, ensure you have:
* API credentials issued by QFPay
* Access to sandbox or production endpoints
* Ability to generate request signatures
* A public callback URL for payment notifications
***
## Next steps
Learn how merchants and outlets are identified.
Generate secure request signatures.
Understand sandbox and production endpoints.
# Credentials & Merchant Structure
Source: https://sdk.qfapi.com/integration/preparation/credentials-and-merchant-structure
Understand the credentials used to identify stores and secure API requests.
To interact with the QFPay OpenAPI, you must use the credentials issued during onboarding. These credentials identify the store and ensure requests are authenticated securely.
***
## Store-Level Integration
QFPay OpenAPI integrations operate at the **store level**.
Each API request is processed for a specific store using the identifiers provided by QFPay.
***
## Required Credentials
QFPay provides the following credentials:
* **`app_code`** — Primary identifier of the store.
* **`client_key`** — Secret key used to generate request signatures.
* **`mchid`** *(optional)* — Store identifier used in certain configurations.
Use only the identifiers provided by QFPay during onboarding.
If an `mchid` is provided, include it when required.\
If it is not provided, do not include it in API requests.
***
## When is `mchid` used?
`mchid` may be required in certain configurations, including:
* agent-managed setups
* specific payment channel requirements
* store identification requirements defined during onboarding
Some integrations use only `app_code` and `client_key`.
Always follow the credentials provided by QFPay.
***
## Security Responsibilities
Keep your credentials secure:
* Never expose the **client\_key** in frontend or mobile applications.
* Do not commit credentials to public repositories.
* Store secrets securely using environment variables or a secrets manager.
# Environments
Source: https://sdk.qfapi.com/integration/preparation/environments
Understand QFPay environments and when to use each during development and deployment.
The QFPay OpenAPI is accessible through three environments: **Production**, **Live Testing**, and **Sandbox**.
Each environment serves a different purpose and uses a distinct base URL. Always ensure you are targeting the correct environment during development and deployment.
***
## Environment Overview
| Environment | Base URL | Purpose |
| ------------ | ----------------------------------- | ----------------------------------------------------- |
| Production | `https://openapi-hk.qfapi.com` | Live payments with real settlement |
| Live Testing | `https://test-openapi-hk.qfapi.com` | Real payment flow using test accounts (no settlement) |
| Sandbox | `https://openapi-int.qfapi.com` | Simulated transactions for development and testing |
***
## When to use each environment
### Sandbox
Use Sandbox for:
* initial development
* testing API connectivity and signatures
* simulating credit card transactions
* verifying request/response handling
Sandbox transactions do **not** deduct real funds.
> Sandbox may not support all payment methods.
***
### Live Testing
Use Live Testing for:
* validating real payment flows
* testing QR code and wallet payments
* end-to-end testing before production launch
Transactions performed using test accounts will **not be settled**.
If real payment methods are used during testing, ensure refunds are processed immediately.
***
### Production
Use Production when:
* integration is fully tested
* you are ready to accept real payments
* settlement and reconciliation are required
***
## Important Notes
Using credentials or endpoints from a different environment will result in authentication or signature errors.
Always confirm with QFPay which environment and credentials should be used for your deployment.
***
## Recommended workflow
1. Develop and test in **Sandbox**
2. Validate payment flow in **Live Testing**
3. Deploy to **Production**
# Introduction
Source: https://sdk.qfapi.com/introduction
Start integrating QFPay and explore key payment integration paths.
looWelcome to the QFPay OpenAPI documentation.
QFPay enables businesses to accept QR, card, and digital wallet payments across in-store and online environments.
Use the guides below to begin your integration.
Make your first payment request in minutes.
Set up credentials, environments, and authentication.
Explore in-store and online payment scenarios.
View request formats, parameters, and status codes.
## Choose Your Integration
Select the integration approach that best matches your business model.
### Business Scenarios
| Scenario | Typical Merchant Setup | Recommended Integration |
| --------------------------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------- |
| [**Self-Service Retail**](/integration/business-scenarios/self-service-retail) | Vending machines, kiosks, self-service terminals | MPM API, POS API |
| [**Physical Store / Restaurant**](/integration/business-scenarios/physical-store) | POS terminal, cashier, ECR integration, table-side QR ordering | POS API, H5 Payment, WAP Payment |
***
## Advanced Payment Features
Enable additional payment capabilities for your business.
Automatically charge returning customers using a saved payment agreement.
**Learn more →**
Reserve card funds for later capture or release.
**Learn more →**
Offer issuer or merchant promotions based on eligible Card BINs.
**Learn more →**
# Quick Start
Source: https://sdk.qfapi.com/quickstart
Send your first payment request using QFPay OpenAPI.
## Overview
This quick start walks you through creating a payment request and receiving a payment notification.
***
## Step 1 — Prepare credentials
To use the QFPay OpenAPI, you need:
* `app_code`
* `client_key`
* `mchid` (if provided)
If you do not have credentials, contact QFPay support.
## Step 2 — Prepare request parameters
```text theme={null}
{
"app_code": "YOUR_APP_CODE",
"out_trade_no": "ORDER_1001",
"total_fee": "100",
"pay_type": "801",
"notify_url": "https://example.com/callback"
}
```
## Step 3 — Generate request signature
Sign all request parameters using your `client_key`.
See **Signature Generation** for full instructions.
## Step 4 — Send payment request
```text theme={null}
POST https://api.qfapi.com/trade/v1/payment
Content-Type: application/json
```
## Step 5 — Handle the response
```text theme={null}
{
"respcd": "0000",
"pay_url": "https://..."
}
```
Use the returned URL or QR code to complete payment.
## Step 6 — Receive payment notification
QFPay sends an asynchronous notification to your `notify_url` when payment is completed.
Ensure your endpoint:
* accepts POST requests
* returns HTTP 200 upon success
* verifies the signature
## Optional: Test locally
Use ngrok or similar tools to receive notifications:
```text theme={null}
ngrok http 8080
```
Import the Postman collection to send test requests:
[https://sdk.qfapi.com/assets/files/qfpay\_openapi\_payment\_request.postman\_collection-c8de8c8fe69f3fcd5a7653d41c289a29.json](https://sdk.qfapi.com/assets/files/qfpay_openapi_payment_request.postman_collection-c8de8c8fe69f3fcd5a7653d41c289a29.json)
***
## Next Steps
Learn how to sign requests securely.
Handle payment status updates.
View supported payment codes.
Use sandbox for testing.