Cashback (1.1)

はじめに

PayPay Open Payment API(OPA)は、様々なシナリオに対応した決済関連操作が設計されており、ユーザーへ快適な決済体験を提供します。 PayPay OPAクライアントとして登録されている場合、決済パートナーは下記の機能が使用可能になります。

  • PayPayユーザーのウォレットから直接支払い金額を引き落とし、決済を完了させる
  • 動的なQRコードを作成し、PayPayアプリで決済を完了させる
  • スムーズな決済を行うため事前にユーザーの認可を取得し、決済フローを確立する
  • PayPayキャッシャーページとWebアプリケーションを統合し、決済を完了させる
  • PayPay OPAが提供する様々なAPIを使用し、独自の決済エクスペリエンスの構築

本書では、PayPay決済パートナー向けに設計したCashback APIに焦点を当てます。

TLSの実装

PayPay Open Payment APIでは、セキュリティ対策としてTLS 1.2以上の使用が必須となっております。TLS1.0およびTLS1.1では接続できませんのでご注意ください。

加盟店を登録する

PayPay OPAの利用を開始するには、事前に定められたプロセスに従ってPayPay加盟店の登録を行なってください。 このプロセスは 情報収集、手動検証、契約確認、およびクレデンシャル情報の発行から構成されます。

PayPayの加盟店として登録された後、以下の項目が設定されます。

  • api key と apiKeySecret
  • webhookの通知先となるエンドポイント
  • クライアントIPのホワイトリスト
  • 加盟店識別子(エージェントクライアントの場合)

これらの設定を管理するには、マーチャントパネルを使用するか、弊社までご連絡ください。

ユーザー認可を取得する

PayPayユーザーのウォレットから決済を回収できるようにするには、ユーザーの認可を明示的に取得する必要があります。

ユーザー認可の取得は、OAuth/OpenID ConnectスタイルのWebView認可および認可フローと同様に、単純なWebリダイレクトフローを通じて行われます。

認可が成功すると、ユーザー認可IDがユーザーごとに発行されます。 このIDをバックエンドに保存し、それを内部ユーザーに関連付ける必要があります。 このIDはOPAへのリクエストで使用され、どのPayPayユーザーから決済を受け取りたいかを識別します。

すべてのユーザー認可には有効期間が設けられています。つまり、ユーザー認可IDは一定期間後に期限切れになります。 これは、オンボーディングプロセス中に定義されたユーザー認可有効期間です。 また、PayPayユーザーは、加盟店に通知することなく、PayPayサービスから認可を取り消すこともできます。

以下の図は、認可取得シーケンスを説明しています。

以下の場合には、このフローを使用してユーザーの認可を取得します。

  • ユーザーのOPA userAuthorizationIdがない場合
  • ユーザー認可が期限切れになったか取り消された場合

ユーザーを認可ページに遷移させる

ユーザーの認可を取得するために、ホストアプリはユーザーにパラメーターapiKeyおよびrequestTokenと共にPayPay認可ウェブページを表示する必要があります。 このページでは、ユーザーはPayPayにログインして、口座引き落とし許可を加盟店に付与することができます。

認可ページURL

https://www.paypay.ne.jp/app/opa/user_authorization?apiKey={apiKey}&requestToken={jwtToken}'

必須パラメーター

  • apiKey は、登録プロセス中に発行されたapi keyです
  • requestToken は、対応するapi keyとsecretで署名されたJWTトークンです
{
  "alg": "HS256",
}
.
{
  "aud" : "paypay.ne.jp",
  "iss" : "<merchant organization id>",
  "exp": 12456,
  "scope" : "cashback",
  "nonce" : "<random case sensitive string>",
  "redirectUrl": "https://<merchant service auth redirect endpoint>",
  "referenceId": "<merchant user reference id>",
  "deviceId": ""
}
.
signature
cliam 種類 説明
aud string "paypay.ne.jp"
iss string 加盟店名
exp number 認可ページURLの有効期限。エポックタイムスタンプ(秒単位)で設定。
scope string cashback
nonce string クライアント側検証のための応答とともに返送されます。
redirectUrl url string クライアントから提供されたコールバックエンドポイント。HTTPSであること、またドメインは許可された 認可コールバックドメイン内になければなりません。
referenceId string マーチャントシステムでユーザーを識別するためのID。再照合目的でPayPayデータベースに格納されます。
deviceId string (オプション)ユーザーの携帯電話デバイスID。提供されている場合は、より快適なUXを提供するために、この携帯電話デバイスIDを使って、ユーザーの確認とSMSの確認を省略できます。

ユーザー認可の結果を受け取る

PayPyaの認可ページでのユーザーの操作に応じて、認可の取得は成功または失敗する可能性があります。 結果をJWTトークンにエンコードして、前述のrequestTokenに指定されているリダイレクトURLに渡し ます。リダイレクトURLに渡す特定のパラメーターは以下のとおりです。

  • apiKey は、登録プロセス中に発行されたapi keyです。
  • responseToken は、対応するapi keyとsecretで署名されたJWTトークンです。
  {
    "typ": "JWT",
    "alg": "HS256",
  }
  .
  {
    "aud" : "<merchant organization id>",
    "iss" : "paypay.ne.jp",
    "exp" : 23567,
    "result": "succeeded",
    "profileIdentifier": "*******5678",
    "nonce" : "<the same nonce in the request>",
    "userAuthorizationId" : "<PayPay user reference id>",
    "referenceId": "<merchant user reference id>"
  }
  .
  signature
claim 種類 説明
iss string 加盟店名
aud string "paypay.ne.jp"
exp number 認可ページURLの有効期限。エポックタイムスタンプ(秒単位)で設定。
result string succeeded, declined , bad_request
nonce string レスポンスを検証するために必要な クライアント側のリクエスト内の同一のnonce。
userAuthorizationId string データベースに保持し、APIの呼び出しの際に使用するPayPayユーザー参照ID。最大長64文字。
referenceId string リクエスト内で同一の参照ID。

ユーザーが認可要求を承認または却下した後は、WebViewを以下のURLにリダイレクトします。

https://<redirect url>?apiKey={apiKey}&responseToken={jwtToken}

API認証

本ドキュメントに記載されているAPIは、HMAC方式での認証が必要です。

Authenticationオブジェクトに必要なパラメーター

Value Description Example
API Key PayPayから発行されたAPI Key APIKeyGenerated
API Key secret PayPayから発行されたAPI Key secret APIKeySecretGenerated
Request URI リクエストURL /v2/codes
Request method HTTPメソッド POST
Epoch 現在のエポックタイムスタンプ(秒単位),
Note : サーバー時刻との差が2分未満である必要があります。
1579843452
Nonce ランダムに生成された文字列。
Note : 任意の文字列、length 8を推奨。
acd028
Request body リクエストで渡されるbody。

{"sampleRequestBodyKey1":"sampleRequestBodyValue1","sampleRequestBodyKey2":"sampleRequestBodyValue2"}

Request content type リクエストヘッダーで渡されるコンテンツタイプ。 application/json;charset=UTF-8;
hash (MD5(Request body, Request content type)) リクエストの本文とコンテンツタイプのハッシュ。以下に記載されているStep1で生成されます。 1j0FnY4flNp5CtIKa7x9MQ==

これらから生成されるサンプルHMAC Authヘッダーは以下の通りです。

hmac OPA-Auth:APIKeyGenerated:NW1jKIMnzR7tEhMWtcJcaef+nFVBt7jjAGcVuxHhchc=:acd028:1579843452:1j0FnY4flNp5CtIKa7x9MQ==

HMAC authヘッダーの生成ステップ

Step 1: MD5アルゴリズムを使用してbodyとcontent-typeをハッシュ化する

サンプルコード

private String requestBody;
private String contentType;

MessageDigest md = MessageDigest.getInstance("MD5");
md.update(contentType.getBytes(StandardCharsets.UTF_8));
md.update(requestBody.getBytes(StandardCharsets.UTF_8));
String hash = new String(
          Base64.getEncoder().encode(md.digest()),
          StandardCharsets.UTF_8);

Note : HTTP GETメソッドの場合など、リクエストの本文がない場合、MD5を生成する必要はありません。代わりに、hashには"empty"がセットされます。

Step 2: HMAC-SHA256でハッシュ化される文字列を生成します。

リクエストの署名には、以下のパラメーターが必要です。

private String requestUrl; //Only the request URI Example: "/v2/codes/payments/dynamic-qr-test-00002"
private String httpMethod;
private String nonce; //Random string
private String epoch;
private String contentType;
private String hash; //Output of step 1
private static final DELIMITER = "\n";

byte[] hmacData = new StringBuilder()
                    .append(path)
                    .append(DELIMITER)
                    .append(method)
                    .append(DELIMITER)
                    .append(nonce)
                    .append(DELIMITER)
                    .append(timestamp)
                    .append(DELIMITER)
                    .append(contentType)
                    .append(DELIMITER)
                    .append(hash != null ? hash : "")
                    .toString()
                    .getBytes(StandardCharsets.UTF_8);

Note : HTTP GETメソッドの場合など、リクエストの本文がない場合、content-typeおよびhashには"empty"がセットされます。

Step 3 : Step2で生成された値と、Key secretを使用してHMACオブジェクトを生成します。

public final String toBase64HmacString() {
    private String apiKeySecret;
    private byte[] dataToSign; //Output from step 2
    try {
      SecretKeySpec signingKey = new SecretKeySpec(apiKeySecret.getBytes(StandardCharsets.UTF_8),
          "HmacSHA256");
      Mac sha256HMAC = Mac.getInstance("HmacSHA256");
      sha256HMAC.init(signingKey);
      byte[] rawHmac = sha256HMAC.doFinal(dataToSign);
      return java.util.Base64.getEncoder().encodeToString(rawHmac);
    } catch (GeneralSecurityException e) {
      LOGGER.error("Unexpected error while creating hash: " + e.getMessage());
      throw new IllegalArgumentException(e);
    }
  }

Step 4 : ヘッダーオブジェクトを生成します。

String authHeader = "hmac OPA-Auth:" + api-key +
":" + macData + ":" + nonce + ":" + epoch + ":" + hash;

Note: macDataはStep3で生成された値を使用してください。また、nonceとepochは、Step2で使用した値を渡す必要があります。

authHeaderの値を、HttpHeader.AUTHORIZATIONにセットして渡してください。
PayPay側で渡ってきたデータをデコードし、SHA256( "key"、requestParams)を再作成します。 再作成したmacDataとヘッダーで渡ってきた値が一致しているかを検証します。

リクエスト時に加盟店(ブランド)を指定する

エージェントクライアントは複数の加盟店(ブランド)を管理する場合があるため、APIのリクエスト時に加盟店識別子をセットする必要があります。 加盟店識別子をセットするには2つの方法があります。

クエリの中にセットする:

assumeMerchant=shop

ヘッダにセットする:

X-ASSUME-MERCHANT: auction

両方を指定した場合、クエリにセットしたパラメーターが優先されます。

エラーハンドリング

PayPay OPAはhttpレスポンスステータスコードとOPAエラーコードを使用してリクエストの成功または失敗を示します。 これらの情報を使用して、どのようなエラー対応をするかを判断できます。 PayPay OPAは以下のhttpレスポンスステータスコードを返します。

HTTP 2xx

200

すべてが期待通りに動作しています。

202

リクエストが受信されたことを意味し、しばらくしてから処理されます。

HTTP 4xx

400

このステータスコードは、リクエストで提供された情報を処理できないことによるエラーを示します。 以下のOPAエラーコードが返される可能性があります。

  • INVALID_REQUEST_PARAMS

    リクエストによって提供された情報に無効なデータが含まれています。 例)サポートされていない通貨がセットされているなど。

  • UNACCEPTABLE_OP

    トランザクション制限の超過などのため、リクエストが処理できません。

  • NO_SUFFICIENT_FUND

    取引のための十分な資金がありません。

  • SUSPECTED_DUPLICATE_PAYMENT

    加盟店が5分以内に同一のユーザーで同一の金額をもう一度決済しようとすると、このエラーコードでリクエストは拒否されます。 この設計は、主に意図しない二重決済を防ぐためのものです。

    意図的に単一のユーザーから複数の同一の額の決済をする場合、クライアントは重複チェックを回避するために特定のパラメータを送信する必要があります。 詳しくは決済API仕様に詳述されています。

  • INVALID_USER_AUTHORIZATION_ID

    ユーザー認可IDが期限切れになったか、または取り消された場合に発生するエラーです。 クライアントはユーザー認可IDを取得するために、再度認可フローを通過する必要があります。

401

このステータスコードは、認証エラーを示します。 以下のOPAエラーコードが返される可能性があります。

  • UNAUTHORIZED

    有効なapi keyとsecretがセットされていません.

  • OP_OUT_OF_SCOPE

    対象APIを呼び出す権限がありません。

  • INVALID_USER_AUTHORIZATION_ID

    指定したuserAuthorizationId(PayPayのユーザー認可ID)が無効です.

  • EXPIRED_USER_AUTHORIZATION_ID

    指定したuserAuthorizationId(PayPayのユーザー認可ID)が期限切れです.

404

この状態コードは、リクエストされたリソースがシステムに存在しないことを示しています。

429

この状態コードは、クライアントが一定期間内に送信したリクエストが多すぎたため、速度制限に達したことを示します。 リクエストの送信を遅くするか、上限を引き上げるためにPayPay株式会社に連絡してください。

HTTP 5xx

500

この状態コードは、PayPay側で何か問題が発生したことを示します。 下記のOPAエラーコードが返却される可能性があります。

  • TRANSACTION_FAILED

    このコードは、PayPay側でトランザクションが失敗したことを意味します。 合理的なバックオフ時間で、同じ目的のために新しいトランザクションを作成できます。

  • INTERNAL_SERVER_ERROR

    このコードは何かがうまくいかなかったことを意味します。 しかし、トランザクションが発生したかどうか正確にはわかりません。

502,503,504

不明な決済の失敗として扱われます。

タイムアウト

推奨タイムアウト値は各APIで指定されています。 決済APIのタイムアウト値は30秒以下であってはいけません。 タイムアウトが発生した場合、それは状態が不明な決済として扱ってください。

状態が不明な決済の処理

この状況に対処する方法は2つあります。

  1. 取引の状況を照会するには、照会APIを使用してください。PayPayにおいて元の取引が失敗になっているか存在しない場合は、同じ取引として新しいトランザクションを開始できます。

  2. キャンセルAPIが提供されている場合は、取引をキャンセルすることもできます。キャンセルが承認された後、同じ取引として新しいトランザクションを開始できます。

OPAに関するその他のFAQについては こちら をご覧ください。

ウォレット

ユーザーのウォレット残高に関して

Get user wallet balance

ユーザー残高参照

Timeout: 15s

Authorizations:
query Parameters
userAuthorizationId
required
string (UserAuthorizationId) <= 64 characters

PayPay内でユーザーを識別するID。

currency
required
string
Value: "JPY"

Responses

200

Success

401

認証エラー

404

要求されたリソースは存在しません

500

PayPay側で何らかの問題が発生しました

get/v6/wallet/balance?userAuthorizationId={userAuthorizationId}&currency={currency}

Production server

https://api.paypay.ne.jp/v6/wallet/balance?userAuthorizationId={userAuthorizationId}&currency={currency}

Staging server

https://stg-api.paypay.ne.jp/v6/wallet/balance?userAuthorizationId={userAuthorizationId}&currency={currency}

Response samples

Content type
application/json
Copy
Expand all Collapse all
{
  • "resultInfo":
    {
    },
  • "data":
    {
    }
}

Give Cashback to User

加盟店からユーザーへCashbackを移動

Authorizations:
Request Body schema: application/json

CreateCashback

merchantCashbackId
required
string (MerchantCashbackId) <= 64 characters

加盟店側で発行したCashbackのトランザクションを一意に特定するコード

userAuthorizationId
required
string (UserAuthorizationId) <= 64 characters

PayPay内でユーザーを識別するID。

amount
required
object (MoneyAmount)
requestedAt
required
integer (EpochTime)

エポックタイムスタンプ(秒単位)

orderDescription
string <= 255 characters

注文の説明

walletType
string
Enum: "PREPAID" "CASHBACK"
expiryDate
date (ExpiryDate)

付与されたCashbackの有効期限日 期限日の深夜に無効化されます。

metadata
object

加盟店用の追加情報

Responses

202

リクエストを受け付けました

400

リクエストに無効なパラメータがあるか、必須パラメータがありません

401

認証エラー

404

要求されたリソースは存在しません

500

PayPay側で何らかの問題が発生しました

post/v2/cashback

Production server

https://api.paypay.ne.jp/v2/cashback

Staging server

https://stg-api.paypay.ne.jp/v2/cashback

Request samples

Content type
application/json
Copy
Expand all Collapse all
{
  • "merchantCashbackId": "string",
  • "userAuthorizationId": "string",
  • "amount":
    {
    },
  • "requestedAt": 0,
  • "orderDescription": "string",
  • "walletType": "PREPAID",
  • "expiryDate": null,
  • "metadata": { }
}

Response samples

Content type
application/json
Copy
Expand all Collapse all
{
  • "resultInfo":
    {
    }
}

Check Cashback Details

付与されたCashbackのトランザクションの状態を参照します

Authorizations:

Responses

200

Success

400

リクエストに無効なパラメータがあるか、必須パラメータがありません

401

認証エラー

404

要求されたリソースは存在しません

500

PayPay側で何らかの問題が発生しました

get/v2/cashback/{merchantCashbackId}

Production server

https://api.paypay.ne.jp/v2/cashback/{merchantCashbackId}

Staging server

https://stg-api.paypay.ne.jp/v2/cashback/{merchantCashbackId}

Response samples

Content type
application/json
Copy
Expand all Collapse all
{
  • "resultInfo":
    {
    },
  • "data":
    {
    }
}

Reverse a given cashback

ユーザーに付与されたCashbackをキャンセルします

Authorizations:
Request Body schema: application/json

CreateCashbackReversal

merchantCashbackReversalId
required
string (MerchantCashbackReversalId) <= 64 characters

Cashbackをキャンセルする際に、加盟店側で発行したCashbackのトランザクションを一意に特定するコード

merchantCashbackId
required
string (MerchantCashbackId) <= 64 characters

加盟店側で発行したCashbackのトランザクションを一意に特定するコード

amount
required
object (MoneyAmount)
requestedAt
required
integer (EpochTime)

エポックタイムスタンプ(秒単位)

reason
string <= 255 characters

Cashback がキャンセルされた理由

metadata
object

加盟店用の追加情報

Responses

202

リクエストを受け付けました

400

リクエストに無効なパラメータがあるか、必須パラメータがありません