mcpField
P
passkit-wallet

passkit-wallet

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

关于 passkit-wallet

面向 Swift 开发者的 PassKit 技能指南,涵盖实体商品与服务的 Apple Pay 支付,以及 Wallet 通行证管理。内容包括配置、授权流程和常见错误检查。

主要功能

  1. 01配置 Apple Pay 商户信息
  2. 02构建并展示支付请求
  3. 03处理支付授权与配送变更
  4. 04添加及检查 Wallet 通行证
  5. 05检查支付实现常见错误

适用场景

  1. 01为实体商品结账集成 Apple Pay
  2. 02根据配送方式更新订单金额
  3. 03将签名的通行证添加到 Wallet
  4. 04检查现有通行证及支付实现

安装

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

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

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

附带文件references/wallet-passes.md

详细介绍

PassKit — Apple Pay 与 Wallet

接受 Apple Pay 支付实体商品和服务,并将凭证添加到用户的 Wallet。涵盖支付按钮、支付请求、授权、Wallet 凭证和商家配置。面向 Swift 6.2 / iOS 26+。

目录

设置

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

显示 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 视图。

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)

按钮类型: .buy、.setUp、.inStore、.donate、.checkout、.book、.subscribe、.reload、.addMoney、.topUp、.order、.rent、.support、.contribute、.tip

创建支付请求

使用商家详情和购买项目构建 PKPaymentRequest。

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: 9.99),
        PKPaymentSummaryItem(label: "Shipping", amount: 4.99),
        PKPaymentSummaryItem(label: "My Store", amount: 14.98) // Total (last item)
    ]

    return request
}

paymentSummaryItems 中的最后一项会被视为总额,其标签会在支付表单中显示为商家名称。

请求配送和联系信息
request.requiredShippingContactFields = [.postalAddress, .emailAddress, .name]
request.requiredBillingContactFields = [.postalAddress]

request.shippingMethods = [
    PKShippingMethod(label: "Standard", amount: 4.99),
    PKShippingMethod(label: "Express", amount: 9.99),
]
request.shippingMethods?[0].identifier = "standard"
request.shippingMethods?[0].detail = "5-7 business days"
request.shippingMethods?[1].identifier = "express"
request.shippingMethods?[1].detail = "1-2 business days"

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,无需视图控制器)。

@MainActor
func startPayment() {
    let request = createPaymentRequest()
    let controller = PKPaymentAuthorizationController(paymentRequest: request)
    controller.delegate = self
    controller.present()
}

处理支付授权

实现 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()
    }
}
处理配送变更
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。

func addPassToWallet(data: Data) {
    guard let pass = try? PKPass(data: data) else { return }

    if let addController = PKAddPassesViewController(pass: pass) {
        addController.delegate = self
        present(addController, animated: true)
    }
}
SwiftUI Wallet 按钮
import PassKit
import SwiftUI

struct AddPassButton: View {
    let passData: Data

    var body: some View {
        Button("Add to Wallet") {
            addPass()
        }
    }

    func addPass() {
        guard let pass = try? PKPass(data: passData) else { return }
        let library = PKPassLibrary()
        library.addPasses([pass]) { status in
            switch status {
            case .shouldReviewPasses:
                // Present review UI
                break
            case .didAddPasses:
                // Passes added successfully
                break
            case .didCancelAddPasses:
                break
            @unknown default:
                break
            }
        }
    }
}

检查凭证库

使用 PKPassLibrary 检查和管理用户已有的凭证。

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 内购买。使用错误的框架会导致 App 审核被拒。

// WRONG: Using StoreKit to sell a physical product
let product = try await Product.products(for: ["com.example.tshirt"])

// CORRECT: Use Apple Pay for physical goods
let request = PKPaymentRequest()
request.paymentSummaryItems = [
    PKPaymentSummaryItem(label: "T-Shirt", amount: 29.99),
    PKPaymentSummaryItem(label: "My Store", amount: 29.99)
]
不要:在多个位置硬编码 Merchant 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]
}
不要:忘记添加总额项目

paymentSummaryItems 中的最后一项是总额行。如果省略,支付表单将不显示商家名称或总额。

// WRONG: No total item
request.paymentSummaryItems = [
    PKPaymentSummaryItem(label: "Widget", amount: 9.99)
]

// CORRECT: Last item is the total with your merchant name
request.paymentSummaryItems = [
    PKPaymentSummaryItem(label: "Widget", amount: 9.99),
    PKPaymentSummaryItem(label: "My Store", amount: 9.99) // Total
]
不要:跳过 canMakePayments 检查
// WRONG: Show Apple Pay button without checking
PayWithApplePayButton(.buy) { startPayment() }

// CORRECT: Only show when available
if PKPaymentAuthorizationController.canMakePayments(
    usingNetworks: PaymentConfig.supportedNetworks
) {
    PayWithApplePayButton(.buy) { startPayment() }
} else {
    // Show alternative checkout or setup button
    Button("Set Up Apple Pay") { /* guide user */ }
}
不要:在完成授权前关闭控制器
// WRONG: Dismissing inside didAuthorizePayment
func paymentAuthorizationController(
    _ controller: PKPaymentAuthorizationController,
    didAuthorizePayment payment: PKPayment,
    handler completion: @escaping (PKPaymentAuthorizationResult) -> Void
) {
    controller.dismiss() // Too early -- causes blank sheet
    completion(.init(status: .success, errors: nil))
}

// CORRECT: Dismiss only in paymentAuthorizationControllerDidFinish
func paymentAuthorizationControllerDidFinish(
    _ controller: PKPaymentAuthorizationController
) {
    controller.dismiss()
}

检查清单

  • 已在 Developer 门户中启用 Apple Pay 功能并配置商户 ID
  • 已生成并安装支付处理证书
  • 显示 Apple Pay 按钮前已检查 canMakePayments(usingNetworks:)
  • paymentSummaryItems 中的最后一项是总金额,并带有商户显示名称
  • 已将支付令牌发送到服务器进行处理(绝不在客户端解码)
  • paymentAuthorizationControllerDidFinish 会关闭控制器
  • 配送方式变更时,通过代理回调重新计算总金额
  • 数字商品使用 StoreKit;实体商品使用 Apple Pay
  • 从已签名的 .pkpass 软件包加载 Wallet 凭证
  • 执行凭证操作前已检查 PKPassLibrary.isPassLibraryAvailable()
  • Apple Pay 按钮使用系统提供的 PKPaymentButton 或 PayWithApplePayButton
  • 已处理授权结果中的错误状态(网络故障、银行卡被拒)

参考资料

评论

支持 Markdown