2020-05-05

マーケットプレイス的なサイトを作ってみる:Stripeを使った決済処理と注文状況を管理する #craftcms

商品を購入・決済するところの処理はいろいろ選択肢もあり、複数個の購入や在庫数がある場合などもある。
いろいろ考えるところは多いのだけど、とりあえず単一の商品でそれを購入する支払い処理をするというところを想定して試してみる。

自社のECサイトみたいなのだと商品がある程度決まっているので、 Craft Commerce とか Stripe で注文用のフォームをつくるとかでもいけそうな気はする。

マーケットプレイス的な支払いの仕組みとしては Stripe や PAY.JP にこの辺の仕組みも用意されているっぽい。

ユーザーが商品を登録して価格も決めるので、決済処理のフォーム部分は値段が全部異なるので、そこをどうするかなぁと。

Checkout overview | Stripe Checkout
https://stripe.com/docs/paymen...

この辺の話っぽいけど、もう少し簡単にやれればということで、「Stripe Checkout」プラグインを使うことにした。

Stripe Checkout
https://plugins.craftcms.com/s...

これでテンプレート側で結構簡単に書くことができる。

    商品購入ボタン

    商品詳細ページのテンプレートに商品購入(決済処理)のボタンを設置する。

    ドキュメントにある感じの書き方を参考にする。

    craft-stripecheckout/creating-charges.md at v2 · jalendport/craft-stripecheckout
    https://github.com/jalendport/...

    Stripe のAPIキーとかはあらかじめ取得しておき、プラグインの設定をしておく。

    購入ボタンのコード部分。

    <form action="" method="post">
      {{ csrfInput() }}
      {{ redirectInput('checkout/confirmation')}}
      {{ hiddenInput('title', entry.title) }} // metadata として商品名(タイトル)を渡す用
      {{ hiddenInput('orderProduct', eID) }}
      {{ hiddenInput('orderOwner', authorID) }}
      {{ hiddenInput('orderMember', uID) }}
      <input type="hidden" name="action" value="stripe-checkout/charge">
    
      {{ checkout({
          amount: entry.productPrice,
          name: entry.title,
          panelLabel: '支払う',
          label: '購入する',
          metadata: ['title','orderProduct','orderOwner','orderMember']
      }) }}
    </form>

    これで金額のところは amountentry.productPrice を渡すことで問題なく決済処理のボタンが出せた。

    Stripe 側でもデータを確認できた。

    決済完了後に checkout/confirmation 画面に遷移するので、 checkout/confirmation.twig を用意しておく。

    注文時のエントリ情報などをmetadataを使って取り出す

    注文の処理をするとその商品は売り切れなのでその対応を入れる。

    商品セクションのエントリデータをどうするか?も考える必要があるが、注文状況セクションのエントリとして、この注文のデータを追加する必要がある。

    そのためにmetadataに情報を引き継いでおくことにした。
    metadataに渡すと処理が終わって checkout/confirmation にリダイレクトされた時に一時データとして受け取ることができる。

    {% set charge = craft.app.session.getFlash('charge') %}
    craft-stripecheckout/creating-charges.md at v2 · jalendport/craft-stripecheckout
    https://github.com/jalendport/...

    エラーがあった時は

    {% set errors = craft.app.session.getFlash('errors') %}

    でとれるっぽいのでエラー処理を入れるならこれで判定する感じになりそう。

    charge.data でとれるデータは Stripe のAPIにあるこういう感じのもの。

    Stripe API Reference
    https://stripe.com/docs/api/ch...

    「Stripe Checkout」プラグインを入れてあるとそのデータはCMS側でも確認ができる。

    このデータの中に購入ボタンで引き渡した metadata も入っている。

    これをつかって、完了画面で処理をするようにする。

    json_decode して値を取り出す

    metadataに渡ってきた値を

    {% set charge = craft.app.session.getFlash('charge') %}

    で受け取ると、連想配列っぽい感じになってるのだけど、丸っとテキストとして入ってくる。
    @tinybeans に json_decode すれば良いと教えてもらった。

    Json'd プラグインを使って json_decode することにした。

    Json'd
    https://plugins.craftcms.com/j...

    これで問題なく

    {% set charge = craft.app.session.getFlash('charge') %}
    {% set orderData = charge.data|json_decoded %}

    というかんじで、 orderData にセットできたので、

    {% set orderData_title = orderData.metadata.title %}
    {% set orderData_orderProduct = orderData.metadata.orderProduct %}
    {% set orderData_orderOwner = orderData.metadata.orderOwner %}
    {% set orderData_orderMember = orderData.metadata.orderMember %}

    こんなかんじで、それぞれ一旦変数にセットしておくことにした。

    注文状況のエントリを作成する

    これらの処理は購入完了ページで処理しているわけなので、これらの値がとれたら裏側で注文状況のセクションにエントリを作っておく必要がある。

    購入時はフォームのボタンを押した時に処理すればよいが、こちらは画面表示で処理しようとおもうので GraphQL でやることにした。

    コードとしてはこんなかんじで。

    {% set variables = {'title':(orderData_title),'orderProduct':(orderData_orderProduct),'orderOwner':(orderData_orderOwner),'orderMember':(orderData_orderMember) } %}
    
    {% set addOrderData = gql('mutation($title:String,$orderProduct:[Int],$orderOwner:[Int],$orderMember:[Int]){
      	save_order_order_Entry(
    	    authorId: 1,
    	    title: $title,
    	    orderProduct: $orderProduct,
    	    orderOwner: $orderOwner,
    	    orderMember: $orderMember
    	  ){
    	    id
    	    url
    	  }
    }', variables) %}
    
    {{ d(addOrderData)}}

    mutationの中で使う各値を一旦変数用のvariables にセットしておく。

    {% set variables = {'title':(orderData_title),'orderProduct':(orderData_orderProduct),'orderOwner':(orderData_orderOwner),'orderMember':(orderData_orderMember) } %}

    mutationの中でそれぞれの変数の型は書いておく。

    {% set addOrderData = gql('mutation($title:String,$orderProduct:[Int],$orderOwner:[Int],$orderMember:[Int]){

    あとはセクションと入力タイプのhandleに合わせて

    save_order_order_Entry(

    エントリを作成するようにして、各フィールドに使う値をセットするようにする。

    (
    	    authorId: 1,
    	    title: $title,
    	    orderProduct: $orderProduct,
    	    orderOwner: $orderOwner,
    	    orderMember: $orderMember
    	  )

    最後に変数を使うように

    }', variables) %}

    を入れておく。

    この完了画面で GraphQL をつかって処理をするのでテンプレートの先頭には

    {% header "Authorization: Bearer hogehoge" %}

    のような、GraphQL のトークンをセットしておく必要がある。

    これで完了画面でエントリの作成も問題なくできた。
    プラグイン作らず(いくつかすでにあるプラグインは使わせてもらったけど)にできるところはできた。
    まぁ、プラグインを作った方が処理としていいこともありそうなきはするから、それはそれという感じで。
    たとえば決済処理が問題なければメールを送るとか。それらはどうするか?とか考えるとプラグインとか作った方が早そうなきはする。

    マーケットプレイスとかだと後はなんだろうな。
    出品者と購入希望者のメッセージのやりとりとかか。

    何かその辺考えてみよう。


    手を動かしてあーだこーだ考えながら作ってみて。
    ダメだったら別の方法を考え、一個できたと思って次へ行くとまた違う問題とか、検討不足が発覚するという感じの繰り返し。

    仕事でやるならこれじゃダメだし、ちゃんと要件とかまとめて仕様を考えて、とやらないといけないなぁと思う。
    とはいえ、実装してみないと仕様を考える時に漏れはでるなぁとおもったので、実装する側も漏れがある前提くらいで考えてないと無理なところもありそうだなぁ。