juspay / juspay/grace

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

Open
#173 2 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, RSync, SetupMandate, RepeatPayment, IncomingWebhook
**Auth scheme:** Signature
**Currency unit:** StringMajor
**Base URL:** https://www.paydollar.com/b2c2/eng/payment/payForm.jsp
**Sandbox URL:** https://test.paydollar.com/b2cDemo/eng/payment/payForm.jsp
**Generated by:** Grace pipeline run `run-2026-05-22T07-24-06-701Z-ca2ad2`

## Discovered Docs

- https://developer.asiapay.com/
- https://developer.asiapay.com/integration-guide/introduction-4
- https://www.paydollar.com/pdf/op/enpdintguide.pdf

## 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) |
| Category | Payment Gateway |
| Owning Company | AsiaPay Limited |
| Brands | PayDollar (HK/APAC), PesoPay (PH), SiamPay (TH), BimoPay (ID) |
| Production Base URL | https://www.paydollar.com |
| Sandbox Base URL | https://test.paydollar.com |
| Supported Regions | Hong Kong, China, Macau, Taiwan, Singapore, Malaysia, India, Vietnam, New Zealand, Australia, Philippines, Thailand, Indonesia |
| Supported Currencies | AED, AUD, BND, CAD, CNY, EUR, GBP, HKD, IDR, INR, JPY, KRW, MOP, MYR, NZD, PHP, SAR, SGD, THB, TWD, USD, VND |
| PCI Compliance | PCI DSS compliant |
| 3DS Support | Yes — Verified by Visa (VbV), Mastercard SecureCode, JCB J/Secure, AMEX SafeKey; 3DS 2.0 supported |
| Currency Format | String with 2 decimal places e.g. "10.95" (StringMajor) |
| API Protocol | HTTPS POST (form-encoded) |

### Brand-specific Base URLs

| Brand | Production Base | Sandbox Base |
|---|---|---|
| PayDollar | https://www.paydollar.com | https://test.paydollar.com |
| PesoPay | https://www.pesopay.com | https://test.pesopay.com |
| SiamPay | https://www.siampay.com | https://test.siampay.com |
| BimoPay | https://www.bimopay.com | Contact AsiaPay |

## Authentication

### Mechanism

AsiaPay uses a **Secure Hash Signature** scheme. All payment requests include a `secureHash` field computed as:

```
secureHash = HASH_FUNCTION(merchantId|orderRef|currCode|amount|payType|secureHashSecret)
```

Where:
- `HASH_FUNCTION` is SHA1 (legacy/deprecated), SHA256 (recommended), or SHA512
- Fields are joined with pipe `|` delimiter
- `secureHashSecret` is the final component appended before hashing

### Incoming Datafeed Signature Verification

For verifying AsiaPay webhook notifications:

```
expectedHash = HASH_FUNCTION(src|prc|successcode|Ref|PayRef|Cur|Amt|payerAuth|secureHashSecret)
```

Validate using constant-time comparison to prevent timing attacks.

### Credential Fields

| Field | Description | Where Used |
|---|---|---|
| `merchantId` | Numeric identifier for merchant account | All payment requests |
| `secureHashSecret` | Secret key for computing HMAC signature | Signature calculation |
| `secureHashFunction` | Hash algorithm: SHA1, SHA256, SHA512 | Signature calculation |
| `loginId` | Merchant API access username (max 30 chars) | Merchant API (capture/void/refund/query) |
| `loginPassword` | Merchant API access password (max 15 chars) | Merchant API (capture/void/refund/query) |

**Notes:**
- `secureHashSecret` is retrieved from PayDollar Merchant Administration Interface: Profile > Payment Information
- `loginId` and `loginPassword` are separate credentials assigned for API operations
- SHA256 is recommended; SHA1 is legacy and being deprecated
- Contact service@paydollar.com to enable Secure Hash function on new accounts

## Supported Flows

| Flow | HTTP Method | Endpoint Path | Idempotency | Notes |
|---|---|---|---|---|
| Authorize | POST | `/b2c2/eng/payment/payForm.jsp` | No | Browser form POST; payType=H for authorize-only, payType=N for sale |
| PSync | POST | `/b2c2/eng/merchant/api/orderApi.jsp` | Yes | Query payment status (actionType=Query) |
| Capture | POST | `/b2c2/eng/merchant/api/orderApi.jsp` | No | Capture authorized payment (actionType=Capture) |
| Void | POST | `/b2c2/eng/merchant/api/orderApi.jsp` | No | Void accepted/authorized payment (actionType=Void) |
| Refund | POST | `/b2c2/eng/merchant/api/orderApi.jsp` | No | Full or partial refund (actionType=Refund) |
| RSync | POST | `/b2c2/eng/merchant/api/orderApi.jsp` | Yes | Query refund status (actionType=Query) |
| IncomingWebhook | POST | Merchant-configured Datafeed URL | N/A | AsiaPay POSTs payment notifications to merchant endpoint |
| RepeatPayment | POST | `/b2c2/eng/payment/payForm.jsp` | No | Reference Payment — reuses card data from previous transaction |

## Request Schema

### Authorize Request

Payment initiation is an HTML form POST to payForm.jsp. The browser redirects the customer to AsiaPay's hosted checkout.

```json
{
"merchantId": "12345",
"orderRef": "ORDER-001",
"amount": "100.00",
"currCode": "344",
"payType": "N",
"lang": "E",
"successUrl": "https://yoursite.com/success",
"failUrl": "https://yoursite.com/fail",
"cancelUrl": "https://yoursite.com/cancel",
"secureHash": "abc123def456..."
}
```

**Required Fields:**

| Field | Type | Max Length | Description |
|---|---|---|---|
| `merchantId` | Number | — | Merchant identifier provided by PayDollar |
| `orderRef` | Text | 35 | Merchant's unique order reference number |
| `amount` | Number | 12,2 | Total charge amount with up to 2 decimal places (e.g., 100.00) |
| `currCode` | Text | 3 | ISO 4217 numeric currency code (e.g., 344=HKD, 840=USD, 978=EUR) |
| `payType` | Text | 1 | N=Normal Sale (auth+capture), H=Hold/Authorize only |
| `lang` | Text | 1 | Language: E=English, C=Traditional Chinese, X=Simplified Chinese, J=Japanese, T=Thai, F=French, G=German, R=Russian, S=Spanish/Vietnamese |
| `successUrl` | Text | 300 | Redirect URL on transaction acceptance |
| `failUrl` | Text | 300 | Redirect URL on transaction rejection |
| `cancelUrl` | Text | 300 | Redirect URL on customer cancellation |
| `secureHash` | Text | 40+ | SHA1/SHA256/SHA512 hash for authentication |

**Optional Fields:**

| Field | Type | Description |
|---|---|---|
| `payMethod` | Text | Payment method: ALL, CC, VISA, Master, JCB, AMEX, Diners, PPS, PAYPAL, CHINAPAY, ALIPAY, TENPAY, 99BILL, MEPS, SCB, BPM, KTB, UOB, KRUNGSRIONLINE, TMB, IBANKING, UPOP |
| `remark` | Text (200) | Merchant-stored metadata |
| `mpsMode` | Text | Multi-currency mode: NIL (disabled), SCP (simple conversion), DCC, MCP |
| `redirect` | Number | Auto-redirect seconds |
| `errorUrl` | Text (300) | Error page redirect |
| `billingFirstName` | Text (60) | Customer first name |
| `billingLastName` | Text (60) | Customer last name |
| `billingStreet1` | Text (40) | Billing address line 1 |
| `billingStreet2` | Text (40) | Billing address line 2 |
| `billingCity` | Text (50) | Billing city |
| `billingState` | Text (2) | Billing state (mandatory for USA/Canada) |
| `billingPostalCode` | Text (10) | Postal code (mandatory for USA/Canada) |
| `billingCountry` | Text (2) | ISO 2-letter country code |
| `billingEmail` | Text (255) | Customer email |
| `custIPAddress` | Text (15) | Customer IP address |
| `installment_service` | Text | T/F — enable installment |
| `installment_period` | Number | Number of installment months |

### PSync / RSync Request (Query)

```json
{
"merchantId": "12345",
"loginId": "merchant_api_user",
"loginPassword": "secret123",
"actionType": "Query",
"payRef": "4780"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `merchantId` | Number | Yes | Merchant identifier |
| `loginId` | Text (30) | Yes | Merchant API login ID |
| `loginPassword` | Text (15) | Yes | Merchant API password |
| `actionType` | Text | Yes | `Query` |
| `payRef` | Text (35) | Yes | PayDollar payment reference number |

### Capture Request

```json
{
"merchantId": "12345",
"loginId": "merchant_api_user",
"loginPassword": "secret123",
"actionType": "Capture",
"payRef": "4780",
"amount": "100.00"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `merchantId` | Number | Yes | Merchant identifier |
| `loginId` | Text (30) | Yes | Merchant API login ID |
| `loginPassword` | Text (15) | Yes | Merchant API password |
| `actionType` | Text | Yes | `Capture` |
| `payRef` | Text (35) | Yes | PayDollar payment reference number from original authorization |
| `amount` | Number (12,2) | Yes | Amount to capture |

### Void Request

```json
{
"merchantId": "12345",
"loginId": "merchant_api_user",
"loginPassword": "secret123",
"actionType": "Void",
"payRef": "4780"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `merchantId` | Number | Yes | Merchant identifier |
| `loginId` | Text (30) | Yes | Merchant API login ID |
| `loginPassword` | Text (15) | Yes | Merchant API password |
| `actionType` | Text | Yes | `Void` |
| `payRef` | Text (35) | Yes | PayDollar payment reference number |

### Refund Request

```json
{
"merchantId": "12345",
"loginId": "merchant_api_user",
"loginPassword": "secret123",
"actionType": "Refund",
"payRef": "4780",
"amount": "50.00"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `merchantId` | Number | Yes | Merchant identifier |
| `loginId` | Text (30) | Yes | Merchant API login ID |
| `loginPassword` | Text (15) | Yes | Merchant API password |
| `actionType` | Text | Yes | `Refund` |
| `payRef` | Text (35) | Yes | PayDollar payment reference number |
| `amount` | Number (12,2) | Yes | Amount to refund (can be partial) |

### RepeatPayment Request

Same as Authorize request with additional field:

| Field | Type | Required | Description |
|---|---|---|---|
| `referencePaymentNo` | Text | Yes | PayRef of the previous successful transaction to reuse card data from |

## Response Schema

### Authorize Response (Datafeed / Webhook)

AsiaPay POSTs these fields to the configured datafeed URL after payment processing:

```json
{
"src": "0",
"prc": "00",
"successcode": "0",
"Ref": "ORDER-001",
"PayRef": "4780",
"Cur": "344",
"Amt": "100.00",
"payerAuth": "Y",
"secureHash": "abc123def456...",
"AuthId": "123456",
"TxTime": "2024-01-15 12:30:00.0",
"Ord": "BANK-REF-001",
"Holder": "JOHN DOE",
"payMethod": "VISA",
"MerchantId": "12345"
}
```

| Field | Type | Description |
|---|---|---|
| `src` | Text | Source/secondary response code |
| `prc` | Text | Primary response code (bank response code) |
| `successcode` | Text | Transaction outcome: 0=success, 1=failure, -1=error |
| `Ref` | Text | Merchant's order reference number (echo) |
| `PayRef` | Text | PayDollar payment reference number |
| `Cur` | Text | ISO numeric currency code |
| `Amt` | Text | Transaction amount (2 decimal places) |
| `payerAuth` | Text | 3D Secure result: Y=authenticated, N=failed, A=attempted, U=unavailable |
| `secureHash` | Text | SHA hash for datafeed verification |
| `AuthId` | Text | Bank authorization/approval code |
| `TxTime` | Text | Transaction timestamp (YYYY-MM-DD HH:MI:SS.0) |
| `Ord` | Text | Bank reference order ID |
| `Holder` | Text | Cardholder name |
| `errMsg` | Text | Error message (if applicable) |
| `payMethod` | Text | Payment method used |
| `cardIssuingCountry` | Text | Card-issuing country |
| `eci` | Text | Electronic Commerce Indicator |
| `channelType` | Text | Channel type |
| `sourceIp` | Text | Customer IP address |
| `ipCountry` | Text | IP geolocation country |
| `mpsAmt` | Text | Multi-Currency Processing amount |
| `mpsCur` | Text | MPS currency |
| `mpsForeignAmt` | Text | MPS foreign amount |
| `mpsForeignCur` | Text | MPS foreign currency |
| `mpsRate` | Text | MPS exchange rate |
| `AlertCode` | Text | Alert code |
| `MerchantId` | Text | Merchant ID echo |

### Capture / Void / Refund Response (Merchant API)

Responses are URL-encoded key-value pairs:

```
resultCode=0&orderStatus=Accepted&ref=ORDER-001&payRef=4780&amt=100.00&cur=344
```

**Success Response:**

| Field | Description |
|---|---|
| `resultCode` | 0=success, -1=failure |
| `orderStatus` | Current transaction status (see Status Mapping) |
| `ref` | Merchant's order reference number |
| `payRef` | PayDollar payment reference number |
| `amt` | Transaction amount |
| `cur` | Currency code |

**Error Response:**

```
resultCode=-1&errMsg=Invalid+Login+Credentials
```

| Field | Description |
|---|---|
| `resultCode` | -1 for failure |
| `errMsg` | Human-readable error message |

## Error Handling

| HTTP Status | Connector Error Code | UCS Error Mapping | Cause |
|---|---|---|---|
| 200 | successcode=0 | Authorized / Charged | Transaction approved by bank |
| 200 | successcode=1 | Failure | Transaction rejected by bank or gateway |
| 200 | successcode=-1 | ProcessingError | System error or invalid request |
| 200 | prc=-1 | ProcessingError | PayDollar system error |
| 200 | prc=EI01 | InvalidPaymentMethod | Invalid payment method specified |
| 200 | prc=ET01 | ProcessingError | System timeout |
| 200 | prc=00 | Authorized | Bank approved |
| 200 | prc=01 | Declined | Refer to card issuer |
| 200 | prc=05 | Declined | Do not honour |
| 200 | prc=14 | InvalidCard | Invalid card number |
| 200 | prc=33 | ExpiredCard | Expired card |
| 200 | prc=51 | InsufficientFunds | Insufficient funds |
| 200 | prc=54 | ExpiredCard | Expired card (bank code) |
| 200 | prc=57 | InvalidTransaction | Transaction not permitted |
| 200 | prc=61 | ExceedsWithdrawalLimit | Exceeds withdrawal amount limit |
| 200 | prc=62 | InvalidCard | Restricted card |
| 200 | prc=65 | ExceedsWithdrawalLimit | Exceeds withdrawal frequency |
| 200 | prc=91 | ProcessingError | Issuer or switch inoperative |
| 200 | resultCode=0 | Success | Merchant API operation succeeded |
| 200 | resultCode=-1 | Failure | Merchant API operation failed |
| 403 | — | AuthenticationFailure | Invalid secureHash or unauthorized access |
| 200 | payerAuth=N | AuthenticationFailure | 3DS authentication failed |
| 200 | payerAuth=Y | Authenticated3DS | 3DS authentication successful |
| 200 | payerAuth=A | AttemptedAuthentication | 3DS attempted but not fully authenticated |
| 200 | payerAuth=U | 3DSNotSupported | 3DS enrollment unavailable |

**Retry Semantics:** AsiaPay does not define specific retry windows. For ProcessingError (successcode=-1 or prc=ET01), a single retry after a brief delay is acceptable. For Declined responses (successcode=1), do not retry without customer re-initiation. For InsufficientFunds or card errors, do not retry.

## Status Mapping

| Connector Status (orderStatus) | UCS AttemptStatus | Notes |
|---|---|---|
| Accepted | Charged | Transaction captured and settled |
| Accepted_Adj | Charged | Accepted with chargeback adjustment |
| Authorized | Authorized | Auth-only; awaiting capture |
| Pending | Pending | Awaiting bank authorization |
| Pending_3D | AuthenticationPending | 3DS process in progress |
| Cancelled | Voided | Cancelled by cardholder |
| Capturing | CaptureInitiated | Capture process pending |
| Voided | Voided | Voided by merchant |
| Reverse Auth | Voided | Authorization withdrawn by merchant |
| Reversal Void | Authorized | Merchant withdrew void request |
| Rejected | Failure | Rejected by bank |
| Refunded | AutoRefunded | Full refund processed |
| Partial Refunded | AutoRefunded | Partial refund processed |
| Request Refund | RefundInitiated | Full refund under processing |
| RequestPartialRefund | RefundInitiated | Partial refund under processing |
| ChargeBack | ChargebackNotification | Full chargeback initiated |
| Partial ChargeBack | ChargebackNotification | Partial chargeback initiated |
| Reversal-CB | ChargebackReversed | Merchant appeal of chargeback succeeded |

**successcode mapping (Datafeed):**
- `0` → Charged or Authorized (depends on payType=N or H)
- `1` → Failure
- `-1` → ProcessingError

## Webhooks

### Configuration

1. Log in to the PayDollar Merchant Administration Interface
2. Navigate to: **Profile > Payment Account Settings**
3. Enable: **Return Value Link (Datafeed)**
4. Enter your datafeed URL (e.g., `https://yourdomain.com/payment/asiapay/webhook`)
5. Test the webhook via AsiaPay's dashboard before going live

### Webhook Endpoint

- **Method:** POST
- **Content-Type:** `application/x-www-form-urlencoded`
- **Expected Response:** `OK` (HTTP 200 with body `OK`)

### Payload Example

```
POST https://yoursite.com/payment/asiapay/webhook
Content-Type: application/x-www-form-urlencoded

src=0&prc=00&successcode=0&Ref=ORDER-001&PayRef=4780&Cur=344&Amt=100.00&payerAuth=Y&secureHash=abc123...&AuthId=123456&TxTime=2024-01-15+12:30:00.0&Holder=JOHN+DOE&MerchantId=12345
```

### Signature Verification

```python
import hmac, hashlib

def verify_signature(data, secret, hash_func='sha256'):
keys = ['src', 'prc', 'successcode', 'Ref', 'PayRef', 'Cur', 'Amt', 'payerAuth']
message = '|'.join(data.get(k, '') for k in keys) + '|' + secret
expected = hashlib.new(hash_func, message.encode()).hexdigest()
received = data.get('secureHash', '')
return hmac.compare_digest(expected.lower(), received.lower())
```

### Event Types and UCS Equivalents

| successcode | payerAuth | Inferred Event | UCS Flow |
|---|---|---|---|
| 0 | Y / A / U | Payment Success | IncomingWebhook → Charged |
| 1 | N | Payment Declined | IncomingWebhook → Failure |
| -1 | — | System Error | IncomingWebhook → ProcessingError |

### Important Notes

- The `successUrl` redirect is for **display purposes only** — always use Datafeed for authoritative status
- Datafeed may arrive before or after the return URL redirect
- Acknowledge all webhook events (HTTP 200 with body `OK`) even on verification failures
- AsiaPay retries datafeed delivery if no `OK` response received

## References

**Official Documentation:**
- https://developer.asiapay.com/
- https://developer.asiapay.com/integration-guide/introduction-4
- https://developer.asiapay.com/integration-guide/functions-of-merchant-api
- https://developer.asiapay.com/integration-guide/transaction-security-by-secure-hash
- https://developer.asiapay.com/reference-payment
- https://developer.asiapay.com/recurring-payment

**Integration Guides:**
- https://www.paydollar.com/pdf/op/enpdintguide.pdf (PayDollar PayGate Integration Guide v3.67)
- https://www.readkong.com/page/paydollar-paygate-integration-guide-version-3-21-3398605 (v3.21 readable)

**Status / Error Reference:**
- https://www.paydollar.com/b2c2/eng/merchant/help/f_onlinehelp_orderstatus.htm

**Open-Source Integration Reference:**
- https://github.com/odoo/odoo/blob/17.0/addons/payment_asiapay/const.py
- https://github.com/odoo/odoo/blob/17.0/addons/payment_asiapay/models/payment_provider.py
- https://github.com/odoo/odoo/blob/17.0/addons/payment_asiapay/controllers/main.py

**Third-party Integration Guides:**
- https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_N1573186.html
- https://www.odoo.com/documentation/18.0/applications/finance/payment_providers/asiapay.html
- https://github.com/asiapay-lib/integration_guide

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.