コネヒト開発者ブログ

コネヒト開発者ブログ

ママリ抽選会にAmazonインセンティブAPIを導入してみました

こんにちは。サーバーサイドエンジニアをしている高橋です。 今回はAmazonAPIの導入に至って同じように導入したいと思っている方へ少しでも参考になったらと思いブログを書いてみました。

導入背景

コネヒトでは3月にリリースされたママリ抽選会という機能があります。 ママリ抽選会については以前ブログで紹介しているのでこちらをどうぞ↓

tech.connehito.com

そこで当選者に対してAmazonギフトコードを送付しているのですが、今まではPdMが自らAmazonの管理画面に入り手動でギフトコードを送付しておりました。 毎月の作業であり、ヒューマンエラーが起こる危険性もあるため、自動化をするに至りました。

調査

調べてみるとAmazonインセンティブAPI が公開されていることはすぐに分かりました。 ですがコネヒトでは導入実績がないため少し不安な気持ちがありました。開発者ガイドを読んでみると、丁寧に書かれていて理解しやすかったため、大きな抵抗なく導入ができそうだと感じました。

検証

Amazonがサンドボックス環境を提供してくれるため、ローカル環境でギフトコードが発行できるところまで実装してみました。しかし、ドキュメントに記載しているリクエストだけではダメだということが分かりました。 そんな時に元々目を通していたAmazonインセンティブAPI実装(Amazonギフト券/giftcode) - Qiitaの記事を思い出し、サンプルコードを使用すれば実装が楽ということを思い出しそちらを使って実装しました。 多少リファクタリングは必要でしたが、簡単に実装することができました。

導入手順

こちらのAmazonインセンティブAPIとの統合プロセスについてと被る部分はありますが、私が実際に行った導入手順を紹介します。

  1. アカウントの作成
  2. アカウントのアクセスキー情報をAmazonにメールで知らせる
    1. サンドボックス環境を構築してくれる。
  3. 管理画面からサンドボックスのアクセスキーとシークレットキーを発行
  4. サンプルコードhtmlSDKv2_php_cr⁩/htmlSDKv2_php/rollups⁩/function.phpをアプリケーションコードにおく。
    1. 今回はギフトコード発行なので利用したのはCreateGiftCardというエンドポイント。
  5. 使いたいクラスでサンプルコードのinvokeRequestを呼び出して実装
    1. エラーハンドリングについてはこちらのエラーとモックエラーに記載されております。
  6. Dev環境(ステージング環境)でテスト
  7. 管理画面から本番のアクセスキーとシークレットキーを発行
  8. 本番でテスト

このような手順で行いました。エラーハンドリングやDB保存をしていたのでテーブル設計などに時間がかかり検証から約1ヶ月半くらいで完了しました。

ハマった点

AmazonギフトコードはDBに保存しない

当初のDB設計ではギフトコードを管理しようと試みていました。しかしAmazonギフト件の取り扱いを読んでみると機密データということと同じRequestIDで再度リクエストすることでギフトコードの再取得が可能だと分かり、RequestIDのみをDBに保存することにしました。ただし同じRequestIDで再取得する場合は同じamountでないと以前の金額と違うよ〜と以下のようなエラーが出るので注意です。

Pre Denomination Mismatch - The Card was created with a different pre-denominated value. Check the specific denominated value in the request matches the pre-denominated value

リクエストは1秒間に10リクエストまで

スロットルレートを事前に見ていたのでsleepを使うように実装しました。PHPのsleepでは秒単位でしか時間を指定できません。そのため今回は理論上0.1秒以上間遅延させればいいのでusleepを使用し以下のように0.15秒遅延させて実装しました。

foreach ($sends as $send) {
    $request = $this->createAmazonApiRequest($send);
    $response = $this->AmazonApi->invokeRequest($request);
    // AmazonApiの使用上10リクエスト/秒の制限があるのでusleep関数を使って0.15秒(150,000マイクロ秒)だけ待つ
    usleep(150000);
}

正常系と異常系でレスポンスオブジェクトが違う

エラーハンドリングをしようとした際に正常系と異常系のレスポンスオブジェクトが異なるため少しハマりました。

<CreateGiftCardResponse>
  <creationRequestId>AwssbTSpecTest001</creationRequestId>
  <cardInfo>
    <value>
      <amount>1.0</amount>
      <currencyCode>USD</currencyCode>
    </value>
    <cardStatus>Fulfilled</cardStatus>
  </cardInfo>
  <status>SUCCESS</status>
  <gcId>A2GCN9BRX5QS76</gcId>
  <gcClaimCode>Z7NV-LBBG39-75MU</gcClaimCode>
</CreateGiftCardResponse>
<AGCODValidationException>
  <Message>Currency Code can't be null or empty</Message>
  <errorType>InvalidCurrencyCodeInput</errorType>
  <errorCode>F200</errorCode>
  <agcodResponse>
    <status>FAILURE</status>
  </agcodResponse>
</AGCODValidationException>

このようにstatusの階層が違うのでエラーかどうか判断するためにはまずオブジェクトの型チェックを先にしなければなりません。 これが最適な方法かはわからないですが今回はエラーコードクラスを参考に、起こり得る確率の高い資金不足エラー以外は同一エラーとしてログに出すことにして以下のように実装しました。

private function checkResponse(array $response)
{
    if (array_key_exists('gcClaimCode', $response)) {
        // 正常系
        return;
    }
    if (array_key_exists('errorType', $response)) {
        if ($response['errorType'] === 'InsufficientFunds') {
            // 資金不足のエラー処理
        }
        // 資金不足以外のエラー処理
    }
    // amazonAPIからそもそもレスポンスがない場合のエラー処理
}

最後に

以下のように大幅なフロー改善に成功しました。

最初はドキュメントを見てスムーズに開発できそうだろうと思い進めましたが、実際アプリケーションに実装していくとなるとエラーになった場合どのような振る舞いをするのかなど考える要素が多く大変でした。何よりドキュメント以外での情報が少なかったので地道にドキュメントを読んでいきました。 社内のフロー改善だけでなく、ユーザーの方にも毎月決まった日時に送れるようになりUXにもいい影響がある開発だったのではないでしょうか。 1からの外部API導入はとてもいい経験になリました。Amazonギフトコード送付はママリ抽選以外でも行っているので、今後ぜひ活用していきたいです!

参考記事: qiita.com

コネヒトでは一緒に働く仲間を募集しています! そして興味持っていただけた方は気軽にご連絡ください!

www.wantedly.com