PayPay決済で注文が確定しない件について
皆様こんにちは塩田です。
以前Laravel&PayPayでECサイトを構築したブログを書きましたが、今回はその続編的な内容となります。
環境
Laravel
https://readouble.com/laravel/8.x/ja/installation.html
PayPayデベロッパー(開発者向け)
https://developer.paypay.ne.jp/
※利用するにはアカウントの登録が必要です
前提条件
注文から商品お渡しまでは各工程にステータスを付与する。
※今回はこのステータスが非常に重要です
例)
- 決済途中
- 新規注文
- 注文確認
- 商品お渡し
- キャンセル
前回までの経緯
LaravelでECサイトを構築、決済方法にPayPay(ウェブペイメント)を採用。
注文ボタンをクリックすることでPayPayの決済ページへ遷移、決済が完了すると元のサイトに戻ってきて注文完了となる。
問題点
PayPayの決済ページから元のサイトへ正しく戻ってこないと注文が確定されない。
まず注文が正しく通るフローを下記に記します。
- 注文ボタンをクリックで注文送信(ここで注文内容をDBに保存)
- PayPayの決済ページに遷移、決済
- 元のサイトに戻る(ここで本当に注文完了)
上記の通りであれば何の問題もないのですが(ステータスは新規注文になる)、ここで元のサイトに戻って来ない(フローの3)と問題が発生します。
ではどのような操作で問題が発生するかといいますと、2通りのパターンが考えられます。
決済完了後に離脱
ステータスは決済途中となります。
下図は決済が完了したあとの画面ですが、上記のフローでいう2と3の間にあたる箇所です。ここで注文が確定したと思ってページを消したりすると、処理が途中で止まる(決済は成立しているが、注文内容が正しくお店に届かない)という状況になります。
つまりお店からは、注文はあったが金額しか分からず、何を用意していいか分からないという宙ぶらりんの非常に困った状態になります。困った。
決済前に離脱
こちらもステータスは決済途中となります。
これはどんな状況で起こり得るかというと、PayPayの残金が残っていなかったため、やっぱりや〜めたなどのケースが考えられます。
これは結論から言えば、決済をする前に注文を止めたということになるので所謂キャンセルとなると思います。しかし元のページで決済ボタンをクリックする(注文内容をDBに保存)という動作は経ているので、注文内容自体は破棄されずに残っています。
またPayPayが返すステータスも上の決済完了後に離脱と同じなので、「決済はされたけど発生した異常」(決済完了後に離脱)なのか、「決済されずに発生した異常」(決済前に離脱)なのか、ステータスでは判断できないという問題があります。
解決策
上に記載した問題や異常はリンク型決済の仕様上、今の所どうしようもないと思いますので、そういった問題が起こる前提で、問題を少しでも減らす、発生した場合は迅速に取引内容(決済の有無、注文内容)を確認できるようにするしかありません。
決済手段でPayPayを選ばれたらポップアップで注意喚起
これは事前の予防策になりますが、決済でPayPayを選ばれたら、「決済が完了しても画面を消さず、元のサイトに戻ってくることを確認すること、そうしないと注文がお店に正しく伝わらない」ということをポップアップで実装(確認ボタンを押さないとポップアップが閉じない)する。
これである程度は問題を回避できると思いますが、それでも完全に問題を回避することはできないでしょう。よって以下に事後策です。
とりあえず注文された内容は、決済状況に関わらず全て表示する
ここからは管理画面の内容となりますが、今回のケースだとどちらもステータスは決済途中ということになります。
しかしながらここで気をつけたいのはステータスが決済途中にも2パターン(決済完了と未決済)が存在するということです。未決済の場合はキャンセル扱いでよいと思いますが、決済完了についてはお客さんはお金を払ってくれているのですから、是非とも注文確定までもっていきたいところです。
ここで確認してほしいのがPayPayの取引番号(merchant_payment_id)です。これはQRコードを発行する際に生成しているはずですので、基本的に全ての取引に付与される筈です。
以下はQRコード(決済画面)の発行処理です(※赤文字の箇所)
一連の流れはコチラ
class CreateQrCodeAction { protected Order $order; public function __construct(Order $order) { $this->order = $order; } public function __invoke(): array { $client = $this->createClient(); $payload = $this->createPayload(); //ここでQRコード生成! $QRCodeResponse = $client->code->createQRCode($payload); if ($QRCodeResponse['resultInfo']['code'] !== "SUCCESS") { //例外処理 ); } //ここでQRコードを返します return $QRCodeResponse; } //以下にQRコード生成のための各処理を記述します protected function getMerchantPaymentId(): string { return "任意の接頭語" . time(); } protected function createClient(): Client { return new Client(config('PayPayのキー'), false); } protected function getAmount(): string { return str_replace(',', '', $this->order->total_price); } protected function createOrderPaypay(): OrderPaypay { $orderPaypay = new OrderPaypay; $orderPaypay->order()->associate($this->order); $orderPaypay->merchant_payment_id = $this->getMerchantPaymentId(); $orderPaypay->createToken(); $orderPaypay->save(); return $orderPaypay; } public function createPayload(): CreateQrCodePayload { $orderPaypay = $this->createOrderPaypay(); $payload = new CreateQrCodePayload(); $payload->setMerchantPaymentId($orderPaypay->merchant_payment_id); $payload->setCodeType("ORDER_QR"); //重要!ここで決済画面へのルートを設定します。 $payload->setRedirectUrl($orderPaypay->getCallbackUrl()); $payload->setAmount([ "amount" => $this->getAmount(), "currency" => "JPY" ]); $payload->setRedirectType('WEB_LINK'); return $payload; } } }
この取引番号を「PayPay for Business」などの管理画面の「取引一覧」で参照していただき、該当する取引が「SUCCESS」であれば支払いを確認できたということですので、ステータス「新規注文」として調理・生産工程へ進むことができるようになります。
また「決済前に離脱」だとそもそも取引一覧には表示されません(当たり前ですが)
まとめ
長々と説明させていただきましたが、現状では ストライプなどのクレジット決済に比べ、若干気を配るべき点が多いと思いますが、決済手段の適度な多様化はクライアント様の利益により貢献できますので、仕様や特性をしっかり踏まえて利用していきたいと思います。
ここまで読んでいただきましてありがとうございました。
おまけ
PayPayの公式HPにもこの件について触れているところがあります。しかし例がマクドナルド・・・
余程こういった事例が多発したのでしょうか。(ページ中断のお支払い方法を参照)