passkit

passkit

热门

使用 PassKit 集成 Apple Pay 支付和 Wallet 凭证(Passes)。适用于添加 Apple Pay 按钮、创建支付请求、处理支付授权、将凭证添加到 Wallet、配置商户能力、管理收货/联系人字段,或者处理 PKPaymentRequest、PKPaymentAuthorizationController、PKPaymentButton、AddPassToWalletButton、PKPass、PKAddPassesViewController、PKPassLibrary、Wallet 凭证分发,以及实体商品、线下服务、捐赠和符合条件的订阅/定期支付等 Apple Pay 结账流程。

967Star
49Fork
更新于 2026/7/31
SKILL.md
只读
名称
passkit
描述

使用 PassKit 集成 Apple Pay 支付和 Wallet 凭证(Passes)。适用于添加 Apple Pay 按钮、创建支付请求、处理支付授权、将凭证添加到 Wallet、配置商户能力、管理收货/联系人字段,或者处理 PKPaymentRequest、PKPaymentAuthorizationController、PKPaymentButton、AddPassToWalletButton、PKPass、PKAddPassesViewController、PKPassLibrary、Wallet 凭证分发,以及实体商品、线下服务、捐赠和符合条件的订阅/定期支付等 Apple Pay 结账流程。

PassKit

使用 PassKit 接入 Apple Pay 支付(适用于实体商品、线下服务、捐赠以及符合条件的定期/自动续订支付),并将凭证添加到用户的 Wallet(钱包)中。覆盖支付按钮、支付请求、授权、Wallet 凭证及商户配置。支持 Swift 6.3 / iOS 26+。

对于高级 Apple Pay 流程,单个 PKPaymentRequest 只能设置一种可选的高级请求类型:自动续订/定期(recurring)、自动充值(automatic reload)、延迟支付(deferred)、Apple Pay Later 可用性或多令牌上下文(multi-token contexts)。如果结账时需要多种模式,请使用独立的支付请求。

目录

配置与准备

项目配置

  1. 在 Xcode 中开启 Apple Pay capability(能力)
  2. 在 Apple Developer 后台创建 Merchant ID(商户标识,格式为:merchant.com.example.app
  3. 为你的 Merchant ID 生成并安装 Payment Processing Certificate(支付处理证书)
  4. 将 Merchant ID 添加到项目的 entitlements 中

可用性检查

在展示 Apple Pay UI 之前,务必先验证当前设备是否支持支付。如果你使用 canMakePayments(usingNetworks:capabilities:) 检查是否有可用绑卡,根据 Apple HIG(人机界面指南)规范,只要进行了这项检查,你就必须将 Apple Pay 作为主要且显眼的支付选项进行展示。

import PassKit

func canMakePayments() -> Bool {
    // 检查设备本身是否支持 Apple Pay
    guard PKPaymentAuthorizationController.canMakePayments() else {
        return false
    }
    // 检查用户是否绑定了你所支持卡组织的银行卡
    return PKPaymentAuthorizationController.canMakePayments(
        usingNetworks: [.visa, .masterCard, .amex, .discover],
        capabilities: .threeDSecure
    )
}

展示 Apple Pay 按钮

SwiftUI

在 SwiftUI 中直接使用内置的 PayWithApplePayButton 视图。只要控件带有 Apple Pay 标识,就必须使用 Apple 官方提供的按钮 API;自定义按钮绝不能包含 Apple Pay Logo 或 "Apple Pay" 字样。

import SwiftUI
import PassKit

struct CheckoutView: View {
    var body: some View {
        PayWithApplePayButton(.buy) {
            startPayment()
        }
        .payWithApplePayButtonStyle(.black)
        .frame(height: 48)
        .padding()
    }
}

UIKit

在基于 UIKit 的界面中使用 PKPaymentButton

let button = PKPaymentButton(
    paymentButtonType: .buy,
    paymentButtonStyle: .black
)
button.cornerRadius = 12
button.addTarget(self, action: #selector(startPayment), for: .touchUpInside)

按钮类型(Button types): .plain.buy.setUp.inStore.donate.checkout.continue.book.subscribe.reload.addMoney.topUp.order.rent.support.contribute.tip

创建支付请求

构建一个包含你的商户信息和商品明细的 PKPaymentRequest
注意:PassKit 的金额 API 使用 NSDecimalNumber,而非 Double

func createPaymentRequest() -> PKPaymentRequest {
    let request = PKPaymentRequest()
    request.merchantIdentifier = "merchant.com.example.app"
    request.countryCode = "US"
    request.currencyCode = "USD"
    request.supportedNetworks = [.visa, .masterCard, .amex, .discover]
    request.merchantCapabilities = .threeDSecure

    request.paymentSummaryItems = [
        PKPaymentSummaryItem(
            label: "Widget",
            amount: NSDecimalNumber(string: "9.99")
        ),
        PKPaymentSummaryItem(
            label: "Shipping",
            amount: NSDecimalNumber(string: "4.99")
        ),
        PKPaymentSummaryItem(
            label: "My Store",
            amount: NSDecimalNumber(string: "14.98")
        ) // 总计(Total)
    ]

    return request
}

paymentSummaryItems 中的最后一项会被视作最终结算总价(Total),它的 label 会显示在支付弹窗的支付确认行中。

请求收货和联系人信息

只请求计价、履约或法律合规所必需的联系人字段。
如果支付弹窗(payment sheet)无法准确收集所选商品规格、可选备注、单件商品的配送目的地或自提点等信息,请在用户点击 Apple Pay 按钮之前先收集完毕。

request.requiredShippingContactFields = [.postalAddress, .emailAddress, .name]
request.requiredBillingContactFields = [.postalAddress]

let standard = PKShippingMethod(
    label: "Standard",
    amount: NSDecimalNumber(string: "4.99")
)
standard.identifier = "standard"
standard.detail = "5-7 business days"

let express = PKShippingMethod(
    label: "Express",
    amount: NSDecimalNumber(string: "9.99")
)
express.identifier = "express"
express.detail = "1-2 business days"

request.shippingMethods = [standard, express]

request.shippingType = .shipping // .delivery, .storePickup, .servicePickup

支持的卡组织/网络(Supported Networks)

卡组织 常量
Visa .visa
Mastercard .masterCard
American Express .amex
Discover .discover
China UnionPay (中国银联) .chinaUnionPay
JCB .JCB
Maestro .maestro
Electron .electron
Interac .interac

可以使用 PKPaymentRequest.availableNetworks() 在运行时查询当前可用的卡组织。

弹出支付页面

使用 PKPaymentAuthorizationController(同时支持 SwiftUI 和 UIKit,无需依赖 View Controller)。控制器(controller)的 delegate 属性是 weak 弱引用,因此在支付页面展示期间请务必强持有该 controller。

final class CheckoutCoordinator: NSObject {
    private var paymentController: PKPaymentAuthorizationController?

    @MainActor
    func startPayment() {
        let controller = PKPaymentAuthorizationController(
            paymentRequest: createPaymentRequest()
        )
        paymentController = controller
        controller.delegate = self
        controller.present { [weak self] presented in
            if !presented {
                self?.paymentController = nil
            }
        }
    }
}

处理支付授权

实现 PKPaymentAuthorizationControllerDelegate 协议来处理支付 Token。

extension CheckoutCoordinator: PKPaymentAuthorizationControllerDelegate {
    func paymentAuthorizationController(
        _ controller: PKPaymentAuthorizationController,
        didAuthorizePayment payment: PKPayment,
        handler completion: @escaping (PKPaymentAuthorizationResult) -> Void
    ) {
        // 将 payment.token.paymentData 发送到你的支付服务网关
        Task {
            do {
                try await paymentService.process(payment.token)
                completion(PKPaymentAuthorizationResult(status: .success, errors: nil))
            } catch {
                completion(PKPaymentAuthorizationResult(status: .failure, errors: [error]))
            }
        }
    }

    func paymentAuthorizationControllerDidFinish(
        _ controller: PKPaymentAuthorizationController
    ) {
        controller.dismiss { [weak self] in
            self?.paymentController = nil
        }
    }
}

处理配送方式变更

func paymentAuthorizationController(
    _ controller: PKPaymentAuthorizationController,
    didSelectShippingMethod shippingMethod: PKShippingMethod,
    handler completion: @escaping (PKPaymentRequestShippingMethodUpdate) -> Void
) {
    let updatedItems = recalculateItems(with: shippingMethod)
    let update = PKPaymentRequestShippingMethodUpdate(paymentSummaryItems: updatedItems)
    completion(update)
}

Wallet 凭证

添加凭证到 Wallet

加载签名后的 .pkpass 数据,检查设备是否支持添加凭证。如果希望用户在添加前预览凭证,可以弹出 PKAddPassesViewControllerPKPass(data:) 接收已签名的凭证数据,可能抛出数据无效或签名无效等错误。在审核指导文档或错误处理逻辑中,应当明确指出 invalid-datainvalid-signature 错误,而不是简单用一个裸 try? 默默吃掉错误。

func addPassToWallet(data: Data) {
    guard PKAddPassesViewController.canAddPasses() else {
        return
    }

    do {
        let pass = try PKPass(data: data)
        guard let addController = PKAddPassesViewController(pass: pass) else {
            return
        }
        addController.delegate = self
        present(addController, animated: true)
    } catch {
        // 签名凭证数据无效或签名无法通过验证
        showRecoverablePassError(error)
    }
}

SwiftUI Wallet 按钮

使用 AddPassToWalletButton 作为 UIKit 中 PKAddPassButton 的 SwiftUI 等价实现。

import PassKit
import SwiftUI

struct AddPassButton: View {
    let passData: Data
    @State private var addedToWallet = false

    var body: some View {
        if PKAddPassesViewController.canAddPasses(),
           let pass = try? PKPass(data: passData) {
            AddPassToWalletButton([pass]) { added in
                addedToWallet = added
            }
            .addPassToWalletButtonStyle(.blackOutline)
            .frame(width: 250, height: 50)
        }
    }
}

检查凭证库

使用 PKPassLibrary 检查并管理用户 Wallet 中现有的凭证。在对凭证库进行操作前先检查 PKPassLibrary.isPassLibraryAvailable(),但要判断设备是否能添加新凭证,请使用 PKAddPassesViewController.canAddPasses()passes() 只会返回你的 App 通过 entitlements 权限有权访问的凭证。替换已有凭证时,请检查 replacePass(with:) 返回的布尔值并做好失败处理。关于签名凭证包构建、更新 Web 服务以及 replacePass(with:) 的详细说明,请参考 references/wallet-passes.md

let library = PKPassLibrary()

// 检查特定凭证是否已在 Wallet 中
let hasPass = library.containsPass(pass)

// 获取当前 App 有权访问的凭证列表
let passes = library.passes()

// 检查凭证库当前是否可用
guard PKPassLibrary.isPassLibraryAvailable() else { return }

常见踩坑与误区

切勿:将 StoreKit 用于实体商品结算

Apple Pay (PassKit) 专门用于实体商品、线下服务、捐赠以及符合条件的订阅/定期支付。StoreKit 则用于虚拟道具/商品、应用内功能开通和数字内容订阅。搞混框架会导致 App Review 被拒。

切勿:在多处硬编码 Merchant ID

// 错误写法:Merchant ID 分散在代码库各处
let request1 = PKPaymentRequest()
request1.merchantIdentifier = "merchant.com.example.app"
// ...其他地方:
let request2 = PKPaymentRequest()
request2.merchantIdentifier = "merchant.com.example.app" // 容易出现配置不一致

// 正确写法:统一集中管理配置
enum PaymentConfig {
    static let merchantIdentifier = "merchant.com.example.app"
    static let countryCode = "US"
    static let currencyCode = "USD"
    static let supportedNetworks: [PKPaymentNetwork] = [.visa, .masterCard, .amex]
}

审核检查清单

  • [ ] 已在 Xcode 中启用 Apple Pay capability 并配置好 Developer 后台的 Merchant ID
  • [ ] 已生成并安装 Payment Processing Certificate(支付处理证书)
  • [ ] 在展示 Apple Pay 按钮前已调用 canMakePayments(usingNetworks:) 进行检查
  • [ ] 只要检测到用户卡片可用,Apple Pay 均处于突出且显眼的位置

<!-- truncated for translation batch; full body continues in source -->