iOSでの利用

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

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

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

なお、以下のサンプルコードはSwiftによって記述されています。Objective-CのサンプルコードはGitHubのリポジトリを参照ください。

payjp/payjp-ios: PAY.JP iOS SDK

SDKをつかったApple Payの導入方法については次のチュートリアルをご覧ください。

Apple Pay

インストール

Carthage または、CocoaPods を使ってインストールします。

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が行うため、アプリは生成されたトークンをサーバに送信するだけという最も簡単な方法です。 フォームのスタイルは FormStyle クラスを使用してカスタマイズ可能です。

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

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

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

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

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

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

直接トークンを生成する

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

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

カードフォーム画面のフローを開始するには、 CardFormViewController#createCardFormViewController(style:delegate:viewType:) を呼び出します。正常に画面が表示されない場合、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)

カードフォーム画面は3種類の表示タイプを CardFormViewType によって指定することができます。何も指定しない場合は .labelStyled が適用されます。フォームやボタンの色はFormStyleによって変更することができます。(見た目を変更する

CardFormViewType スクリーンショット
.tableStyled
.labelStyled
.displayStyled

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: フォームの組み込み

カードフォームを表示するには CardFormTableStyledView, CardFormLabelStyledView, CardFormDisplayStyledView のいずれかを利用します。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 というキーで説明を追加します。

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

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

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

見た目を変更する

カードフォームの種類は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 ApplePayのトークン不正エラー。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 レートリミット超過エラー。混雑のため、一時的に利用が制限されています。時間を空けて再度お試し下さい。 等時間をあけた再試行を提案してください。

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

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

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

ステータスの更新は NotificationCenter 経由で通知されます。 PAYTokenOperationStatus.acceptable のときにTokenを作成できるように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