PAY.JP Android SDKは、PAY.JP を Android アプリに導入するための SDK です。
SDK の提供する「カード情報のトークン化」機能を利用することで、カード情報を事業者さまのサーバーで扱うことなく、安全に支払い処理をおこなうことができます。
ここでは、アプリに SDK を組み込み、クレジットカード情報をトークン化する方法について説明します。
なお、以下のサンプルコードは Kotlin によって記述されています。Java のサンプルコードは、GitHub のリポジトリを参照してください。
payjp/payjp-android: PAY.JP Android SDK
インストール
build.gradle の dependencies
に以下を追加します。バージョンは最新のものを指定してください。
implementation "jp.pay:payjp-android:$latest_version"
// カードのスキャン機能を利用する場合のみ追加
implementation "jp.pay:payjp-android-cardio:$latest_version"
SDKの初期化
まず、android.app.Application
クラスを継承したアプリケーションクラスの onCreate()
で、PAY.JP の公開鍵を設定して SDK を初期化します。
SDK のあらゆる操作を行う前に、アプリケーションクラスの onCreate()
内で Payjp.init()
を実行する必要があります。
デバッグオプション等を設定したい場合は、Payjp.init(String)
の代わりに Payjp.init(PayjpConfiguration)
を利用してください。
以下のサンプルコードでは、テストモードの公開鍵を設定しています。
class SampleApplication: Application() {
override fun onCreate() {
super.onCreate()
// または Payjp.init("pk_test_0383a1b8f91e8a6e3ea0e2a9")
Payjp.init(PayjpConfiguration.Builder("pk_test_0383a1b8f91e8a6e3ea0e2a9")
.setDebugEnabled(BuildConfig.DEBUG)
.setLocale(Locale.JAPAN)
.build())
}
}
アプリに組み込む方法を選択する
アプリに SDK を組み込み、トークンを取得する方法は以下の3つから選択できます。事業者さまの要件に合わせて最適な方法を選んでください。
1. 用意されたカードフォーム画面を利用する(推奨)
これから組み込みを始める場合は、この方法を推奨します。
SDK によってコントロールされたフォーム画面を表示します。カード情報のトークン化は、フォーム画面内で SDK がおこなうため、アプリは生成されたトークンをサーバーに送信するだけという最も簡単な方法です。 フォームのスタイルは、Theme を編集してカスタマイズ可能です。
2. アプリで用意した画面にカードフォームを組み込む
カードフォームの Fragment をアプリの画面に追加する方法です。この場合もカードフォームは SDK が提供するので、アプリはカード情報を扱う必要がありません。 追加の支払い情報などを合わせて表示したい等、1 の方法では実現できないような画面が必要な場合はこちらを推奨します。
3. SDKのUIを使わずに直接トークンを生成する
1、2 いずれの方法でも要件を満たせない場合、カード情報から直接トークンを生成することも可能です。
1. カードフォーム画面を利用する場合(推奨)
STEP 1: フォーム画面を開始する
カードフォーム画面のフローを開始するには、Payjp.cardForm().start()
を呼び出します。正常に画面が表示されない場合、SDKの初期化に問題のある可能性があります。
// ActivityやFragmentから呼び出す
fun onClickAddCard() {
Payjp.cardForm().start(activity = this@SampleActivity, face = PayjpCardForm.FACE_MULTI_LINE)
}
このとき呼び出す Payjp.cardForm().start()
に渡すことのできる引数は以下です。
引数名 | 説明 |
---|---|
activity または fragment |
フォームへの遷移を行う Activity または Fragment です。 |
requestCode |
取得したトークンを受け取るための requestCode です。デフォルトでは PayjpCardFormActivity.DEFAULT_CARD_FORM_REQUEST_CODE を使用します。 |
tenant |
PAY.JP Platform の Marketplace 型を利用している場合に TenantId クラスのオブジェクトを指定します。( トークン作成時のテナント ID について ) |
face |
カードフォームの表示タイプを切り替えます。デフォルトは FACE_MULTI_LINE が適用されます。 |
extraAttributes |
カードフォームに追加の属性項目を設定できます。デフォルトはメールアドレスと電話番号が表示されます。 |
カードフォームの face
の変更
カードフォーム画面は、face
によって 2 種類の表示タイプを指定できます。
フォームやボタンの色は、Theme によって変更できます(見た目を変更する)。
face | スクリーンショット |
---|---|
FACE_MULTI_LINE |
|
FACE_CARD_DISPLAY |
追加の属性項目を設定する
引数 extraAttributes
に ExtraAttribute
の配列を渡すことで、追加の属性項目を変更できます。
属性の種類はメールアドレス(ExtraAttribute.Email
)と電話番号(ExtraAttribute.Phone
)の2種類があり、入力した内容はカードオブジェクトにセットされ、 3Dセキュア認証実施時に送信されます。
STEP 2: フォームの結果を受け取る
カードフォーム画面を呼び出した Activity または Fragment の onActivityResult
にカードフォームの結果が渡されます。以下のように、Payjp.cardForm().handleResult
を使って結果を取得します。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
Payjp.cardForm().handleResult(data, PayjpCardFormResultCallback { result ->
if (result.isSuccess()) {
val token = result.retrieveToken()
Log.i(TAG, "token => $token")
}
})
super.onActivityResult(requestCode, resultCode, data)
}
STEP 3: 生成されたトークンをサーバに送信する
通常、カード情報から生成されたトークンは事業者さまのサーバーに送信し、顧客データや支払いデータを作成する等といった処理を行います。 トークン取得後の一連の処理が何らかの事由(たとえばネットワークエラーなど)によって中断した場合、カードフォーム画面が終了していると、再度カードフォーム画面を表示し顧客に入力を求めなければなりません。
そのため、トークン取得後のアプリ内の処理を実行するハンドラをあらかじめ登録し、カードフォーム内でエラーをハンドリングする仕組みを提供しています。
まず、PayjpTokenBackgroundHandler
を実装したクラスを作成します。
このクラスは、handleTokenInBackground
という関数を実装する必要があります。この関数の中で、トークンをサーバーに送信するなどのハンドリングを行います。このときバックグラウンドで実行される関数のため、UI の操作を直接実行しないように注意してください。
handleTokenInBackground
は結果に応じて CardFormStatus
を返して終了します。カードフォームは、この CardFormStatus
によってトークンハンドラー実行後の振る舞いを決定します。
CardFormStatus#Complete
の場合、カードフォーム画面は終了します。CardFormStatus#Error
の場合、渡されたメッセージを顧客に表示します。
class SampleSendTokenHandler() : PayjpTokenBackgroundHandler {
override fun handleTokenInBackground(token: Token): CardFormStatus {
try {
apiService.saveToken(token)
// トークンの保存に成功したらカードフォームを終了する
return CardFormStatus.Complete()
} catch (e: IOException) {
// 失敗したらメッセージを表示する(カードフォームは終了しない)
return CardFormStatus.Error("error message...")
}
}
}
ハンドラーを作成したら、SDK を初期化する際に作成したハンドラーをセットします。
class SampleApplication: Application() {
override fun onCreate() {
super.onCreate()
Payjp.init(PayjpConfiguration.Builder("pk_test_0383a1b8f91e8a6e3ea0e2a9")
.setDebugEnabled(BuildConfig.DEBUG)
.setLocale(Locale.JAPAN)
.setTokenBackgroundHandler(SampleSendTokenHandler()) // <-- この部分を追加
.build())
}
}
セットされていない場合、カードフォーム画面はトークンを作成後そのまま終了します。作成されたトークンは Payjp.cardForm().handleResult
で取得できます。
以上がカードフォーム画面を使ったアプリへの組み込みの基本となります。
2. カードフォームをアプリの画面に組み込む場合
PayjpCardFormAbstractFragment
は、カード情報の入力フォームを持つコンポーネントです。アプリの画面に追加し、任意のタイミングでトークンの作成をリクエストできます。カードフォーム画面のように利用ブランドの表示や登録ボタンなどは付属していませんが、その分より柔軟にアプリに組み込むことができるのがメリットとなります。
サンプルアプリの例(色や TextField のスタイルはカスタマイズ可能です)
STEP 1: フォームの組み込み
カードフォームを表示するには、カードフォームに必要なパラメーターをファクトリーメソッドに渡し、PayjpCardFormAbstractFragment
を継承した具象クラスのインスタンスを作成します。
override fun onCreate() {
// ...
cardFormFragment = Payjp.cardForm().newCardFormFragment()
supportFragmentManager.beginTransaction()
.replace(R.id.card_form_view, cardFormFragment, TAG_CARD_FORM)
.commit()
submitButton.setOnClickListener {
onClickSubmit()
}
}
カードフォーム画面を利用する場合と同様に、引数に face
を指定して表示タイプを切り替えることができます。指定しない場合は FACE_MULTI_LINE
が適用されます。
STEP 2: フォームの送信
PayjpCardFormAbstractFragment#createToken
は、Task<Token>
オブジェクトを返します。
Task<Token>
オブジェクトはトークン生成を同期/非同期で実行し、返り値またはコールバックで結果を取得します。トークンの生成に失敗した場合は、取得した例外クラスを利用してハンドリングしてください。詳細はエラーのハンドリングをご確認ください。
fun onClickSubmit() {
if (!cardFormFragment.validateCardForm()) return
cardFormFragment.createToken().enqueue(object : Task.Callback<Token> {
override fun onSuccess(data: Token) {
Log.i("CardFormViewSample", "token => $data")
}
override fun onError(throwable: Throwable) {
Log.e("CardFormViewSample", "failure creating token", throwable)
}
})
}
STEP 3: フォームのバリデーション
カードフォームは、入力内容が適切か入力のたびに検証します。
検証の結果のみを得るには、PayjpCardFormAbstractFragment#isValid
を利用します。
PayjpCardFormAbstractFragment#validateCardForm
は、暗黙の入力エラーを表示し、検証結果を返します。たとえば、送信ボタンを押したタイミングで入力の検証を行い、未入力の項目があればエラーメッセージを表示したい場合はこちらを利用します。
また、検証結果の変更を受け取りたい場合は、PayjpCardFormView.OnValidateInputListener#onValidateInput
をカードフォームのホストに実装します。
override fun onValidateInput(view: PayjpCardFormView, isValid: Boolean) {
// 入力が正しくなければ送信ボタンを無効にする
button_create_token.isEnabled = isValid
}
フォームをカスタマイズする
クレジットカード情報をカメラで読み取る
カメラでクレジットカードの券面をスキャンして自動で入力する機能を追加できます。
この機能を利用するには、Card IOのAndroid SDKと PAY.JP Android SDK の Card IO プラグインを追加します(いずれも最新のバージョンを利用してください)。
implementation "jp.pay:payjp-android:$latest_version"
implementation "jp.pay:payjp-android-cardio:$latest_version"
implementation "io.card:android-sdk:x.y.z"
SDK 初期化時にプラグインを設定します。
Payjp.init(PayjpConfiguration.Builder(API_KEY)
.setCardScannerPlugin(PayjpCardScannerPlugin)
.build())
これでカードフォームに番号読み取りのカメラアイコンが表示されます。
カード保有者名の入力を非表示にする
PayjpCardFormAbstractFragment
を利用する場合、カード保有者名は Payjp.cardForm().newCardFormFragment()
の引数または PayjpCardFormAbstractFragment#setCardHolderNameInputEnabled
で表示/非表示の切り替えができます(デフォルトは表示)。
カードフォーム画面を利用する場合は非表示にできません。
追加の属性項目を変更する
ExtraAttribute
で入力された情報は3Dセキュアの認証情報として使用されます。
3Dセキュアは2025年3月末までの運用開始が義務化されており、その一つとしてこの追加項目が求められます。
詳細は3Dセキュア認証 - 導入の義務化およびPAY.JPにおける3D セキュア - 3Dセキュア認証における追加項目をご覧ください。
Payjp.cardForm().start()
または Payjp.cardForm().newCardFormFragment()
に渡す引数 extraAttributes
によって、追加の属性項目を設定できます。
- メールアドレスと電話番号両方を表示する場合は、いずれかの入力が必須となります。
- メールアドレスか、電話番号どちらかのみ表示する場合は、表示した項目の入力が必須となります。
- 初期値を設定した場合はフォームに入力された状態で表示され、ユーザーは入力内容を編集して送信できます。
- いずれの項目も表示しない場合は、空の配列を渡すようにしてください。
たとえば、メールアドレスのみを初期値を設定して表示する場合は、以下のサンプルコードのように指定します。
// アプリを利用しているユーザーのメールアドレスを初期値として表示する
val extraAttributes = arrayOf(
ExtraAttribute.Email(preset = user.email),
// ExtraAttribute.Phone(), // 電話番号の入力は要求しない
)
Payjp.cardForm().start(activity = this, extraAttributes = extraAttributes)
完全なサンプルコードは GitHub リポジトリ でも公開していますのでご参照ください。
見た目を変更する
カードフォーム画面のスタイルは Payjp.Theme.CardForm
という Theme を編集することでカスタマイズ可能です。
アプリのリソースに以下のように定義します。
<resources>
<style name="Payjp.Theme.CardForm" parent="Payjp.Theme.BaseCardForm">
<!-- ActionBarやボタン、カーソルなどの色 -->
<item name="colorPrimary">@color/primaryColor</item>
<!-- StatusBarなどの色 -->
<item name="colorPrimaryDark">@color/primaryDarkColor</item>
<!-- ProgressBarなどの色 -->
<item name="colorSecondary">@color/secondaryColor</item>
<!-- TextInputのスタイル -->
<item name="textInputStyle">
@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox
</item>
<!-- 背景の色 -->
<item name="android:windowBackground">@color/backgroundColor</item>
</style>
</resources>
PayjpCardFormAbstractFragment
はアプリの Theme が適用されるため、アプリの Theme を変更することでカスタマイズ可能です。
3. カードフォームを利用せずトークン化する場合
以下のカード情報から直接トークンを生成もできます。
- クレジットカード番号(例:
4242424242424242
) - CVC(例:
123
) - 有効期限(月)(例:
02
) - 有効期限(年)(例:
2020
) - (任意)カード保有者名(例:
PAY TARO
)
Payjp.token().createToken()
を上記のカード情報を引数に呼び出します。
Task<Token>
オブジェクトは同期/非同期でトークンの生成処理を実行し、返り値またはコールバックで生成結果を取得できます。トークンの生成に失敗した場合は、取得した例外クラスを利用してハンドリングしてください。詳細は エラーのハンドリング をご確認ください。
エラーのハンドリング
カードフォーム画面を利用する以外の方法でアプリに組み込む場合、トークン生成に失敗した際のエラーハンドリングが必要です。取得した例外クラスに応じて適切にハンドリングしてください。
API からエラーレスポンスを受け取った場合は PayjpApiException
クラスの例外として HTTP ステータスコードや構造化されたレスポンス情報を扱うことができます。PayjpApiException#httpStatusCode
で HTTP ステータスコードを、PayjpApiException#apiError
で PAYJP のエラーコードなどが参照可能です。
ApiError
クラスの code
は APIリファレンス のエラーコードに相当します。同様に message
はエラーメッセージに相当しますが、このメッセージはエンドユーザー向けのメッセージではないことに注意してください。
また、PayjpApiException
にはサブクラスが存在します。
送信したカード情報に問題がある場合、PayjpCardException
クラスの例外を返します。たとえば入力したカード番号に誤りがある、カードの有効期限が切れている等、エラーコードに応じてユーザーにフィードバックしてください。
PayjpRateLimitException
は レートリミット 超過を表す例外クラスです。時間をあけた再試行を提案してください(例:混雑のため、一時的に利用が制限されています。少し時間をおいて再度お試しください。
)。
以下は例外クラスに応じてハンドリングするサンプルコードです。
fun handleErrorMessage(t: Throwable) {
when (t) {
is PayjpCardException -> showMessageByCode(t.apiError.code)
is PayjpRateLimitException -> showMessage(
"混雑のため、一時的に利用が制限されています。少し時間をおいて再度お試しください。"
)
is PayjpApiException -> {
sendlog("statusCode:" + t.httpStatusCode)
sendlog("payErrorCode:" + t.apiError.code)
when (t.httpStatusCode) {
in 500 until 600 -> showMessage("カード決済システムに問題が発生しています。")
else -> showMessage("カードの認証に問題が発生しました。")
}
}
is IOException -> showMessage("ネットワークへの接続を確認してください。")
else -> showMessage("問題が発生しました。")
}
}
生成済みのトークン情報を取得する
トークン ID を利用してすでに生成済みのトークンを取得するには、Payjp.token().getToken
を呼び出します。
Payjp.token().getToken(id = tokenId)
.enqueue(object : Task.Callback<Token> {
override fun onSuccess(data: Token) {
Log.i("PAY.JP", "token => $data")
}
override fun onError(throwable: Throwable) {
Log.e("PAY.JP", "failure getting token", throwable)
}
})
Kotlin Coroutineと一緒に利用する
トークンの生成などコールバック形式で提供しているインタフェースを Kotlin Coroutine の suspend function で利用できるプラグインを提供しています。
implementation "jp.pay:payjp-android:$latest_version"
implementation "jp.pay:payjp-android-coroutine:$latest_version"
このプラグインには Task
オブジェクトを返す SDK のインタフェースに対応する suspend function が定義されています。以下はトークンの生成の例です。
private fun createToken() = launch {
updateLoadingUI()
try {
val token = withContext(Dispatchers.IO) {
// createToken()の代わりにsuspend functionを使う
cardFormFragment.createTokenSuspend()
}
updateSuccessUI(token)
} catch (t: Throwable) {
updateErrorUI(t, "failure creating token")
}
}
トークン作成リクエストのバーストを抑制する
カード情報入力フォームでユーザーの連打などにより意図せず連続でトークンの作成をリクエストしてしまうと、レートリミット の制限に達する可能性があります。
SDK が提供するカードフォーム画面はあらかじめ対策がなされていますが、カードフォーム画面を利用しない方法でアプリに組み込む場合は、PayjpTokenOperationStatus
を利用してユーザーによる連打などで意図せずトークンの作成を連続リクエストしないよう抑制できます。
Payjp.token().getTokenOperationObserver()
でステータスの更新を監視します。
PayjpTokenOperationStatus
が ACCEPTABLE
のときにトークンを作成できるように UI を更新してください。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
Payjp.token().getTokenOperationObserver().addListener { status ->
toggleButton(enabled = status == PayjpTokenOperationStatus.ACCEPTABLE)
}
}
override fun onDestroy() {
// ...
Payjp.token().getTokenOperationObserver().removeAllListeners()
super.onDestroy()
}
PayjpTokenOperationObserverService.addListener
で追加したリスナーは removeListener
あるいは removeAllListeners
で削除してください。
ソースコード・サンプルコード
SDK は GitHub で公開されています。 SDK のソースコードのほか、Java/Kotlin のサンプルコードをご覧いただけます。