GLODIPAY 服务器对服务器 (S2S) 银行卡 API 规范
版本 2.0.0
目录
使用测试环境。不产生实际费用。
| 无 3DS | 4111 1111 1111 1111 | 01/30 | 029 | — |
| 无 3DS | 5555 5555 5555 4444 | 01/30 | 029 | — |
| 3DS 付款 | 4012 8888 8888 1881 | 01/30 | 029 | 成功:123456 / 失败:111111 |
| 3DS 付款 | 5111 1111 1111 1118 | 01/30 | 029 | 成功:123456 / 失败:111111 |
| 3DS 付款 | 4141 4141 4141 4141 | 12/30 | 123 | 成功:123456 / 失败:111111 |
介绍
本文档描述了 GLODIPAY 服务器对服务器 (S2S) 银行卡 API v2,它允许商户直接从自己的服务器提交银行卡付款详情,而无需将买家重定向到 GLODIPAY 托管的页面。
主要功能:
- 直接提交银行卡信息: 卡号、有效期和 CVV 在 API 请求中发送。商户负责在自己的页面上安全地收集银行卡详情(需要符合 PCI DSS 合规要求)。
- 自动级联 (Auto-Cascade): 当商户账户启用此功能时,网关将按优先级顺序自动在多个支付服务提供商 (PSP) 之间重试扣款。单个 PSP 的失败将被抑制 —— 仅返回最终结果。
- 3DS 支持: 如果渠道商要求进行 3D Secure 认证,系统将返回一个
redirect响应,其中包含完成验证的 URL。完成 3DS 认证后,结果将通过 IPN 发送,买家将被重定向回商户页面。
接口地址 (Endpoints)
| 测试环境 | 将另行提供 |
| 生产环境 | 从商户后台的 API Keys 页面获取 |
签名 (Signature)
所有发送给 GLODIPAY 的请求必须包含 signature 字段。所有来自 GLODIPAY 的响应和 IPN Payload 也包含 signature 字段以验证真实性。
签名使用 RSA with MD5 (md5WithRSAEncryption)。
生成签名 (商户 -> GLODIPAY)
使用您的 商户私钥(可从商户后台获取)。
步骤:
- 将除
signature以外的所有请求参数收集为扁平的键值对象。 - 仅按 自然升序 对 顶层键 进行排序(PHP 中的
SORT_NATURAL/ JavaScript 中带 有{ numeric: true }的localeCompare)。不要对嵌套对象(如fees、paymentMethodDetails)内部的键进行排序。 - 递归修整所有字符串值的空格。
- 序列化为 JSON 字符串。
- 使用
openssl_sign(..., 'md5WithRSAEncryption')进行签名。 - 对二进制输出进行 Base64 编码。
验证签名 (GLODIPAY -> 商户)
使用 GLODIPAY 公钥(可在门户网站获取)。
步骤:
- 从 Payload 中移除
signature。 - 仅按自然升序对 顶层键 进行排序。不要对嵌套对象内部的键进行排序。
- 递归修整所有字符串值的空格。
- 序列化为 JSON 字符串。
- 使用
openssl_verify(..., base64_decode($signature), $publicKey, 'md5WithRSAEncryption')进行验证。 - 返回值
1= 有效。
注意 (Node.js):在排序/序列化之前将所有数字值转换为字符串。转义 JSON 字符串中的正斜杠:.replace(///g, '\/')。
POST PAYMENT
直接从您的服务器提交银行卡详情以处理银行卡付款。
接口地址: POST /v2/card/api
请求方法: POST
Content-Type: application/json
请求参数
| merchantId | String(1,50) | M | 商户 ID |
| orderRef | String(1,250) | M | 每个商户唯一的交易参考号 |
| amount | Float | M | 发票金额。最小值:1。最多 2 位小数 |
| currency | String(3) | M | ISO 4217 货币代码。例如:USD |
| cancelUrl | String(1,300) | M | 取消时重定向买家的 URL。必须为 https |
| callbackUrl | String(1,300) | M | 付款成功后重定向买家的 URL。必须为 https |
| notificationUrl | String(1,300) | M | 您的服务器接收 IPN Webhook 的接口。必须为 https |
| errorUrl | String(1,300) | M | 出错时重定向买家的 URL。必须为 https |
| orderDescription | String(max:3000) | M | 订单简短描述 |
| metadata | JSON | O | 附加到会话的键值对。在 IPN 和查询响应中返回 |
| transactionDocuments | JSON | O | 交易的辅助文档 |
| paymentMethod | String | M | 对于 S2S 银行卡付款,必须为 card |
| feeBySeller | Number(0-100) | O | 商户支付的手续费百分比。0 = 买家支付 100%。最多 2 位小数 |
| billingFirstName | String(max:255) | M | 账单名字 |
| billingLastName | String(max:255) | M | 账单姓氏 |
| billingStreet1 | String(max:255) | M | 账单街道 1 |
| billingStreet2 | String(max:255) | O | 账单街道 2 |
| billingCity | String(max:255) | M | 账单城市 |
| billingEmail | String(max:255) | M | 买家电子邮件地址 |
| billingState | String(min:2, max:255) | C | 账单州/省。当 billingCountry 为 US 或 CA 时必填 |
| billingCountry | String | M | ISO 3166-1 alpha-2 国家代码 |
| billingPostalCode | String(max:25) | M | 邮政编码 |
| billingPhoneCountryCode | String(max:10) | O | 电话国家代码。例如:美国为 1 |
| billingPhoneNumber | String(max:30) | M | 电话号码 |
| cardNumber | String(12,19) | M | 卡号。例如:4111111111111111 |
| cardMonth | String | M | 到期月份。例如:12 |
| cardYear | String | M | 到期年份(2 位数字)。例如:30 |
| cardSecurityCode | String(3,4) | M | CVV / CVC |
| customerIp | String | M | 客户的 IP 地址 |
| websiteUrl | String(max:300) | O | 商户网站 URL |
| signature | String(max:750) | M | RSA-MD5 签名。请参阅“签名”部分 |
| expiresAt | String | O | ISO 8601 格式的会话过期时间。默认:24 小时 |
| browserDetails | JSON | M | 浏览器指纹。请参阅 browserDetails 对象 |
M = 必填,O = 可选,C = 条件必填
browserDetails 对象
每个 S2S 卡片请求都必须提供。可以作为 JSON 对象(嵌套)或 JSON 编码的字符串提交。键名使用下表所列的 snake_case;camelCase 也会被自动规范化。
| accept_header | String | M | 浏览器 Accept 头部。例如:text/html,application/xhtml+xml |
| screen_width | String | M | 屏幕宽度(像素)。例如:1920 |
| screen_height | String | M | 屏幕高度(像素)。例如:1080 |
| screen_color_depth | String | M | 屏幕颜色深度(位)。例如:24 |
| window_width | String | M | 视口宽度(像素)。例如:1440 |
| window_height | String | M | 视口高度(像素)。例如:900 |
| language | String | M | 浏览器语言。例如:en-US |
| java_enabled | String | M | 是否启用 Java。“true”或“false” |
| user_agent | String | M | 浏览器 User Agent 字符串 |
| time_zone | String | M | 相对于 UTC 的时区偏移(小时)。例如:UTC+7 为 7 |
| time_zone_name | String | M | IANA 时区名称。例如:Asia/Shanghai |
用于收集 browserDetails 的 JavaScript 片段:
document.addEventListener('DOMContentLoaded', () => {
const browserDetails = {
accept_header: "{{ request()->header('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8') }}",
screen_width: window.screen.width.toString(),
screen_height: window.screen.height.toString(),
screen_color_depth: window.screen.colorDepth.toString(),
window_width: String(window.innerWidth || document.documentElement.clientWidth || screen.width),
window_height: String(window.innerHeight || document.documentElement.clientHeight || screen.height),
language: navigator.language,
java_enabled: 'false',
user_agent: navigator.userAgent,
time_zone: String(-new Date().getTimezoneOffset() / 60),
time_zone_name: Intl.DateTimeFormat().resolvedOptions().timeZone
};
document.getElementById('browserDetails').value = JSON.stringify(browserDetails, null, 2);
});
请求示例
{
"merchantId": "1100000123",
"orderRef": "ORDER-001",
"amount": "100.00",
"currency": "USD",
"paymentMethod": "card",
"callbackUrl": "https://yoursite.com/callback",
"notificationUrl": "https://yoursite.com/webhook",
"cancelUrl": "https://yoursite.com/cancel",
"errorUrl": "https://yoursite.com/error",
"orderDescription": "Test order",
"billingFirstName": "John",
"billingLastName": "Doe",
"billingStreet1": "123 Main St",
"billingStreet2": "",
"billingCity": "New York",
"billingEmail": "john@example.com",
"billingState": "NY",
"billingCountry": "US",
"billingPostalCode": "10001",
"billingPhoneCountryCode": "1",
"billingPhoneNumber": "5551234567",
"cardNumber": "4111111111111111",
"cardMonth": "12",
"cardYear": "30",
"cardSecurityCode": "123",
"customerIp": "1.2.3.4",
"browserDetails": {
"accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"screen_width": "1920",
"screen_height": "1080",
"screen_color_depth": "24",
"window_width": "1440",
"window_height": "900",
"language": "en-US",
"java_enabled": "false",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"time_zone": "7",
"time_zone_name": "Asia/Shanghai"
},
"signature": "base64-encoded-signature"
}
响应
Content-Type: application/json
交易完成 (无需 3DS):
{
"status": "success",
"message": "The transaction has been successfully completed.",
"data": {
"transactionId": "01jwz13qfcx4z61ded3jcj0tf2"
}
}
需要 3DS 认证:
{
"status": "redirect",
"message": "Please redirect the user to complete the payment.",
"data": {
"transactionId": "01jwz0ty1640apxvmyzqpvc18a",
"url": "https://payment.gpayprocessing.com/card/3ds/01jwz0ty1640apxvmyzqpvc18a"
}
}
将买家重定向到 data.url 以完成 3DS。验证后,GLODIPAY 处理交易,通过 IPN 将结果发送到 notificationUrl,并将买家重定向到 callbackUrl。
待处理 (等待 PSP 确认):
{
"status": "pending",
"message": "pending",
"data": {
"transactionId": "01jwz0ty1640apxvmyzqpvc18a"
}
}
验证错误 (HTTP 422):
{
"status": "error",
"message": "Invalid request data.",
"errors": [
{
"field": "billingEmail",
"message": ["The billing email field is required."]
}
]
}
处理错误 (HTTP 400/500):
{
"status": "error",
"message": "No payment provider could process this transaction. Please try again or contact support.",
"data": {
"transactionId": "01jwz0ty1640apxvmyzqpvc18a"
}
}
自动级联:当商户账户启用此功能时,网关在返回最终响应之前,会按优先级顺序自动在所有活动的 PSP 之间重试银行卡扣款。在级联期间,单个 PSP 的失败将被抑制 —— 仅返回最终结果并通过 IPN 发送。
交易查询 (TRANSACTION QUERY)
查询交易的当前状态和完整详情。
接口地址: POST /v2/checkout/query
请求方法: POST
Content-Type: application/json
请求 (Request)
| transactionId | String (ULID) | M | GLODIPAY 交易 ID |
| signature | String(max:750) | M | RSA-MD5 签名 |
响应 (Response)
Content-Type: application/json
| merchantId | String | 商户 ID |
| transactionId | String | GLODIPAY 交易 ID (ULID) |
| transactionNumber | String | GLODIPAY 人类可 读的交易编号 |
| ref | String | 商户的 orderRef |
| currency | String | ISO 4217 货币代码 |
| amount | Float | 发票金额 |
| paidAmount | Float | 实际向买家收取的金额(包括买家费用) |
| settlementAmount | Float | 结算给商户的金额 |
| estimationSettlementAt | ISO 8601 datetime | 预计结算日期时间 |
| fees | JSON | 费用明细。请参阅 fees 对象 |
| status | String | 交易状态。请参阅“状态值”部分 |
| statusCode | Number | 数字状态码。请参阅“状态码”部分 |
| metadata | JSON | 原始会话中的键值对 |
| transactionDocuments | JSON | 辅助文档 |
| paymentMethodDetails | JSON | 所使用的付款方式。请参阅 paymentMethodDetails 对象 |
| message | String | 人类可读的状态消息 |
| descriptor | String | 交易描述符 |
| transactionCreatedAt | ISO 8601 datetime | GLODIPAY 系统中的交易创建时间 |
| originalTransactionCreatedAt | ISO 8601 datetime | 支付渠道侧的交易创建时间 |
| signature | String | RSA-MD5 签名 —— 使用 GLODIPAY 公钥验证 |
通知 (NOTIFICATION)
当交易达到终端状态时,GLODIPAY 会向您的 notificationUrl 发送 HTTP POST 请求。
请求方法: POST
Content-Type: application/json
重试策略: GLODIPAY 可能会重新发送尚未确认交易的 IPN。您的服务器应在收到通知后立即返回 {"returnCode":"100"}。
Payload
| merchantId | String | M | 商户 ID |
| transactionId | String | M | GLODIPAY 交易 ID (ULID) |
| transactionNumber | String | M | GLODIPAY 人类可读的交易编号 |
| ref | String | M | 商户的 orderRef |
| currency | String | M | ISO 4217 货币代码 |
| amount | Float | M | 发票金额 |
| paidAmount | Float | O | 实际向买家收取的金额(包括买家费用) |
| settlementAmount | Float | O | 结算给商户的金额 |
| estimationSettlementAt | ISO 8601 datetime | O | 预计结算日期时间 |
| fees | JSON | O | 费用明细。请参阅 fees 对象 |
| paymentMethodDetails | JSON | O | 所使用的付款方式。请参阅 paymentMethodDetails 对象 |
| status | String | M | 交易状态。请参阅“状态值”部分 |
| statusCode | Number | M | 数字状态码。请参阅“状态码”部分 |
| metadata | JSON | O | 原始会话中的键值对 |
| transactionDocuments | JSON | O | 辅助文档 |
| message | String | O | 人类可读的状态消息 |
| descriptor | String | O | 交易描述符 |
| transactionCreatedAt | ISO 8601 datetime | M | GLODIPAY 系统中的交易创建时间 |
| originalTransactionCreatedAt | ISO 8601 datetime | M | 支付渠道侧的交易创建时间 |
| signature | String | M | RSA-MD5 签名 —— 使用 GLODIPAY 公钥验证 |
fees 对象
| buyer | Float | 面向买家的费用金额 |
| seller | Float | 商户费用金额 |
| rolling | Float | 滚动保证金金额 |
| operate | Float | 总运营费用(渠道商 + GLODIPAY + 合作伙伴) |
| estimationRollingReleaseAt | ISO 8601 datetime | 预计滚动保证金释放日期时间 |
paymentMethodDetails 对象
| displayName | String | 付款方式标签 |
| group | String | 付款方式组类型 |
| family | String | 付款方式系列类型 |
| type | String | 付款方式类型 |
{type} | JSON | 可选。付款方式特定的详细信息。键名等于 type 的值(如 card)。仅在银行卡详细信息可用时出现在银行卡付款中。请参阅下面的 card 对象。 |
card 对象 (paymentMethodDetails.card)
| name | 持卡人姓名 |
| firstSixDigits | 卡号前 6 位 (BIN) |
| lastFourDigits | 卡号后 4 位 |
| expiryMonth | 过期月份 (MM) |
| expiryYear | 过期年份 (YY) |
| type | 银行卡品牌 (visa, mastercard, amex 等) |
| issuer | 发卡行名称 (取决于渠道) |
| issuerCountryCode | 发卡行国家代码 (ISO 3166-1 alpha-2) (取决于渠道) |
| funding | 银行卡资金类型 (credit, debit, prepaid) (取决于渠道) |
| authorizationCode | 发卡行授权码 (取决于渠道) |
| clientIP | 付款时的客户 IP 地址 (取决于渠道) |
| checks | AVS/CVC 验证结果 (取决于渠道) |
| threeDSecure | 3D Secure 认证详细信息 (取决于渠道) |
IPN Payload 示例:
{
"merchantId": "1100000123",
"transactionId": "01jwz13qfcx4z61ded3jcj0tf2",
"transactionNumber": "2604-1713100800",
"ref": "ORDER-001",
"currency": "USD",
"amount": 100.00,
"paidAmount": 105.00,
"settlementAmount": 95.00,
"estimationSettlementAt": "2026-04-16T00:00:00+00:00",
"fees": {
"buyer": 5.00,
"seller": 5.00,
"rolling": 2.00,
"operate": 3.00,
"estimationRollingReleaseAt": "2026-05-14T00:00:00+00:00"
},
"status": "successful",
"statusCode": 6,
"paymentMethodDetails": {
"displayName": "Credit / Debit Card",
"group": "card",
"family": "card",
"type": "card"
},
"metadata": { "orderId": "12345" },
"transactionCreatedAt": "2026-04-14T10:00:00+00:00",
"originalTransactionCreatedAt": "2026-04-14T10:00:01+00:00",
"signature": "base64-encoded-rsa-signature"
}
响应 (商户 -> GLODIPAY)
您的服务器必须在 30 秒 内响应:
{
"returnCode": "100",
"description": "Received"
}
| returnCode | String | R | 必须为 "100" 以确认收到 |
| description | String(1,1500) | O | 可选描述 |
模拟付款
使用测试环境。不产生实际费用。
| 无 3DS | 4111 1111 1111 1111 | 01/30 | 029 | — |
| 无 3DS | 5555 5555 5555 4444 | 01/30 | 029 | — |
| 3DS 付款 | 4012 8888 8888 1881 | 01/30 | 029 | 成功:123456 / 失败:111111 |
| 3DS 付款 | 5111 1111 1111 1118 | 01/30 | 029 | 成功:123456 / 失败:111111 |
| 3DS 付款 | 4141 4141 4141 4141 | 12/30 | 123 | 成功:123456 / 失败:111111 |
附录
状态值
在 IPN Payload 和查询响应的 status 字段中返回的字符串值。
| incomplete | 交易已发起,等待操作 |
| pending | 等待付款确认 |
| under_review | 交易审核中 |
| successful | 付款成功完成 |
| failed | 付款失败 |
| error | 发生系统错误 |
| canceled | 交易已取消 |
| rejected | 交易被拒绝 |
| expired | 交易已过期 |
| released | 资金已释放 / 结算 |
| documents_uploaded | 辅助文档已上传 |
| refund_initiated | 退款请求已发起 |
| refund_under_review | 退款审核中 |
| refund_successful | 退款成功完成 |
| refund_failed | 退款失败 |
| refund_partially_successful | 部分退款完成 |
| refund_partially_failed | 部分退款失败 |
| void_initiated | 撤销已发起 |
| void_under_review | 撤销审核中 |
| void_successful | 撤销成功完成 |
| void_failed | 撤销失败 |
| void_partially_successful | 部分撤销完成 |
| void_partially_failed | 部分撤销失败 |
| chargeback_alert | 收到拒付警报 |
| chargebacked | 交易已拒付 |
| dispute | 争议已开启 |
状态码
在 IPN Payload 和查询响应的 statusCode 字段中的数字代码。
| 1 | incomplete | 交易已发起 |
| 2 | pending | 等待确认 |
| 3 | error | 系统错误 |
| 4 | failed | 付款失败 |
| 5 | under_review | 审核中 |
| 6 | successful | 付款成功 |
| 7 | released | 已释放 / 结算 |
| 8 | refund_initiated | 退款已发起 |
| 9 | refund_failed | 退款失败 |
| 10 | refund_under_review | 退款审核中 |
| 11 | refund_successful | 退款成功 |
| 12 | refund_partially_failed | 部分退款失败 |
| 13 | refund_partially_successful | 部分退款成功 |
| 14 | canceled | 已取消 |
| 15 | rejected | 已拒绝 |
| 16 | expired | 已过期 |
| 17 | documents_uploaded | 文档已上传 |
| 18 | void_initiated | 撤销已发起 |
| 19 | void_under_review | 撤销审核中 |
| 20 | void_successful | 撤销成功 |
| 21 | void_failed | 撤销失败 |
| 22 | void_partially_successful | 部分撤销成功 |
| 23 | void_partially_failed | 部分撤销失败 |
| 24 | chargeback_alert | 拒付警报 |
| 25 | chargebacked | 已拒付 |
| 26 | dispute | 争议已开启 |
货币代码
GLODIPAY 遵循 ISO 4217 标准。接受的货币取决于为您商户账户配置的支付渠道。常用的支持值包括:
| USD | 美元 |
| EUR | 欧元 |
| GBP | 英镑 |
| AUD | 澳元 |
| AED | 阿联酋迪拉姆 |
| VND | 越南盾 |
国家代码
GLODIPAY 在 billingCountry 字段中使用 ISO 3166-1 alpha-2 两位字母代码。
| AD | 安道尔 |
| AE | 阿拉伯联合酋长国 |
| AF | 阿富汗 |
| AG | 安提瓜和巴布达 |
| AI | 安圭拉 |
| AL | 阿尔巴尼亚 |
| AM | 亚美尼亚 |
| AO | 安哥拉 |
| AQ | 南极洲 |
| AR | 阿根廷 |
| AS | 美属萨摩亚 |
| AT | 奥地利 |
| AU | 澳大利亚 |
| AW | 阿鲁巴 |
| AX | 奥兰群岛 |
| AZ | 阿塞拜疆 |
| BA | 波斯尼亚和黑塞哥维那 |
| BB | 巴巴多斯 |
| BD | 孟加拉国 |
| BE | 比利时 |
| BF | 布基纳法索 |
| BG | 保加利亚 |
| BH | 巴林 |
| BI | 布隆迪 |
| BJ | 贝宁 |
| BL | 圣巴泰勒米 |
| BM | 百慕大 |
| BN | 文莱达鲁萨兰国 |
| BO | 多民族玻利维亚国 |
| BQ | 博内尔、圣尤斯特歇斯和萨巴 |
| BR | 巴西 |
| BS | 巴哈马 |
| BT | 不丹 |
| BV | 布韦岛 |
| BW | 博茨瓦纳 |
| BY | 白俄罗斯 |
| BZ | 伯利兹 |
| CA | 加拿大 |
| CC | 科科斯(基林)群岛 |
| CD | 刚果民主共和国 |
| CF | 中非共和国 |
| CG | 刚果 |
| CH | 瑞士 |
| CI | 科特迪瓦 |
| CK | 库克群岛 |
| CL | 智利 |
| CM | Cameroon |
| CN | 中国 |
| CO | Colombia |
| CR | Costa Rica |
| CU | Cuba |
| CV | Cabo Verde |
| CW | Curaçao |
| CX | Christmas Island |
| CY | Cyprus |
| CZ | Czechia |
| DE | 德国 |
| DJ | Djibouti |
| DK | Denmark |
| DM | Dominica |
| DO | Dominican Republic |
| DZ | Algeria |
| EC | Ecuador |
| EE | Estonia |
| EG | 埃及 |
| EH | Western Sahara |
| ER | Eritrea |
| ES | 西班牙 |
| ET | Ethiopia |
| FI | Finland |
| FJ | Fiji |
| FK | Falkland Islands (Malvinas) |
| FM | Micronesia, Federated States of |
| FO | Faroe Islands |
| FR | 法国 |
| GA | Gabon |
| GB | 英国 |
| GD | Grenada |
| GE | Georgia |
| GF | French Guiana |
| GG | Guernsey |
| GH | Ghana |
| GI | Gibraltar |
| GL | Greenland |
| GM | Gambia |
| GN | Guinea |
| GP | Guadeloupe |
| GQ | Equatorial Guinea |
| GR | 希腊 |
| GS | South Georgia and the South Sandwich Islands |
| GT | Guatemala |
| GU | Guam |
| GW | Guinea-Bissau |
| GY | Guyana |
| HK | 香港 |
| HM | Heard Island and McDonald Islands |
| HN | Honduras |
| HR | Croatia |
| HT | Haiti |
| HU | Hungary |
| ID | Indonesia |
| IE | Ireland |
| IL | Israel |
| IM | Isle of Man |
| IN | 印度 |
| IO | British Indian Ocean Territory |
| IQ | Iraq |
| IR | Iran, Islamic Republic of |
| IS | Iceland |
| IT | 意大利 |
| JE | Jersey |
| JM | Jamaica |
| JO | Jordan |
| JP | 日本 |
| KE | Kenya |
| KG | Kyrgyzstan |
| KH | 柬埔寨 |
| KI | Kiribati |
| KM | Comoros |
| KN | Saint Kitts and Nevis |
| KP | 朝鲜 |
| KR | 韩国 |
| KW | Kuwait |
| KY | Cayman Islands |
| KZ | Kazakhstan |
| LA | 老挝 |
| LB | Lebanon |
| LC | Saint Lucia |
| LI | Liechtenstein |
| LK | Sri Lanka |
| LR | Liberia |
| LS | Lesotho |
| LT | Lithuania |
| LU | Luxembourg |
| LV | Latvia |
| LY | Libya |
| MA | 摩洛哥 |
| MC | Monaco |
| MD | Moldova, Republic of |
| ME | Montenegro |
| MF | Saint Martin (French part) |
| MG | Madagascar |
| MH | Marshall Islands |
| MK | North Macedonia |
| ML | Mali |
| MM | Myanmar |
| MN | Mongolia |
| MO | Macao |
| MP | Northern Mariana Islands |
| MQ | Martinique |
| MR | Mauritania |
| MS | Montserrat |
| MT | Malta |
| MU | Mauritius |
| MV | Maldives |
| MW | Malawi |
| MX | Mexico |
| MY | Malaysia |
| MZ | Mozambique |
| NA | Namibia |
| NC | New Caledonia |
| NE | Niger |
| NF | Norfolk Island |
| NG | Nigeria |
| NI | Nicaragua |
| NL | 荷兰 |
| NO | Norway |
| NP | Nepal |
| NR | Nauru |
| NU | Niue |
| NZ | New Zealand |
| OM | Oman |
| PA | Panama |
| PE | Peru |
| PF | French Polynesia |
| PG | Papua New Guinea |
| PH | Philippines |
| PK | Pakistan |
| PL | Poland |
| PM | Saint Pierre and Miquelon |
| PN | Pitcairn |
| PR | Puerto Rico |
| PS | Palestine, State of |
| PT | 葡萄牙 |
| PW | Palau |
| PY | Paraguay |
| QA | Qatar |
| RE | Réunion |
| RO | Romania |
| RS | Serbia |
| RU | 俄罗斯 |
| RW | Rwanda |
| SA | 沙特阿拉伯 |
| SB | Solomon Islands |
| SC | Seychelles |
| SD | Sudan |
| SE | Sweden |
| SG | 新加坡 |
| SH | Saint Helena, Ascension and Tristan da Cunha |
| SI | Slovenia |
| SJ | Svalbard and Jan Mayen |
| SK | Slovakia |
| SL | Sierra Leone |
| SM | San Marino |
| SN | Senegal |
| SO | Somalia |
| SR | Suriname |
| SS | South Sudan |
| ST | Sao Tome and Principe |
| SV | El Salvador |
| SX | Sint Maarten (Dutch part) |
| SY | Syrian Arab Republic |
| SZ | Eswatini |
| TC | Turks and Caicos Islands |
| TD | Chad |
| TF | French Southern Territories |
| TG | Togo |
| TH | Thailand |
| TJ | Tajikistan |
| TK | Tokelau |
| TL | Timor-Leste |
| TM | Turkmenistan |
| TN | Tunisia |
| TO | Tonga |
| TR | 土耳其 |
| TT | Trinidad and Tobago |
| TV | Tuvalu |
| TW | 台湾 |
| TZ | Tanzania, United Republic of |
| UA | Ukraine |
| UG | Uganda |
| UM | United States Minor Outlying Islands |
| US | 美国 |
| UY | Uruguay |
| UZ | Uzbekistan |
| VA | Holy See |
| VC | Saint Vincent and the Grenadines |
| VE | Venezuela, Bolivarian Republic of |
| VG | Virgin Islands (British) |
| VI | Virgin Islands (U.S.) |
| VN | 越南 |
| VU | Vanuatu |
| WF | Wallis and Futuna |
| WS | Samoa |
| YE | Yemen |
| YT | Mayotte |
| ZA | 南非 |
| ZM | Zambia |
| ZW | Zimbabwe |
卡类型
| 1 | VISA | visa |
| 2 | MASTERCARD | mastercard |
| 3 | AMERICAN EXPRESS | amex |
| 4 | JCB | jcb |
| 5 | MAESTRO | maestro |
| 6 | DISCOVER | discover |
| 7 | UNION PAY | union-pay |
| 8 | DINERS | diners |
代码示例
PHP
<?php
function generateSignature(array $data, string $privateKeyStr): string
{
$key = openssl_pkey_get_private($privateKeyStr);
// 仅按自然升序对顶层键进行排序
ksort($data, SORT_NATURAL);
// 递归修整空格
array_walk_recursive($data, function (&$v) {
$v = is_string($v) ? trim($v) : $v;
});
openssl_sign(json_encode($data), $signature, $key, 'md5WithRSAEncryption');
return base64_encode($signature);
}
function verifySignature(array $data, string $publicKeyStr): bool
{
$signature = $data['signature'];
unset($data['signature']);
ksort($data, SORT_NATURAL);
array_walk_recursive($data, function (&$v) {
$v = is_string($v) ? trim($v) : $v;
});
$key = openssl_pkey_get_public($publicKeyStr);
$result = openssl_verify(json_encode($data), base64_decode($signature), $key, 'md5WithRSAEncryption');
return $result === 1;
}
Node.js
// Save as script.mjs and run: node script.mjs
import { createSign } from 'crypto';
const PRIVATE_KEY = `-----BEGIN PRIVATE KEY-----
YOUR_PRIVATE_KEY_HERE
-----END PRIVATE KEY-----`;
function phpCast(v) {
if (typeof v === 'number') return String(v);
if (typeof v === 'boolean') return v ? '1' : '';
if (typeof v === 'string') return v.trim();
if (Array.isArray(v)) return v.map(phpCast);
if (v && typeof v === 'object') return Object.fromEntries(Object.entries(v).map(([k, val]) => [k, phpCast(val)]));
return v;
}
function generateSignature(data) {
const sorted = {};
Object.keys(data)
.filter(k => k !== 'signature')
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }))
.forEach(k => { sorted[k] = data[k]; });
const canonical = JSON.stringify(phpCast(sorted))
.replace(/\//g, '\\/')
.replace(/[\u0080-\uffff]/g, c => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'));
const sign = createSign('md5WithRSAEncryption');
sign.update(canonical);
return sign.sign(PRIVATE_KEY, 'base64');
}