更新记录
v1.0.0(2026-04-19)
初始化
平台兼容性
uni-app(4.86)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | × | × | √ | √ | 4.4 | 13 | √ |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| × | × | × | × | × | × | × | × | × | × | × | × |
passkit-apple-pay
iOS Apple Pay (PassKit) UTS 插件。非应用内购 (IAP)。
插件只负责:
- 检测设备能力
- 弹起 Apple Pay 收银台
- 把 Face ID / Touch ID 授权后的加密
PKPaymentToken交给你 - 根据你的后端校验结果,给 Apple Pay 卡片"打钩"或"×"
插件不负责:
- 扣款(必须由你后端对接支付网关完成)
- 应用内购(虚拟商品请用 StoreKit 插件)
- Android / Harmony(会返回
NOT_SUPPORTED)
整体工作流程
┌──────────┐ ① ┌──────────────┐ ②弹卡片 ┌──────────┐
│ 你的 JS │──requestPayment──→ │ 本插件 │──present──→ │ Apple Pay │
│ 代码 │ │ (UTS+Swift) │ │ 卡片 │
└──────────┘ └──────────────┘ └──────────┘
▲ │ │
│ │ │ Face ID
│ ⑦resolve/reject │ ③事件回调 │ 通过
│ │ (多次触发) │
│ ↓ │
│ ┌─────────────┐ ④didAuthorize │
│ │ onEvent │←──────────────────┘
│ │ 回调事件流 │
│ └─────────────┘
│ │
│ │ ⑤把 paymentData 发后端
│ ↓
│ ┌─────────────┐
│ │ 你的后端 │───→ 支付网关(Stripe/银联/Adyen)
│ │ │←─── 扣款成功/失败
│ └─────────────┘
│ │
│ │ ⑥respondAuthorization(status)
│ ↓
│ ┌──────────┐
│ │ Apple │
│ │ Pay 卡片 │ 打 ✓/× 并关闭
└───────────────────────────────┴──────────┘
关键点:onEvent 回调不是一次性的。同一次支付会话中会触发 presented → shippingContactSelect (可选) → authorized → finished 等多个事件,插件内部已用 @UTSJS.keepAlive 保活。
部署前提 Checklist
在你能真正跑通 Apple Pay 之前,以下每一项都必须完成:
① Apple Developer 后台
- Identifiers → App IDs:为你的 App 启用
In-App Paymentscapability - Identifiers → Merchant IDs:新建一个 Merchant ID(格式如
merchant.com.yourco.app) - Provisioning Profiles:重新生成 provisioning profile,勾选上面那个 Merchant ID
② 本插件的 entitlements
编辑 utssdk/app-ios/UTS.entitlements,把 com.apple.developer.in-app-payments 数组里的占位 merchant.com.example.replace-me 改成上一步创建的真实 Merchant ID。
⚠️ 这一步不做,调用
requestPayment时 Apple Pay 卡片会弹出但 Face ID 后显示"未完成付款"(或直接 iOS 层崩溃)。
③ 支付网关对接(后端工作)
Apple Pay 只产出加密 token,不会直接扣款。后端必须和一个支付网关对接才能把 token 换成真钱:
| 业务场景 | 推荐网关 |
|---|---|
| 国内 CN + chinaUnionPay | 银联 Apple Pay 接入(需银联商户审核) |
| 海外 | Stripe / Adyen / Braintree / Checkout.com |
| 多国 | Stripe(简单)或 Adyen(覆盖广) |
后端大致流程:
接收 JS 传来的 { paymentData, transactionIdentifier, orderNo }
↓
调网关 API:Stripe 叫 `payment_method_data[apple_pay]`,
Adyen 叫 `paymentMethod.applepay.token` 等
↓
网关返回扣款结果 → 返回给 JS
④ 设备 & Wallet
- 真机(模拟器不支持 Apple Pay)
- Wallet 里已添加一张你
supportedNetworks列表内的卡
安装
- 把本目录整体放入
uni_modules/passkit-apple-pay/ - 完成上面 Checklist 的 ① ②
- HBuilderX → 运行 → 真机运行 → 制作自定义调试基座
首次运行会自动链接 PassKit.framework。
API 总览
import {
canMakePayments,
canMakePaymentsWithNetworks,
openSetup,
requestPayment,
respondUpdate,
respondAuthorization,
dismiss
} from '@/uni_modules/passkit-apple-pay'
能力检测(同步)
canMakePayments(): boolean
设备 & 系统是否具备 Apple Pay 能力(不看 Wallet 里有没有卡)。
canMakePaymentsWithNetworks(networks, capabilities?): boolean
Wallet 里是否有可用于指定卡组织的卡。
networks:string[]—— 如["visa","masterCard","chinaUnionPay"]capabilities?:string[]—— 如["3DS","EMV","credit","debit"]
openSetup(): void
打开 Wallet 的"添加卡"界面。当 canMakePaymentsWithNetworks 返回 false 时,可引导用户来添加卡。
支付主流程
requestPayment(request, onEvent): void
这是最核心的 API。 一次调用开启一个会话,onEvent 会被触发多次,直到收到 finished 事件会话才结束。
request 参数:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
merchantIdentifier |
string | ✅ | 真实 Merchant ID,和 entitlements 里一致 |
countryCode |
string | ✅ | ISO 国家码,"CN"/"US"/... |
currencyCode |
string | ✅ | ISO 货币码,"CNY"/"USD"/... |
supportedNetworks |
string[] | ✅ | 接受的卡组织,如 ["chinaUnionPay"] |
merchantCapabilities |
string[] | ✅ | 支付能力,如 ["3DS","credit","debit"] |
summaryItems |
SummaryItem[] |
✅ | 金额行,最后一项自动显示为"总计" |
shippingMethods |
ShippingMethod[] |
❌ | 配送方式,传了才触发 shippingMethodSelect |
shippingType |
string | ❌ | "shipping" / "delivery" / "storePickup" / "servicePickup" |
requiredShippingContactFields |
string[] | ❌ | 要求用户填的联系人字段,触发 shippingContactSelect |
requiredBillingContactFields |
string[] | ❌ | 账单联系人 |
applicationData |
string | ❌ | 塞入 token 的 app 自定义数据,后端可校验(如订单号) |
onEvent 回调 —— 同一会话多次触发:
| event.kind | 触发时机 | 其他字段 | JS 必须响应? |
|---|---|---|---|
shippingContactSelect |
用户切配送地址 | requestId, contact |
✅ 3s 内调 respondUpdate(requestId, {summaryItems?, shippingMethods?, errors?}) |
shippingMethodSelect |
用户切运送方式 | requestId, method |
✅ 3s 内调 respondUpdate(requestId, {summaryItems}) |
authorized |
Face ID/Touch ID 通过,拿到加密 token | requestId, result |
✅ 25s 内调 respondAuthorization(requestId, {status}) |
updateTimeout |
JS 超时未响应;原生已用当前 summaryItems 自动放行 | requestId |
❌ 已自动放行 |
cancelled |
JS 主动调了 dismiss() |
— | ❌ |
failed |
参数非法 / 会话冲突 / 卡片弹出失败 | error |
❌ |
finished |
卡片已关闭,会话彻底结束 | — | ❌ |
插件故意不实现
paymentMethodSelect事件 —— 该回调在卡片弹起瞬间同步触发,JS 往返延迟易导致 PassKit 断言崩溃。
authorized 事件的 result:
{
paymentData: string // PKPaymentToken.paymentData 的 base64 —— 发给后端
transactionIdentifier: string // 交易 ID
paymentMethod: {
displayName: string // "UnionPay 1234"
network: string // "chinaUnionPay"
type: number // 0 unknown / 1 debit / 2 credit / 3 prepaid / 4 store / 5 eMoney
}
shippingContact?: { ... } // 只有请求时指定了 requiredShippingContactFields 才有
billingContact?: { ... }
shippingMethod?: { ... }
}
响应 API
respondUpdate(requestId, update): void
响应 shippingContactSelect / shippingMethodSelect。
update = {
summaryItems?: SummaryItem[], // 新的金额行
shippingMethods?: ShippingMethod[], // 新的配送方式(仅 shippingContactSelect)
errors?: Array<{ // 字段级错误(红色高亮)
field: "postalAddress" | "emailAddress" | "phoneNumber" | "name",
subField?: string, // postalAddress 的子字段,如 "postalCode"
message: string
}>
}
respondAuthorization(requestId, result): void
响应 authorized。告诉 Apple Pay 后端扣款的结果。
result = {
status: "success" | "failure",
errors?: 同上 // 失败时可给出字段级错误让用户修改地址等
}
dismiss(): void
主动关闭 Apple Pay 卡片。极少用;正常流程 Apple 自己会关。
响应超时与幂等
- 可选事件(shipping 两个) 3 秒未响应 → 原生用当前 summaryItems 自动 complete,额外发一个
updateTimeout事件通知 JS - 授权事件 25 秒未响应 → 同上机制
- 同一
requestId多次调respondUpdate/respondAuthorization,只认第一次,后续返回failed { ALREADY_RESPONDED }但不影响已放行的会话
最简 Promise 封装(推荐)
import { canMakePayments, requestPayment, respondAuthorization } from '@/uni_modules/passkit-apple-pay'
function payWithApplePay({ orderNo, amount, merchantName = 'Your Brand' }) {
return new Promise((resolve, reject) => {
if (!canMakePayments()) {
return reject({ errCode: 'NOT_SUPPORTED', errMsg: '当前设备不支持 Apple Pay' })
}
requestPayment({
merchantIdentifier: 'merchant.com.yourco.app',
countryCode: 'CN',
currencyCode: 'CNY',
supportedNetworks: ['chinaUnionPay'],
merchantCapabilities: ['3DS', 'credit', 'debit'],
summaryItems: [{ label: merchantName, amount: String(amount) }],
applicationData: JSON.stringify({ orderNo })
}, (event) => {
switch (event.kind) {
case 'authorized':
// 交给后端调支付网关
yourBackend.chargeApplePay({
orderNo,
paymentData: event.result.paymentData,
transactionIdentifier: event.result.transactionIdentifier
}).then(res => {
const ok = !!res?.success
respondAuthorization(event.requestId, { status: ok ? 'success' : 'failure' })
ok ? resolve(res) : reject({ errCode: 'BACKEND_REJECT', errMsg: res?.message || '支付失败' })
}).catch(err => {
respondAuthorization(event.requestId, { status: 'failure' })
reject(err)
})
break
case 'cancelled':
reject({ errCode: 'USER_CANCELLED', errMsg: '用户取消支付' })
break
case 'failed':
reject(event.error)
break
// shippingContactSelect / shippingMethodSelect / updateTimeout / finished
// 当前配置(无 shipping 配置)不会触发或无需响应
}
})
})
}
// 使用
payWithApplePay({ orderNo: 'O123', amount: '99.00' })
.then(res => console.log('支付成功', res))
.catch(err => console.log('支付失败', err.errCode, err.errMsg))
动态运费示例
带配送地址时,用户切换地址要重算运费:
requestPayment({
merchantIdentifier: 'merchant.com.yourco.app',
countryCode: 'US',
currencyCode: 'USD',
supportedNetworks: ['visa', 'masterCard'],
merchantCapabilities: ['3DS'],
summaryItems: [
{ label: '商品', amount: '9.99' },
{ label: '运费', amount: '2.00' },
{ label: 'Total', amount: '11.99' }
],
shippingMethods: [
{ identifier: 'std', label: '标准', amount: '2.00', detail: '3-5 天' },
{ identifier: 'exp', label: '加急', amount: '8.00', detail: '次日达' }
],
shippingType: 'shipping',
requiredShippingContactFields: ['postalAddress', 'name', 'phoneNumber']
}, (event) => {
if (event.kind === 'shippingContactSelect') {
// 根据 event.contact.postalAddress 调后端算运费
const newShipping = computeShipping(event.contact.postalAddress)
respondUpdate(event.requestId, {
summaryItems: [
{ label: '商品', amount: '9.99' },
{ label: '运费', amount: newShipping },
{ label: 'Total', amount: (9.99 + Number(newShipping)).toFixed(2) }
]
})
} else if (event.kind === 'shippingMethodSelect') {
const shipping = Number(event.method.amount)
respondUpdate(event.requestId, {
summaryItems: [
{ label: '商品', amount: '9.99' },
{ label: '运费', amount: event.method.amount },
{ label: 'Total', amount: (9.99 + shipping).toFixed(2) }
]
})
} else if (event.kind === 'authorized') {
// 同前面
}
})
错误码
| code | 含义 |
|---|---|
NOT_SUPPORTED |
非 iOS 平台 |
CANNOT_MAKE_PAYMENTS |
系统不支持 |
NO_CARD_FOR_NETWORKS |
Wallet 没符合的卡(可用 openSetup() 引导添加) |
INVALID_REQUEST |
request 字段非法 |
SESSION_ACTIVE |
上一次会话还没结束 |
PRESENT_FAILED |
卡片弹出失败 |
USER_CANCELLED |
用户主动取消 |
UPDATE_TIMEOUT |
JS 响应超时,已自动放行 |
ALREADY_RESPONDED |
同一 requestId 重复响应 |
INTERNAL_ERROR |
其他 |
常见问题
Q: 卡片弹不出来 / 弹出后 Face ID 通过但显示"未完成付款" A: 99% 是 Merchant ID / entitlements / provisioning profile 三者没对齐。检查:
utssdk/app-ios/UTS.entitlements里的 merchant ID 是不是真实的(不是merchant.com.example.replace-me占位)- Apple Developer 后台 App ID 启用了 In-App Payments
- provisioning profile 里勾了这个 Merchant ID 且已下发到手机
- CN + chinaUnionPay 特别注意:仅 Apple 侧开通不够,还需银联商户那边审核接入
Q: 切换地址金额不刷新
A: 确认你的 respondUpdate(requestId, ...) 里的 requestId 和事件里收到的一致;必须在 3 秒内响应,否则被原生兜底放行。
Q: paymentData 是啥格式?后端怎么处理?
A: 是 PKPaymentToken.paymentData 的 base64。有两种用法:
- 直接扔给网关:Stripe 的
payment_method_data[apple_pay]、Adyen 的applepay.token等,网关帮你解密 - 自行解密(需要 Merchant Payment Processing Certificate 和私钥):按 Apple 官方文档
Payment Token Format Reference做 ECDH + AES-GCM 解密
Q: onEvent 回调收到一次之后就不响应了?
A: 检查插件版本,index.uts 必须有 @UTSJS.keepAlive 装饰器(HBuilderX 4.27+)。否则 UTS 把 JS 回调当一次性。
Q: requestPayment 调用后 App 闪退
A: 常见两个原因:
- entitlements 配置错误 → Apple Pay 卡片在调起瞬间触发 iOS 进程杀死
- JS 传给
requestPayment的 request 字段类型错(比如amount写成 number 而不是 string)→ UTS-Swift 桥接失败
排查建议先拿设备的 .ips 崩溃日志(设置 → 隐私与安全性 → 分析与改进 → 分析数据)。
不支持
- 应用内购买(IAP 请用 StoreKit 插件)
- Android / Harmony(会返回
NOT_SUPPORTED) - 模拟器(Apple Pay 必须真机)

收藏人数:
购买源码授权版(
试用
赞赏(0)
下载 0
赞赏 0
下载 11579524
赞赏 1905
赞赏
京公网安备:11010802035340号