mcpField
P
passkit

passkit

github.com/dpearson2699开发1.2k3.1k 次安装13 周前更新
代码仓库

关于 passkit

PassKit 是用于实现 Apple Pay 支付和 Wallet 卡券的 Swift 开发指南,涵盖支付配置、授权、配送更新和卡券管理,适用于处理实物商品、服务、捐赠或符合条件的定期付款的应用。

主要功能

  1. 01Apple Pay 按钮与支付请求配置
  2. 02支付界面展示与授权
  3. 03配送方式和总额更新
  4. 04添加已签名的 Wallet 卡券
  5. 05检查和替换卡券库内容

适用场景

  1. 01为实物商品结账接入 Apple Pay
  2. 02处理支付授权令牌
  3. 03在 Apple Wallet 中提供已签名卡券

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill passkit

安装后在 Claude Code 中输入/passkit确认已加载。

附带文件evals/evals.jsonreferences/wallet-passes.md

详细介绍

PassKit

使用 Apple Pay 接受实物商品、现实世界服务、捐赠和符合条件的定期付款,并将凭证添加到用户的 Wallet。涵盖付款按钮、付款请求、授权、Wallet 凭证和商家配置。目标版本为 Swift 6.3 / iOS 26+。

对于高级 Apple Pay 流程,一个 PKPaymentRequest 只能设置一种可选的高级请求类型:定期付款、自动充值、延期付款、Apple Pay Later 可用性或多令牌上下文。结账流程需要使用多种模式时,请使用多个付款请求。

目录

设置

项目配置
  1. 在 Xcode 中启用 Apple Pay 功能
  2. 在 Apple Developer 门户中创建 Merchant ID(格式:merchant.com.example.app)
  3. 为 Merchant ID 生成并安装支付处理证书
  4. 将 Merchant ID 添加到你的授权文件中
可用性检查

显示 Apple Pay 界面前,务必确认设备可以付款。如果使用 canMakePayments(usingNetworks:capabilities:) 检查是否有有效卡片,Apple 的 HIG 要求你在所有进行此类检查的地方,都将 Apple Pay 作为主要且醒目的付款选项。

import PassKit

func canMakePayments() -> Bool {
    // Check device supports Apple Pay at all
    guard PKPaymentAuthorizationController.canMakePayments() else {
        return false
    }
    // Check user has cards for the networks you support
    return PKPaymentAuthorizationController.canMakePayments(
        usingNetworks: [.visa, .masterCard, .amex, .discover],
        capabilities: .threeDSecure
    )
}

显示 Apple Pay 按钮

SwiftUI

在 SwiftUI 中使用内置的 PayWithApplePayButton 视图。对于任何标有 Apple Pay 的控件,都应使用 Apple 提供的按钮 API;自定义按钮不得包含 Apple Pay 标志或“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)

按钮类型: .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 中的最后一项会被视为总额,其标签会显示在付款表单的 Pay 行中。

请求配送和联系信息

仅请求用于确定价格、履行订单或依法处理订单所需的联系信息。 如果付款表单无法准确收集,请在 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
支持的网络
网络常量
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,无需视图控制器)。控制器的代理是弱引用,因此请在付款表单显示期间保留控制器。

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 以处理付款令牌。

extension CheckoutCoordinator: PKPaymentAuthorizationControllerDelegate {
    func paymentAuthorizationController(
        _ controller: PKPaymentAuthorizationController,
        didAuthorizePayment payment: PKPayment,
        handler completion: @escaping (PKPaymentAuthorizationResult) -> Void
    ) {
        // Send payment.token.paymentData to your payment processor
        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 数据,验证设备是否可以添加通行证,然后在希望用户添加前检查通行证时展示 PKAddPassesViewController。PKPass(data:) 需要已签名的通行证数据,并且可能抛出数据无效或签名无效错误。在审查指南中,应明确指出 invalid-data 和 invalid-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 {
        // Signed pass data is invalid or the signature cannot be validated.
        showRecoverablePassError(error)
    }
}
SwiftUI Wallet 按钮

使用 AddPassToWalletButton,作为 SwiftUI 中 PKAddPassButton 的对应组件。

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 检查和管理用户已有的通行证。在执行通行证库操作前,检查 PKPassLibrary.isPassLibraryAvailable();但要使用 PKAddPassesViewController.canAddPasses() 判断设备是否可以添加通行证。passes() 仅返回应用可通过其 entitlements 访问的通行证。替换现有通行证时,检查 replacePass(with:) 返回的布尔值并处理失败情况。有关已签名通行证包的构建、Web 服务更新以及 replacePass(with:),请参阅 references/wallet-passes.md。

let library = PKPassLibrary()

// Check if a specific pass is already in Wallet
let hasPass = library.containsPass(pass)

// Retrieve passes your app can access
let passes = library.passes()

// Check if pass library is available
guard PKPassLibrary.isPassLibraryAvailable() else { return }

常见错误

不要:将 StoreKit 用于实物商品

Apple Pay(PassKit)适用于实物商品、现实世界的服务、捐赠以及符合条件的定期付款。StoreKit 适用于虚拟商品、应用功能和数字内容订阅。使用错误的框架会导致 App Review 拒绝审核。

不要:在多个位置硬编码商户 ID
// WRONG: Merchant ID scattered across the codebase
let request1 = PKPaymentRequest()
request1.merchantIdentifier = "merchant.com.example.app"
// ...elsewhere:
let request2 = PKPaymentRequest()
request2.merchantIdentifier = "merchant.com.example.app" // easy to get out of sync

// CORRECT: Centralize configuration
enum PaymentConfig {
    static let merchantIdentifier = "merchant.com.example.app"
    static let countryCode = "US"
    static let currencyCode = "USD"
    static let supportedNetworks: [PKPaymentNetwork] = [.visa, .masterCard, .amex]
}

审查清单

  • 已启用 Apple Pay 功能,并在 Developer 门户中配置商户 ID
  • 已生成并安装 Payment Processing Certificate
  • 在显示 Apple Pay 按钮前检查了 canMakePayments(usingNetworks:)
  • 在检查可用活跃卡片的位置显著展示 Apple Pay
  • 在付款界面显示前收集商品选项、可选详情和复杂配送选项
  • paymentSummaryItems 中的最后一项是包含商户显示名称的总金额
  • 付款摘要和令牌上下文中的金额使用 NSDecimalNumber
  • 将付款令牌发送到服务器处理(绝不在客户端解码)
  • 展示期间保留 PKPaymentAuthorizationController,结束后清除
  • paymentAuthorizationControllerDidFinish 会关闭控制器
  • 配送方式更改时,通过代理回调重新计算总额
  • 虚拟商品/数字内容使用 StoreKit;实物商品、服务、捐赠以及符合条件的定期付款使用 Apple Pay
  • 从已签名的 .pkpass 包加载 Wallet 通行证
  • 显示 PKPass(data:) 的数据无效和签名无效错误
  • 通行证操作使用 PKPassLibrary.isPassLibraryAvailable(),而非添加通行证能力检查
  • 显示添加通行证界面前检查 PKAddPassesViewController.canAddPasses()
  • 替换通行证时检查 PKPassLibrary.replacePass(with:) 返回的布尔值
  • Apple Pay 按钮使用系统提供的 PKPaymentButton 或 PayWithApplePayButton
  • 添加到 Wallet 的界面使用系统提供的 PKAddPassButton、AddPassToWalletButton 或 PKAddPassesViewController
  • 处理授权结果中的错误状态(网络故障、银行卡被拒)

参考资料

评论

支持 Markdown