juspay / juspay/grace

[New Connector] asiapay — Authorize, PSync, Capture, Void, Refund, IncomingWebhook

Open
#175 0 comments 0 reactions 0 assignees View on GitHub
connector-integration
Dominant language
Python
Stars
4
Forks
6
PR merge metrics
No merged PRs in 30d

Description

# asiapay — New Connector Integration

**Workflow:** new-connector
**Flows:** Authorize, PSync, Capture, Void, Refund, IncomingWebhook
**Auth scheme:** Signature
**Currency unit:** StringMajor
**Base URL:** https://www.paydollar.com/b2c2/eng
**Sandbox URL:** https://test.paydollar.com/b2cDemo/eng
**Generated by:** Grace pipeline run `run-2026-05-27T08-12-02-861Z-3c225d`

## Discovered Docs

- https://www.readkong.com/page/paydollar-paygate-integration-guide-version-3-21-3398605
- https://silo.tips/download/paydollar-paygate-integration-guide-version-39
- https://github.com/stormwild/paydollar-woocommerce/blob/master/index.php

## Acceptance Criteria

- [ ] Authorize flow compiles and runs
- [ ] gRPC Authorize call succeeds against sandbox

---

## Tech Spec

# AsiaPay UCS Connector Integration Technical Specification

## Connector Profile

| Field | Value |
|-------|-------|
| Connector Name | AsiaPay (PayDollar) |
| Owning Company | AsiaPay Limited (Hong Kong) |
| Primary Brand | PayDollar |
| Regional Brands | PesoPay (Philippines), SiamPay (Thailand), BimoPay (Indonesia) |
| Production Base URL | https://www.paydollar.com/b2c2/eng |
| Sandbox Base URL | https://test.paydollar.com/b2cDemo/eng |
| Developer Portal | https://developer.asiapay.com/ (requires merchant login) |
| Admin Portal | https://admin.paydollar.com/b2c2/eng/merchant/index.jsp |

**Supported Countries:** Hong Kong, China, Macau, Taiwan, Singapore, Malaysia, India, Vietnam, Australia, New Zealand, Philippines, Thailand, Indonesia

**Supported Currencies (ISO 4217 numeric codes used in API):**

| Currency | ISO Alpha | ISO Numeric (`currCode`) | Brand |
|----------|-----------|--------------------------|-------|
| Hong Kong Dollar | HKD | 344 | PayDollar |
| US Dollar | USD | 840 | PayDollar |
| Singapore Dollar | SGD | 702 | PayDollar |
| Chinese Yuan | CNY | 156 | PayDollar |
| Japanese Yen | JPY | 392 | PayDollar |
| Taiwan Dollar | TWD | 901 | PayDollar |
| Australian Dollar | AUD | 036 | PayDollar |
| Euro | EUR | 978 | PayDollar |
| British Pound | GBP | 826 | PayDollar |
| Canadian Dollar | CAD | 124 | PayDollar |
| Thai Baht | THB | 764 | SiamPay |
| Philippine Peso | PHP | 608 | PesoPay |

> **Note on regional brands:** PesoPay, SiamPay, and BimoPay share the same API structure as PayDollar but use different base URLs (e.g., `https://www.pesopay.com/b2c2/eng`, `https://www.siampay.com/b2c2/eng`, `https://www.bimopay.com/b2c2/eng`).

---

## Authentication

AsiaPay uses a **Secure Hash (SHA1 or SHA256 signature)** scheme. The hash is computed by the merchant server, covering key transaction fields, and included as a form POST body parameter named `secureHash`. No HTTP Authorization header is used.

### Scheme
- **Primary auth (payment requests):** Signature — SHA1 (default) or SHA256 (request from AsiaPay)
- **Merchant API auth (capture/void/refund/query):** Form body credentials — `loginId` + `password`
- **Auth Location:** Body (form-encoded POST parameter)

### Credential Fields

| Field | Type | Purpose |
|-------|------|---------|
| `merchantId` | Number | Unique merchant identifier assigned by AsiaPay |
| `secureHashSecret` | String | Secret key for hash computation; must be enabled by contacting AsiaPay |
| `loginId` | String (max 30 chars) | Separate API login ID for Merchant API operations |
| `password` | String (max 15 chars) | API password for Merchant API operations |

### Secure Hash Calculation (Request)

Concatenate the following fields with `|` as delimiter, then apply SHA1 (or SHA256 if configured):

```
secureHash = SHA1(merchantId + "|" + orderRef + "|" + currCode + "|" + amount + "|" + payType + "|" + secureHashSecret)
```

**Example:**
```
SHA1("88888|ORDER-001|344|100.00|H|MY_SECRET") => "a1b2c3d4e5f6..."
```

### Secure Hash Verification (Datafeed Response)

The datafeed POST from AsiaPay includes a `secureHash` field. Verify it as:

```
expectedHash = SHA1(merchantId + "|" + Ref + "|" + PayRef + "|" + Cur + "|" + Amt + "|" + successcode + "|" + secureHashSecret)
```

Compare with the received `secureHash`. Discard the datafeed if they do not match.

---

## Supported Flows

| Flow | HTTP Method | Endpoint Path | Idempotency Key | Notes |
|------|------------|---------------|-----------------|-------|
| Authorize | POST | /directPay/payComp.jsp | `orderRef` | Server-to-server card submission; `payType=H` for auth-only |
| Capture | POST | /merchant/api/orderApi.jsp | `payRef` | `actionType=Capture`; uses separate Merchant API credentials |
| Void | POST | /merchant/api/orderApi.jsp | `payRef` | `actionType=Void`; cancels authorized or accepted payment |
| Refund | POST | /merchant/api/orderApi.jsp | `payRef` | `actionType=Refund`; must be within 14 days of original transaction |
| PSync | POST | /merchant/api/orderApi.jsp | `payRef` | `actionType=Query`; returns current `orderStatus` |
| IncomingWebhook | POST | {merchantConfiguredUrl} | `PayRef` | Datafeed POSTed asynchronously to merchant Return Value Link |

> **Normal Sale vs. Authorize-only:** Set `payType=N` for an immediate auth+capture (no separate Capture call needed). Set `payType=H` (Hold) for authorize-only; then call Capture via Merchant API.

---

## Request Schema

### Authorize Request

Endpoint: `POST https://www.paydollar.com/b2c2/eng/directPay/payComp.jsp`

Content-Type: `application/x-www-form-urlencoded`

```json
{
"merchantId": "88888",
"orderRef": "ORDER-2024-001",
"amount": "100.00",
"currCode": "344",
"payType": "H",
"payMethod": "CC",
"cardNo": "4111111111111111",
"cardHolder": "Test User",
"epMonth": "12",
"epYear": "2026",
"securityCode": "123",
"lang": "E",
"successUrl": "https://merchant.example.com/success",
"failUrl": "https://merchant.example.com/fail",
"cancelUrl": "https://merchant.example.com/cancel",
"secureHash": "a1b2c3d4e5f6789abc"
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `merchantId` | Number | Yes | Merchant identifier assigned by AsiaPay |
| `orderRef` | String (max 35) | Yes | Merchant's unique order reference |
| `amount` | Decimal String | Yes | Amount in major units (e.g., `"100.00"` for 100 HKD) |
| `currCode` | String (3 digits) | Yes | ISO 4217 numeric currency code (e.g., `"344"` = HKD, `"840"` = USD) |
| `payType` | String | Yes | `"N"` = Normal/Sale (auth+capture); `"H"` = Hold (authorize only) |
| `payMethod` | String | No | `"ALL"`, `"CC"`, `"ALIPAY"`, `"WECHATPAY"`, `"UNIONPAY"`, `"PAYPAL"`, `"CHINAPAY"`, `"TENPAY"`, `"99BILL"`, `"PPS"` |
| `cardNo` | String (16) | Cond | Card PAN (required for server-side direct payment) |
| `cardHolder` | String (max 20) | Cond | Cardholder name |
| `epMonth` | String (MM) | Cond | Card expiry month |
| `epYear` | String (YYYY) | Cond | Card expiry year |
| `securityCode` | String (3–4) | No | CVV2/CVC2 |
| `lang` | String | No | `E`=English, `C`=Traditional Chinese, `X`=Simplified Chinese, `K`=Korean, `J`=Japanese, `T`=Thai, `F`=French, `G`=German, `R`=Russian, `S`=Spanish |
| `successUrl` | String | No | Redirect URL on payment success |
| `failUrl` | String | No | Redirect URL on payment failure |
| `cancelUrl` | String | No | Redirect URL on cancellation |
| `secureHash` | String | Yes | SHA1 or SHA256 signature |
| `redirect` | Number | No | Auto-redirect delay (seconds) |
| `mpsMode` | String | No | Multi-currency mode: `"NIL"`, `"SCP"`, `"DCC"`, `"MCP"` |
| `remark` | String | No | Merchant remark (passed through to datafeed) |

### Capture Request

Endpoint: `POST https://www.paydollar.com/b2c2/eng/merchant/api/orderApi.jsp`

Content-Type: `application/x-www-form-urlencoded`

```json
{
"merchantId": "88888",
"loginId": "api_login",
"password": "api_pass",
"actionType": "Capture",
"payRef": "4780",
"amount": "100.00"
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `merchantId` | Number | Yes | Merchant identifier |
| `loginId` | String (max 30) | Yes | Merchant API login ID |
| `password` | String (max 15) | Yes | Merchant API password |
| `actionType` | String | Yes | Must be `"Capture"` |
| `payRef` | String (max 35) | Yes | PayDollar payment reference from original authorization |
| `amount` | Decimal String | Yes | Capture amount; must be ≤ original authorized amount |

### Void Request

Endpoint: `POST https://www.paydollar.com/b2c2/eng/merchant/api/orderApi.jsp`

Content-Type: `application/x-www-form-urlencoded`

```json
{
"merchantId": "88888",
"loginId": "api_login",
"password": "api_pass",
"actionType": "Void",
"payRef": "4780"
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `merchantId` | Number | Yes | Merchant identifier |
| `loginId` | String (max 30) | Yes | Merchant API login ID |
| `password` | String (max 15) | Yes | Merchant API password |
| `actionType` | String | Yes | Must be `"Void"` |
| `payRef` | String (max 35) | Yes | PayDollar payment reference to void |

### Refund Request

Endpoint: `POST https://www.paydollar.com/b2c2/eng/merchant/api/orderApi.jsp`

Content-Type: `application/x-www-form-urlencoded`

```json
{
"merchantId": "88888",
"loginId": "api_login",
"password": "api_pass",
"actionType": "Refund",
"payRef": "4780",
"amount": "50.00"
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `merchantId` | Number | Yes | Merchant identifier |
| `loginId` | String (max 30) | Yes | Merchant API login ID |
| `password` | String (max 15) | Yes | Merchant API password |
| `actionType` | String | Yes | Must be `"Refund"` |
| `payRef` | String (max 35) | Yes | PayDollar payment reference |
| `amount` | Decimal String | No | Refund amount; omit for full refund; provide for partial refund (must be ≤ original) |

### PSync Request

Endpoint: `POST https://www.paydollar.com/b2c2/eng/merchant/api/orderApi.jsp`

Content-Type: `application/x-www-form-urlencoded`

```json
{
"merchantId": "88888",
"loginId": "api_login",
"password": "api_pass",
"actionType": "Query",
"payRef": "4780"
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `merchantId` | Number | Yes | Merchant identifier |
| `loginId` | String (max 30) | Yes | Merchant API login ID |
| `password` | String (max 15) | Yes | Merchant API password |
| `actionType` | String | Yes | Must be `"Query"` |
| `payRef` | String (max 35) | Yes | PayDollar payment reference to query |

### IncomingWebhook

AsiaPay POSTs the datafeed as `application/x-www-form-urlencoded` to the merchant-configured URL.

| Field | Type | Description |
|-------|------|-------------|
| `successcode` | String | `"0"` = success, `"1"` = failure, `"-1"` = system error |
| `Ref` | String | Merchant's original order reference |
| `PayRef` | String | AsiaPay internal payment reference |
| `Amt` | Decimal String | Transaction amount in major units |
| `Cur` | String | ISO numeric currency code |
| `prc` | String | Primary response code (bank response) |
| `src` | String | Secondary response code |
| `AuthId` | String | Bank authorization code |
| `errMsg` | String | Human-readable result message |
| `Holder` | String | Cardholder name |
| `TxTime` | DateTime | Transaction timestamp (`YYYY-MM-DD HH:mm:ss.S`) |
| `ip` | String | Cardholder IP address |
| `eci` | String | ECI code (3D Secure result) |
| `payerAuth` | String | Payer authentication result |
| `mpsMode` | String | Multi-currency processing mode used |
| `remark` | String | Merchant remark (passed through) |
| `secureHash` | String | SHA1/SHA256 response signature for verification |
| `payType` | String | `"N"` or `"H"` |
| `Ord` | String | Order ID |

---

## Response Schema

### Authorize Response

For server-to-server direct post (`/directPay/payComp.jsp`), the result is returned **synchronously** in the HTTP response body as URL-encoded parameters.

**Success Response:**
```json
{
"successcode": "0",
"Ref": "ORDER-2024-001",
"PayRef": "4780",
"Amt": "100.00",
"Cur": "344",
"prc": "0",
"src": "0",
"AuthId": "123456",
"errMsg": "Transaction completed",
"Holder": "Test User",
"TxTime": "2024-01-15 10:30:00.0",
"Ord": "6697090",
"eci": "05",
"secureHash": "b2c3d4e5f6..."
}
```

**Error Response:**
```json
{
"successcode": "1",
"Ref": "ORDER-2024-001",
"PayRef": "4780",
"Amt": "100.00",
"Cur": "344",
"prc": "5",
"src": "1",
"errMsg": "Transaction declined by bank"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `successcode` | String | `"0"` = success, `"1"` = failure, `"-1"` = system error |
| `Ref` | String | Merchant's order reference |
| `PayRef` | String | AsiaPay payment reference (store for subsequent Capture/Void/Refund) |
| `Amt` | Decimal String | Transaction amount |
| `Cur` | String | ISO numeric currency code |
| `prc` | String | Primary response code from bank |
| `src` | String | Secondary response code |
| `AuthId` | String | Bank authorization approval code |
| `errMsg` | String | Result message |
| `eci` | String | ECI code (3DS status) |
| `secureHash` | String | Response signature for verification |

### Capture / Void / Refund / PSync Response

All Merchant API operations return URL-encoded query string in the response body.

**Capture Success:**
```
resultCode=0&orderStatus=Accepted&ref=ORDER-2024-001&payRef=4780&amt=100.00&cur=344&errMsg=Capture+Successfully
```

**Void Success:**
```
resultCode=0&orderStatus=Voided&ref=ORDER-2024-001&payRef=4780&amt=100.00&cur=344&errMsg=Void+Successfully
```

**Refund Success:**
```
resultCode=0&orderStatus=Refunded&ref=ORDER-2024-001&payRef=4780&amt=100.00&cur=344&errMsg=Refund+Successfully
```

**Failure:**
```
resultCode=-1&orderStatus=Authorized&ref=ORDER-2024-001&payRef=4780&amt=100.00&cur=344&errMsg=Operation+failed
```

| Field | Type | Description |
|-------|------|-------------|
| `resultCode` | String | `"0"` = success, `"-1"` = failure |
| `orderStatus` | String | Current/updated order status (see Status Mapping) |
| `ref` | String | Merchant's order reference |
| `payRef` | String | PayDollar payment reference |
| `amt` | Decimal String | Transaction amount |
| `cur` | String | ISO numeric currency code |
| `errMsg` | String | Error or success message |

---

## Error Handling

| HTTP Status | Connector Field | Value | UCS Error Mapping | Cause |
|-------------|----------------|-------|-------------------|-------|
| 200 | `successcode` | `"0"` + `prc=0` | `AttemptStatus::Charged` | Transaction successful |
| 200 | `successcode` | `"1"` + `prc=5` | `AttemptStatus::Failure` | Card declined |
| 200 | `successcode` | `"1"` + `prc=9` | `AttemptStatus::Failure` | Transaction rejected |
| 200 | `successcode` | `"1"` + any `prc` | `AttemptStatus::Failure` | Generic bank decline |
| 200 | `successcode` | `"-1"` | `AttemptStatus::SystemError` | AsiaPay/bank system error |
| 200 | `resultCode` | `"-1"` | `AttemptStatus::Failure` | Merchant API operation failed |
| 200 | `orderStatus` | `"Rejected"` | `AttemptStatus::Failure` | Bank rejected transaction |
| 200 | `orderStatus` | `"Cancelled"` | `AttemptStatus::Voided` | Cancelled by cardholder |

**PRC (Primary Response Code) meanings:**
- `0` = Approved / Success
- `1` = Referral (contact bank)
- `5` = Declined
- `9` = Hold / Pickup card
- Other values = Bank-specific decline or error codes

**Retry Semantics:**
- `successcode=-1` (system error): safe to retry with the same `orderRef`
- `successcode=1` (declined): do NOT retry without cardholder action (new card details or 3DS)
- Merchant API `resultCode=-1`: investigate via Query before retrying to avoid double operations
- Refund has a 14-day window from the original transaction; after expiry the call will fail with `resultCode=-1`

---

## Status Mapping

| Connector Status (verbatim) | UCS AttemptStatus | Notes |
|----------------------------|-------------------|-------|
| `Pending` | `AuthenticationPending` | Authorization in progress at issuing bank |
| `Pending_3D` | `AuthenticationPending` | 3D Secure verification in progress |
| `Authorized` | `Authorized` | Auth-only hold placed; awaiting merchant Capture |
| `Capturing` | `CaptureInitiated` | Capture in progress |
| `Accepted` | `Charged` | Authorized + captured / settled |
| `Accepted_Adj` | `Charged` | Accepted with partial chargeback or partial refund applied |
| `Rejected` | `Failure` | Transaction rejected by bank or AsiaPay |
| `Cancelled` | `Voided` | Cancelled by cardholder before completion |
| `Voided` | `Voided` | Voided by merchant via Merchant API |
| `Reverse Auth` | `Voided` | Authorization reversed by merchant |
| `Reversal Void` | `Charged` | Prior void was reversed; payment restored |
| `Request Refund` | `AutoRefunding` | Full refund request submitted, processing |
| `RequestPartialRefund` | `AutoRefunding` | Partial refund submitted, processing |
| `Refunded` | `AutoRefunded` | Full refund completed |
| `Partial Refunded` | `PartialCharged` | Partial refund completed |
| `ChargeBack` | `AutoRefunded` | Full chargeback from cardholder/bank |
| `Partial ChargeBack` | `PartialCharged` | Partial chargeback |
| `Reversal-CB` | `Charged` | Successful chargeback reversal by merchant |

---

## Webhooks

### Subscription Mechanism

Merchants manually configure the datafeed URL in the AsiaPay Merchant Administration Portal:
1. Log in to https://admin.paydollar.com/b2c2/eng/merchant/index.jsp
2. Navigate to **Profile → Payment Account Settings → Return Value Link (Datafeed)**
3. Enable the option by selecting the **Enable** radio button
4. Enter the merchant-side webhook URL in the **Return Value Link** text field
5. Click **Update** and confirm

### Payload Structure

AsiaPay POSTs the datafeed as `application/x-www-form-urlencoded`:

```
successcode=0&Ref=ORDER-001&PayRef=4780&Amt=100.00&Cur=344&prc=0&src=0&AuthId=123456&errMsg=Transaction+completed&Holder=John+Doe&TxTime=2024-01-15+10%3A30%3A00.0&eci=05&secureHash=a1b2c3d4e5f6
```

### Signature Verification

Header: none (signature embedded as `secureHash` field in POST body).

Algorithm:
```
expectedHash = SHA1(merchantId + "|" + Ref + "|" + PayRef + "|" + Cur + "|" + Amt + "|" + successcode + "|" + secureHashSecret)
```
If `expectedHash != received secureHash`, reject the datafeed.

### Event Types

| Event | `successcode` | Trigger |
|-------|---------------|---------|
| Payment Success | `"0"` | Transaction authorized and/or captured |
| Payment Failure | `"1"` | Transaction declined or failed |
| System Error | `"-1"` | Processing error at AsiaPay or bank |

### Retry / Delivery Policy
- AsiaPay retries datafeed delivery if the merchant endpoint does not return HTTP 2xx
- Implement idempotent webhook handling keyed on `PayRef`
- If datafeed delivery fails persistently, use the PSync (Query) API to reconcile transaction status

### 3D Secure ECI Codes

| Network | ECI | Meaning |
|---------|-----|--------|
| Visa | `05` | Full 3DS authentication successful |
| Visa | `06` | 3DS attempted; one party not enrolled |
| Visa | `07` | 3DS not attempted or not supported |
| MasterCard | `02` | Full 3DS authentication successful |
| MasterCard | `01` | 3DS attempted; one party not enrolled |
| MasterCard | `00` | 3DS not attempted or not supported |
| JCB | `05` | Full 3DS authentication successful |
| JCB | `06` | 3DS attempted; one party not enrolled |
| JCB | `07` | 3DS not attempted or not supported |

---

## References

### Official AsiaPay
- https://developer.asiapay.com/ — AsiaPay Developer Portal (requires merchant authentication)
- https://developer.asiapay.com/integration-guide — Integration Guide
- https://developer.asiapay.com/integration-guide/functions-of-merchant-api — Merchant API Functions
- https://developer.asiapay.com/reference-payment — Reference Payment API
- https://www.paydollar.com/pdf/op/enpdintguide.pdf — PayDollar PayGate Integration Guide v3.67 (PDF)
- https://www.paydollar.com/b2c2/eng/merchant/help/f_onlinehelp_orderstatus.htm — Order Status Definitions
- https://www.paydollar.com/b2c2/eng/merchant/help/f_onlinehelp_eci.htm — ECI Code Definitions

### Public Mirrors / Integration Guides
- https://www.readkong.com/page/paydollar-paygate-integration-guide-version-3-21-3398605 — PayGate Guide v3.21 (text)
- https://silo.tips/download/paydollar-paygate-integration-guide-version-39 — PayGate Guide v3.9
- https://www.yumpu.com/en/document/view/22776107/paydollar-paygate-integration-guide-v37pdf — PayGate Guide v3.7
- https://github.com/asiapay-lib/integration_guide — Official AsiaPay integration guide repository (PDFs)

### Reference Implementations
- https://github.com/stormwild/paydollar-woocommerce/blob/master/index.php — WooCommerce plugin
- https://github.com/joussyd/asiapay/blob/master/src/Factory.php — PHP library

### Third-Party Platform Docs
- https://www.odoo.com/documentation/19.0/applications/finance/payment_providers/asiapay.html — Odoo integration
- https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4609624270.html — NetSuite integration

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.