メインコンテンツまでスキップ
Markdownをコピー

Apple Payトークン取得 実装サンプル

Apple Payの決済用トークンを取得する際の実装サンプルを示します。対象は以下の2パターンです。

  • iOSアプリ実装(PassKitを利用)
  • Webブラウザ実装(Apple Pay on the Web / JavaScriptを利用)

いずれの場合も、Apple公式の基本実装(ボタン表示、セッション制御等)に加えて、
当サービスのAPIとの連携が必要になる箇所(加盟店検証・決済実行)を中心に記載します。

備考

ユースケースにより、使用するApple Payトークンの種類(DPAN/MPAN)が異なります。
詳細はApple Payトークンを参照してください。

iOSアプリ実装サンプル

設定値

項目設定値
supportedNetworks加盟店のApple Pay契約に基づき、利用可能ブランドを設定
merchantCapabilitiesPKMerchantCapability.capability3DS(契約内容によっては.capabilityCredit / .capabilityDebitの追加が必要な場合あり)
currencyCode"JPY"(日本円のみ利用可能)

※ 上記の設定値の考え方は実装手順の内容と共通です。

a. DPAN(通常決済)

Apple Payの決済ボタン表示、利用可否チェック、決済リクエスト作成、および決済結果受信までの一連の実装例です。

import UIKit
import PassKit

// 加盟店設定
//
// 実際にご利用の際は、下記の値・コメント箇所をご自身の契約内容に合わせて変更してください。

enum ApplePayConfig {
// Apple Developer Portal で取得したMerchant IDに置き換えてください。
static let merchantIdentifier = "example.merchant.com"

static let countryCode = "JP"
static let currencyCode = "JPY"

// 当サービスの加盟店契約で対応しているブランドのみを列挙してください。
// ここに列挙したブランドと、canMakePayments(usingNetworks:) に渡す配列は
// 必ず同じものを使用します(異なる配列を使うと判定がずれます)。
static let supportedNetworks: [PKPaymentNetwork] = [.visa, .masterCard, .JCB, .amex]

// 3Dセキュアが基本ですが、JCBを含む契約内容によっては
// .capabilityCredit / .capabilityDebit の追加が必要な場合があります。
// 当サービスの加盟店契約内容に応じて調整してください。
static let merchantCapabilities: PKMerchantCapability = .capability3DS
}

class ViewController: UIViewController, PKPaymentAuthorizationViewControllerDelegate {

@IBOutlet weak var applePayButton: UIView!
private var pkPaymentButton: PKPaymentButton?

override func viewDidLoad() {
super.viewDidLoad()
setupApplePayButton()
updateApplePayButtonVisibility()
}

// ボタンのセットアップ
private func setupApplePayButton() {
let button = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
button.addTarget(self, action: #selector(pay), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false

applePayButton.addSubview(button)
NSLayoutConstraint.activate([
button.leadingAnchor.constraint(equalTo: applePayButton.leadingAnchor),
button.trailingAnchor.constraint(equalTo: applePayButton.trailingAnchor),
button.topAnchor.constraint(equalTo: applePayButton.topAnchor),
button.bottomAnchor.constraint(equalTo: applePayButton.bottomAnchor),
button.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)
])

self.pkPaymentButton = button
}

// Apple Pay 利用可否チェック

/// request.supportedNetworks と同じ配列で判定するのがポイント。
/// availableNetworks() (端末・地域で使えるブランド全般) を使うと、
/// 加盟店の対応ブランドとズレて、ボタンは出るのにカードが選べないケースが生じ得る。
private func updateApplePayButtonVisibility() {
let canPayWithSupportedNetworks = PKPaymentAuthorizationViewController.canMakePayments(
usingNetworks: ApplePayConfig.supportedNetworks
)

if canPayWithSupportedNetworks {
// 対応ブランドのカードが登録済み → ボタン表示
pkPaymentButton?.isHidden = false
} else if PKPaymentAuthorizationViewController.canMakePayments() {
// 端末はApple Pay対応だが、対応ブランドのカードが未登録
// 必要に応じて「カードを追加」ボタン(openPaymentSetup)を出す運用も可能
pkPaymentButton?.isHidden = false
} else {
// Apple Pay非対応端末 → ボタン非表示、他の決済手段へ誘導
pkPaymentButton?.isHidden = true
}
}

// 決済開始
@objc private func pay() {
let request = PKPaymentRequest()
request.merchantIdentifier = ApplePayConfig.merchantIdentifier
request.currencyCode = ApplePayConfig.currencyCode
request.countryCode = ApplePayConfig.countryCode
request.supportedNetworks = ApplePayConfig.supportedNetworks
request.merchantCapabilities = ApplePayConfig.merchantCapabilities

let item = PKPaymentSummaryItem(label: "MULPAY TEST ITEM", amount: NSDecimalNumber(string: "100"))
request.paymentSummaryItems = [item]

guard let paymentVC = PKPaymentAuthorizationViewController(paymentRequest: request) else {
// ここに到達する場合は request の設定内容(merchantIdentifier等)に不備がある可能性があります
return
}
paymentVC.delegate = self
present(paymentVC, animated: true, completion: nil)
}

// PKPaymentAuthorizationViewControllerDelegate
func paymentAuthorizationViewController(
_ controller: PKPaymentAuthorizationViewController,
didAuthorizePayment payment: PKPayment,
handler completion: @escaping (PKPaymentAuthorizationResult) -> Void
) {
// 送信用データをBase64エンコードします。
let paymentDataBase64 = payment.token.paymentData.base64EncodedString()
let paymentNetwork = payment.token.paymentMethod.network // .visa / .JCB 等、DPAN/MAPNいずれの場合も取得可能

// TODO: ここで当サービスのAPIへ上記データを送信し、結果に応じて
// completion に success/failure を渡してください。
// このメソッド内で無条件に成功を返してはいけません。
//
// 例:
// MulpayClient.authorize(paymentDataBase64: paymentDataBase64, network: paymentNetwork) { result in
// switch result {
// case .success:
// completion(PKPaymentAuthorizationResult(status: .success, errors: nil))
// case .failure(let error):
// completion(PKPaymentAuthorizationResult(status: .failure, errors: [error]))
// }
// }

completion(PKPaymentAuthorizationResult(status: .success, errors: nil))
// ↑ 仮実装。実装時は上記コメントを参考に差し替えてください。
}

func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
// Apple Payの支払シートを閉じる
controller.dismiss(animated: true, completion: nil)
}
}

b. MPAN(継続支払い)

継続支払い(PKRecurringPaymentRequest)を利用する場合の実装例です。
ボタン設置・delegate実装・completion処理はDPANサンプルと共通で、決済リクエストの作成部分が異なります。

import UIKit
import PassKit

// MPAN(Merchant Token)サンプルについて
//
// 実際にご利用の際は、下記の値・コメント箇所をご自身の契約内容に合わせて変更してください。

enum ApplePayConfig {
// Apple Developer Portal で取得したMerchant IDに置き換えてください。
static let merchantIdentifier = "example.merchant.com"

static let countryCode = "JP"
static let currencyCode = "JPY"

// 当サービスの加盟店契約で対応しているブランドのみを列挙してください。
// ここに列挙したブランドと、canMakePayments(usingNetworks:) に渡す配列は
// 必ず同じものを使用します(異なる配列を使うと判定がずれます)。
static let supportedNetworks: [PKPaymentNetwork] = [.visa, .masterCard, .JCB, .amex]

// 3Dセキュアが基本ですが、JCBを含む契約内容によっては
// .capabilityCredit / .capabilityDebit の追加が必要な場合があります。
// 当サービスの加盟店契約内容に応じて調整してください。
static let merchantCapabilities: PKMerchantCapability = .capability3DS

// ユーザーがApple Walletの「定期支払い」から遷移する、支払い管理画面のURL。
static let managementURL = URL(string: "https://example.com/mypage/subscription")!
}

class ViewController: UIViewController, PKPaymentAuthorizationViewControllerDelegate {

@IBOutlet weak var applePayButton: UIView!
private var pkPaymentButton: PKPaymentButton?

override func viewDidLoad() {
super.viewDidLoad()
setupApplePayButton()
updateApplePayButtonVisibility()
}

// ボタンのセットアップ
private func setupApplePayButton() {
let button = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
button.addTarget(self, action: #selector(pay), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false

applePayButton.addSubview(button)
NSLayoutConstraint.activate([
button.leadingAnchor.constraint(equalTo: applePayButton.leadingAnchor),
button.trailingAnchor.constraint(equalTo: applePayButton.trailingAnchor),
button.topAnchor.constraint(equalTo: applePayButton.topAnchor),
button.bottomAnchor.constraint(equalTo: applePayButton.bottomAnchor),
button.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)
])

self.pkPaymentButton = button
}

// Apple Pay 利用可否チェック

/// request.supportedNetworks と同じ配列で判定するのがポイント。
/// availableNetworks() (端末・地域で使えるブランド全般) を使うと、
/// 加盟店の対応ブランドとズレて、ボタンは出るのにカードが選べないケースが生じ得る。
private func updateApplePayButtonVisibility() {
let canPayWithSupportedNetworks = PKPaymentAuthorizationViewController.canMakePayments(
usingNetworks: ApplePayConfig.supportedNetworks
)

if canPayWithSupportedNetworks {
// 対応ブランドのカードが登録済み → ボタン表示
pkPaymentButton?.isHidden = false
} else if PKPaymentAuthorizationViewController.canMakePayments() {
// 端末はApple Pay対応だが、対応ブランドのカードが未登録
// 必要に応じて「カードを追加」ボタン(openPaymentSetup)を出す運用も可能
pkPaymentButton?.isHidden = false
} else {
// Apple Pay非対応端末 → ボタン非表示、他の決済手段へ誘導
pkPaymentButton?.isHidden = true
}
}

// 継続支払いリクエストの作成
@objc private func pay() {
guard #available(iOS 16.0, *) else {
// iOS 16未満はrecurringPaymentRequestが使用できないため、
// 必要であればDPANベースの通常決済にフォールバックしてください。
return
}

let request = PKPaymentRequest()
request.merchantIdentifier = ApplePayConfig.merchantIdentifier
request.currencyCode = ApplePayConfig.currencyCode
request.countryCode = ApplePayConfig.countryCode
request.supportedNetworks = ApplePayConfig.supportedNetworks
request.merchantCapabilities = ApplePayConfig.merchantCapabilities

// 定期課金の内訳(月額プランの例)
let regularBilling = PKRecurringPaymentSummaryItem(
label: "{{プラン名}}",
amount: NSDecimalNumber(string: "{{月額金額}}")
)
regularBilling.startDate = Date()
regularBilling.intervalUnit = .month
regularBilling.intervalCount = 1
// 終了日を設けない(無期限)場合は endDate を設定しない

let recurringRequest = PKRecurringPaymentRequest(
paymentDescription: "{{サービス名}} 月額プラン",
regularBilling: regularBilling,
managementURL: ApplePayConfig.managementURL
)
// ユーザーに表示される課金同意文言。契約条件に応じて設定してください。
recurringRequest.billingAgreement = "ご登録後、毎月{{課金日}}に{{月額金額}}円が請求されます。解約はいつでもマイページから可能です。"

request.recurringPaymentRequest = recurringRequest

// 決済シートに表示する合計金額の内訳(最終行が合計)
request.paymentSummaryItems = [regularBilling]

guard let paymentVC = PKPaymentAuthorizationViewController(paymentRequest: request) else {
// ここに到達する場合は request の設定内容(merchantIdentifier等)に不備がある可能性があります
return
}
paymentVC.delegate = self
present(paymentVC, animated: true, completion: nil)
}

// PKPaymentAuthorizationViewControllerDelegate
func paymentAuthorizationViewController(
_ controller: PKPaymentAuthorizationViewController,
didAuthorizePayment payment: PKPayment,
handler completion: @escaping (PKPaymentAuthorizationResult) -> Void
) {
// 送信用データをBase64エンコードします。
let paymentDataBase64 = payment.token.paymentData.base64EncodedString()
let paymentNetwork = payment.token.paymentMethod.network // .visa / .JCB 等、DPAN/MAPNいずれの場合も取得可能

// TODO: ここで当サービスのAPIへ上記データ(+定期課金であることを示す情報)を送信し、
// 結果に応じてcompletionにsuccess/failureを渡してください。
// このメソッド内で無条件に成功を返してはいけません。
// 送信されたトークンがDPAN/MPANいずれであるかはサーバー側の判定結果によります。
//
// 例:
// MulpayClient.authorizeRecurring(paymentDataBase64: paymentDataBase64, network: paymentNetwork) { result in
// switch result {
// case .success:
// completion(PKPaymentAuthorizationResult(status: .success, errors: nil))
// case .failure(let error):
// completion(PKPaymentAuthorizationResult(status: .failure, errors: [error]))
// }
// }

completion(PKPaymentAuthorizationResult(status: .success, errors: nil))
// ↑ 仮実装。実装時は上記コメントを参考に差し替えてください。
}

func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
// Apple Payの支払シートを閉じる
controller.dismiss(animated: true, completion: nil)
}
}

c. その他MPAN(参考)

PKRecurringPaymentRequest 以外のMPANリクエスト(PKAutomaticReloadPaymentRequest / PKDeferredPaymentRequest)を利用する場合は、上記bサンプルの決済リクエスト作成部分のみを以下のように置き換えます。1つのPKPaymentRequestに設定できるのはこの3種類のうち1つのみです。

// ============================================================
// 他のMPANリクエストタイプとの差分
// ============================================================
//
// MPANを要求する3種類(recurringPaymentRequest / automaticReloadPaymentRequest /
// deferredPaymentRequest)は、いずれも pay() メソッド内の
// 「定期課金の内訳」〜「request.recurringPaymentRequest = recurringRequest」
// の部分を置き換えるだけで対応できます。ボタン設置、delegate実装、
// completion処理などその他の構造は共通です。

// --------------------------------------------------------------
// [差分1] automaticReloadPaymentRequest を使う場合
// --------------------------------------------------------------
//
let reloadBilling = PKAutomaticReloadPaymentSummaryItem(
    label: "{{サービス名}} 自動チャージ",
amount: NSDecimalNumber(string: "{{チャージ金額}}")
)
// 残高がこの金額を下回ったら自動チャージを行う、という閾値
reloadBilling.thresholdAmount = NSDecimalNumber(string: "{{残高閾値}}")

let reloadRequest = PKAutomaticReloadPaymentRequest(
    paymentDescription: "{{サービス名}} 自動チャージ",
    automaticReloadBilling: reloadBilling,
    managementURL: ApplePayConfig.managementURL
)
// ユーザーに表示される課金同意文言。契約条件に応じて設定してください。
reloadRequest.billingAgreement = "残高が{{残高閾値}}円を下回ると、自動的に{{チャージ金額}}円がチャージされます。"

request.automaticReloadPaymentRequest = reloadRequest
// 決済シートに表示する合計金額の内訳(最終行が合計)
request.paymentSummaryItems = [reloadBilling]

// --------------------------------------------------------------
// [差分2] deferredPaymentRequest を使う場合
// --------------------------------------------------------------
//
let deferredBilling = PKDeferredPaymentSummaryItem(
label: "{{商品/予約名}}",
amount: NSDecimalNumber(string: "{{予定金額}}")
)
// 実際に課金される予定日(確定額はこの日までに変わる可能性がある旨を
// billingAgreementでユーザーに明示するのが一般的です)
deferredBilling.deferredDate = Calendar.current.date(byAdding: .day, value: 7, to: Date())! // 例: 7日後

let deferredRequest = PKDeferredPaymentRequest(
    paymentDescription: "{{商品/予約名}} の後日確定課金",
deferredBilling: deferredBilling,
managementURL: ApplePayMPANConfig.managementURL
)
// ユーザーに表示される課金同意文言。契約条件に応じて設定してください。
deferredRequest.billingAgreement = "{{予定日}}に確定金額を請求します。金額は予約状況により変動する場合があります。"

request.deferredPaymentRequest = deferredRequest
// 決済シートに表示する合計金額の内訳(最終行が合計)
request.paymentSummaryItems = [deferredBilling]

Webブラウザ実装サンプル

Apple Pay on the Web(JavaScript)の基本的な実装方法(ApplePaySessionの使い方、ボタン表示、各イベントハンドラの記述など)は、Apple公式のインタラクティブデモを参照してください。

Apple Pay on the Web デモ: https://applepaydemo.apple.com/

デモページの「Show Source」から、実際に動作するソースコードを確認・コピーいただけます。
実装の詳細や最新の仕様変更は、常にAppleの公式情報を優先して確認してください。

以下では、Apple Pay on the Webを利用する際に当サービスのAPIとの連携が必要になる箇所のみを示します。

設定値

項目設定値
supportedNetworks加盟店のApple Pay契約に基づき、利用可能ブランドを設定
merchantCapabilitiessupports3DS
currencyCode"JPY"(日本円のみ利用可能)

※ 上記の設定値の考え方は実装手順の内容と共通です。

加盟店検証(onvalidatemerchant)

event.validationURL を貴社サーバーに送信し、Appleへの加盟店検証で取得した加盟店セッション情報を completeMerchantValidation() に渡してください。

加盟店検証は、Apple加盟店証明書を用いて貴社サーバーから直接Appleへ送信する処理であり、当サービスのAPIは関与しません。
実装方法はApple公式のインタラクティブデモを参照してください。

決済認可(onpaymentauthorized)

event.payment.token を当サービスの決済実行APIに送信し、結果に応じて session.completePayment() にステータスを渡してください。 無条件に成功を返す実装は行わないでください。

session.onpaymentauthorized = async (event) => {
const response = await fetch("{当サービスの決済実行APIエンドポイント}", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token: event.payment.token }),
});
const result = await response.ok;
session.completePayment({
status: result ? ApplePaySession.STATUS_SUCCESS : ApplePaySession.STATUS_FAILURE,
});
};

関連ドキュメント

🤖 LLMですか?llms.txtllms-apis.txtに各ページの概要とリンクをまとめています。回答生成に活用してください。