passkit

passkit

熱門

使用 PassKit 整合 Apple Pay 付款與 Wallet 票卡(Passes)。適用於新增 Apple Pay 按鈕、建立付款請求、處理付款授權、將票卡加入 Wallet、設定商家功能、管理運送/聯絡資訊欄位,或使用 PKPaymentRequest、PKPaymentAuthorizationController、PKPaymentButton、AddPassToWalletButton、PKPass、PKAddPassesViewController、PKPassLibrary、Wallet 票卡發行,以及實體商品、真實世界服務、捐款與符合條件的定期付款等 Apple Pay 結帳流程。

967星標
49分支
更新於 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

支援實體商品、真實世界服務、捐款以及符合條件的定期付款之 Apple Pay 結帳,並可將票卡加入使用者的 Wallet。涵蓋付款按鈕、付款請求、授權、Wallet 票卡與商家設定。適用於 Swift 6.3 / iOS 26+。

針對進階的 Apple Pay 流程,單一 PKPaymentRequest 只能設定一種可選的進階請求類型:定期付款(recurring)、自動儲值(automatic reload)、延期付款(deferred)、Apple Pay Later 可用性,或多 Token 上下文(multi-token contexts)。當結帳流程需要上述多種模式時,請使用獨立的付款請求。

Contents

Setup

Project Configuration

  1. 在 Xcode 中啟用 Apple Pay capability
  2. 在 Apple Developer Portal 建立 Merchant ID(格式:merchant.com.example.app
  3. 為您的 Merchant ID 產生並安裝 Payment Processing Certificate
  4. 將 Merchant ID 新增至您的 entitlements

Availability Check

在顯示 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
    )
}

Displaying the Apple Pay Button

SwiftUI

在 SwiftUI 中使用內建的 PayWithApplePayButton View。任何標示為 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

Creating a Payment Request

建構包含您的商家詳細資訊以及購買項目的 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")
        ) // 總額
    ]

    return request
}

paymentSummaryItems 中的最後一個項目會被視為總額,其標籤會顯示在付款單的付款明細列中。

Requesting Shipping and Contact Info

僅請求計價、履約或法律上處理訂單所必需的聯絡資訊欄位。
當付款單無法精確收集時,請在點擊 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() 查詢可用的網路。

Presenting the Payment Sheet

使用 PKPaymentAuthorizationController(同時適用於 SwiftUI 與 UIKit,不需要 View Controller)。控制器的 delegate 為弱參照(weak),因此在付款單開啟期間需保持強參照以保留該控制器。

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
            }
        }
    }
}

Handling Payment Authorization

實作 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
        }
    }
}

Handling Shipping Changes

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

Adding a Pass to 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 Button

使用 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)
        }
    }
}

Checking Pass Library

使用 PKPassLibrary 檢查與管理使用者已擁有的票卡。在進行票卡庫操作前先檢查 PKPassLibrary.isPassLibraryAvailable(),但判定裝置是否能加入票卡時請使用 PKAddPassesViewController.canAddPasses()passes() 只會回傳您 App 透過其 entitlement 權限可存取的票卡。替換既有票卡時,請檢查 replacePass(with:) 返回的 Boolean 結果並處理失敗情況。關於已簽署票卡包建構、更新 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 }

Common Mistakes

DON'T: Use StoreKit for physical goods

Apple Pay (PassKit) 用於實體商品、真實世界服務、捐款以及符合條件的定期付款。StoreKit 則用於虛擬商品、App 功能與數位內容訂閱。使用錯誤的框架將導致 App 審查(App Review)被拒絕。

DON'T: Hardcode merchant ID in multiple places

// 錯誤: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]
}

Review Checklist

  • [ ] 已啟用 Apple Pay capability 並於 Developer Portal 完成 Merchant ID 設定
  • [ ] 已產生並安裝 Payment Processing Certificate
  • [ ] 在顯示 Apple Pay 按鈕前已先檢查 canMakePayments(usingNetworks:)
  • [ ] 在任何確認卡片可用性的地方,Apple Pay 皆處於顯眼位置

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