stripe checkout の server side を gin でつくる

stripe は go の API も用意してくれていて親切です。なんとなく php から疎遠になり、バージョン間の差異の煩わしさにキレて python とは絶縁状態、ruby は前世紀に最初に読んだ本が相性が悪く食わず嫌い状態、java なんて JAVA だった頃から逆縁だらけだった上に C++ は永遠の入門レベル(追いつくまもなく膨らんでいくのでたぶん死ぬまでそうなんだと思う、死ぬときに「ああ、僕は C++ の手のひらの中から一歩も出ることができなかった」って思いながら死んでいくんだと思う)な偏食な身にはとても有り難い次第です

gin を使うと go でも (python 並に、とは言えずとも遠からずという歯がゆさはのこりますが)楽にサーバーが書ける上に速いので嬉しいです

ポイント

stripe のAccept a payment の通りなのですが一箇所落とし穴がありまして、フィードバックしておいたのでいまでもそうなのかはわからないのですが必要な import が抜けていて、つど補ってあげないとコンパイルも通りません、詳細はこちら

Set up Stripe

こちら のとおりなのですが、後で session と webhook を使うのでそれらも import しておく必要があります



 
 


import (
	stripe "github.com/stripe/stripe-go/v71"
	"github.com/stripe/stripe-go/v71/checkout/session"
	"github.com/stripe/stripe-go/v71/webhook"
)

リクエストハンドラの作成

func main() の中にCheckout Session の生成を受け付ける API を例えば /buy として、 Webhook のエンドポイント になる API を例えば /webhook としてそれぞれ用意します

stripe API を使う実際の session の生成と webhook のハンドリングはそれぞれ例えば後で作る stripeGetSessionID()handleWebhook(c) で行います

main.go の中では stripe の API は直接つかわないので stripe のための import の追加は不要です



 
 












 
 

 












    r := gin.Default()

    r.GET("/buy", func(c *gin.Context) {
      sessionid, err := stripeGetSessionID()
      if err != nil {
        log.Println(err)
        c.JSON(500, gin.H{
          "message": err,
        })
      } else {
        c.JSON(http.StatusOK, gin.H{
          "SessionID": sessionid,
        })	
      }
    });

    r.GET("/buy", func(c *gin.Context) {
      err := handleWebhook(c)
      if err != nil {
        c.JSON(400, gin.H{
          "message": err,
        })
      } else {
        c.JSON(http.StatusOK, gin.H{
          "message": "OK",
        })
      }
    });

    r.Run(":8080")
}  

stripe API の利用

checkout session の生成と webhook の endpoint は例えば以下のように、例えば stripe.go みたいなファイルにを用意して









 
 
 



 















 














 
















 








 




package main

import (
	"github.com/UedaTakeyuki/erapse"
	"github.com/gin-gonic/gin"
	"log"
	"time"

	stripe "github.com/stripe/stripe-go/v71"
	"github.com/stripe/stripe-go/v71/checkout/session"
	"github.com/stripe/stripe-go/v71/webhook"
)

func stripeInit() {
	stripe.Key = "sk_test_YOURKEY"
}

func stripeGetSessionID() (sessionid string, err error) {
	defer erapse.ShowErapsedTIme(time.Now())
	stripeInit()

	params := &stripe.CheckoutSessionParams{
		PaymentMethodTypes: stripe.StringSlice([]string{
				"card",
//				"ideal",
		}),
		LineItems: []*stripe.CheckoutSessionLineItemParams{
				&stripe.CheckoutSessionLineItemParams{
						PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{
							Currency: stripe.String("usd"),
							Product: stripe.String("prod_YOURPRODUCT"),
							/*
							ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{
								Name: stripe.String("T-shirt"),
							},
							*/
							UnitAmount: stripe.Int64(200),
						},
						Quantity: stripe.Int64(1),
				},
		},
		Mode: stripe.String("payment"),
		SuccessURL: stripe.String("https://example.com/success?session_id={CHECKOUT_SESSION_ID}"),
		CancelURL: stripe.String("https://example.com/cancel"),
	}
	params.AddMetadata("user", "aabbcc")
	s, err := session.New(params)
	sessionid = s.ID
	log.Println("session id = ",sessionid)

	return
}

func handleWebhook(c *gin.Context) (err error){
	defer erapse.ShowErapsedTIme(time.Now())
	stripeInit()

	buf := make([]byte, 2048)
	n, _ := c.Request.Body.Read(buf)
	body := buf[0:n]
	log.Println("body = ", string(body))

	endpointSecret := "whsec_YOURSECRET"

	event, err := webhook.ConstructEvent(body, c.Request.Header.Get("Stripe-Signature"), endpointSecret)
	if err != nil {
		return
	}

	// Handle the checkout.session.completed event
	if event.Type == "checkout.session.completed" {
		log.Println("user", event.Data.Object["metadata"].(map[string]interface {})["user"].(string))
	}
	return
}

sk_test_YOURKEY, prod_YOURPRODUCT, whsec_YOURSECRET はそれぞれ自分のキー、商品ID、シークレットの文字列におきかえてください

事前定義した商品を使わず動的に商品を生成する場合は Product: のかわりに ProductData: でもいいです

webhook event を取得するために生の request body と Stripe-Signature の request header を取得して webhook.ConstructEvent に渡してあげる必要があるのですが、gin で request header を取ったり request body を生で触ったりする方法が ドキュメンt から見つけられずジタバタとググって上記のように書いてみたのですが、もっとオサレな書き方があるのかは自信ないです

defer erapse.ShowErapsedTIme(time.Now()) は関数の先頭にこれを書いておくだけで経過時間が表示されるので、性能的に「あれっ?」って思う処理を書いてしまってもすぐにその場で原因が見つかるので好きでいつもつかっているのですが、stripe とは関係ないのでなくてもいいです。ちなみになんで i まで大文字にしてしまったのか今となっては思い出せません...

チュートリアル にはないのですが metadata は以下のように使います

  1. session の生成時に AddMetadata() で登録
  2. webhook のイベントからは event.Data.Object["metadata"] を (map[string]interface {}) に型アサーションして、取り出した interface {} を再度実際の型にアサーション

gin のおかげでだいぶ楽とはいえ、実行時の型をちゃんとわかってるくせによしなに変換してくれない石頭ぶりがちょっとうざいです。型アサーションって間違ってたときに「正解は○○なんだけどお前は☓☓を指定したぞ」っていう上から目線なエラーメッセージを賜るのですが、正解わかってるんだったら意地悪言ってないで暗黙で変換してよねって思います


Last Updated: 8/7/2020, 5:17:18 AM