iOS での利用

PAY.JP iOS SDKは、iOS アプリに PAY.JP を導入するための SDK です。

この SDK が提供する「カード情報のトークン化」機能を利用することで、事業者さまのサーバーでカード情報を扱うことなく、安全に支払い処理をおこなうことができます。

ここでは、アプリに SDK を組み込み、クレジットカード情報をトークン化する方法について説明します。

以下のサンプルコードは Swift で記述されています。Objective-C のサンプルコードについては、GitHub のリポジトリをご覧ください。

payjp/payjp-ios: PAY.JP iOS SDK

なお、SDK を使った Apple Pay の導入方法については、次のドキュメントをご参照ください。

Apple Pay

インストール

以下の方法をサポートしています。

Swift Package Manager を使う場合

https://github.com/payjp/payjp-ios.git を追加してください。

Carthage を使う場合

github "payjp/payjp-ios"

CocoaPods を使う場合

pod 'PAYJP'

SDKの初期化

まず、UIApplicationDelegate を継承したクラスで、PAY.JP の公開鍵を設定し、SDK を初期化します。

以下のサンプルコードではテストモードの公開鍵を設定しています。

import PAYJP

class AppDelegate: UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions:
            [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {

        PAYJPSDK.publicKey = "pk_test_0383a1b8f91e8a6e3ea0e2a9"
        PAYJPSDK.locale = Locale.current

        return true
    }
}

アプリに組み込む方法の選択

SDK をアプリに組み込み、トークンを取得する方法は複数用意されており、事業者さまの要件に合わせて選択いただけます。

1. 用意されたカードフォーム画面を利用する(推奨)

これから SDK の組み込みを始める場合は、この方法を推奨します。

SDK が提供するフォーム画面を表示し、カード情報のトークン化をフォーム画面内で SDK が行います。アプリは生成されたトークンをサーバーに送信するだけなので、最も簡単な方法です。 フォームのスタイルは FormStyle クラスを使ってカスタマイズできます。

カードフォーム画面を利用する

2. アプリで用意した画面にカードフォームを組み込む

CardFormTableStyledViewCardFormLabelStyledViewCardFormDisplayStyledView のいずれかをアプリの画面に追加する方法です。この場合もカードフォームは SDK が提供するので、アプリがカード情報を扱う必要はありません。 追加の支払い情報などを合わせて表示したいなど、1 の方法では実現できないような画面が必要な場合におすすめです。

画面にカードフォームを組み込む

3. SDKのUIを使わずに直接トークンを生成する

1、2 いずれの方法でも要件を満たせない場合は、カード情報から直接トークンを生成することも可能です。

直接トークンを生成する

1. カードフォーム画面を利用する場合(推奨)

STEP 1: フォーム画面を開始する

カードフォーム画面のフローを開始するには、CardFormViewController#createCardFormViewController を呼び出します。正常に画面が表示されない場合は、SDKの初期化に問題がある可能性があります。

// ViewControllerから呼び出す
let style = FormStyle(
    labelTextColor: .black,
    inputTextColor: .black,
    tintColor: .blue)
let cardFormViewController = CardFormViewController.createCardFormViewController(
    style: style, delegate: self, viewType: .labelStyled
)
self.navigationController?.pushViewController(
    cardFormViewController, animated: true
)

このとき呼び出す CardFormViewController#createCardFormViewController に渡すことのできる引数は以下です。

引数名 説明
style フォームのカラーや見た目を変更する場合は FormStyle を指定して上書きできます。
tenantId PAY.JP Platform の Marketplace 型を利用している場合にテナント ID を指定できます。( トークン作成時のテナント ID について
delegate カードフォームの結果を受け取るための CardFormViewControllerDelegate を指定します。
viewType カードフォームの表示タイプを切り替えます。デフォルトは .labelStyled が適用されます。
extraAttributes カードフォームに追加の属性項目を設定できます。デフォルトはメールアドレスと電話番号が表示されます。
カードフォームの見た目を変更する

カードフォーム画面は 3 種類の表示タイプを CardFormViewType で指定できます。 フォームやボタンの色は FormStyle で変更できます。(見た目を変更する

CardFormViewType スクリーンショット
.tableStyled
.labelStyled
.displayStyled
追加の属性項目を設定する

引数 extraAttributesExtraAttribute の配列を渡すことで、追加の属性項目を変更できます。 属性の種類はメールアドレス(ExtraAttributeEmail)と電話番号(ExtraAttributePhone)の2種類があり、入力した内容はカードオブジェクトにセットされ、 3Dセキュア認証実施時に送信されます。

追加の属性項目の設定について詳しく知る

STEP 2: フォームの結果を受け取る

カードフォーム画面を呼び出した ViewController を CardFormViewControllerDelegate に適合させ、cardFormViewController(_:didProduced:completionHandler:) を実装します。

func cardFormViewController(_: CardFormViewController,
                            didProduced token: Token,
                            completionHandler: @escaping (Error?) -> Void) {
    print("token = \(token)")
}

STEP 3: 生成されたトークンをサーバに送信する

通常、カード情報から生成されたトークンは事業者さまのサーバーに送信し、顧客データや支払いデータを作成する等の処理を行います。 トークン取得後の一連の処理が何らかの事由(たとえばネットワークエラーなど)で中断した場合、カードフォーム画面が終了していると、再度カードフォーム画面を表示し顧客に入力を求めなければなりません。

そのため、トークン取得時に呼び出されるデリゲートを通じて、フォームを表示した状態でアプリ側からエラーメッセージを渡せる仕組みを提供しています。

STEP 2 で実装した cardFormViewController(_:didProduced:completionHandler:) の中で、トークンをサーバーに送信するなどの処理を行います。このメソッドはバックグラウンドで実行されるため、UI の操作を直接実行しないよう注意してください。

処理の最後に引数の completionHandler: を呼び出し、カードフォームに結果を反映します。

トークンの処理に問題があった場合は、completionHandler: にカードフォームにエラーとして表示するメッセージを渡します。問題なく完了した場合は引数を nil として呼び出すことで、カードフォームを終了します。

func cardFormViewController(_: CardFormViewController,
                            didProduced token: Token,
                            completionHandler: @escaping (Error?) -> Void) {
    // サーバにトークンを送信
    apiService.saveToken(withToken: token) { (error) in
        if let error = error {
            completionHandler(error)
        } else {
            completionHandler(nil)
        }
    }
}

カードフォームはその結果に応じて CardFormResult を返して終了します。 結果を受け取るには、CardFormViewControllerDelegate#cardFormViewController(_:didCompleteWith:) を実装します。

func cardFormViewController(_: CardFormViewController,
                            didCompleteWith result: CardFormResult) {
    switch result {
    case .cancel:
        // カードフォームをキャンセルした場合の処理
    case .success:
        // トークンの保存に成功したらカードフォームを終了する
        DispatchQueue.main.async { [weak self] in
            self?.navigationController?.popViewController(animated: true)
        }
    }
}

以上が、カードフォーム画面を使ったアプリへの組み込みの基本的な流れとなります。

2. カードフォームをアプリの画面に組み込む場合

SDK では、事業者さまのアプリにそのまま組み込むことができるカードフォームを提供しています。事業者さまはカードフォームをアプリに組み込むことで、カード情報を取り扱うことなく、SDK によって安全にトークンを取得できます。また、入力補完や入力ミスの表示など、カード情報の入力に適切な顧客体験を提供します。

サンプルアプリの例(色や TextField のスタイルはカスタマイズ可能です)

STEP 1: フォームの組み込み

カードフォームを表示するには、CardFormTableStyledViewCardFormLabelStyledViewCardFormDisplayStyledView のいずれかを利用します。CocoaPods を使用する場合は Storyboard に View を配置して利用できますが、Carthage を使用する場合は Storyboard での利用ができません。そのため、コード上で View を addSubView する形で実装します。

CocoaPods をご利用の方は example-objc、Carthage をご利用の方は example-swift のサンプルコードを参照ください。

STEP 2: フォームの送信

CardFormAction#createTokenResult<Token, Error> を返し、生成したトークンまたはエラーを取得できます。 エラー項目についてはErrorを参照ください。

func onClickSubmit() {
    cardFormView.createToken() { result in
        switch result {
        case .success(let token):
            print("[token] => \(token)")
        case .failure(let error):
            print("[failure creating token] \(error)")
        }
    }
}

STEP 3: フォームのバリデーション

カードフォームは入力内容が適切か入力のたびに検証します。 検証の結果のみを得るには CardFormAction#isValid を利用します。

CardFormAction#validateCardForm は暗黙の入力エラーを表示し、検証結果を返します。たとえば、送信ボタンを押したタイミングで入力の検証を行い、未入力の項目があればエラーメッセージを表示したい場合はこちらを利用します。

また、検証結果の通知を受け取りたい場合は、ViewController を CardFormViewDelegate に適合させ、formInputValidated(in:isValid:) を実装します。

func formInputValidated(in cardFormView: UIView, isValid: Bool) {
    createTokenButton.isEnabled = isValid
}

フォームをカスタマイズする

クレジットカード情報をカメラで読み取る

カメラでクレジットカードの券面をスキャンし、自動で入力する機能を追加できます。

この機能を利用するには、Card IOのiOS SDKをインストールします(最新のバージョンを利用してください)。

pod 'CardIO'

iOS アプリでカメラを利用する場合、なぜ必要なのかをユーザーに明示する必要があります。Info.plistNSCameraUsageDescriptionというキーで説明を追加します。

これでカードフォームに番号読み取りのカメラアイコンが表示されます。

カード保有者名の入力を非表示にする

カード保有者名の入力を非表示にする機能は、SDK バージョン 2.0.0 より削除されました。

カード保有者名は @IBInspectable で定義している isHolderRequired を Storyboard から設定、またはコード上から値をセットすることで表示/非表示の切り替えができます(デフォルトは表示)。 コード上からセットする場合は、CardFormStylable#setCardHolderRequired を利用してください。

追加の属性項目を変更する

ExtraAttribute で入力された情報は3Dセキュアの認証情報として使用されます。
3Dセキュアは2025年3月末までの運用開始が義務化されており、その一つとしてこの追加項目が求められます。
詳細は3Dセキュア認証 - 導入の義務化およびPAY.JPにおける3D セキュア - 3Dセキュア認証における追加項目をご覧ください。

カードフォームに表示される追加の属性項目は ExtraAttribute の配列を CardFormViewController または組み込み用の View クラスに渡すことで変更できます。

CardFormViewController の場合は CardFormViewController#createCardFormViewController の引数の extraAttributes を設定します。
組み込み用の View クラスの場合は CardFormAction#apply(extraAttributes:) をユーザーが画面を操作する前に呼び出すようにしてください。

  • メールアドレスと電話番号両方を表示する場合は、いずれかの入力が必須となります。
  • メールアドレスか、電話番号どちらかのみ表示する場合は、表示した項目の入力が必須となります。
  • 初期値を設定した場合はフォームに入力された状態で表示され、ユーザーは入力内容を編集して送信できます。
  • いずれの項目も表示しない場合は、空の配列を渡すようにしてください。

たとえば、メールアドレスのみを初期値を設定して表示する場合は、以下のサンプルコードのように指定します。

// アプリを利用しているユーザーのメールアドレスを初期値として表示する
let extraAttributes = [
  ExtraAttributeEmail(preset: user.email)
  // ExtraAttribute.Phone() // 電話番号の入力は要求しない
]
let cardForm = CardFormViewController.createCardFormViewController(delegate: self, extraAttributes: attributes)
present(cardForm, animated: true)

完全なサンプルコードは GitHub リポジトリ でも公開していますのでご参照ください。

見た目を変更する

カードフォームの種類は、UITableView に適した CardFormTableStyledView、ラベル付きの CardFormLabelStyledView、カード表示ありの CardFormDisplayStyledView の 3 種類があります。 共通のインターフェースを持っているため、基本的な機能は同じです。 フォームをカスタマイズするには CardFormStylable#apply を利用してください。FormStyle クラスで UIColor を設定することで、各コンポーネントの色を変更できます。

let style = FormStyle(
    labelTextColor: .black,
    inputTextColor: .black,
    tintColor: .blue,
    inputFieldBackgroundColor: .white)
cardFormView.apply(style: style)
コンポーネント FormStyle
Label 文字の色 labelTextColor
TextField 入力文字の色 inputTextColor
TextField エラーメッセージの色 errorTextColor
TextField カーソルの色 tintColor
TextField 背景の色 inputFieldBackgroundColor
Button 背景の色 submitButtonColor
Highlight ハイライトの色 highlightColor

3. カードフォームを利用せずトークン化する場合

トークンを生成するには以下のカード情報が必要です。

  • クレジットカード番号(例: 4242424242424242
  • CVC(例: 123
  • 有効期限(月)(例: 02
  • 有効期限(年)(例: 2020
  • (任意)カード保有者名(例: PAY TARO

APIClient#createToken は上記のカード情報を引数に呼び出すことで、Result<Token, APIError> を返し、生成したトークンまたはエラーを取得できます。 エラー項目についてはErrorを参照ください。

payjpClient.createToken(
    with: "4242424242424242",
    cvc: "123",
    expirationMonth: "02",
    expirationYear: "2020",
    name: "PAY TARO") { result in
    switch result {
    case .success(let token):
        print("token_id is \(token.identifier)")
    case .failure(let error):
        print(error.localizedDescription)
    }
}

生成済みのトークン情報を取得する

トークン ID を利用して、すでに生成済みのトークンの情報を取得するには、APIClient#getToken を呼び出します。

payjpClient.getToken(with: tokenId) { result in
    switch result {
    case .success(let token):
        print("token_id is \(token.identifier)")
    case .failure(let error):
        print(error.localizedDescription)
    }
}

トークン情報を利用する

生成したトークンは、支払いをはじめとする各種処理に利用できます。 トークンオブジェクト(PAYJP.Token)には、以下のような情報が含まれます。

private extension Token {
    func print() {
        print("id=\(identifier)")
        print("card.id=\(card.identifier)")
        print("card.last4=\(card.last4Number)")
        print("card.exp=\(card.expirationMonth)/\(card.expirationYear)")
        print("card.name=\(card.name ?? "nil")")
    }
    // id=tok_5ca06b51685e001723a2c3b4aeb4
    // card.id=car_e3ccd4e0959f45e7c75bacc4be90
    // card.last4=4242
    // card.exp=2/2020
    // card.name=PAY TARO
}

トークンオブジェクト(PAYJP.Token)の詳細については、APIリファレンスを参照ください。

Error

トークンのリクエストが失敗した場合、Result に格納される Error として APIErrorLocalError があります。 これらのエラーは errorCode に応じて以下のように分類されます。

Error errorCode 説明
.invalidApplePayToken 0 Apple Pay のトークン不正エラー。userInfo にキー PAYErrorInvalidApplePayTokenObject で対象のトークンが格納されています。
.systemError 1 URLSession に由来するエラー。userInfo にキー PAYErrorSystemErrorObject で元のエラーが格納されています。
.invalidResponse 2 レスポンスの不正エラー。userInfo にキー PAYErrorInvalidResponseObject で HTTP レスポンスが格納されています。
.serviceError 3 サーバーエラー。userInfo にキー PAYErrorServiceErrorObject でエラーレスポンスが格納されています。エラーレスポンスの typecode を確認し、API リファレンスのErrorを参照ください。
.invalidJSON 4 JSON データの不正エラー。userInfo にキー PAYErrorInvalidJSONObject で JSON データ、PAYErrorInvalidJSONErrorObject でエラーが格納されています。
.invalidFormInput 5 入力値不正エラー。カードフォームの入力値に問題がある場合にこのエラーが返されます。
.requiredThreeDSecure 6 3D セキュア認証の要求エラー。userInfo にキー PAYErrorRequiredThreeDSecureIdObject で 3D セキュアトークンが格納されています。
.rateLimitExceeded 7 レートリミット超過エラー。「混雑のため、一時的に利用が制限されています。時間を空けて再度お試しください。」など、時間をあけた再試行を提案してください。

トークン作成リクエストのバーストを抑制する

カード情報入力フォームがユーザーの連打などで意図せず連続でトークンの作成をリクエストできてしまうと、レートリミットの制限に達する可能性があります。

SDK が提供するカードフォーム画面はあらかじめ対策がなされていますが、カードフォーム画面を利用する以外の方法でアプリに組み込む場合は、PAYTokenOperationStatus を利用して、ユーザーによる連打などで意図せず Token の作成を連続リクエストしないよう抑制できます。

ステータスの更新は NotificationCenter 経由で通知されます。 PAYTokenOperationStatus.acceptable のときにトークンを作成できるよう、UI を更新してください。

override func viewDidLoad() {
    // ...
    NotificationCenter.default.addObserver(self,
        selector: #selector(handleTokenOperationStatusChange(notification:)),
        name: .payjpTokenOperationStatusChanged,
        object: nil)
}

@objc private func handleTokenOperationStatusChange(
    notification: Notification
) {
    if let value = notification.userInfo?[
        PAYNotificationKey.newTokenOperationStatus
    ] as? Int,
       let newStatus = TokenOperationStatus.init(rawValue: value) {
        self.toggleButton(enabled: newStatus == .acceptable)
    }
}

ソースコード・サンプルコード

SDK は GitHub で公開されています。 SDK のソースコードのほか、Swift/Objective-C のサンプルコードを参照できます。

payjp/payjp-ios: PAY.JP iOS SDK