passkit-wallet

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

PassKit — Apple Pay & Wallet

PassKit — Apple Pay 与 Wallet

Accept Apple Pay payments for physical goods and services, and add passes to the user's Wallet. Covers payment buttons, payment requests, authorization, Wallet passes, and merchant configuration. Targets Swift 6.2 / iOS 26+.
支持为实体商品和服务接入Apple Pay支付,以及向用户的Wallet中添加凭证。内容涵盖支付按钮、支付请求、授权流程、Wallet凭证及商户配置。适配Swift 6.2 / iOS 26+版本。

Contents

目录

Setup

配置步骤

Project Configuration

项目配置

  1. Enable the Apple Pay capability in Xcode
  2. Create a Merchant ID in the Apple Developer portal (format:
    merchant.com.example.app
    )
  3. Generate and install a Payment Processing Certificate for your merchant ID
  4. Add the merchant ID to your entitlements
  1. 在Xcode中启用Apple Pay功能
  2. 在Apple开发者后台创建商户ID(格式:
    merchant.com.example.app
  3. 为商户ID生成并安装支付处理证书
  4. 将商户ID添加到项目授权文件中

Availability Check

可用性检查

Always verify the device can make payments before showing Apple Pay UI.
swift
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界面之前,务必先验证设备是否支持支付功能。
swift
import PassKit

func canMakePayments() -> Bool {
    // 检查设备是否支持Apple Pay
    guard PKPaymentAuthorizationController.canMakePayments() else {
        return false
    }
    // 检查用户是否有支持对应支付网络的卡片
    return PKPaymentAuthorizationController.canMakePayments(
        usingNetworks: [.visa, .masterCard, .amex, .discover],
        capabilities: .threeDSecure
    )
}

Displaying the Apple Pay Button

显示Apple Pay按钮

SwiftUI

SwiftUI

Use the built-in
PayWithApplePayButton
view in SwiftUI.
swift
import SwiftUI
import PassKit

struct CheckoutView: View {
    var body: some View {
        PayWithApplePayButton(.buy) {
            startPayment()
        }
        .payWithApplePayButtonStyle(.black)
        .frame(height: 48)
        .padding()
    }
}
使用SwiftUI内置的
PayWithApplePayButton
视图。
swift
import SwiftUI
import PassKit

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

UIKit

UIKit

Use
PKPaymentButton
for UIKit-based interfaces.
swift
let button = PKPaymentButton(
    paymentButtonType: .buy,
    paymentButtonStyle: .black
)
button.cornerRadius = 12
button.addTarget(self, action: #selector(startPayment), for: .touchUpInside)
Button types:
.buy
,
.setUp
,
.inStore
,
.donate
,
.checkout
,
.book
,
.subscribe
,
.reload
,
.addMoney
,
.topUp
,
.order
,
.rent
,
.support
,
.contribute
,
.tip
对于基于UIKit的界面,使用
PKPaymentButton
swift
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

Creating a Payment Request

创建支付请求

Build a
PKPaymentRequest
with your merchant details and the items being purchased.
swift
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
}
The last item in
paymentSummaryItems
is treated as the total and its label appears as the merchant name on the payment sheet.
构建包含商户信息和购买商品的
PKPaymentRequest
swift
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: "小部件", amount: 9.99),
        PKPaymentSummaryItem(label: "配送费", amount: 4.99),
        PKPaymentSummaryItem(label: "我的商店", amount: 14.98) // 总计(最后一项)
    ]

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

Requesting Shipping and Contact Info

请求配送与联系信息

swift
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
swift
request.requiredShippingContactFields = [.postalAddress, .emailAddress, .name]
request.requiredBillingContactFields = [.postalAddress]

request.shippingMethods = [
    PKShippingMethod(label: "标准配送", amount: 4.99),
    PKShippingMethod(label: "加急配送", amount: 9.99),
]
request.shippingMethods?[0].identifier = "standard"
request.shippingMethods?[0].detail = "5-7个工作日"
request.shippingMethods?[1].identifier = "express"
request.shippingMethods?[1].detail = "1-2个工作日"

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

Supported Networks

支持的支付网络

NetworkConstant
Visa
.visa
Mastercard
.masterCard
American Express
.amex
Discover
.discover
China UnionPay
.chinaUnionPay
JCB
.JCB
Maestro
.maestro
Electron
.electron
Interac
.interac
Query available networks at runtime with
PKPaymentRequest.availableNetworks()
.
支付网络常量
Visa
.visa
Mastercard
.masterCard
American Express
.amex
Discover
.discover
中国银联
.chinaUnionPay
JCB
.JCB
Maestro
.maestro
Electron
.electron
Interac
.interac
可通过
PKPaymentRequest.availableNetworks()
在运行时查询可用的支付网络。

Presenting the Payment Sheet

展示支付表单

Use
PKPaymentAuthorizationController
(works in both SwiftUI and UIKit, no view controller needed).
swift
@MainActor
func startPayment() {
    let request = createPaymentRequest()
    let controller = PKPaymentAuthorizationController(paymentRequest: request)
    controller.delegate = self
    controller.present()
}
使用
PKPaymentAuthorizationController
(适用于SwiftUI和UIKit,无需视图控制器)。
swift
@MainActor
func startPayment() {
    let request = createPaymentRequest()
    let controller = PKPaymentAuthorizationController(paymentRequest: request)
    controller.delegate = self
    controller.present()
}

Handling Payment Authorization

处理支付授权

Implement
PKPaymentAuthorizationControllerDelegate
to process the payment token.
swift
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()
    }
}
实现
PKPaymentAuthorizationControllerDelegate
来处理支付令牌。
swift
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()
    }
}

Handling Shipping Changes

处理配送方式变更

swift
func paymentAuthorizationController(
    _ controller: PKPaymentAuthorizationController,
    didSelectShippingMethod shippingMethod: PKShippingMethod,
    handler completion: @escaping (PKPaymentRequestShippingMethodUpdate) -> Void
) {
    let updatedItems = recalculateItems(with: shippingMethod)
    let update = PKPaymentRequestShippingMethodUpdate(paymentSummaryItems: updatedItems)
    completion(update)
}
swift
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 Passes

Wallet凭证

Adding a Pass to Wallet

向Wallet添加凭证

Load a
.pkpass
file and present
PKAddPassesViewController
.
swift
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)
    }
}
加载
.pkpass
文件并展示
PKAddPassesViewController
swift
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 Button

SwiftUI Wallet按钮

swift
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
            }
        }
    }
}
swift
import PassKit
import SwiftUI

struct AddPassButton: View {
    let passData: Data

    var body: some View {
        Button("添加至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:
                // 展示审核界面
                break
            case .didAddPasses:
                // 凭证添加成功
                break
            case .didCancelAddPasses:
                break
            @unknown default:
                break
            }
        }
    }
}

Checking Pass Library

检查凭证库

Use
PKPassLibrary
to inspect and manage passes the user already has.
swift
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 }
使用
PKPassLibrary
检查和管理用户已有的凭证。
swift
let library = PKPassLibrary()

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

// 获取你的应用可访问的凭证
let passes = library.passes()

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

Common Mistakes

常见错误

DON'T: Use StoreKit for physical goods

错误做法:使用StoreKit售卖实体商品

Apple Pay (PassKit) is for physical goods and services. StoreKit is for digital content, subscriptions, and in-app purchases. Using the wrong framework leads to App Review rejection.
swift
// 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)
]
Apple Pay(PassKit)适用于实体商品和服务。StoreKit则用于数字内容、订阅和内购。使用错误的框架会导致应用审核被拒。
swift
// 错误:使用StoreKit售卖实体产品
let product = try await Product.products(for: ["com.example.tshirt"])

// 正确:使用Apple Pay售卖实体商品
let request = PKPaymentRequest()
request.paymentSummaryItems = [
    PKPaymentSummaryItem(label: "T恤", amount: 29.99),
    PKPaymentSummaryItem(label: "我的商店", amount: 29.99)
]

DON'T: Hardcode merchant ID in multiple places

错误做法:在多处硬编码商户ID

swift
// 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]
}
swift
// 错误:商户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]
}

DON'T: Forget the total line item

错误做法:遗漏总计行项目

The last item in
paymentSummaryItems
is the total row. If you omit it, the payment sheet shows no merchant name or total.
swift
// 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
]
paymentSummaryItems
中的最后一项是总计行。如果遗漏,支付表单将不会显示商户名称和总计金额。
swift
// 错误:没有总计项
request.paymentSummaryItems = [
    PKPaymentSummaryItem(label: "小部件", amount: 9.99)
]

// 正确:最后一项是包含商户显示名称的总计
request.paymentSummaryItems = [
    PKPaymentSummaryItem(label: "小部件", amount: 9.99),
    PKPaymentSummaryItem(label: "我的商店", amount: 9.99) // 总计
]

DON'T: Skip the canMakePayments check

错误做法:跳过canMakePayments检查

swift
// 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 */ }
}
swift
// 错误:未检查就显示Apple Pay按钮
PayWithApplePayButton(.buy) { startPayment() }

// 正确:仅在支持时显示
if PKPaymentAuthorizationController.canMakePayments(
    usingNetworks: PaymentConfig.supportedNetworks
) {
    PayWithApplePayButton(.buy) { startPayment() }
} else {
    // 显示替代结账方式或配置按钮
    Button("设置Apple Pay") { /* 引导用户 */ }
}

DON'T: Dismiss the controller before completing authorization

错误做法:完成授权前关闭控制器

swift
// 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()
}
swift
// 错误:在didAuthorizePayment中关闭控制器
func paymentAuthorizationController(
    _ controller: PKPaymentAuthorizationController,
    didAuthorizePayment payment: PKPayment,
    handler completion: @escaping (PKPaymentAuthorizationResult) -> Void
) {
    controller.dismiss() // 时机过早 -- 会导致表单空白
    completion(.init(status: .success, errors: nil))
}

// 正确:仅在paymentAuthorizationControllerDidFinish中关闭控制器
func paymentAuthorizationControllerDidFinish(
    _ controller: PKPaymentAuthorizationController
) {
    controller.dismiss()
}

Review Checklist

审核检查清单

  • Apple Pay capability enabled and merchant ID configured in Developer portal
  • Payment Processing Certificate generated and installed
  • canMakePayments(usingNetworks:)
    checked before showing Apple Pay button
  • Last item in
    paymentSummaryItems
    is the total with merchant display name
  • Payment token sent to server for processing (never decoded client-side)
  • paymentAuthorizationControllerDidFinish
    dismisses the controller
  • Shipping method changes recalculate totals via delegate callback
  • StoreKit used for digital goods; Apple Pay used for physical goods
  • Wallet passes loaded from signed
    .pkpass
    bundles
  • PKPassLibrary.isPassLibraryAvailable()
    checked before pass operations
  • Apple Pay button uses system-provided
    PKPaymentButton
    or
    PayWithApplePayButton
  • Error states handled in authorization result (network failures, declined cards)
  • 已启用Apple Pay功能,并在开发者后台配置了商户ID
  • 已生成并安装支付处理证书
  • 在显示Apple Pay按钮前检查了
    canMakePayments(usingNetworks:)
  • paymentSummaryItems
    的最后一项是带有商户显示名称的总计
  • 支付令牌已发送至服务器处理(切勿在客户端解码)
  • paymentAuthorizationControllerDidFinish
    负责关闭控制器
  • 配送方式变更时通过代理回调重新计算总计
  • 数字商品使用StoreKit;实体商品使用Apple Pay
  • Wallet凭证从已签名的
    .pkpass
    包加载
  • 在操作凭证前检查了
    PKPassLibrary.isPassLibraryAvailable()
  • Apple Pay按钮使用系统提供的
    PKPaymentButton
    PayWithApplePayButton
  • 授权结果中处理了错误状态(网络故障、卡片被拒等)

References

参考资料