跳到主要内容

GLODIPAY 服务器对服务器 (S2S) 银行卡 API 规范

版本 2.0.0

目录

使用测试环境。不产生实际费用。

无 3DS4111 1111 1111 111101/30029
无 3DS5555 5555 5555 444401/30029
3DS 付款4012 8888 8888 188101/30029成功:123456 / 失败:111111
3DS 付款5111 1111 1111 111801/30029成功:123456 / 失败:111111
3DS 付款4141 4141 4141 414112/30123成功: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)。不要对嵌套对象(如 feespaymentMethodDetails)内部的键进行排序。
  • 递归修整所有字符串值的空格。
  • 序列化为 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

请求参数

merchantIdString(1,50)M商户 ID
orderRefString(1,250)M每个商户唯一的交易参考号
amountFloatM发票金额。最小值:1。最多 2 位小数
currencyString(3)MISO 4217 货币代码。例如:USD
cancelUrlString(1,300)M取消时重定向买家的 URL。必须为 https
callbackUrlString(1,300)M付款成功后重定向买家的 URL。必须为 https
notificationUrlString(1,300)M您的服务器接收 IPN Webhook 的接口。必须为 https
errorUrlString(1,300)M出错时重定向买家的 URL。必须为 https
orderDescriptionString(max:3000)M订单简短描述
metadataJSONO附加到会话的键值对。在 IPN 和查询响应中返回
transactionDocumentsJSONO交易的辅助文档
paymentMethodStringM对于 S2S 银行卡付款,必须为 card
feeBySellerNumber(0-100)O商户支付的手续费百分比。0 = 买家支付 100%。最多 2 位小数
billingFirstNameString(max:255)M账单名字
billingLastNameString(max:255)M账单姓氏
billingStreet1String(max:255)M账单街道 1
billingStreet2String(max:255)O账单街道 2
billingCityString(max:255)M账单城市
billingEmailString(max:255)M买家电子邮件地址
billingStateString(min:2, max:255)C账单州/省。当 billingCountry 为 US 或 CA 时必填
billingCountryStringMISO 3166-1 alpha-2 国家代码
billingPostalCodeString(max:25)M邮政编码
billingPhoneCountryCodeString(max:10)O电话国家代码。例如:美国为 1
billingPhoneNumberString(max:30)M电话号码
cardNumberString(12,19)M卡号。例如:4111111111111111
cardMonthStringM到期月份。例如:12
cardYearStringM到期年份(2 位数字)。例如:30
cardSecurityCodeString(3,4)MCVV / CVC
customerIpStringM客户的 IP 地址
websiteUrlString(max:300)O商户网站 URL
signatureString(max:750)MRSA-MD5 签名。请参阅“签名”部分
expiresAtStringOISO 8601 格式的会话过期时间。默认:24 小时
browserDetailsJSONM浏览器指纹。请参阅 browserDetails 对象

M = 必填,O = 可选,C = 条件必填

browserDetails 对象

每个 S2S 卡片请求都必须提供。可以作为 JSON 对象(嵌套)或 JSON 编码的字符串提交。键名使用下表所列的 snake_case;camelCase 也会被自动规范化。

accept_headerStringM浏览器 Accept 头部。例如:text/html,application/xhtml+xml
screen_widthStringM屏幕宽度(像素)。例如:1920
screen_heightStringM屏幕高度(像素)。例如:1080
screen_color_depthStringM屏幕颜色深度(位)。例如:24
window_widthStringM视口宽度(像素)。例如:1440
window_heightStringM视口高度(像素)。例如:900
languageStringM浏览器语言。例如:en-US
java_enabledStringM是否启用 Java。“true”或“false”
user_agentStringM浏览器 User Agent 字符串
time_zoneStringM相对于 UTC 的时区偏移(小时)。例如:UTC+7 为 7
time_zone_nameStringMIANA 时区名称。例如: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)

transactionIdString (ULID)MGLODIPAY 交易 ID
signatureString(max:750)MRSA-MD5 签名

响应 (Response)

Content-Type: application/json

merchantIdString商户 ID
transactionIdStringGLODIPAY 交易 ID (ULID)
transactionNumberStringGLODIPAY 人类可读的交易编号
refString商户的 orderRef
currencyStringISO 4217 货币代码
amountFloat发票金额
paidAmountFloat实际向买家收取的金额(包括买家费用)
settlementAmountFloat结算给商户的金额
estimationSettlementAtISO 8601 datetime预计结算日期时间
feesJSON费用明细。请参阅 fees 对象
statusString交易状态。请参阅“状态值”部分
statusCodeNumber数字状态码。请参阅“状态码”部分
metadataJSON原始会话中的键值对
transactionDocumentsJSON辅助文档
paymentMethodDetailsJSON所使用的付款方式。请参阅 paymentMethodDetails 对象
messageString人类可读的状态消息
descriptorString交易描述符
transactionCreatedAtISO 8601 datetimeGLODIPAY 系统中的交易创建时间
originalTransactionCreatedAtISO 8601 datetime支付渠道侧的交易创建时间
signatureStringRSA-MD5 签名 —— 使用 GLODIPAY 公钥验证

通知 (NOTIFICATION)

当交易达到终端状态时,GLODIPAY 会向您的 notificationUrl 发送 HTTP POST 请求。

请求方法: POST Content-Type: application/json

重试策略: GLODIPAY 可能会重新发送尚未确认交易的 IPN。您的服务器应在收到通知后立即返回 {"returnCode":"100"}

Payload

merchantIdStringM商户 ID
transactionIdStringMGLODIPAY 交易 ID (ULID)
transactionNumberStringMGLODIPAY 人类可读的交易编号
refStringM商户的 orderRef
currencyStringMISO 4217 货币代码
amountFloatM发票金额
paidAmountFloatO实际向买家收取的金额(包括买家费用)
settlementAmountFloatO结算给商户的金额
estimationSettlementAtISO 8601 datetimeO预计结算日期时间
feesJSONO费用明细。请参阅 fees 对象
paymentMethodDetailsJSONO所使用的付款方式。请参阅 paymentMethodDetails 对象
statusStringM交易状态。请参阅“状态值”部分
statusCodeNumberM数字状态码。请参阅“状态码”部分
metadataJSONO原始会话中的键值对
transactionDocumentsJSONO辅助文档
messageStringO人类可读的状态消息
descriptorStringO交易描述符
transactionCreatedAtISO 8601 datetimeMGLODIPAY 系统中的交易创建时间
originalTransactionCreatedAtISO 8601 datetimeM支付渠道侧的交易创建时间
signatureStringMRSA-MD5 签名 —— 使用 GLODIPAY 公钥验证

fees 对象

buyerFloat面向买家的费用金额
sellerFloat商户费用金额
rollingFloat滚动保证金金额
operateFloat总运营费用(渠道商 + GLODIPAY + 合作伙伴)
estimationRollingReleaseAtISO 8601 datetime预计滚动保证金释放日期时间

paymentMethodDetails 对象

displayNameString付款方式标签
groupString付款方式组类型
familyString付款方式系列类型
typeString付款方式类型
{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 地址 (取决于渠道)
checksAVS/CVC 验证结果 (取决于渠道)
threeDSecure3D 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"
}
returnCodeStringR必须为 "100" 以确认收到
descriptionString(1,1500)O可选描述

模拟付款

使用测试环境。不产生实际费用。

无 3DS4111 1111 1111 111101/30029
无 3DS5555 5555 5555 444401/30029
3DS 付款4012 8888 8888 188101/30029成功:123456 / 失败:111111
3DS 付款5111 1111 1111 111801/30029成功:123456 / 失败:111111
3DS 付款4141 4141 4141 414112/30123成功: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 字段中的数字代码。

1incomplete交易已发起
2pending等待确认
3error系统错误
4failed付款失败
5under_review审核中
6successful付款成功
7released已释放 / 结算
8refund_initiated退款已发起
9refund_failed退款失败
10refund_under_review退款审核中
11refund_successful退款成功
12refund_partially_failed部分退款失败
13refund_partially_successful部分退款成功
14canceled已取消
15rejected已拒绝
16expired已过期
17documents_uploaded文档已上传
18void_initiated撤销已发起
19void_under_review撤销审核中
20void_successful撤销成功
21void_failed撤销失败
22void_partially_successful部分撤销成功
23void_partially_failed部分撤销失败
24chargeback_alert拒付警报
25chargebacked已拒付
26dispute争议已开启

货币代码

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智利
CMCameroon
CN中国
COColombia
CRCosta Rica
CUCuba
CVCabo Verde
CWCuraçao
CXChristmas Island
CYCyprus
CZCzechia
DE德国
DJDjibouti
DKDenmark
DMDominica
DODominican Republic
DZAlgeria
ECEcuador
EEEstonia
EG埃及
EHWestern Sahara
EREritrea
ES西班牙
ETEthiopia
FIFinland
FJFiji
FKFalkland Islands (Malvinas)
FMMicronesia, Federated States of
FOFaroe Islands
FR法国
GAGabon
GB英国
GDGrenada
GEGeorgia
GFFrench Guiana
GGGuernsey
GHGhana
GIGibraltar
GLGreenland
GMGambia
GNGuinea
GPGuadeloupe
GQEquatorial Guinea
GR希腊
GSSouth Georgia and the South Sandwich Islands
GTGuatemala
GUGuam
GWGuinea-Bissau
GYGuyana
HK香港
HMHeard Island and McDonald Islands
HNHonduras
HRCroatia
HTHaiti
HUHungary
IDIndonesia
IEIreland
ILIsrael
IMIsle of Man
IN印度
IOBritish Indian Ocean Territory
IQIraq
IRIran, Islamic Republic of
ISIceland
IT意大利
JEJersey
JMJamaica
JOJordan
JP日本
KEKenya
KGKyrgyzstan
KH柬埔寨
KIKiribati
KMComoros
KNSaint Kitts and Nevis
KP朝鲜
KR韩国
KWKuwait
KYCayman Islands
KZKazakhstan
LA老挝
LBLebanon
LCSaint Lucia
LILiechtenstein
LKSri Lanka
LRLiberia
LSLesotho
LTLithuania
LULuxembourg
LVLatvia
LYLibya
MA摩洛哥
MCMonaco
MDMoldova, Republic of
MEMontenegro
MFSaint Martin (French part)
MGMadagascar
MHMarshall Islands
MKNorth Macedonia
MLMali
MMMyanmar
MNMongolia
MOMacao
MPNorthern Mariana Islands
MQMartinique
MRMauritania
MSMontserrat
MTMalta
MUMauritius
MVMaldives
MWMalawi
MXMexico
MYMalaysia
MZMozambique
NANamibia
NCNew Caledonia
NENiger
NFNorfolk Island
NGNigeria
NINicaragua
NL荷兰
NONorway
NPNepal
NRNauru
NUNiue
NZNew Zealand
OMOman
PAPanama
PEPeru
PFFrench Polynesia
PGPapua New Guinea
PHPhilippines
PKPakistan
PLPoland
PMSaint Pierre and Miquelon
PNPitcairn
PRPuerto Rico
PSPalestine, State of
PT葡萄牙
PWPalau
PYParaguay
QAQatar
RERéunion
RORomania
RSSerbia
RU俄罗斯
RWRwanda
SA沙特阿拉伯
SBSolomon Islands
SCSeychelles
SDSudan
SESweden
SG新加坡
SHSaint Helena, Ascension and Tristan da Cunha
SISlovenia
SJSvalbard and Jan Mayen
SKSlovakia
SLSierra Leone
SMSan Marino
SNSenegal
SOSomalia
SRSuriname
SSSouth Sudan
STSao Tome and Principe
SVEl Salvador
SXSint Maarten (Dutch part)
SYSyrian Arab Republic
SZEswatini
TCTurks and Caicos Islands
TDChad
TFFrench Southern Territories
TGTogo
THThailand
TJTajikistan
TKTokelau
TLTimor-Leste
TMTurkmenistan
TNTunisia
TOTonga
TR土耳其
TTTrinidad and Tobago
TVTuvalu
TW台湾
TZTanzania, United Republic of
UAUkraine
UGUganda
UMUnited States Minor Outlying Islands
US美国
UYUruguay
UZUzbekistan
VAHoly See
VCSaint Vincent and the Grenadines
VEVenezuela, Bolivarian Republic of
VGVirgin Islands (British)
VIVirgin Islands (U.S.)
VN越南
VUVanuatu
WFWallis and Futuna
WSSamoa
YEYemen
YTMayotte
ZA南非
ZMZambia
ZWZimbabwe

卡类型

1VISAvisa
2MASTERCARDmastercard
3AMERICAN EXPRESSamex
4JCBjcb
5MAESTROmaestro
6DISCOVERdiscover
7UNION PAYunion-pay
8DINERSdiners

代码示例

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');
}

Bookmarks

No bookmarks yet.
Hover over a heading and click to save a section.