内嵌 SDK
PingPong 内嵌 SDK 是基于 SDK 集成的支付组件,旨在帮助商户在自有 Web 页面或移动 App 内快速承载收银台能力,减少支付方式展示、支付交互和客户端适配成本,并为买家提供更连贯的支付体验。
- Web/WAP 端:适用于浏览器或 H5 页面环境,通过 JavaScript-SDK 在商户页面内渲染 PingPong 收银台。
- App 端(iOS / Android):适用于商户自有移动应用,通过 Native SDK 在 App 内唤起半屏收银台。
各端内嵌 SDK 支持的主要能力如下表所示:
| 特性 | 描述 | Web/WAP | iOS | Android |
|---|---|---|---|---|
| 支付方式列表渲染 | SDK 根据支付会话展示可用支付方式,商户无需自行维护支付方式列表和基础交互。 | 支持 | 支持 | 支持 |
| 站内嵌入体验 | 支付表单直接嵌入商户 Web/H5 页面,买家可在商户页面内完成支付操作。 | 支持 | 不支持 | 不支持 |
| 半屏收银台体验 | 在商户 App 内唤起 PingPong Native SDK 半屏支付面板,支付流程在 App 内完成。 | 不支持 | 支持 | 支持 |
| 界面与主题配置 | 支持按端侧能力配置收银台展示效果;Web/WAP 可通过 customizeConfig、customizeStyles 等配置布局、主题和交互。 | 支持 | 不支持 | 不支持 |
| 自定义支付按钮 | Web/WAP 可隐藏 SDK 原生支付按钮,并通过 PingPong.Checkout.pay.run() 由商户自定义按钮触发支付。 | 支持 | 不支持 | 不支持 |
| 支付结果回调 | Web/WAP 可通过 checkoutResultHook 接收前端支付结果;iOS / Android 可通过客户端回调接收结果。商户服务端仍应以支付通知或交易查询确认最终交易状态。 | 支持 | 支持 | 支持 |
接入摘要
内嵌 SDK(预下单)是一种低代码站内收银台方案。商户服务端先调用 prePay(预下单)接口 创建支付会话,再由前端通过 JavaScript-SDK 在商户页面内直接渲染 PingPong 收银台。该方案既能保留买家在商户站内完成支付的体验,也能减少商户自建支付页、支付方式展示和支付交互处理的复杂度。
适用场景
适合希望将收银台直接嵌入商户页面、降低支付页前端开发成本并保留站内支付体验的团队;若可接受买家跳转至 PingPong 托管收银台完成支付,建议选择跳转收银台。
支付体验
在内嵌SDK模式下,您可以将结账体验直接嵌入到自己的网站中。在网页端,结账页面会显示在您的网站内,使客户无需跳转至独立的托管页面即可完成支付。

在内嵌SDK模式下,允许您将结账体验无缝集成至自有网站中。针对移动端小屏幕进行了专属 UI 优化,客户在手机网页或 App 内即可完成支付,全程无需跳转至外部托管页面,从而提供更流畅、沉浸的移动端支付体验。

支付流程
买家下单
- 买家在商户页面发起结账并提交订单。
- 商户侧开始创建支付会话,准备初始化内嵌收银台。
创建支付会话
- 商户服务端调用 prePay(预下单)接口。
- 获取
token、innerJsUrl等收银台初始化所需参数。
初始化并展示收银台
- 商户前端加载 JavaScript-SDK,并将
accessToken与locale传给pp-checkout。 - PingPong 收银台在商户页面内完成渲染并展示可用支付方式。
- 商户前端加载 JavaScript-SDK,并将
买家完成支付
- 买家选择支付方式、填写支付信息并确认支付。
- 若触发 3D Secure,买家在当前支付流程中完成验证。
确认支付结果
- 支付完成后,前端可通过
checkoutResultHook接收 SDK 返回的前端支付结果并展示商户自有结果页;未配置 Hook 时,仍按原有结果页或payResultUrl逻辑处理。 - 商户服务端仍需结合异步通知或查询接口确认最终支付状态。
- 支付完成后,前端可通过
关键注意事项
- 支付结果以异步通知(
notificationUrl)或对应查询接口确认结果为准,checkoutResultHook和前端结果页仅用于买家展示; - 优先使用
prePay(预下单)接口返回的bizContent.innerJsUrl动态加载 SDK,以避免环境切换或版本变更带来的地址不一致问题; ready、error等事件监听器需要在设置accessToken之前注册,否则可能错过初始化事件;- 在
locale未传入的情况下,收银台默认展示英文;如果传了locale,则以传入值为准。具体枚举值可参考语言列表。
集成步骤
步骤 1:创建支付会话
在初始化 JS-SDK 收银台之前,商户服务端需要先调用 prePay(预下单)接口 获取 SDK 初始化所需的关键参数。
支付请求关键参数:
| 参数 | 必填 | 说明 |
|---|---|---|
captureDelayHours | O | 资金请款方式。默认自动请款;如需手动请款,请按支付方式能力和接口规则配置 |
amount | M | 交易金额 |
currency | M | ISO 4217 三位交易币种 |
merchantTransactionId | M | 商户网站订单流水号,订单唯一标识 |
shopperIP | M | 买家下单 IP |
merchantUserId | M | 商户侧用户唯一 ID |
goods | M | 商品信息,至少包含商品名称、单价和数量 |
customer | C | 买家信息。不同支付方式可能要求邮箱、手机号或账单信息,请以目标支付方式要求为准 |
paymentMethods | O | 指定收银台可展示的支付方式范围,不传或为空时按店铺配置展示 |
notificationUrl | O | 支付结果通知地址,建议传入 |
payResultUrl | O | 买家完成支付流程后的商户结果页地址 |
language | O | 收银台展示语言,未传入时默认展示英文,枚举值参考语言列表 |
有关完整参数的更多信息,请参阅 prePay(预下单)接口。
支付请求示例:
{
"captureDelayHours": "0",
"amount": "100",
"currency": "USD",
"merchantTransactionId": "{{merchantTransactionId}}",
"payResultUrl": "https://test-acquirerpay.pingpongx.com/qa/result.html",
"notificationUrl": "https://test-acquirerpay.pingpongx.com/qa/result.html",
"language": "en",
"shopperIP": "222.126.52.23",
"merchantUserId": "USER_12345",
"customer": {
"email": "buyer@gmail.com"
},
"paymentMethods": [],
"goods": [
{
"name": "商品名称mepsking1",
"description": "商品描述",
"unitPrice": "1",
"number": "1",
"virtualProduct": "Y",
"imgUrl": "https://xiu.mepsking.top/material/1/16606169805015585.png"
}
]
}支付响应示例:
{
"amount": "1000",
"currency": "USD",
"transactionId": "2023092050004591",
"merchantTransactionId": "PMT-PR5GFUNZQP1695182276082",
"token": "EU:vr_YVR8u7rn7C1gG97DOg9_-Y66ubtNtoayJ_wiEEzdCnxCHYIk0pXordJYBjq1g",
"paymentUrl": "https://sandbox-acquirer-payment-ssr.pingpongx.com/v3/checkout?token=...",
"innerJsUrl": "https://paycdn.pingpongx.com/production/static/sdk/ppPay.min.js?token=..."
}支付响应关键字段:
| 参数 | 说明 |
|---|---|
transactionId | PingPong 交易流水号,用于后续查询、退款、对账 |
merchantTransactionId | 商户网站订单流水号 |
token | SDK 初始化令牌,用于设置 pp-checkout 的 accessToken 属性 |
innerJsUrl | 动态加载 SDK 的 URL,推荐使用此地址加载 JavaScript-SDK |
paymentUrl | 支付页面 URL;如业务后续切换为跳转收银台模式,可直接复用该地址 |
步骤 2:引入Javascript-SDK
复制以下代码,通过CDN地址引入 PingPongCheckout Javascript-SDK
<script type="module" src="https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-web/sandbox/pp-checkout.js"></script><script type="module" src="https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-web/pp-checkout.js"></script><script type="module" src="https://acquirer-cdn.pingpongx.com/acquirer/checkout-web/production-sg/pp-checkout.js"></script><script type="module" src="https://acquirer-cdn.pingpongx.com/acquirer/checkout-web/production-us/pp-checkout.js"></script>动态获取 SDK 地址(推荐)
除了使用固定的 CDN 地址外,推荐从 prePay(预下单)接口 的响应中动态获取 SDK 地址。
调用 prePay(预下单)接口(POST /v4/payment/prePay)成功后,响应中会返回 bizContent.innerJsUrl 字段,该字段包含当前环境对应的 JS-SDK 地址。
async function initCheckoutWithDynamicSDK() {
// 1. 调用服务端 prePay(预下单)接口
const response = await fetch('/api/v4/payment/prePay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
accId: '2018092714313010016291',
clientId: '2018092714313010016',
signType: 'SHA256',
version: '1.0',
bizContent: {
amount: '100',
currency: 'USD',
merchantTransactionId: 'ORDER_' + Date.now(),
notificationUrl: 'https://your-domain.com/notify',
payResultUrl: 'https://your-domain.com/result.html',
customer: {
email: 'customer@example.com',
firstName: 'John',
lastName: 'Doe'
}
}
})
});
const data = await response.json();
// 检查响应状态
if (data.code !== '000000') {
console.error('预下单失败:', data.description);
return;
}
const { token, innerJsUrl } = data.bizContent;
// 2. 动态加载 JS-SDK
await loadScript(innerJsUrl);
// 3. 配置收银台
const ppCheckout = document.querySelector('pp-checkout');
ppCheckout.setAttribute('accessToken', token);
ppCheckout.setAttribute('locale', 'en');
}
/**
* 动态加载脚本
* @param {string} src - 脚本地址
* @returns {Promise<void>}
*/
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.type = 'module';
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
initCheckoutWithDynamicSDK();提示
使用动态获取的 SDK 地址可以确保始终使用与当前环境匹配的最新版本,无需在代码中硬编码环境地址。
步骤 3:初始化并渲染收银台
提示
从沙箱环境切换到生产环境时,请务必检查并且完成下列的操作,否则会导致收银台无法正常渲染。
- 将引入Javascript-SDK的CDN地址切换到生产环境指定的URL
当你在联调沙箱环境时,需要在引入沙箱环境 的PingPongCheckout Javascript-SDK地址(发布到生产环境的时候别忘了切换成生产环境的地址)
将
pp-checkout标签插入 html body 中index.html<pp-checkout></pp-checkout>将预下单获取到的
accessToken传入,接口文档详见prePay(预下单)接口index.html<pp-checkout accessToken='{token}'></pp-checkout>你可以将收银台所要展示的语种(默认为英文,更多语言详见Locale)通过标签属性的方式传递给
pp-checkout,如下:index.html<pp-checkout locale='en'></pp-checkout>
通过以上步骤,你已经成功渲染了 Javascript-SDK 收银台。
步骤 4:收银台行为与界面配置
在使用全局变量前,请确保Javascript-SDK 加载完成。
customizeConfig 布局与界面配置
通过 PingPong.Checkout.customizeConfig 可以自定义收银台的布局和界面元素:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
layout | 'tab'|'accordion' | 'tab' | 页面布局样式,可选值:"tab"(标签页样式)、"accordion"(平铺样式) |
displayCheckoutHeader | boolean | true | 是否显示收银台顶部标题栏 |
originalPay | boolean | true | 是否显示原生支付按钮 |
toPingPongResult | boolean | true | 支付完成后是否跳转到 PingPong 结果页。false 表示直接跳转到商户配置的结果页面 |
hideStoredCards | boolean | false | 是否隐藏 COF(Card On File)列表 |
onlyDisplaySavedCard | boolean | false | 设为 true 时,收银台仅展示已存储的卡信息区域(隐藏新卡输入)。适用于 复购 CVV 收集收银台 |
disableCardRemoval | boolean | false | 设为 true 时,禁止用户删除已存储的卡信息。适用于 复购 CVV 收集收银台 |
displayCardPrompt | boolean | true | 是否显示卡支付提示信息 |
localizationErrorMsg | boolean | false | 是否翻译支付错误消息 |
displayCardsLogo | boolean | true | 是否显示卡品牌 logo 列表 |
isChallengeIframe | boolean | false | 是否使用 iframe 展示 3DS Challenge 页面。设为 true 时,将通过 iframe 承载 Challenge 流程 |
注意
toPingPongResult 配置需要在服务端创建支付会话时设置。客户端设置可能会被服务端配置覆盖。如需禁用 PingPong 结果页跳转,请确保服务端已正确配置。
// customizeConfig 布局与界面配置
PingPong.Checkout.customizeConfig = {
layout: "accordion", // 平铺样式
displayCheckoutHeader: false, // 隐藏标题栏
originalPay: false, // 隐藏原生支付按钮,需自定义按钮触发支付
toPingPongResult: false, // 支付完成后不跳转到 PingPong 结果页
hideStoredCards: false, // 显示已保存的卡列表
displayCardPrompt: true, // 显示卡支付提示信息
localizationErrorMsg: false, // 不翻译错误消息
displayCardsLogo: true, // 显示卡品牌 logo
isChallengeIframe: true // 使用 iframe 展示 3DS Challenge 页面
};自定义支付按钮(可选)
当 originalPay 设置为 false 时,需要自定义按钮点击事件来触发支付。
// 初始化参数中 originalPay 为 false 时, 需自定义支付按钮点击事件
document.querySelector('#pay').onclick = function () {
PingPong.Checkout.pay.run()
}customizeStyles 主题样式
通过 PingPong.Checkout.customizeStyles 可以自定义收银台的视觉样式:
| 配置项 | 类型 | 说明 | 示例值 |
|---|---|---|---|
themeColor | string | 主题色,支持 CSS 颜色值 | "rgb(26, 29, 37)", "#1a1d25" |
themeColorLight | string | 主题色浅色变体 | "rgba(26, 29, 37, 0.4)" |
fontFamily | string | 字体系列 | "'Montserrat', sans-serif" |
// 品牌定制:深色主题
PingPong.Checkout.customizeStyles = {
themeColor: "rgb(26, 29, 37)", // 主题色
themeColorLight: "rgba(26, 29, 37, 0.4)", // 主题色浅色变体
fontFamily: "'Montserrat', 'PingFang SC', 'Microsoft YaHei', sans-serif"
};提示
主题色和字体配置需在 SDK 加载完成后、设置 accessToken 之前进行。
PingPong.Checkout.beforeCheckoutHook
type:
(() => void) | (() => Promise<void>)beforeCheckoutHook 用来设置发起支付请求前的钩子函数。
当你在用户点击支付按钮,发起支付请求前,需要执行你自己的业务逻辑,如:上报埋点、检查库存等,可以设置该钩子函数。
该函数可以返回一个Promise,后续的支付流程会等待该 Promise 状态变为 Fulfilled 后才会继续执行。如果你想在 Promise 状态为 Rejected 或者异步结果不满足你的业务条件时,可以抛出异常,SDK在捕获到异常后中断支付流程。
// 支付前钩子:检查库存
PingPong.Checkout.beforeCheckoutHook = () => {
return fetch('/api/requestInventory').then(res => {
const { inventoryQuantity } = res;
if(inventoryQuantity < MIN_QUANTITY) {
throw new Error('库存不足,需中断交易')
}
}).catch((error) => {
throw new Error('接口异常,需中断交易')
})
};PingPong.Checkout.checkoutResultHook
type:
(payload: CheckoutResultHookPayload) => void | Promise<void>checkoutResultHook 用于接收 SDK 前端可直接得到的支付结果,并由商户前端接管支付完成后的页面展示或跳转。请在用户发起支付前注册该 Hook。该 Hook 挂载在 PingPong.Checkout 上,不是 <pp-checkout> 标签属性。
checkoutResultHook 会把支付结果统一收敛为以下三种状态:
| 状态 | 含义 | 建议处理 |
|---|---|---|
success | 支付成功 | 展示商户成功页,或跳转到商户成功页 |
pending | 支付处理中或待确认,包含 SDK 内部的 PENDING、PROCESSING、REVIEW 等非终态场景 | 展示处理中页面,并通过服务端查询或异步通知确认最终结果 |
failed | 支付失败 | 展示失败页,提供重试或返回商户站点的入口 |
Payload 类型定义:
type CheckoutResultHookPayload = {
status: 'success' | 'pending' | 'failed';
code: string;
message: string;
merchantTransactionId?: string;
paymentMethod?: string;
};Payload 字段说明:
| 字段 | 类型 | 是否必返 | 说明 |
|---|---|---|---|
status | success | pending | failed | M | SDK 归一化后的前端支付结果 |
code | string | M | 支付结果码或错误码;无特定错误时可能返回 0 |
message | string | M | 支付结果描述或错误描述 |
merchantTransactionId | string | O | 商户订单号 |
paymentMethod | string | O | 支付方式 |
Payload 示例:
{
"status": "pending",
"code": "0",
"message": "Payment is processing",
"merchantTransactionId": "ORDER_202607200001",
"paymentMethod": "VISA"
}接入示例:
PingPong.Checkout.checkoutResultHook = async (payload) => {
const orderNo = encodeURIComponent(payload.merchantTransactionId || '');
switch (payload.status) {
case 'success':
window.location.href = `/payment/success?orderNo=${orderNo}`;
break;
case 'pending':
window.location.href = `/payment/pending?orderNo=${orderNo}`;
break;
case 'failed':
window.location.href = `/payment/failed?code=${encodeURIComponent(payload.code)}`;
break;
default:
window.location.href = `/payment/result?orderNo=${orderNo}`;
break;
}
};触发与跳转规则:
| 商户配置 | success / pending 场景 | failed 场景 | 结果页跳转 |
|---|---|---|---|
| 未配置 Hook | 不触发 Hook | 不触发 Hook | 保持原有结果页跳转逻辑 |
仅配置 checkoutResultHook | 触发 checkoutResultHook | 触发 checkoutResultHook | 命中 Hook 后不再跳转 PingPong 结果页 |
仅配置 checkoutFailedHook | 不触发 Hook | 触发 checkoutFailedHook | 保持原有失败处理逻辑 |
| 同时配置两个 Hook | 触发 checkoutResultHook | 优先触发 checkoutFailedHook | 失败场景不重复回调 |
建议新接入商户优先配置 checkoutResultHook。如果历史上已经接入 checkoutFailedHook,请在确认业务兼容后再迁移到 checkoutResultHook,避免同一失败结果被重复处理。
注意
checkoutResultHook是浏览器侧回调,不能作为商户后台更新订单状态的唯一依据。最终支付结果请以 PingPong 服务端异步通知或订单查询接口为准。- 如果同时配置
checkoutResultHook和checkoutFailedHook,支付失败场景会优先触发checkoutFailedHook,SDK 不会再重复触发checkoutResultHook。如需由一个 Hook 统一处理success、pending、failed,请只配置checkoutResultHook。 pending不是失败。请展示处理中状态,并在商户后台继续等待最终支付结果。- Hook 支持返回
Promise,SDK 会等待 Promise 完成。请避免在 Hook 内执行长时间阻塞逻辑。 - Hook 内部异常不会恢复 PingPong 结果页跳转。商户应在 Hook 内自行处理异常,并准备兜底页面。
- 需要跳转到 3DS 认证页面,或渠道返回
redirectUrl、中间页地址时,SDK 仍会继续执行原有跳转流程。
PingPong.Checkout.checkoutFailedHook
type:
(() => void) | (() => Promise<void>)checkoutFailedHook 接收以下参数:
(code: string, message: string) => void | Promise<void>;
// code: string - 错误码
// message: string - 错误消息checkoutFailedHook 用来自定义错误逻辑
当用户支付失败时,PingPong 默认会弹窗提示用户失败原因。如果你想自定义弹窗 UI 或文本,可以设置该钩子函数。
该函数可以返回一个 Promise。如果返回 Promise,后续的流程会等待该 Promise 状态变为 Fulfilled 后才继续执行
// 支付失败钩子:自定义错误提示
PingPong.Checkout.checkoutFailedHook = (code: string, message: string) => {
notification.open({
message: 'Error title',
description: `${code}: ${message}`
})
};事件监听(可选)
SDK 支持监听初始化过程中的 ready 和 error 事件,方便外部进行状态管理和错误处理。
// 监听 SDK 初始化事件
// 监听初始化成功事件
document.querySelector('pp-checkout').addEventListener('ready', (e) => {
console.log('SDK初始化成功'); // 初始化成功回调
// 可以在此处执行初始化完成后的逻辑,如隐藏加载动画
});
// 监听初始化失败事件
document.querySelector('pp-checkout').addEventListener('error', (e) => {
console.log('SDK初始化失败', e.detail); // 错误信息
// 可以在此处执行错误处理逻辑,如显示错误提示、重试等
});事件说明:
ready: 当 SDK 初始化成功时触发,表示收银台已准备就绪。event.detail包含 SDK 实例信息error: 当 SDK 初始化失败时触发,如 accessToken 无效、网络错误等。event.detail包含{ message, code }错误信息
注意
事件监听器必须在 setAttribute 之前添加,否则可能无法捕获事件。
// ❌ 错误:先设置属性再添加监听器
ppCheckout.setAttribute('accessToken', token);
ppCheckout.addEventListener('ready', handler); // 可能无法触发!
// ✅ 正确:先添加监听器再设置属性
ppCheckout.addEventListener('ready', handler);
ppCheckout.setAttribute('accessToken', token);使用示例
原生 JavaScript 完整示例
以下示例展示了如何在原生 JavaScript 项目中集成 SDK,包含完整的项目结构、API 调用和错误处理。
index.html
styles.css
config.js
api.js
main.js
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PingPong Checkout SDK - 原生 JS 示例</title>
<!-- 引入沙箱环境 SDK -->
<script type="module" src="https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-web/sandbox/pp-checkout.js"></script>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div class="container">
<h1>PingPong 支付收银台</h1>
<!-- 加载状态 -->
<div id="loading" class="loading">
<div class="spinner"></div>
<p>正在初始化收银台...</p>
</div>
<!-- 错误提示 -->
<div id="error" class="error" style="display: none;">
<p id="error-message"></p>
<button onclick="location.reload()">重新加载</button>
</div>
<!-- 收银台容器 -->
<div id="checkout-wrap">
<pp-checkout accessToken="" locale="zh"></pp-checkout>
</div>
</div>
<script src="./config.js"></script>
<script src="./api.js"></script>
<script src="./main.js"></script>
</body>
</html>* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
color: #333;
margin-bottom: 30px;
text-align: center;
}
.loading {
text-align: center;
padding: 40px;
}
.spinner {
width: 40px;
height: 40px;
margin: 0 auto 20px;
border: 4px solid #f3f3f3;
border-top: 4px solid #1890ff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
padding: 20px;
background: #fff2f0;
border: 1px solid #ffccc7;
border-radius: 4px;
color: #ff4d4f;
text-align: center;
}
.error button {
margin-top: 15px;
padding: 8px 20px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.error button:hover {
background: #40a9ff;
}
#checkout-wrap {
display: none;
}// SDK 配置
const CONFIG = {
// API 端点配置
apiEndpoint: '/api/reserve',
// SDK CDN 地址(根据环境切换)
sdkUrl: {
sandbox: 'https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-web/sandbox/pp-checkout.js',
production: 'https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-web/pp-checkout.js'
},
// 默认语言
defaultLocale: 'zh',
// 请求超时时间(毫秒)
timeout: 30000,
// 最小库存数量(用于演示 beforeCheckoutHook)
minInventory: 1
};// API 调用封装
const API = {
/**
* 获取 AccessToken
* @returns {Promise<string>} AccessToken
*/
async getAccessToken() {
try {
const response = await fetch(CONFIG.apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
// 订单信息
amount: 100.00,
currency: 'USD',
// ... 其他必要参数
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (!data.accessToken) {
throw new Error('响应中缺少 accessToken');
}
return data.accessToken;
} catch (error) {
console.error('获取 AccessToken 失败:', error);
throw error;
}
},
/**
* 检查库存(示例)
* @returns {Promise<{inventoryQuantity: number}>}
*/
async checkInventory() {
// 模拟 API 调用
return new Promise((resolve) => {
setTimeout(() => {
resolve({ inventoryQuantity: 10 });
}, 500);
});
}
};// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', async () => {
await initCheckout();
});
/**
* 初始化收银台
*/
async function initCheckout() {
const loadingEl = document.getElementById('loading');
const errorEl = document.getElementById('error');
const errorMessageEl = document.getElementById('error-message');
const checkoutWrap = document.getElementById('checkout-wrap');
const ppCheckout = document.querySelector('pp-checkout');
try {
// 1. 等待 SDK 加载完成
await waitForSDK();
// 2. 配置 SDK hooks
setupHooks();
// 3. 获取 AccessToken
const accessToken = await API.getAccessToken();
// 4. 设置 AccessToken 到 SDK
ppCheckout.setAttribute('accessToken', accessToken);
ppCheckout.setAttribute('locale', CONFIG.defaultLocale);
// 5. 显示收银台
loadingEl.style.display = 'none';
checkoutWrap.style.display = 'block';
console.log('收银台初始化成功');
} catch (error) {
console.error('初始化失败:', error);
loadingEl.style.display = 'none';
errorMessageEl.textContent = `初始化失败: ${error.message}`;
errorEl.style.display = 'block';
}
}
/**
* 等待 SDK 加载完成
*/
function waitForSDK() {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('SDK 加载超时'));
}, CONFIG.timeout);
const checkSDK = () => {
if (window.PingPong && window.PingPong.Checkout) {
clearTimeout(timeout);
resolve();
} else {
setTimeout(checkSDK, 100);
}
};
checkSDK();
});
}
/**
* 配置 SDK Hooks
*/
function setupHooks() {
// 支付前钩子:检查库存
PingPong.Checkout.beforeCheckoutHook = async () => {
try {
const { inventoryQuantity } = await API.checkInventory();
if (inventoryQuantity < CONFIG.minInventory) {
throw new Error('库存不足,无法完成支付');
}
console.log('库存检查通过,库存数量:', inventoryQuantity);
} catch (error) {
console.error('库存检查失败:', error);
throw error;
}
};
// 支付结果钩子:接管支付完成后的页面展示或跳转
PingPong.Checkout.checkoutResultHook = async (payload) => {
const orderNo = encodeURIComponent(payload.merchantTransactionId || '');
if (payload.status === 'success') {
window.location.href = `/payment/success?orderNo=${orderNo}`;
return;
}
if (payload.status === 'pending') {
window.location.href = `/payment/pending?orderNo=${orderNo}`;
return;
}
window.location.href = `/payment/failed?code=${encodeURIComponent(payload.code)}`;
};
}Vue 3 完整示例
以下示例展示了如何在 Vue 3 项目中集成 SDK,使用 Composition API 实现响应式状态管理。
App.vue
api
checkout.js
config
index.js
composables
useCheckout.js
index.html
main.js
<template>
<div class="checkout-container">
<h1>PingPong 支付收银台</h1>
<!-- 加载状态 -->
<div v-if="loading" class="loading">
<div class="spinner"></div>
<p>正在初始化收银台...</p>
</div>
<!-- 错误提示 -->
<div v-if="error" class="error">
<p>{{ error }}</p>
<button @click="retry">重试</button>
</div>
<!-- 收银台 -->
<div v-show="!loading && !error" id="checkout-wrap">
<pp-checkout
:accessToken="accessToken"
:locale="locale">
</pp-checkout>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { initSDK } from './composables/useCheckout';
import { getAccessToken } from './api/checkout';
// 响应式状态
const accessToken = ref('');
const locale = ref('zh');
const loading = ref(true);
const error = ref('');
// 初始化收银台
async function initCheckout() {
loading.value = true;
error.value = '';
try {
// 1. 初始化 SDK 并配置 hooks
await initSDK();
// 2. 获取 AccessToken
const token = await getAccessToken();
accessToken.value = token;
console.log('收银台初始化成功');
} catch (err) {
console.error('初始化失败:', err);
error.value = err.message || '初始化收银台失败,请重试';
} finally {
loading.value = false;
}
}
// 重试
function retry() {
initCheckout();
}
// 组件挂载后初始化
onMounted(() => {
initCheckout();
});
</script>
<style scoped>
.checkout-container {
max-width: 1200px;
margin: 0 auto;
padding: 30px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
color: #333;
margin-bottom: 30px;
text-align: center;
}
.loading {
text-align: center;
padding: 40px;
}
.spinner {
width: 40px;
height: 40px;
margin: 0 auto 20px;
border: 4px solid #f3f3f3;
border-top: 4px solid #1890ff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
padding: 20px;
background: #fff2f0;
border: 1px solid #ffccc7;
border-radius: 4px;
color: #ff4d4f;
text-align: center;
}
.error button {
margin-top: 15px;
padding: 8px 20px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.error button:hover {
background: #40a9ff;
}
</style>import { CONFIG } from '../config';
/**
* 获取 AccessToken
* @returns {Promise<string>}
*/
export async function getAccessToken() {
try {
const response = await fetch(CONFIG.apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
// 订单信息
amount: 100.00,
currency: 'USD',
// ... 其他必要参数
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (!data.accessToken) {
throw new Error('响应中缺少 accessToken');
}
return data.accessToken;
} catch (error) {
console.error('获取 AccessToken 失败:', error);
throw error;
}
}
/**
* 检查库存
* @returns {Promise<{inventoryQuantity: number}>}
*/
export async function checkInventory() {
// 模拟 API 调用
return new Promise((resolve) => {
setTimeout(() => {
resolve({ inventoryQuantity: 10 });
}, 500);
});
}export const CONFIG = {
// API 端点配置
apiEndpoint: '/api/reserve',
// SDK CDN 地址
sdkUrl: {
sandbox: 'https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-web/sandbox/pp-checkout.js',
production: 'https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-web/pp-checkout.js'
},
// 默认语言
defaultLocale: 'zh',
// 请求超时时间(毫秒)
timeout: 30000,
// 最小库存数量
minInventory: 1
};import { CONFIG } from '../config';
import { checkInventory } from '../api/checkout';
/**
* 等待 SDK 加载完成
*/
function waitForSDK() {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('SDK 加载超时'));
}, CONFIG.timeout);
const checkSDK = () => {
if (window.PingPong && window.PingPong.Checkout) {
clearTimeout(timeout);
resolve();
} else {
setTimeout(checkSDK, 100);
}
};
checkSDK();
});
}
/**
* 配置 SDK Hooks
*/
function setupHooks() {
// 支付前钩子:检查库存
window.PingPong.Checkout.beforeCheckoutHook = async () => {
try {
const { inventoryQuantity } = await checkInventory();
if (inventoryQuantity < CONFIG.minInventory) {
throw new Error('库存不足,无法完成支付');
}
console.log('库存检查通过,库存数量:', inventoryQuantity);
} catch (error) {
console.error('库存检查失败:', error);
throw error;
}
};
// 支付结果钩子:接管支付完成后的页面展示或跳转
window.PingPong.Checkout.checkoutResultHook = async (payload) => {
const orderNo = encodeURIComponent(payload.merchantTransactionId || '');
if (payload.status === 'success') {
window.location.href = `/payment/success?orderNo=${orderNo}`;
return;
}
if (payload.status === 'pending') {
window.location.href = `/payment/pending?orderNo=${orderNo}`;
return;
}
window.location.href = `/payment/failed?code=${encodeURIComponent(payload.code)}`;
};
}
/**
* 初始化 SDK
*/
export async function initSDK() {
await waitForSDK();
setupHooks();
}<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PingPong Checkout SDK - Vue 3 示例</title>
<!-- 引入沙箱环境 SDK -->
<script type="module" src="https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-web/sandbox/pp-checkout.js"></script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
app.mount('#app');Javascript-SDK调试工具 你可以在沙箱环境中体验 Javascript-SDK 的功能,请前往Javascript-SDK 调试工具。
接入摘要
Native SDK 适用于商户自有 iOS 或 Android App 内的支付场景。商户服务端调用 prePay(预下单)接口 创建支付会话,App 客户端使用响应中的 token 唤起 PingPong 半屏收银台,买家无需离开当前 App 即可完成支付流程。
支付体验

支付流程
创建支付会话
商户服务端调用 prePay(预下单)接口 创建支付会话,并保存
merchantTransactionId、transactionId与订单状态。返回 SDK token
商户服务端从响应中获取
token,再返回给 iOS 或 Android 客户端。salt、签名逻辑和完整请求参数必须只保存在服务端。唤起半屏收银台
App 客户端完成 SDK 初始化后,传入
token唤起 PingPong 半屏收银台。买家在半屏面板内选择支付方式并完成支付。处理客户端回调
SDK 回调用于驱动 App 页面交互,例如展示支付中、失败提示或用户取消提示。
completed/Completed只表示 SDK 支付流程已提交,不代表交易最终成功。确认最终结果
商户服务端通过支付通知或交易查询确认最终交易状态,并以幂等方式更新订单、发货或发放权益。
接入准备
开始接入前,请确认以下信息已经准备完成。
| 准备项 | 说明 |
|---|---|
| 账号信息 | 已获取 clientId、accId,并确认店铺状态可正常交易 |
| API 规则 | 已了解 API 使用基本规则 和 API 端点地址 |
| 签名能力 | 已根据 签名规约 在服务端完成请求签名,salt 不可下发到 App 客户端 |
| 通知地址 | 已准备公网可访问的 notificationUrl,用于接收支付结果异步通知 |
| 结果确认 | 已在商户服务端支持支付通知验签、金额币种核对、幂等更新和交易查询兜底 |
| 支付方式 | 已开通本次需要展示在 App 收银台中的支付方式,并确认支持的国家、币种、金额范围和退款能力 |
注意
notificationUrl 必须是公网可访问的完整地址,不能填写 localhost、127.0.0.1 或内网 IP。客户端回调不能作为订单落账、发货或发放权益的唯一依据。
步骤 1:创建支付会话
买家在 App 内确认订单后,商户服务端调用 prePay(预下单)接口 创建支付会话。请不要在 iOS 或 Android 客户端直接调用该接口,避免签名密钥泄露。
支付请求关键参数:
| 参数 | 必填 | 说明 |
|---|---|---|
captureDelayHours | O | 资金请款方式。默认自动请款;如需手动请款,请按支付方式能力和接口规则配置 |
amount | M | 交易金额 |
currency | M | ISO 4217 三位交易币种 |
merchantTransactionId | M | 商户网站订单流水号,订单唯一标识 |
shopperIP | M | 买家下单 IP |
merchantUserId | M | 商户侧用户唯一 ID |
goods | M | 商品信息,至少包含商品名称、单价和数量 |
customer | C | 买家信息。不同支付方式可能要求邮箱、手机号或账单信息,请以目标支付方式要求为准 |
billingAddress | C | 账单地址。实物类或特定行业场景可能必传,请以接口必传规则和支付方式要求为准 |
paymentMethods | O | 指定收银台可展示的支付方式范围,不传或为空时按店铺配置展示 |
notificationUrl | O | 支付结果通知地址,强烈建议传入 |
closeNotificationUrl | O | 关单通知地址。传入后,可接收主动关单或被动关单结果通知 |
payResultUrl | O | 支付完成后的商户结果页地址。Native SDK 场景通常由 App 自行处理页面展示 |
language | O | 收银台展示语言,未传入时默认展示英文,枚举值参考语言列表 |
有关完整参数的更多信息,请参阅 prePay(预下单)接口。
支付请求示例(仅展示 bizContent 内容):
{
"captureDelayHours": "0",
"amount": "100",
"currency": "USD",
"merchantTransactionId": "{{merchantTransactionId}}",
"payResultUrl": "https://www.example.com/app/payment-result",
"notificationUrl": "https://www.example.com/checkout/notify",
"closeNotificationUrl": "https://www.example.com/checkout/close-notify",
"language": "en",
"shopperIP": "222.126.52.23",
"merchantUserId": "USER_12345",
"customer": {
"email": "buyer@example.com"
},
"paymentMethods": [],
"goods": [
{
"name": "Digital product",
"description": "Order item",
"unitPrice": "100",
"number": "1",
"virtualProduct": "Y",
"imgUrl": "https://www.example.com/product/SKU_001.png"
}
]
}支付响应示例(仅展示 bizContent 内容):
{
"amount": "100",
"currency": "USD",
"transactionId": "26070600000751051115",
"merchantTransactionId": "PMT-A1XGH96WNE1783303688835",
"token": "EU:OYUi5wmfOMRNFJnqmPRR2QHvtJihf8cTKvay2yeHTuRbnqIBpMJra9KtN9YQ_sXo8DsnSxGh_e_6tMpzRzlXjA==",
"paymentUrl": "https://sandbox-safepay.pingpongx.com?token=EU:OYUi5wmfOMRNFJnqmPRR2QHvtJihf8cTKvay2yeHTuRbnqIBpMJra9KtN9YQ_sXo8DsnSxGh_e_6tMpzRzlXjA==",
"innerJsUrl": "https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-web/sandbox/pp-checkout.js?token=EU:OYUi5wmfOMRNFJnqmPRR2QHvtJihf8cTKvay2yeHTuRbnqIBpMJra9KtN9YQ_sXo8DsnSxGh_e_6tMpzRzlXjA=="
}支付响应关键字段:
| 参数 | 说明 |
|---|---|
token | Native SDK 初始化令牌。App 客户端使用该值唤起半屏收银台 |
transactionId | PingPong 交易流水号,用于后续查询、退款、对账 |
merchantTransactionId | 商户网站订单流水号 |
amount | 交易金额 |
currency | 交易币种 |
Token 传递
商户服务端应只向 App 客户端返回唤起 SDK 所需的 token、订单号和必要展示信息。accId、clientId、sign、salt 等签名相关信息不应下发到客户端。
步骤 2:前端引入 SDK
环境要求
| 项目 | 要求 |
|---|---|
| iOS 版本 | 15.6+ |
手动集成
请在完成“接入准备”后,再将 iOS SDK 包引入 App 项目。
- 下载 iOS SDK 包,解压后获取
.framework和PPCashDeskSDKBundle.bundle资源文件。 - 将以上两个文件拖动到项目路径中。
- 确认
.framework已加入 App Target 的Frameworks, Libraries, and Embedded Content,并按项目实际情况设置为Embed & Sign。
依赖项
Podfile 中需保证以下依赖项:
| 依赖库 | 版本 | 说明 |
|---|---|---|
| AFNetworking | 4.x | 网络请求 |
| SDWebImage | 5.x | 图片加载与缓存 |
| MJExtension | 3.x | JSON 与模型转换 |
| MJRefresh | 3.x | 下拉刷新与上拉加载 |
| Masonry | 1.x | 自动布局 |
| MBProgressHUD | 1.x | 加载提示 |
| Bugly | - | 错误监控 |
iOS 端差异说明
上方“支付流程”已覆盖 Native SDK 的完整链路。iOS 端与通用流程的差异主要体现在客户端调用方式:App 完成 SDK 初始化后,通过 initWithToken 唤起半屏收银台,并在支付过程中处理 completed、failure、cancel 三类回调。
步骤 3:配置并初始化 SDK
关键对象说明
| 类名 | 说明 |
|---|---|
| PPCDManager | SDK 主入口类,单例模式 |
| PPCDConfig | SDK 配置类 |
| PPPaymentRequest | 支付请求参数 |
| PPPaymentResult | 支付结果回调 |
环境配置
环境枚举:
typedef NS_ENUM(NSInteger, PPCDEnvironmentType) {
PPCDEnvironmentTypeRelease = 1, // 生产欧洲
PPCDEnvironmentTypeSandBox = 4, // 沙箱
PPCDEnvironmentTypeReleaseUS = 6, // 生产美国
};| 枚举值 | 值 | 说明 | API 端点 |
|---|---|---|---|
| PPCDEnvironmentTypeSandBox | 4 | 沙箱环境 | https://sandbox-acquirer-payment.pingpongx.com |
| PPCDEnvironmentTypeRelease | 1 | 生产环境-欧洲 | https://acquirer-payment.pingpongx.com |
| PPCDEnvironmentTypeReleaseUS | 6 | 生产环境-美国 | https://acquirer-payment-checkout-us.pingpongx.com |
配置示例:
PPCDConfig *config = [[PPCDConfig alloc] init];
config.environmentType = PPCDEnvironmentTypeSandBox; // 沙箱
// config.environmentType = PPCDEnvironmentTypeRelease; // 生产欧洲
// config.environmentType = PPCDEnvironmentTypeReleaseUS; // 生产美国初始化 SDK
#import <PPCashDeskSDK/PPCashDeskSDK.h>
// 获取 SDK 实例
PPCDManager *manager = [PPCDManager sharedInstance];
// 配置 SDK
PPCDConfig *config = [[PPCDConfig alloc] init];
config.environmentType = PPCDEnvironmentTypeRelease; // 设置网络环境
config.shouldStartRecLog = YES; // 开启日志记录
config.cardBinLengthValue = 11; // 设置卡Bin位数长度
manager.config = config;配置参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| environmentType | enum | PPCDEnvironmentTypeSandBox / PPCDEnvironmentTypeRelease |
| shouldStartRecLog | BOOL | 是否开启日志记录 |
| cardBinLengthValue | int | 设置卡Bin位数长度 |
步骤 4:唤起半屏支付
[manager initWithToken:@"your_token"
completed:^(NSString *code) {
// 支付流程已提交,需继续向商户服务端确认最终状态
NSLog(@"支付流程完成: %@", code);
}
failure:^(NSError *error) {
// 支付失败回调
NSLog(@"支付流程失败: %@", error.localizedDescription);
}
cancel:^{
// 支付取消回调
NSLog(@"用户取消支付");
}];回调说明
completed 回调不等同于支付成功。App 收到该回调后,应调用商户服务端查询订单状态,或等待服务端通过支付通知完成订单状态更新。
步骤 5:确认最终支付结果
Native SDK 的客户端回调只用于说明当前 App 内支付流程的交互状态,不应直接作为订单成功依据。商户服务端应通过 支付通知 或 交易查询 获取最终交易状态。
| 确认方式 | 说明 | 处理建议 |
|---|---|---|
| 支付通知 | PingPongCheckout 主动向 notificationUrl 推送交易结果 | 推荐优先接入。收到通知后先验签、核对金额币种,再按 status 幂等更新订单 |
| 交易查询 | 商户服务端主动调用交易查询接口获取交易状态 | 用于通知延迟、未达、回调处理异常,或 App 需要立即刷新订单状态的场景 |
重要说明
客户端收到 iOS completed 或 Android Completed 回调时,建议在 App 内展示“支付处理中”或向商户服务端查询订单状态。订单落账、发货和发放权益必须以服务端确认的最终状态为准。
SDK 回调处理建议
| 场景 | iOS 回调 | Android 回调 | App 侧处理建议 |
|---|---|---|---|
| 支付流程已提交 | completed | Completed | 展示支付处理中,并调用商户服务端查询订单状态 |
| 用户关闭半屏收银台 | cancel | Canceled | 返回订单页,可提示用户支付已取消或允许重新发起支付 |
| SDK 调用异常或支付流程失败 | failure | Failed | 展示失败原因或通用重试提示,必要时允许重新创建支付会话 |
支付通知处理建议
如果通过异步通知获取结果,商户服务端应优先返回 HTTP 2xx 确认已接收,再根据通知中的 status 更新订单状态。
支付通知示例:
{
"clientId": "2023121216311410287",
"code": "000000",
"bizContent": "{\"exchangedCurrency\":\"USD\",\"amount\":\"100.000000\",\"transactionTime\":\"1783303837000\",\"transactionId\":\"26070600000751051115\",\"notifyType\":\"RECHARGE\",\"transactionEndTime\":\"1783303856604\",\"merchantTransactionId\":\"PMT-A1XGH96WNE1783303688835\",\"paymentMethod\":{\"type\":\"ApplePay\"},\"currency\":\"USD\",\"exchangedAmount\":\"100.000000\",\"captureDelayHours\":0,\"status\":\"SUCCESS\"}",
"sign": "A9383DCF4FF42C07423C2BDD77490622AA08DF9155D125683BA8BB794C24CCEC",
"accId": "2023121216311410287522",
"description": "Transaction succeeded",
"signType": "SHA256"
}通知关键字段:
| 参数 | 说明 | 处理建议 |
|---|---|---|
transactionId | PingPong 交易流水号 | 保存并用于查询、退款、对账 |
merchantTransactionId | 商户网站订单流水号 | 用于匹配商户侧订单 |
status | 交易状态 | 按状态处理订单,不要仅依赖通知接口返回 code |
amount | 交易金额 | 与商户订单金额核对 |
currency | 交易币种 | 与商户订单币种核对 |
paymentMethod.type | 买家实际使用的支付方式 | 可用于订单展示、对账或支付方式统计 |
notifyType | 通知类型,支付通知固定为 RECHARGE | 用于区分通知事件类型 |
通知中的 status 建议按以下方式处理:
| status | 含义 | 商户处理建议 |
|---|---|---|
SUCCESS | 支付成功 | 将订单更新为支付成功,继续履约、发货或发放权益 |
FAILED | 支付失败 | 将订单更新为支付失败,可引导买家重新支付 |
CANCEL | 风控审核拒绝 | 将订单更新为失败或关闭状态,不应继续履约 |
AUTH_SUCCESS | 预授权成功 | 将订单更新为已授权,后续按业务需要发起 Capture 或 Void |
CLOSED | 订单关闭 | 仅在上送 closeNotificationUrl 的情况下,才会发送主动关单或被动关单通知 |
收到通知后,您无需对响应内容进行加签处理。但无论订单支付是否成功,均必须严格按照以下固定格式返回响应。
通知响应示例:
{
"status": "SUCCESS"
}交易查询处理建议
当异步通知延迟、未达、回调处理失败,或 App 需要在买家完成支付后立即刷新订单状态时,可以调用 交易查询 获取交易最新状态。
交易查询请求示例(仅展示 bizContent 内容):
{
"merchantTransactionId": "PMT-A1XGH96WNE1783303688835"
}交易查询响应示例(仅展示 bizContent 内容):
{
"resultCode": "000000",
"transactionTime": "1783303690000",
"merchantTransactionId": "PMT-A1XGH96WNE1783303688835",
"currency": "USD",
"exchangedAmount": "100.000000",
"resultDescription": "Transaction succeeded",
"exchangedCurrency": "USD",
"amount": "100.000000",
"transactionEndingTime": "1783303857000",
"transactionId": "26070600000751051115",
"paymentMethod": {
"type": "ApplePay"
},
"captureDelayHours": 0,
"status": "SUCCESS"
}查询响应关键字段:
| 参数 | 说明 | 处理建议 |
|---|---|---|
transactionId | PingPong 交易流水号 | 保存并用于退款、对账或后续查询 |
merchantTransactionId | 商户网站订单流水号 | 用于匹配商户侧订单 |
status | 交易状态 | 商户订单状态应以该字段为准 |
amount | 交易金额 | 与商户订单金额核对 |
currency | 交易币种 | 与商户订单币种核对 |
paymentMethod.type | 买家实际使用的支付方式 | 可用于订单展示、对账或支付方式统计 |
transactionEndingTime | 交易结束时间 | 可用于订单完成时间记录 |
查询状态处理建议:
| status | 含义 | 商户处理建议 |
|---|---|---|
INIT | 初始态 | 不更新为成功,继续等待通知或稍后查询 |
PROCESSING | 进行中 | 不更新为成功,继续等待通知或稍后查询 |
SUCCESS | 成功 | 更新订单为支付成功,继续履约、发货或发放权益 |
FAILED | 失败 | 更新订单为支付失败,可引导买家重新支付 |
AUTH_SUCCESS | 预授权成功 | 更新订单为已授权,后续按业务需要发起 Capture 或 Void |
CANCEL | 预授权取消/人工审核拒绝 | 更新订单为失败或关闭状态,不应继续履约 |
上线检查
上线前建议完成以下检查:
- 服务端已完成请求签名、响应验签、通知验签和幂等处理。
- App 客户端不会保存或暴露
salt、sign等服务端签名信息。 notificationUrl已在公网可访问,并能在收到通知后返回固定响应。- 支付成功、失败、取消、处理中、预授权成功等状态均已完成订单状态映射。
- 已分别在沙箱环境验证银行卡、Apple Pay 或 Google Pay 等目标支付方式。
- 生产环境上线前,已切换到对应区域的 API 端点和 SDK 环境枚举。
Apple Pay 配置
如需在 iOS Native SDK 中展示 Apple Pay,请先确认商户号和 App 已完成 Apple Pay 相关配置。更多支付方式能力说明可参考 Apple Pay。
配置开发者账户
创建商家标识符:
- 登录 Apple 开发者中心,选择 "Merchant IDs"
- 输入唯一标识符(格式:
merchant.com.{app_name})
生成付款处理证书:
- 在开发者中心选择对应商家标识符,点击 "Create Certificate"
- 下载 CSR 文件(通过 Xcode 或终端生成),上传后获取
.cer证书文件
传入 Merchant ID:
PPCDConfig *config = [[PPCDConfig alloc] init];
config.applePayMerchantId = @"merchant.com.yourapp";
manager.config = config;接入摘要
Native SDK 适用于商户自有 iOS 或 Android App 内的支付场景。商户服务端调用 prePay(预下单)接口 创建支付会话,App 客户端使用响应中的 token 唤起 PingPong 半屏收银台,买家无需离开当前 App 即可完成支付流程。
支付体验

支付流程
创建支付会话
商户服务端调用 prePay(预下单)接口 创建支付会话,并保存
merchantTransactionId、transactionId与订单状态。返回 SDK token
商户服务端从响应中获取
token,再返回给 iOS 或 Android 客户端。salt、签名逻辑和完整请求参数必须只保存在服务端。唤起半屏收银台
App 客户端完成 SDK 初始化后,传入
token唤起 PingPong 半屏收银台。买家在半屏面板内选择支付方式并完成支付。处理客户端回调
SDK 回调用于驱动 App 页面交互,例如展示支付中、失败提示或用户取消提示。
completed/Completed只表示 SDK 支付流程已提交,不代表交易最终成功。确认最终结果
商户服务端通过支付通知或交易查询确认最终交易状态,并以幂等方式更新订单、发货或发放权益。
接入准备
开始接入前,请确认以下信息已经准备完成。
| 准备项 | 说明 |
|---|---|
| 账号信息 | 已获取 clientId、accId,并确认店铺状态可正常交易 |
| API 规则 | 已了解 API 使用基本规则 和 API 端点地址 |
| 签名能力 | 已根据 签名规约 在服务端完成请求签名,salt 不可下发到 App 客户端 |
| 通知地址 | 已准备公网可访问的 notificationUrl,用于接收支付结果异步通知 |
| 结果确认 | 已在商户服务端支持支付通知验签、金额币种核对、幂等更新和交易查询兜底 |
| 支付方式 | 已开通本次需要展示在 App 收银台中的支付方式,并确认支持的国家、币种、金额范围和退款能力 |
注意
notificationUrl 必须是公网可访问的完整地址,不能填写 localhost、127.0.0.1 或内网 IP。客户端回调不能作为订单落账、发货或发放权益的唯一依据。
步骤 1:创建支付会话
买家在 App 内确认订单后,商户服务端调用 prePay(预下单)接口 创建支付会话。请不要在 iOS 或 Android 客户端直接调用该接口,避免签名密钥泄露。
支付请求关键参数:
| 参数 | 必填 | 说明 |
|---|---|---|
captureDelayHours | O | 资金请款方式。默认自动请款;如需手动请款,请按支付方式能力和接口规则配置 |
amount | M | 交易金额 |
currency | M | ISO 4217 三位交易币种 |
merchantTransactionId | M | 商户网站订单流水号,订单唯一标识 |
shopperIP | M | 买家下单 IP |
merchantUserId | M | 商户侧用户唯一 ID |
goods | M | 商品信息,至少包含商品名称、单价和数量 |
customer | C | 买家信息。不同支付方式可能要求邮箱、手机号或账单信息,请以目标支付方式要求为准 |
billingAddress | C | 账单地址。实物类或特定行业场景可能必传,请以接口必传规则和支付方式要求为准 |
paymentMethods | O | 指定收银台可展示的支付方式范围,不传或为空时按店铺配置展示 |
notificationUrl | O | 支付结果通知地址,强烈建议传入 |
closeNotificationUrl | O | 关单通知地址。传入后,可接收主动关单或被动关单结果通知 |
payResultUrl | O | 支付完成后的商户结果页地址。Native SDK 场景通常由 App 自行处理页面展示 |
language | O | 收银台展示语言,未传入时默认展示英文,枚举值参考语言列表 |
有关完整参数的更多信息,请参阅 prePay(预下单)接口。
支付请求示例(仅展示 bizContent 内容):
{
"captureDelayHours": "0",
"amount": "100",
"currency": "USD",
"merchantTransactionId": "{{merchantTransactionId}}",
"payResultUrl": "https://www.example.com/app/payment-result",
"notificationUrl": "https://www.example.com/checkout/notify",
"closeNotificationUrl": "https://www.example.com/checkout/close-notify",
"language": "en",
"shopperIP": "222.126.52.23",
"merchantUserId": "USER_12345",
"customer": {
"email": "buyer@example.com"
},
"paymentMethods": [],
"goods": [
{
"name": "Digital product",
"description": "Order item",
"unitPrice": "100",
"number": "1",
"virtualProduct": "Y",
"imgUrl": "https://www.example.com/product/SKU_001.png"
}
]
}支付响应示例(仅展示 bizContent 内容):
{
"amount": "100",
"currency": "USD",
"transactionId": "26070600000751051115",
"merchantTransactionId": "PMT-A1XGH96WNE1783303688835",
"token": "EU:OYUi5wmfOMRNFJnqmPRR2QHvtJihf8cTKvay2yeHTuRbnqIBpMJra9KtN9YQ_sXo8DsnSxGh_e_6tMpzRzlXjA==",
"paymentUrl": "https://sandbox-safepay.pingpongx.com?token=EU:OYUi5wmfOMRNFJnqmPRR2QHvtJihf8cTKvay2yeHTuRbnqIBpMJra9KtN9YQ_sXo8DsnSxGh_e_6tMpzRzlXjA==",
"innerJsUrl": "https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-web/sandbox/pp-checkout.js?token=EU:OYUi5wmfOMRNFJnqmPRR2QHvtJihf8cTKvay2yeHTuRbnqIBpMJra9KtN9YQ_sXo8DsnSxGh_e_6tMpzRzlXjA=="
}支付响应关键字段:
| 参数 | 说明 |
|---|---|
token | Native SDK 初始化令牌。App 客户端使用该值唤起半屏收银台 |
transactionId | PingPong 交易流水号,用于后续查询、退款、对账 |
merchantTransactionId | 商户网站订单流水号 |
amount | 交易金额 |
currency | 交易币种 |
Token 传递
商户服务端应只向 App 客户端返回唤起 SDK 所需的 token、订单号和必要展示信息。accId、clientId、sign、salt 等签名相关信息不应下发到客户端。
步骤 2:前端引入 SDK
环境要求
| 项目 | 要求 |
|---|---|
| Android Gradle Plugin(AGP) | 8.13.2 |
| Java | 17 |
| Android SDK | compileSdk 36,targetSdk 36,minSdk 24 |
| OkHttp | 4.12.0 |
| Gson | 2.11.0 |
| Retrofit | 2.11.0 |
手动集成
请在完成“接入准备”后,再将 Android SDK 包引入 App 项目。
下载 Android SDK 包,解压后获取 aar 文件。
Android Studio 工程中引入依赖。
将 aar 文件复制到 module 的 libs 目录下,在 module 的 gradle 文件中添加依赖:
dependencies {
implementation files('libs/payment-android-sdk-1.0.0.aar')
}Android 端差异说明
上方“支付流程”已覆盖 Native SDK 的完整链路。Android 端与通用流程的差异主要体现在客户端调用方式:App 侧先创建 PPPayment 实例,再通过 presentPayment 唤起半屏收银台,并在支付过程中处理 Completed、Failed、Canceled 三类回调。
步骤 3:配置并初始化 SDK
关键对象说明
| 类名 | 说明 |
|---|---|
| PPPayment | SDK 主入口类 |
| PaymentConfig | SDK 配置类 |
| PaymentResult | 支付结果回调(Sealed Class) |
| Environment | 环境枚举(SANDBOX/ONLINE/ONLINE_US) |
环境配置
环境枚举:
enum class Environment {
SANDBOX, // 沙箱环境
ONLINE, // 生产环境-欧洲
ONLINE_US // 生产环境-美国
}| 枚举值 | 说明 | API 端点 |
|---|---|---|
| SANDBOX | 沙箱环境 | https://sandbox-acquirer-payment.pingpongx.com |
| ONLINE | 生产环境-欧洲 | https://acquirer-payment.pingpongx.com |
| ONLINE_US | 生产环境-美国 | https://acquirer-payment-checkout-us.pingpongx.com |
跨平台映射:
| Android | iOS 等价 | 说明 |
|---|---|---|
SANDBOX | PPCDEnvironmentTypeSandBox | 沙箱环境 |
ONLINE | PPCDEnvironmentTypeRelease | 生产欧洲 |
ONLINE_US | PPCDEnvironmentTypeReleaseUS | 生产美国 |
配置示例:
val config = PaymentConfig(
environment = Environment.SANDBOX, // 沙箱
// environment = Environment.ONLINE, // 生产欧洲
// environment = Environment.ONLINE_US // 生产美国
logEnabled = true,
cardBinLength = true
)初始化 SDK
val config = PaymentConfig(
environment = Environment.SANDBOX, // 控制环境切换
logEnabled = true, // 控制 SDK 日志开关
cardBinLength = true // 设置卡 Bin 位数长度
)步骤 4:唤起半屏支付
// 1. 创建支付实例
val payment = PPPayment(activity, PaymentResultCallback { result ->
when (result) {
is PaymentResult.Completed -> {
// 支付流程已提交,需继续向商户服务端确认最终状态
}
is PaymentResult.Canceled -> {
// 用户取消
}
is PaymentResult.Failed -> {
// 支付失败
}
}
})
// 2. 唤起收银台
payment.presentPayment(
token = "your_token",
config = config
)回调说明
Completed 回调不等同于支付成功。App 收到该回调后,应调用商户服务端查询订单状态,或等待服务端通过支付通知完成订单状态更新。
步骤 5:确认最终支付结果
Native SDK 的客户端回调只用于说明当前 App 内支付流程的交互状态,不应直接作为订单成功依据。商户服务端应通过 支付通知 或 交易查询 获取最终交易状态。
| 确认方式 | 说明 | 处理建议 |
|---|---|---|
| 支付通知 | PingPongCheckout 主动向 notificationUrl 推送交易结果 | 推荐优先接入。收到通知后先验签、核对金额币种,再按 status 幂等更新订单 |
| 交易查询 | 商户服务端主动调用交易查询接口获取交易状态 | 用于通知延迟、未达、回调处理异常,或 App 需要立即刷新订单状态的场景 |
重要说明
客户端收到 iOS completed 或 Android Completed 回调时,建议在 App 内展示“支付处理中”或向商户服务端查询订单状态。订单落账、发货和发放权益必须以服务端确认的最终状态为准。
SDK 回调处理建议
| 场景 | iOS 回调 | Android 回调 | App 侧处理建议 |
|---|---|---|---|
| 支付流程已提交 | completed | Completed | 展示支付处理中,并调用商户服务端查询订单状态 |
| 用户关闭半屏收银台 | cancel | Canceled | 返回订单页,可提示用户支付已取消或允许重新发起支付 |
| SDK 调用异常或支付流程失败 | failure | Failed | 展示失败原因或通用重试提示,必要时允许重新创建支付会话 |
支付通知处理建议
如果通过异步通知获取结果,商户服务端应优先返回 HTTP 2xx 确认已接收,再根据通知中的 status 更新订单状态。
支付通知示例:
{
"clientId": "2023121216311410287",
"code": "000000",
"bizContent": "{\"exchangedCurrency\":\"USD\",\"amount\":\"100.000000\",\"transactionTime\":\"1783303837000\",\"transactionId\":\"26070600000751051115\",\"notifyType\":\"RECHARGE\",\"transactionEndTime\":\"1783303856604\",\"merchantTransactionId\":\"PMT-A1XGH96WNE1783303688835\",\"paymentMethod\":{\"type\":\"ApplePay\"},\"currency\":\"USD\",\"exchangedAmount\":\"100.000000\",\"captureDelayHours\":0,\"status\":\"SUCCESS\"}",
"sign": "A9383DCF4FF42C07423C2BDD77490622AA08DF9155D125683BA8BB794C24CCEC",
"accId": "2023121216311410287522",
"description": "Transaction succeeded",
"signType": "SHA256"
}通知关键字段:
| 参数 | 说明 | 处理建议 |
|---|---|---|
transactionId | PingPong 交易流水号 | 保存并用于查询、退款、对账 |
merchantTransactionId | 商户网站订单流水号 | 用于匹配商户侧订单 |
status | 交易状态 | 按状态处理订单,不要仅依赖通知接口返回 code |
amount | 交易金额 | 与商户订单金额核对 |
currency | 交易币种 | 与商户订单币种核对 |
paymentMethod.type | 买家实际使用的支付方式 | 可用于订单展示、对账或支付方式统计 |
notifyType | 通知类型,支付通知固定为 RECHARGE | 用于区分通知事件类型 |
通知中的 status 建议按以下方式处理:
| status | 含义 | 商户处理建议 |
|---|---|---|
SUCCESS | 支付成功 | 将订单更新为支付成功,继续履约、发货或发放权益 |
FAILED | 支付失败 | 将订单更新为支付失败,可引导买家重新支付 |
CANCEL | 风控审核拒绝 | 将订单更新为失败或关闭状态,不应继续履约 |
AUTH_SUCCESS | 预授权成功 | 将订单更新为已授权,后续按业务需要发起 Capture 或 Void |
CLOSED | 订单关闭 | 仅在上送 closeNotificationUrl 的情况下,才会发送主动关单或被动关单通知 |
收到通知后,您无需对响应内容进行加签处理。但无论订单支付是否成功,均必须严格按照以下固定格式返回响应。
通知响应示例:
{
"status": "SUCCESS"
}交易查询处理建议
当异步通知延迟、未达、回调处理失败,或 App 需要在买家完成支付后立即刷新订单状态时,可以调用 交易查询 获取交易最新状态。
交易查询请求示例(仅展示 bizContent 内容):
{
"merchantTransactionId": "PMT-A1XGH96WNE1783303688835"
}交易查询响应示例(仅展示 bizContent 内容):
{
"resultCode": "000000",
"transactionTime": "1783303690000",
"merchantTransactionId": "PMT-A1XGH96WNE1783303688835",
"currency": "USD",
"exchangedAmount": "100.000000",
"resultDescription": "Transaction succeeded",
"exchangedCurrency": "USD",
"amount": "100.000000",
"transactionEndingTime": "1783303857000",
"transactionId": "26070600000751051115",
"paymentMethod": {
"type": "ApplePay"
},
"captureDelayHours": 0,
"status": "SUCCESS"
}查询响应关键字段:
| 参数 | 说明 | 处理建议 |
|---|---|---|
transactionId | PingPong 交易流水号 | 保存并用于退款、对账或后续查询 |
merchantTransactionId | 商户网站订单流水号 | 用于匹配商户侧订单 |
status | 交易状态 | 商户订单状态应以该字段为准 |
amount | 交易金额 | 与商户订单金额核对 |
currency | 交易币种 | 与商户订单币种核对 |
paymentMethod.type | 买家实际使用的支付方式 | 可用于订单展示、对账或支付方式统计 |
transactionEndingTime | 交易结束时间 | 可用于订单完成时间记录 |
查询状态处理建议:
| status | 含义 | 商户处理建议 |
|---|---|---|
INIT | 初始态 | 不更新为成功,继续等待通知或稍后查询 |
PROCESSING | 进行中 | 不更新为成功,继续等待通知或稍后查询 |
SUCCESS | 成功 | 更新订单为支付成功,继续履约、发货或发放权益 |
FAILED | 失败 | 更新订单为支付失败,可引导买家重新支付 |
AUTH_SUCCESS | 预授权成功 | 更新订单为已授权,后续按业务需要发起 Capture 或 Void |
CANCEL | 预授权取消/人工审核拒绝 | 更新订单为失败或关闭状态,不应继续履约 |
上线检查
上线前建议完成以下检查:
- 服务端已完成请求签名、响应验签、通知验签和幂等处理。
- App 客户端不会保存或暴露
salt、sign等服务端签名信息。 notificationUrl已在公网可访问,并能在收到通知后返回固定响应。- 支付成功、失败、取消、处理中、预授权成功等状态均已完成订单状态映射。
- 已分别在沙箱环境验证银行卡、Apple Pay 或 Google Pay 等目标支付方式。
- 生产环境上线前,已切换到对应区域的 API 端点和 SDK 环境枚举。
Google Pay 配置
如需在 Android Native SDK 中展示 Google Pay,请先确认店铺已开通 Google Pay,App 运行环境满足 Google Pay 要求,并按业务所属地区完成必要配置。更多支付方式能力说明可参考 Google Pay。
支付后操作
以下支付后操作适用于 Web/WAP、iOS 和 Android 集成形态,相关操作均由商户服务端完成。前端结果页或 SDK 回调只用于买家展示与交互反馈,不应直接作为订单最终成功依据;订单状态仍应以 支付通知 或 交易查询 的服务端确认结果为准。
查询交易
当买家从结果页返回、App 收到支付完成回调、异步通知延迟或未达,或商户需要执行补偿任务时,可以调用 交易查询 获取交易最新状态。
查询时建议优先使用商户侧保存的 merchantTransactionId 或 PingPong 返回的 transactionId 定位交易;查询请求成功仅表示查询接口处理成功,不代表支付成功,商户订单状态仍应以查询响应中的 status 为准。
退款
支付成功后,如买家申请退款、商户取消已支付订单或需要退回部分或全部款项,可调用 申请退款 对原交易发起退款。
退款结果可通过 退款通知 接收;如通知延迟、未达或需要再次确认,可调用 退款查询 主动查询。
不同支付方式的退款能力不同,接入前请确认目标支付方式是否支持退款、退款有效期、是否支持部分退款以及是否支持多次部分退款。更完整的退款接入说明可参考 Refund。
