前置组件 - CARD
1. 交互流程
%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#e6f0ff',
'primaryTextColor': '#333',
'primaryBorderColor': '#5b9bd5',
'lineColor': '#888',
'actorMargin': 40,
'noteBkgColor': '#0056b3',
'noteTextColor': '#ffffff',
'noteBorderColor': '#004a99'
}
}}%%
sequenceDiagram
participant User as 用户
participant Page as 商户页面
participant Component as PayerMax
前置组件
participant MServer as 商户服务端
participant PMServer as PayerMax服务器
participant Channel as 支付渠道
钱包/银行等
%% 1. 初始化阶段
User->>Page: 1.1 选择商品下单
Page->>MServer: 1.2 发送订单信息
如:收单国家、订单币种等
MServer->>PMServer: 1.3 获取前置组件初始化信息
clientKey和sessionKey
PMServer-->>MServer: 1.4 返回结果
clientKey和sessionKey
MServer-->>Page: 1.5 返回结果
含clientKey和sessionKey
Page->>Component: 1.6 创建并挂载PayerMax组件
%% 2. 获取 Token 阶段
User->>Page: 2.1 用户输入支付信息
Page->>Component: 2.2 获取paymentToken
Component->>PMServer: 2.3 获取paymentToken
PMServer-->>Component: 2.4 返回结果
含paymentToken
Component-->>Page: 返回paymentToken
%% 3. 提交订单与支付
User->>Page: 3.1 确认支付
Page->>Component: 3.2 提交支付
含paymentToken
Component->>MServer: 3.3 提交订单
含paymentToken
MServer->>PMServer: 3.4 创建支付
调用前置组件下单接口
PMServer->>Channel: 3.5 支付请求
PMServer-->>MServer: 3.6 返回结果
MServer-->>Component: 3.7 返回结果
Component-->>Page: 3.8 返回结果
%% 4. 获取支付结果 (逻辑框)
rect rgb(235, 245, 255)
Note over MServer, PMServer: 获取支付结果
Note over MServer, PMServer: 通过支付结果通知
PMServer->>MServer: 4.1 支付结果异步通知
MServer->>MServer: 4.2 更新支付结果
MServer-->>PMServer: 4.3 返回响应
Note over MServer, PMServer: 通过支付订单查询
MServer->>PMServer: 5.1 查询支付交易单
PMServer-->>MServer: 5.2 交易详情,含支付结果
MServer->>MServer: 5.3 更新支付结果
end
2. 接口介绍
2.1 接口列表
| 关联交互时序 | 调用方向 | 接口类型 | 接口PATH |
| 3.1 获取前置组件初始化信息 | 商户 -> PayerMax | 后端接口 | /applyDropinSession |
| 3.3 创建支付,调用前置组件下单接口 | 商户 -> PayerMax | 后端接口 | /orderAndPay |
| 3.4.1 支付结果异步通知 | PayerMax -> 商户 | 后端接口 | /collectResultNotifyUrl |
| 3.4.2 查询支付交易 | 商户 -> PayerMax | 后端接口 | /orderQuery |
2.2 环境信息
测试环境:https://
pay-gate-uat.payermax.com/aggregate-pay/api/gateway/<接口PATH>集成环境:https://
pay-gate.payermax.com/aggregate-pay/api/gateway/<接口PATH>
2.3 请求header
"headers": {
"Accept": "application/json",
"sign": "请参考签名规则:https://docs-v2.payermax.com/202506-version/developer/config-settings.html",//必须
"Content-Type": "application/json"
}3.开始集成
3.1 获取前置组件初始化信息
商户服务端通过/applyDropinSession API 接口,发起HTTP POST请求,获取前置组件初始化所需的客户端令牌clientKey和会话令牌sessionKey。
- /applyDropinSession API 接口请求示例:
{
"requestTime": "2024-11-20T01:56:36.753-02:00",
"keyVersion": "1",
"data": {
"country":"US",
"currency": "USD",
"totalAmount": "19.99",
"componentList": ["CARD","APPLEPAY"],
"userId": "1447410849000200"
},
"appId": "381ded7c863c439a9e29b4519867965a",
"version": "1.1",
"merchantNo": ""
}- /applyDropinSession API 接口响应示例:
{
"msg": "",
"code": "APPLY_SUCCESS",
"data": {
"sessionKey": "964bffa7c9eb4951bd5ba11a2691e5ed",
"notSupportedComponent": [],
"clientKey": "8eef820ecbd443b7a608c2e0863750eb"
}
}3.2 渲染前置组件
- 在相关 HTML 页面上引入 CDN 包。
<script src="https://cdn.payermax.com/dropin/js/pmdropin.min.js"></script>- 通过过
div标签,在商户页面嵌入一个card待展示区域。
<div class="frame-card">
<!-- 表单内容 -->
</div>- 初始化 PayerMax Frames。
// 初始化卡组件
const card = PMdropin.create('card', {
clientKey: "客户端公钥", // 在步骤3.1中获取到的 data.clientKey
sessionKey: "会话令牌", // 在步骤3.1中获取到的 data.sessionKey
sandbox: false, // 默认是 false,即生产环境
hideSaveCard: false, //是否隐藏保存卡信息选项,默认是false展示
hideCardBrands: false, //是否隐藏左上角卡品牌的Logo,默认是false展示
});
// 挂载实例
card.mount('.frame-card'); // 将挂载至匹配到的第一个dom元素上
// 组件加载完成时机
card.on('ready', () => {
// 移除自定义loading
})- 监听表单填写状态,动态设置支付按钮(可选)。通过表单监听事件,可实时监控用户填写信息的合法性,以此动态设定 按钮是否可点击。
card.on('form-check', (res) => {
// res.isFormValid 为表单状态。取值是false/true
// true 表示表单校验通过,可支付;false 表示校验未通过,不可支付,无法点击支付按钮。
console.log('[dropin][form-check]:', res)
})- 用户点击支付按钮,商户提交支付到PayerMax服务端
//用户点击支付按钮,提交支付
function goPay(){
card.emit('setDisabled', true) // 点击支付按钮后冻结表单,防止重复提交支付过程
card.emit('canMakePayment')
.then(res => {
if (res.code === 'APPLY_SUCCESS') {
const paymentToken = res.data.paymentToken // 支付token,支付接口使用
// 发起支付接口
// 商户自己请求后端接口进行下单,
// 商户自己用params构造请求参数,需要带上paymentToken
_postapi('orderAndPay',params).then(res =>{
const code = (res || {}).code
//商户将支付结果返回到前端
if (code == 'APPLY_SUCCESS') {
if(res.threeDSUrl){
//使用PayerMax组件内弹窗完成3ds;也可以将threeDSUrl新开浏览器tab页打开完成3ds
handle3DS(threeDSUrl)
}
//支付成功,展示支付结果
} else {
//支付失败,展示失败结果
}
}
card.emit('setDisabled', false) // 支付接口完成后解冻表单
}
})
.catch(err => {
card.emit('setDisabled', false) // 发生异常后解冻表单
})
}
// orderAndPay后若获取到返回的url(即为threeDSUrl)就使用handle3DS方法唤起弹窗
function handle3DS(threeDSUrl) {
card.create3DSPopup({
url: threeDSUrl,
// width/height 不传则按设备与视口自动计算;也可显式传入如 '80%'、'400px' 等
})
.then(res => {
if (res.code === '3DS_PROCESSED') {
// 3DS 流程完成,继续支付流程。
console.log('3DS 流程完成', res.data);
// 此处商户需要维持现有逻辑,商户侧应该主动查询支付结果 继续商户侧后续的交互
proceedPayment(res.data);
}
})
.catch(err => {
if (err.code === 'USER_CANCEL') {
// 商户也可不做处理,但是应允许用户继续点击支付按钮
showMessage('您已关闭认证窗口');
} else {
showMessage('认证出现异常,请重试');
}
});
}- 当用户在前端确认支付后,前端
canMakePayment接口会返回paymentToken,用作后端发起支付。
前端返回数据结构如下:
{
"msg": "",
"code": "APPLY_SUCCESS",
"data": {
"paymentToken": "CPT771e98494eff41f1a03a715ebab69cc9",
"cardOrg": "VISA",
"maskCardNumber": "444433****1111",
"cardExpirationMonth": "12",
"cardType": "CREDIT",
"cardExpirationYear": "26",
"cardHolderFullName": "Jemy Cheung",
"agreementAccepted": true,
"cardBinNo": "444433",
"cardIssuingCountry": "US"
}
}3.3 创建支付
- 商户服务端:调用/orderAndPay API 接口发起HTTP POST请求,创建支付。
/orderAndPay API 接口请求示例:
{
"requestTime": "2024-11-20T01:56:45.802-02:00",
"keyVersion": "1.5",
"data": {
"currency": "USD",
"country": "US",
"totalAmount": 19.99,
"expireTime": "3600",
"paymentDetail": {
+ "paymentToken": "dbe78b7dd4fa4b668bacfdcc7153821d", // 用户输入卡信息后点击支付会回调前端paymentDetail
"buyerInfo": {
"clientIp": "146.75.136.237",
"userAgent": "Chrome"
},
+ "sessionKey": "abf62fdcf9c4408cb73117db6e740713"
},
"frontCallbackUrl": "https://",
"subject": "xxx Game HK Limited",
"outTradeNo": "ov1_5b89ced71d764ed9994e6882d88082f7",
"notifyUrl": "https://",
"userId": "1447410849000200",
"integrate": "Direct_Payment",
"terminalType": "WEB"
},
"appId": "381ded7c863c439a9e29b4519867965a",
"version": "1.4",
"merchantNo": "P04010116880289"
}- /orderAndPay API 接口响应示例:
WARNING
注意:返回中可能存在data.redirectUrl,这时请返回给前端引导用户完成3ds认证。
{
"msg": "Success.",
"code": "APPLY_SUCCESS",
"data": {
"outTradeNo": "test_da78b1f3c2f9443b966347fc89305fc9",
"tradeToken": "T2024052805951921811176",
"status": "SUCCESS"
}
}3.4 获取支付结果
创建支付/orderAndPay API 接口响应的data.status并非支付终态,因此,商户不应直接使用其更新支付结果。
3.4.1 支付结果通知
请查看支付结果-支付结果通知。
3.4.2 支付结果查询
请查看支付结果-支付结果查询。
4.卡支付前端接口
4.1 API
使用方法 PMdropin.API。
| API | 描述 | 详情 |
|---|---|---|
| create | 实例化一个内置组件 | 参阅create |
| mount | 将实例化组件挂载到div标签 | 参阅mount |
| on | 监听事件 | 参阅on |
| emit | 触发事件 | 参阅emit |
4.1.1 create
用于初始化组件,使用方法 PMdropin.create(ComponentName, Options)。
ComponentName详解
| ComponentName | 字段类型 | 描述 |
|---|---|---|
| card | string | 卡组件 |
Options详解
| Options | 是否必填 | 字段类型 | 描述 | 默认值 |
|---|---|---|---|---|
| clientKey | Y | String | 客户端公钥 | - |
| sessionKey | Y | String | 安全访问令牌 | - |
| sandbox | N | Boolean | 沙盒环境 | false |
| language | N | String | 语言 | en |
| theme | N | String | 主题 | light |
| customLocalization | N | Object | 自定义语言 | - |
| customTheme | N | Object | 自定义主题 | - |
| grayscale | N | String | 页面灰度 | '0' |
| isRtl | N | Boolean | 从右到左 | false |
| hideSaveCard | N | Boolean | 隐藏Save保存 | false |
| hideCardBrands | N | Boolean | 隐藏头部卡组织LOGO | false |
| hideCardHolderName | N | Boolean | 隐藏卡姓名 | false |
| saveCardChecked | N | Boolean | 保存卡信息的checkbox是否选中 true - 勾选 false - 不勾选 注:当hideSaveCard = false时,该字段生效 | true |
| ignoreUserCheckedCache | N | Boolean | 是否忽略用户勾选缓存 false - 默认不忽略 true - 忽略 注:当hideSaveCard = false时,该字段生效 | false |
| hideRecommendAccount | N | Boolean | 是否隐藏推荐卡模块 | false |
| openTermsInCurrentPage | N | Boolean | 是否在当前页面打开协议弹框 | false |
| termsPopupConfig | N | Object | 协议弹框配置 注:当openTermsInCurrentPage = true时,该字段生效 | - |
| >>showMask | N | Boolean | 是否展示遮罩层 | true |
| >>closeOnClickMask | N | Boolean | 点击遮罩是否关闭弹框 | true |
4.1.2 mount
用于挂载初始化组件实例,使用方法PMdropin.mount(Tag)。
Tag详解
| Tag | 描述 |
|---|---|
| id | 需要挂载的id元素值,如 PMdropin.mount('#card-frame') |
| class | 需要挂载的class元素值,如 PMdropin.mount('.card-frame') |
4.1.3 on
用于监听组件内置响应事件,使用方法PMdropin.on(Event, CallbackFunction)。
Event详解
| Tag | 描述 | 返回值 |
|---|---|---|
| ready | 组件加载完成时触发 | null |
| form-check | 实时监听卡组件校验状态 | 参阅下方 Event Response |
| load | 组件加载完成时触发 | Object - type:组件类型card/applepay/googlepay - code:响应码 SUCCESS 表示加载成功,其余枚举表示加载失败 - msg:响应 message |
示例:
PMdropin.on('form-check', function(event) {
if (event.isFormValid) {
// 表单校验通过
}
});4.1.4 create3DSPopup
用于在组件内弹窗打开3DS认证页面
**语言设置说明:**SDK会会自动从实例配置中获取语言,无需在create3DSPopup中单独传入
入参说明:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| url | string | 是 | - | 3DS 认证页面 URL(从 orderandpay 接口获取) |
| showMask | boolean | 否 | true | 是否显示背景遮罩 |
| width | string | 否 | 响应式计算 | 弹框宽度,不传则按设备/视口自动计算 |
| height | string | 否 | 响应式计算 | 弹框高度,不传则按设备/视口自动计算 |
返回:
| CODE | 说明 | 处理建议 |
|---|---|---|
3DS_PROCESSED | 3DS 流程已处理 | 继续后续支付流程,通过接口查询最终认证结果 |
USER_CANCEL | 用户关闭弹框 | 提示用户已关闭,可重试 |
INVALID_PARAMS | 调用入参不合法 | 根据 err.msg 检查入参(如 url 必填且非空、类型正确等),修正后重试 |
4.1.5 emit
用于调用组件内置方法,使用方法PMdropin.emit(Event, Params)。
| Event | Params | 描述 |
|---|---|---|
| canMakePayment | - | 获取卡标识 |
| switchLanguage | string | 切换语言 |
| switchTheme | string | 切换主题 |
| addLocalization | Object | 添加自定义语言 |
| addTheme | Object | 添加自定义主题 |
| setDisabled | Boolean | 设置组件可用状态 |
| setRtl | Boolean | 设置组件从右到左布局 |
| setGrayscale | string | 设置组件页面灰度 |
1. emit.canMakePayment
检查当前组件状态是否具备发起支付条件,如果校验通过则返回卡标识。
// 示例
PMdropin.emit('canMakePayment')
.then(function(response) {
// 验证成功
if (response.code === 'APPLY_SUCCESS') {
// 获取 paymentToken
var paymentToken = response.data.paymentToken
// 进行后续支付操作
}
})
.catch(function(error) {
// 捕获内部异常错误
console.log(error);
})canMakePayment Response:
// 接口成功示例
{
code: 'APPLY_SUCCESS',
data: {
// 卡标识
paymentToken: 'xxx'
},
msg: ''
}
// 接口失败示例
{
code: 'FORM_INVALID',
msg: 'Invalid params: cvv'
}| CODE码 | 描述 |
|---|---|
| APPLY_SUCCESS | 成功获取 paymentToken |
| FORM_INVALID | 表单校验失败 |
| UNKNOWN_ISSUE | 异常信息 |
2. emit.switchLanguage
+您可以在我们预置语言列表中快速设置本地化参数。如language: 'zh'。 支持的预置语言如下:
| 语言 | 语言编码 |
|---|---|
| 英语 | en 默认 |
| 中文 | zh |
- 初始化预置语言(zh):
PMdropin.create('card', {
clientKey: '',
sessionKey: '',
language: 'zh',
...
});- 动态切换语言:
PMdropin.emit('switchLanguage', 'zh')3. emit.switchTheme
当预置的语言无法满足您的使用场景,您可以通过customLocalization自定义语言。
PMdropin.create('card', {
clientKey: '',
sessionKey: '',
customLocalization: {
// 添加中文示例
'zh': {
loading: '加载中',
loadingFailed: '加载失败',
refresh: '刷新',
confirm: '确定',
cancel: '取消',
removeCard: '移除卡',
removeCardTip: '确定移除当前选中卡吗?',
addNewCard: '使用新卡',
useSavedCard: '使用已存卡',
cardnum: '银行卡号',
cardnumHint: 'XXXX XXXX XXXX XXXX',
cardnumErrTip: '卡号不正确',
cardbinErrTip: {
// {cardOrg} 变量字段,无需翻译
CARD_NOT_SUPPORT: '不支持{cardOrg},请检查卡号或者更换其他卡重试',
// {cardType} 变量字段,无需翻译
CARD_INVALID: '不支持{cardType}的卡类型',
CARD_NO_INVALID: '请确认卡号输入是否正确',
},
expdate: '过期日期',
expdateHint: '月/年',
expdateErrTip: '过期日期不正确',
cvv: 'CVV/CVC',
cvvHint: '123',
cvvErrTip: 'CVV/CVC 格式不正确',
name: '持卡人姓名',
nameHint: 'XX XX',
nameErrTip: '姓名格式不正确',
saveCardInfoTip: '为下次支付保存信息',
// 以下为新增多语言字段
// 本地卡额外采集要素
buyerEmail: '邮箱',
buyerEmailHint: '',
buyerEmailErrTip: '格式错误,请复核',
buyerFullName: '姓名',
buyerFullNameHint: '',
buyerFullNameErrTip: '格式错误,请复核',
buyerFirstName: '名',
buyerFirstNameHint: '',
buyerFirstNameErrTip: '格式错误,请复核',
buyerLastName: '姓',
buyerLastNameHint: '',
buyerLastNameErrTip: '格式错误,请复核',
// 秘鲁 Document(ID)
dni: 'DNI',
dniHint: '',
dniErrTip: '格式错误,请复核',
// 阿根廷 Document(ID)
dni_cuit: 'DNI/CUIT',
dni_cuitHint: '',
dni_cuitErrTip: '格式错误,请复核',
// 南非 Document(ID)
idCard: 'ID',
idCardHint: '',
idCardErrTip: '格式错误,请复核',
// 哥伦比亚 Document(ID)
cc: 'CC',
ccHint: '',
ccErrTip: '格式错误,请复核',
// 墨西哥 Document(ID)
curp: 'CURP',
curpHint: '',
curpErrTip: '格式错误,请复核',
// 巴西 Document(ID)
cpf: 'CPF',
cpfHint: '',
cpfErrTip: '格式错误,请复核',
// 智利/巴拉圭/乌拉圭 Document(ID)
ci: 'CI',
ciHint: '',
ciErrTip: '格式错误,请复核',
buyerPhoneNo: '手机号码',
buyerPhoneNoHint: '',
buyerPhoneNoErrTip: '格式错误,请复核',
buyerPhoneNoRegion: '手机区号',
buyerPhoneNoRegionHint: '',
buyerPhoneNoRegionErrTip: '格式错误,请复核',
}
},
...
});- 动态添加语言:
PMdropin.emit('addLocalization', {
'zh': {
// 参数如上所示
}
});4. emit.addLocalization
使用我们的主题功能,帮助您构建一个更加符合自身网站的样式搭配。
- 使用预置主题 我们预置了两款主题色,您可以通过
theme自定义主题。 支持的预置主题如下:
| 主题名称 | 主题编码 |
|---|---|
| 白色 | light 默认 |
| 黑色 | dark |
- 初始化示例:
PMdropin.create('card', {
clientKey: '',
sessionKey: '',
theme: 'dark'
});- 或者动态切换:
PMdropin.emit('switchTheme', 'dark');5. emit.addTheme
自定义主题,您可以通过自定义主题包来修改样式,示例如下:
PMdropin.create('card', {
clientKey: '',
sessionKey: '',
theme: 'red',
customTheme: [
{
// 主题名称【必填】
name: 'red',
// 用于填充底色的样式【可选】
base: 'light',
style: `:root {
/* --- common --- */
--bg-primary: #ffffff;
--color-primary: #3782ff;
--border-color-primary: #eaeaea;
--border-color-hover-primary: #dcdcdc;
--font-size-primary: 13px;
--border-radius-primary: 6px;
/* --- frame --- */
--padding-frame: 16px;
--bg-color-frame: var(--bg-primary);
--bg-color-mask: rgba(255, 255, 255, 0.8);
/* --- text --- */
--color-text-primary: #333333;
--color-text-secondary: #666666;
--color-text-tip: #c8c8c8;
--color-text-error: #e64949;
/* --- button --- */
/* plain button */
--bg-color-btn-plain: var(--bg-primary);
--border-color-btn-plain: var(--border-color-primary);
--border-color-hover-btn-plain: var(--border-color-hover-primary);
--font-size-btn-plain: var(--font-size-primary);
--border-radius-btn-plain: var(--border-radius-primary);
/* text button */
--color-btn: var(--color-primary);
--color-active-btn: #2c68cc;
--font-size-btn-text: var(--font-size-primary);
/* --- input --- */
--bg-color-input: var(--bg-primary);
--border-color-input: var(--border-color-primary);
--border-color-hover-input: var(--border-color-hover-primary);
--border-color-focus-input: var(--color-primary);
--shadow-color-focus-input: rgba(55, 130, 255, 0.2);
--label-color-input: var(--color-text-primary);
--label-color-focus-input: var(--color-primary);
--action-color-input: var(--border-color-input);
--action-color-hover-input: var(--border-color-hover-input);
--action-color-active-input: #bbbbbb;
--color-placeholder-input: #dcdcdc;
--color-input: var(--color-text-primary);
--font-size-input: 12px;
--font-size-input-label: var(--font-size-input);
--font-size-input-tip: var(--font-size-input);
--height-input: 36px;
--size-checkbox: 16px;
--border-radius-input: var(--border-radius-primary);
--border-radius-checkbox: calc(var(--border-radius-input)/2);
/* --- item --- */
--bg-color-item: var(--bg-primary);
--bg-color-selected-item: #f5f9ff;
--border-color-item: var(--border-color-primary);
--border-color-hover-item: var(--border-color-hover-primary);
--border-color-selected-item: var(--color-primary);
--font-size-item: 15px;
--height-item: 36px;
--height-item-btn: var(--height-item);
--height-selected-item: 48px;
--border-radius-item: var(--border-radius-primary);
/* --- others --- */
--divider-color: #f4f4f4;
}`
}
]
});- 或者动态添加主题:
PMdropin.emit('addTheme', [
{
name: 'red',
base: 'light',
style: ''
}
]);6. emit.setDisabled
- 设置组件可用状态
- 类型
Boolean - 默认:
false
// 不可编辑状态
PMdropin.emit('setDisabled', true)
// 可编辑状态
PMdropin.emit('setDisabled', false)7. emit.setRtl
- 设置组件从右到左布局
- 类型:
Boolean - 默认:
false
// 从右到左布局
PMdropin.emit('setRtl', true)
// 从左到右布局
PMdropin.emit('setRtl', false)8. emit.setGrayscale
- 设置组件页面灰度
- 类型:
String - 默认:
0
// 设置 0 - 1 灰度值
PMdropin.emit('setGrayscale', '1')