Androidアプリでの事前承認支払い (Future Payments)

ここでは、PayPalアカウントを使用してFuture Paymentsを行うためにユーザーの同意を得る方法を説明します。
まだ実行していない場合は、プロジェクトにSDKを追加するための基本的な概要と手順のREADMEを参照してください。

概要

ご使用のアプリは、モバイルSDKを使って異なるタイミングで2つの情報を取得します。
最初に、PayPalアカウントからの支払いを受け取るため、お客さまの同意を得る必要があります。これは以下のように実行されます。
PayPal Android SDKは

  • ユーザーがPayPalアカウントの使用を承認するためのUIを表示します。
  • Future PaymentsでPayPalを使用するためのOAuthアクセストークンスコープに対する同意をユーザーに求めます。
  • アプリに、OAuth2の認可コードを返します。

アプリは

  • SDKからOAuth2の認可コードを受け取ります。
  • サーバーに認可コードを送ります。サーバーはコードをOAuth2のアクセストークンおよびリフレッシュトークンと交換します。

後に、事前承認された支払いの開始時に、Client Metadata IDを取得する必要があります。これは以下のように実行されます。

PayPal Android SDKは

  • Client Metadata IDを提供します。


アプリは

  • 相関IDと取引情報をサーバーに送ります。
  • サーバーは、OAuth2のトークン、Client Metadata ID、および取引情報を使用して支払いを作成します。

お客さまの同意を得る

サンプルアプリには、より完全な例が用意されています。ただし、最低限以下を行う必要があります。

1.AndroidManifest.xmlファイルに許可を追加します。

<!-- for most things, including card.io & paypal -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE">
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE">
<uses-permission android:name="android.permission.INTERNET">
                            </uses-permission></uses-permission></uses-permission>

2.AndroidManifest.xmlファイルのタグでSDKのサービスとアクティビティを宣言します。

<service android:name="com.paypal.android.sdk.payments.PayPalService" android:exported="false">
 
<activity android:name="com.paypal.android.sdk.payments.LoginActivity">
<activity android:name="com.paypal.android.sdk.payments.PayPalFuturePaymentActivity">
<activity android:name="com.paypal.android.sdk.payments.FuturePaymentConsentActivity">
<activity android:name="com.paypal.android.sdk.payments.FuturePaymentInfoActivity">
                            </activity></activity></activity></activity></service>

3.PayPalConfigurationオブジェクトを作成します。このオブジェクトにより、SDKのさまざまな側面を設定できます。

private static PayPalConfiguration config = new PayPalConfiguration()
 
// モック環境で開始します。準備完了後、sandbox (ENVIRONMENT_SANDBOX)
// または本番 (ENVIRONMENT_PRODUCTION)環境に切り替えます。
.environment(PayPalConfiguration.ENVIRONMENT_NO_NETWORK)
 
.clientId("<YOUR_CLIENT_ID>")
 
// 最低限3つのマーチャント情報プロパティを設定する必要があります。
//  これらは、アプリを登録した際にPayPalに提供した値と同じである必要があります。
.merchantName("Example Store")
.merchantPrivacyPolicyUri(Uri.parse("https://www.example.com/privacy"))
.merchantUserAgreementUri(Uri.parse("https://www.example.com/legal"));
                            

4.アクティビティの作成時にPayPalServiceを開始し、破棄の際に停止します。

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
 
Intent intent = new Intent(this, PayPalService.class);
 
intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, config);
 
startService(intent);
}
 
@Override
public void onDestroy() {
stopService(new Intent(this, PayPalService.class));
super.onDestroy();
}
                            

5.PayPalのFuture Paymentsアクティビティを、たとえばボタンが押された場合などに起動します。

public void onFuturePaymentPressed(View pressed) {
Intent intent = new Intent(SampleActivity.this, PayPalFuturePaymentActivity.class);
 
startActivityForResult(intent, REQUEST_CODE_FUTURE_PAYMENT);
}
                            

6.onActivityResult()を実装します。

@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
PayPalAuthorization auth = data
.getParcelableExtra(PayPalFuturePaymentActivity.EXTRA_RESULT_AUTHORIZATION);
if (auth != null) {
try {
String authorization_code = auth.getAuthorizationCode();
 
sendAuthorizationToServer(auth);
 
} catch (JSONException e) {
Log.e("FuturePaymentExample", "非常にまれなエラーが発生しました: ", e);
}
}
} else if (resultCode == Activity.RESULT_CANCELED) {
Log.i("FuturePaymentExample", "ユーザーがキャンセルしました。");
} else if (resultCode == PayPalFuturePaymentActivity.RESULT_EXTRAS_INVALID) {
Log.i("FuturePaymentExample",
"PayPalServiceを前に開始するための処理で、無効なPayPalConfigurationが含まれていた可能性があります。ドキュメントを参照してください。");
}
}
                            

7.プロセスを完了するため、サーバーに認可応答を送信します。

private void sendAuthorizationToServer(PayPalAuthorization authorization) {
 
// TODO:
// 認可応答をサーバーに送信します。サーバーは認可コードを
// OAuthのアクセストークンおよびリフレッシュトークンと交換できます。
//
// サーバーはこれらのトークンを保管するので、サーバーコードは、今後このユーザーの
// 支払いを実行できます。
 
}
                            

Client Metadata IDの取得

モバイル端末から事前承認された支払い(「Future Payments」)を開始する場合、モバイルアプリは、Client Metadata IDをモバイルSDKから取得し、サーバーに渡す必要があります。サーバーは、PayPalに送信する支払いリクエストにこのClient Metadata IDを含める必要があります。
PayPalは、このClient Metadata IDを使用して、ユーザーが同意した有効な端末およびアプリから支払いが行われていることを認証します。これは、不正取引や拒否を減らすことに役立ちます。PayPalは、適切にClient Metadata IDを提供しない取引での損失についてはいっさい保護しません。
この値をキャッシュまたは格納しないでください。

例:

public void onFuturePaymentPurchasePressed(View pressed) {
// SDKからClient Metadata IDを取得します。
String metadataId = PayPalConfiguration.getClientMetadataId(this);
 
// TODO: PayPalでの処理のため、相関IDと取引の詳細をサーバーに
// 送信します。
}
                            

サーバーは、PayPalへの支払いリクエストを作成する際、HTTPヘッダーPayPal-Client-Metadata-Idに、SDKから取得したこのClient Metadata IDの値を含める必要があります。

事前承認に基く支払い

ユーザーが、自分のPayPalアカウントの使用をアプリに承認すると、この承認を使用して、そのユーザーからの今後の支払いを作成するトークンを取得できます。
このドキュメントには以下の方法が記載されています。

  1. OAuth2のトークンを取得する
  2. 取得したトークンを使用して支払いを作成する

ユーザーに、アプリで今後の支払いを承認し、同意してもらうには、今後の支払いを参照してください。

OAuth2のトークンを取得する

  1. 認可コードを取得する
  2. 認可コードとリフレッシュ/アクセストークンを交換する

認可コードを取得する
モバイルSDKは、今後の支払いについてユーザーの承認を得るためのAPIを提供しています。アプリにSDKを統合し、それを使ってユーザーを認証し、ユーザーの同意を得ます。SDKは、PayPal認証サーバーで認証および承認を処理し、OAuth2の認可コードをアプリに返します。
この認可応答はJSONオブジェクトです。
例:

{
"response_type": "paypal_authorization_code",
"response": {
"code": "EBYhRW3ncivudQn8UopLp4A28xIlqPDpAoqd7biDLpeGCPvORHjP1Fh4CbFPgKMGCHejdDwe9w1uDWnjPCp1lkaFBjVmjvjpFtnr6z1YeBbmfZYqa9faQT_71dmgZhMIFVkbi4yO7hk0LBHXt_wtdsw",
},
"client": {
"environment": "live",
"paypal_sdk_version": "2.0.0",
"platform": "Android",
"product_name": "PayPal Android SDK"
}
}
                            

アプリは、この認可応答をサーバーに送信する必要があります。サーバーは、次のセクションで説明するように、認可コードと、リフレッシュトークンおよびアクセストークンを交換する必要があります。

認可コードとリフレッシュ/アクセストークンを交換する
サーバーが認可コードを取得すると、これを使用して、有効期限の長いリフレッシュトークンと有効期限の短いアクセストークンを取得できるようになります。サーバーとPayPal APIの通信は、標準的なOAuth2の認可コードグラントアクセスリクエストです。
リフレッシュトークンの有効期限は長く、現在のところ10年です。安全に保管する必要があります。
返されるコードやトークンの値はすべて可変長として扱ってください。今後さらに長くなる可能性がありますので、最大長が固定されているものとして扱わないでください。
サンプルリクエスト

-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic QWZV...==" \
-d 'grant_type=authorization_code&response_type=token&redirect_uri=urn:ietf:wg:oauth:2.0:oob&code=EBYhRW3ncivudQn8UopLp4A28...'
                            

  • grant_type: トークンのグラントタイプです。値はauthorization_codeに設定する必要があります。
  • redirect_uri: リダイレクトURLです。値はurn:ietf:wg:oauth:2.0:oobに設定する必要があります。
  • code: 認可サーバーから以前に受信した認可コードです。

応答

{
"access_token": "6oyryV79E.KtpAvPudpI8VIko.ntdPikU9HCDfg0tO0",
"expires_in": 28800,
"refresh_token": "MFYQJTPW3zlCAjznPs2D0VQlQXwiEfTesR-dRiU_qhbUngzxR3NmeBxqKELcmGtSI739R-awwvOyGVO1LJbowy7n8Ul3vsf5HQDTCzUlDylqBvW0",
"token_type": "Bearer"
}
                            

  • access_token: 認可サーバーによって発行されたアクセストークンです。
  • expires_in: アクセストークンの秒単位での有効期限です。アクセストークンの期限切れ後、refresh_tokenを使用してアクセストークンを更新します。
  • refresh_token: リフレッシュトークン。OAuth2.0 RFC6749 - セクション6で説明されているように、これにより、同じ認可グラントを使用する新しいアクセストークンを取得することができます。
  • scope: このリフレッシュトークンに関連するスコープ値をスペースで区切ったリストです。
  • token_type: OAuth2.0 RFC6749 - セクション7.1で説明されているタイプの発行済みトークンです。値は、大文字と小文字が区別されます。

支払いの作成

今度の支払いについて同意したユーザーが支払いを開始した場合、サーバーコードで以下を実行する必要があります。

  • ユーザーのリフレッシュトークンを検索し、そのトークンを使用して新しいアクセストークンを取得する
  • 有効なアクセストークンを使用して支払いを作成する

アクセストークンの更新

前述したように、アクセストークンには有効期限があります。アクセストークンの期限が切れた場合は、リフレッシュトークンを使用して新しいアクセストークンを取得する必要があります。
サンプルリクエスト

-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic QWZVa...==" \
-d 'grant_type=refresh_token&refresh_token=MFYQ...'
                            

  • grant_type: トークンのグラントタイプです。値はrefresh_tokenに設定する必要があります。必須です。
  • refresh_token: 更新されるアクセストークンとともに以前に受信したリフレッシュトークンです。必須です。

応答

{
"access_token": "WfXdnxmyJtdF4q59ofxuQuAAk6eEV-Njm6puht3Nk3w",
"app_id": "APP-3TS46380HB829954H",
"expires_in": 28800,
"token_type": "Bearer"
}
                            

有効なアクセストークンを使用した支払いの作成
次のように、アクセストークンをHTTPのAuthorizationヘッダーの値として使用して支払いを完了します。

Authorization: Bearer YOUR_ACCESS_TOKEN

支払いの作成時に、直接の販売や後日支払いを回収する承認を行うことができます。
ヒント:

  • 詳細は支払いの作成を参照してください。
  • モバイル端末から支払いが開始された場合、すべての決済APIリクエストに、[モバイルSDK]を使用して取得されたPayPal-Client-Metadata-Idヘッダー値(future_payments_mobile.md#obtain-an-application-correlation-id)が含まれる必要があります。
  • 1回払いの実例を示す標準的なREST APIのドキュメントとは異なり、今後の支払いでは、最初にユーザーの同意を得た後は、そのつど支払いの承認を得る必要はありません。支払いは、ユーザーによって事前承認されています。

承認と回収の例

たとえば、最初に支払いを承認するには、次のようなリクエストを使用します。
サンプルリクエスト

-H "Content-Type: application/json" \
-H "PayPal-Client-Metadata-Id: c2edbd6e97b14ff2b19ddb8eec9d264c" \
-H "Authorization: Bearer WfXdnxmyJtdF4q59ofxuQuAAk6eEV-Njm6puht3Nk3w" \
-d '{
"intent":"authorize",
"payer":{
"payment_method":"paypal"
},
"transactions":[
{
"amount":{
"currency":"USD",
"total":"1.88"
},
"description":"future of sauces"
}
]
}'
                            

応答

{
"create_time": "2013-10-01T00:43:25Z",
"id": "PAY-2C433581AX997613HKJFBVLI",
"intent": "authorize",
"links": [
{
"method": "GET",
"rel": "self"
}
],
"payer": {
"payer_info": {
"email": "tnm10@paypal.com",
"first_name": "George",
"last_name": "Martin",
"payer_id": "QLR7PGAJCMCA8"
},
"payment_method": "paypal"
},
"state": "approved",
"transactions": [
{
"amount": {
"currency": "USD",
"details": {
"subtotal": "1.88"
},
"total": "1.88"
},
"description": "future of sauces",
"related_resources": [
{
"authorization": {
"amount": {
"currency": "USD",
"details": {
"subtotal": "1.88"
},
"total": "1.88"
},
"create_time": "2013-10-01T00:43:25Z",
"id": "4TD55050SV609544L",
"links": [
{
"method": "GET",
"rel": "self"
},
{
"method": "POST",
"rel": "capture"
},
{
"method": "POST",
"rel": "void"
},
{
"method": "POST",
"rel": "reauthorize"
},
{
"method": "GET",
"rel": "parent_payment"
}
],
"parent_payment": "PAY-2C433581AX997613HKJFBVLI",
"state": "authorized",
"update_time": "2013-10-01T00:43:28Z",
"valid_until": "2013-10-30T00:43:25Z"
}
}
]
}
],
"update_time": "2013-10-01T00:43:28Z"
}
                            

サービスの完了または商品の提供後、支払いを回収します。

サンプルリクエスト

-H "Content-Type: application/json" \
-H "PayPal-Client-Metadata-Id: c2edbd6e97b14ff2b19ddb8eec9d264c" \
-H "Authorization: Bearer WfXdnxmyJtdF4q59ofxuQuAAk6eEV-Njm6puht3Nk3w" \
-d '{
"amount":{
"currency":"USD",
"total":"1.50"
},
"is_final_capture":true
}'
                            

応答

{
"amount": {
"currency": "USD",
"total": "1.50"
},
"create_time": "2013-10-01T00:43:28Z",
"id": "9BS66645698646420",
"is_final_capture": true,
"links": [
{
"method": "GET",
"rel": "self"
},
{
"method": "POST",
"rel": "refund"
},
{
"method": "GET",
"rel": "authorization"
},
{
"method": "GET",
"rel": "parent_payment"
}
],
"parent_payment": "PAY-2C433581AX997613HKJFBVLI",
"state": "completed",
 "update_time": "2013-10-01T00:43:30Z"
}
                            

stateがcompletedと表示されると、販売は完了です。