CNY Payout(new encryption)
Summary
Unlock seamless cross-border payments with the Payout API for CNY, enabling businesses/customers to send funds to recipients in China efficiently.
Integration steps
Generate bearer token
Create a quotation
Get bank codes (BANK_ACCOUNT and BANK_CARD only)
Upload Attachment (B2B only)
Initiate a transfer
Listen for status
Source wallet
All transfers would be debited from the respective payout currency wallet balance. Ensure you have sufficient balance before making any transfer request.
Generate a token bearer
Token GenerationCreate Quotation
Payout quotation is created based on the category. Here is the list of categories
B2B
BANK_ACCOUNT
Not applicable
C2C
BANK_ACCOUNT
Not applicable
C2C
BANK_CARD
UNIONPAY
C2C
WALLET
ALIPAY
C2C
WALLET
WECHATPAY
Make a POST call to the create quotation API
POST
{{env_url}}/wallet/merchant/quotation/v2
You would need to pass, as a header the x-auth-token. This can be obtained from your merchant dashboard → Settings → Generate API keys → Merchant public key.
Headers
Content-Type
application/json
Authorization
Bearer <token here>
x-auth-token
MERCHANT PUBLIC KEY
Request body (encrypted):
{
"message": "encrypted-request-body"
}
Request body (plain)
{
"serviceCode": "UNIONPAY",
"service": "BANK_CARD",
"transferType": "C2C",
"destinationCurrency": "CNY",
"sourceCurrency": "USD",
"fundSource": "CASH",
"destinationAmount": "10"
}
Response
{
"id": 472,
"sourceAmount": 9.40,
"sourceCurrency": "USD",
"destinationAmount": 70.00,
"destinationCurrency": "CNY",
"fxRate": 7.44,
"fee": 6.00,
"expiration": 1734490800000,
"reference": "f7c32b34-ec79-4de2-ae8a-42529",
"expired": false
}
Encryption algorithm
public static String encryptCBC(String stringToEncrypt, String encryptionkey) {
try {
SecureRandom sr = new SecureRandom();
byte[] salt = new byte[8];
sr.nextBytes(salt);
final byte[][] keyAndIV =GenerateKeyAndIV(32, 16, 1, salt, encryptionkey.getBytes(StandardCharsets.UTF_8),
MessageDigest.getInstance("MD5"));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyAndIV[0], "AES"), new IvParameterSpec(keyAndIV[1]));
byte[] encryptedData = cipher.doFinal(stringToEncrypt.getBytes(StandardCharsets.UTF_8));
byte[] prefixAndSaltAndEncryptedData = new byte[16 + encryptedData.length];
// Copy prefix (0-th to 7-th bytes)
System.arraycopy("Salted__".getBytes(StandardCharsets.UTF_8), 0, prefixAndSaltAndEncryptedData, 0, 8);
// Copy salt (8-th to 15-th bytes)
System.arraycopy(salt, 0, prefixAndSaltAndEncryptedData, 8, 8);
// Copy encrypted data (16-th byte and onwards)
System.arraycopy(encryptedData, 0, prefixAndSaltAndEncryptedData, 16, encryptedData.length);
return Base64.getEncoder().encodeToString(prefixAndSaltAndEncryptedData);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
md.reset();
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
md.update(password);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
}
generatedLength += digestLength;
}
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte) 0);
}
}
Get Bank Codes
Get Bank codes by BANK_ACCOUNT
GET
{{env_url}}/
wallet/merchant/bank/transfer/request/banks/CNY
Get Bank codes by BANK_CARD
GET
{{env_url}}/
wallet/merchant/bank/transfer/request/banks/CNY?type=bank_card
Headers
Content-Type
application/json
Authorization
Bearer <token here>
{
"message": "success",
"error": null,
"data": [
{
"code": "403100000004",
"name": "Postal Savings Bank of China (中国邮政储蓄银行)"
},
{
"code": "102100099996",
"name": "Industrial and Commercial Bank of China (中国工商银行)"
},
{
"code": "103100000026",
"name": "Agricultural Bank of China (中国农业银行)"
}
...]
}
Upload attachment
Supported Attachment Types
COMMERCIAL_DOCUMENTS
LOGISTICS_DOCUMENTS
LOGISTICS_COMMUNICATION_DOCUMENTS
CONTRACT_DOCUMENTS
ORDER_DOCUMENTS
CUSTOMS_DECLARATION
OTHERS
Make a POST call to upload file
POST
{{env_url}}/
wallet/merchant/file
You would need to pass, as a header the x-auth-token. This can be obtained from your merchant dashboard → Settings → Generate API keys → Merchant public key.
Headers
Context-Type
multipart/form-data
Authorization
Bearer <token here>
x-auth-token
MERCHANT PUBLIC TOKEN
Form request:
{
--form 'file=@"/path/to/file"' \
--form 'purpose="Party"' \
--form 'attachmentType="COMMERCIAL_DOCUMENTS"'
}
Initiate a transfer
Make a POST call to the initiate transfer request API
POST
{{env_url}}
/wallet/merchant/{businessId}/bank/transfer/v2/request
You would need to pass, as a header the x-auth-token. This can be obtained from your merchant dashboard → Settings → Generate API keys → Merchant public key.
Headers
Content-Type
application/json
x-auth-token
MERCHANT PUBLIC KEY
Authorization
Bearer <token here>
ID TYPE
Values
Description
ID_CARD
national id card
UNIFIED_SOCIAL_CREDIT_ID
unified social credit id of China registered company
PASSPORT
passport
BUSINESS_REGISTRATION_CERT
business registration certificate
ACCOUNT ID
Values
Description
MOBILE
ALIPAY and WECHATPAY
ALIPAY only
Relationship
SELF
SPOUSE
PARENTS
SONS_AND_DAUGHTERS
BROTHERS_AND_SISTERS
GRANDPARENTS
GRANDPARENTS_IN_LAW
GRANDCHILDREN
MATERNAL_GRANDCHILDREN
Account Type
INDIVIDUAL
COMPANY
TradeType
GOODS
Purpose
Values
Description
GOODS_PURCHASE
B2B
SALARY
C2C
FAMILY_SUPPORT
C2C
TRAVEL
C2C
INSURANCE
C2C
INVESTMENT
C2C
SERVICE_CHARGES
C2C
PATENT_ROYALTY
C2C
OTHER
C2C
Request body (encrypted):
{
"message": "encrypted-request-body"
}
Request body (plain)
{
"receiverLastName": "杜",
"bankCode": "03010000",
"receiverBusinessIdType": "ID_CARD",
"purpose": "GOODS_PURCHASE",
"quotationId": 1,
"accountName": "约翰·杜",
"creditAccountCountry": "CN",
"document": {
"totalAmount": "1500",
"attachments": [
{
"fileId": "1"
}
],
"orderNumber": "982f38-C",
"orderTime": "100",
"orderCurrency": "CNY",
"logistics": {
"company": "China pay",
"orderNo": "123456789"
},
"tradeType": "GOODS",
"products": [
{
"name": "Motors",
"quantity": "1"
}
]
},
"accountType": "INDIVIDUAL",
"senderFirstName": "Sender",
"senderBusinessAddress": {
"city": "Beijing",
"streetAddress": "West Street",
"countryCode": "CN",
"postcode": "065001",
"state": "Heilongjiang"
},
"bankName": "Bank of communications",
"receiverBusinessRegisteredName": "圣公司",
"receiverBusinessAddress": {
"city": "Beijing",
"streetAddress": "West Street",
"countryCode": "CN",
"postcode": "065001",
"state": "Heilongjiang"
},
"accountNumber": "10000000000",
"receiverBusinessIdNumber": "1000000000",
"senderBusinessRegisteredName": "圣公司",
"receiverBusinessMobileNumber": "+86 18612345678",
"requestId": "China_payout",
"receiverFirstName": "约翰",
"senderLastName": "China"
}
Fields specification: C2C, BANK_CARD
Field
Description
receiverFirstName
Receiver first name (必须是中文)
receiverLastName
Receiver last name (必须是中文)
senderFirstName
Sender first name
senderLastName
Sender last name
senderNationality
Sender nationality
senderIdType
Sender ID type. Please refer to the supported ID type list
senderIdNumber
Sender ID number
senderAddress
Sender’s address. Refer to the address object
bankName
Name of the bank
bankCode
Bank ID
creditAccountCountry
Bank’s country
cardNumber
Card number
cardHolderName
Name on card
quotationId
Quotation ID obtained after creating a quotation
purpose
Reason for the payout. Please refer to the supported purpose list
requestId
It’s a unique transaction reference will be used to uniquely identify a transfer
Fields specification: C2C, BANK_ACCOUNT
Field
Description
receiverFirstName
Receiver first name (必须是中文)
receiverLastName
Receiver last name (必须是中文)
receiverMobileNumber
Receiver mobile number. Format: +86 18612345678
receiverEmail
Receiver email
receiverRelationship
Relationship with the receiver
receiverIdType
Receiver ID type. Please refer to the supported ID type list
receiverIdNumber
Receiver ID number
senderFirstName
Sender first name
senderLastName
Sender last name
senderNationality
Sender nationality
senderIdType
Sender ID type. Please refer to the supported ID type list
senderIdNumber
Sender ID number
senderAddress
Sender address. Refer to the address object
bankName
Name of the bank
bankCode
Bank ID
creditAccountCountry
Credit account bank’s country
accountNumber
Credit account number
accountName
Credit account name (必须是中文)
accountType
Credit account type. Please refer to the supported account type list
quotationId
Quotation ID obtained after creating a quotation
purpose
Reason for the payout. Please refer to the supported purpose list
requestId
It’s a unique transaction reference will be used to uniquely identify a transfer
Fields specification: C2C, WALLET (ALIPAY, WECHATPAY)
Field Name
Description
receiverIdNumber
Receive ID number
receiverFirstName
Receiver first name (必须是中文)
receiverLastName
Receiver last name (必须是中文)
receiverRelationship
Relationship with the receiver
senderFirstName
Sender first name
senderLastName
Sender last name
senderBirthDate
Sender date of birth
senderNationality
Sender nationality
senderIdType
Sender ID type. Please refer to the supported ID type list
senderIdNumber
Sender ID number
senderAddress
Sender address. Refer to the address object
creditAccountCountry
Credit account bank’s country
accountNumber
Credit account number - this is either an email or mobile number for ALIPAY or a mobile number for WECHAT PAY. The login account ID to WeChat or Alipay account.
accountId
The account ID type of either the WeChat or Alipay account. Please refer to the supported account ID type.
quotationId
Quotation ID obtained after creating a quotation
purpose
Reason for the payout. Please refer to the supported purpose list
requestId
It’s a unique transaction reference will be used to uniquely identify a transfer
Fields specification: B2B, BANK_ACCOUNT
Field
Description
receiverFirstName
Receiver first name (必须是中文)
receiverLastName
Receiver last name (必须是中文)
receiverBusinessMobileNumber
Receiver mobile number. Format: +86 18612345678
receiverEmail
Receiver email
receiverBusinessIdType
Receiver ID type. Please refer to the supported ID type list
receiverBusinessIdNumber
Receiver ID number
receiverBusinessRegisteredName
Receiver business name. Chinese characters are required if it's in China
receiverBusinessAddress
Receiver business address
senderFirstName
Sender first name
senderLastName
Sender last name
senderBusinessAddress
Sender business address. Refer to the address object
senderBusinessRegisteredName
Sender business name
bankName
Name of the bank
bankCode
Bank ID
creditAccountCountry
Credit account bank’s country
accountNumber
Credit account number
accountName
Credit account name (必须是中文)
accountType
Credit account type. Please refer to the supported account type list
quotationId
Klasha quotation ID
purpose
Reason for the payout. Please refer to the supported purpose list
requestId
It’s a unique transaction reference will be used to uniquely identify a transfer
document
See: Document
Fields specification: Document
Field
Description
totalAmount
Total amount
orderNumber
Order number
orderTime
Order time
orderCurrency
Order currency
logistics
See: Logistics
tradeType
Please refer to the supported TradeType
products
List<Product>
Product
attachments
List<Attachment>
See: Attachment
Fields specification: Product
Field
Description
name
Product name
quantity
Quantity
Fields specification: Attachment
Field
Description
fileId
Klasha file ID
Fields specification: Logistics
Field
Description
company
Logistics company
orderNo
Logistics order number
Fields specification: Address
Field
Description
city
City
streetAddress
Street address
countryCode
Country code
postcode
Postcode
state
Province or state
Response
{
"id": 1,
"amount": 70.00,
"payoutStatus": "PENDING",
"requestId": "ed5fa-a33ed-fc4dc",
"narration": "GOODS_PURCHASE",
"country": "CN",
"fee": 6.00,
"bankCode": "03010000",
"bankName": "Bank of communications",
"accountNumber": "10000000000",
"accountName": "Test Flow"
}
Listen for a status response
When a transfer is initiated, it could take a few seconds or minutes to be processed. This is why we recommend relying on webhooks for verification as opposed to polling.
Once a transfer is processed, we send the final status of the transfer as a POST request to your webhook URL
{
"data": {
"reference": "kbtr-3857-011-null-166993253331236",
"createdAt": "2023-03-28T23:01:45.336",
"amount": 1000,
"accountName": "Pastor Bright",
"narration": "certification",
"name": "Steph and sons",
"currency": "NGN",
"bankName": "ACCESS BANK NIGERIA",
"accountNumber": "0690000032",
"status": "successful"
},
"event": "payout"
}
The possible statuses are listed and described in the following table:
successful
This is sent when the transfer is successful
failed
This is sent when the transfer fails
pending
This transfer is still in progress. Please wait for either success or failed.
Query payout status
Make a call to fetch status of initiated payouts.
Last updated