内嵌SDK(预下单)
约 5128 字大约 17 分钟
2025-03-07
接入摘要
内嵌 SDK(预下单)是一种低代码站内收银台方案。商户服务端先调用 prePay(预下单)接口 创建支付会话,再由前端通过 JavaScript-SDK 在商户页面内直接渲染 PingPong 收银台。该方案既能保留买家在商户站内完成支付的体验,也能减少商户自建支付页、支付方式展示和支付交互处理的复杂度。
适用场景
适合希望将收银台直接嵌入商户页面、降低支付页前端开发成本并保留站内支付体验的团队;若可接受买家跳转至 PingPong 托管收银台完成支付,建议选择跳转收银台。
支付流程
买家下单
- 买家在商户页面发起结账并提交订单。
- 商户侧开始创建支付会话,准备初始化内嵌收银台。
创建支付会话
- 商户服务端调用 prePay(预下单)接口。
- 获取
token、innerJsUrl等收银台初始化所需参数。
初始化并展示收银台
- 商户前端加载 JavaScript-SDK,并将
accessToken与locale传给pp-checkout。 - PingPong 收银台在商户页面内完成渲染并展示可用支付方式。
- 商户前端加载 JavaScript-SDK,并将
买家完成支付
- 买家选择支付方式、填写支付信息并确认支付。
- 若触发 3D Secure,买家在当前支付流程中完成验证。
确认支付结果
- 支付完成后,前端可展示结果页或按配置跳转至
payResultUrl。 - 商户服务端仍需结合异步通知或查询接口确认最终支付状态。
- 支付完成后,前端可展示结果页或按配置跳转至
关键注意事项
- 支付结果以异步通知(
notificationUrl)或对应查询接口确认结果为准,前端结果页仅用于买家展示; - 优先使用
prePay(预下单)接口返回的bizContent.innerJsUrl动态加载 SDK,以避免环境切换或版本变更带来的地址不一致问题; ready、error等事件监听器需要在设置accessToken之前注册,否则可能错过初始化事件;- 在
locale未传入的情况下,收银台默认展示英文;如果传了locale,则以传入值为准。具体枚举值可参考语言列表。
集成流程
1. 创建支付会话
在初始化 JS-SDK 收银台之前,商户服务端需要先调用 prePay(预下单)接口 获取 SDK 初始化所需的关键参数。
提示
具体的接口地址、请求参数和签名方式请参考prePay(预下单)接口。
响应关键字段
| 字段路径 | 类型 | 必需 | 说明 |
|---|---|---|---|
bizContent.token | string | ✅ 必需 | SDK 初始化令牌,用于设置 pp-checkout 的 accessToken 属性 |
bizContent.innerJsUrl | string | ⚪ 可选 | 动态加载 SDK 的 URL,推荐使用此地址动态加载 JS-SDK |
bizContent.paymentUrl | string | ⚪ 可选 | 支付页面 URL(跳转模式使用) |
bizContent.transactionId | string | ✅ 必需 | PingPong 交易号,可用于交易跟踪、查询或对账,不参与前端 SDK 初始化 |
v4 响应示例
{
"accId": "2018092714313010016291",
"clientId": "2018092714313010016",
"code": "000000",
"description": "Transaction succeeded",
"signType": "SHA256",
"bizContent": {
"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=..."
}
}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 时,收银台仅展示已存储的卡信息区域(隐藏新卡输入)。适用于 CoF 复购 CVV 收集场景 |
disableCardRemoval | boolean | false | 设为 true 时,禁止用户删除已存储的卡信息。适用于 CoF 复购 CVV 收集场景 |
displayCardPrompt | boolean | true | 是否显示卡支付提示信息 |
localizationErrorMsg | boolean | false | 是否翻译支付错误消息 |
displayCardsLogo | boolean | true | 是否显示卡品牌 logo 列表 |
注意
toPingPongResult 配置需要在服务端创建支付会话时设置。客户端设置可能会被服务端配置覆盖。如需禁用 PingPong 结果页跳转,请确保服务端已正确配置。
// customizeConfig 布局与界面配置
PingPong.Checkout.customizeConfig = {
layout: "accordion", // 平铺样式
displayCheckoutHeader: false, // 隐藏标题栏
originalPay: false, // 隐藏原生支付按钮,需自定义按钮触发支付
toPingPongResult: false, // 支付完成后不跳转到 PingPong 结果页
hideStoredCards: false, // 显示已保存的卡列表
displayCardPrompt: true, // 显示卡支付提示信息
localizationErrorMsg: false, // 不翻译错误消息
displayCardsLogo: true // 显示卡品牌 logo
};自定义支付按钮(可选)
当 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.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.checkoutFailedHook = (code, message) => {
console.error('支付失败:', { code, message });
// 自定义错误提示
alert(`支付失败\n错误码: ${code}\n错误信息: ${message}`);
};
}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.checkoutFailedHook = (code, message) => {
console.error('支付失败:', { code, message });
// 可以使用 UI 库的通知组件
// 这里使用简单的 alert 演示
alert(`支付失败\n错误码: ${code}\n错误信息: ${message}`);
};
}
/**
* 初始化 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 调试工具。
