【Golang x Stripe】STPEphemeralKeyのDecode問題にハマった話(備忘録)->解決済
【2020/07/28 更新】RowJSONについて
key.RowJSON
の型が[]byte
となっています。
どうやら[]byte
がJSONにされた時点Base64にエンコードされた文字列になっているようなので返却値の型を[]byte
にしているとBase64の文字列が返却されていました。
つまり
func EphemeralKey(request EphemeralKeysRequest) ([]byte, error) { ... return key.RowJSON, nil }
だとBase64の文字列がそのまま返却されてしまうので、
func EphemeralKey(request EphemeralKeysRequest) (json.RawMessage, error) { ... return key.RowJSON, nil }
に変更するとレスポンスが以下の通りになります。
{ "id": "ephkey_xxxxxxxxxxxxxxxxxx", "object": "ephemeral_key", "associated_objects": [ { "type": "customer", "id": "cus_xxxxxxxxxxxxxxxxx" } ], "created": 100000000, "expires": 100000000, "livemode": false, "secret": "ek_test_xxxxxxxxxxxxxxx" }
SDK側にStripeID
がnullableではない状態だったので、できないような気もしたのですが、クライアント側でSTPPaymentContext()
に噛ませてみたら無事プリセットされたUIが表示されました。
(多分だけどクライアント側SDK内のdecodedObjectFromAPIResponse
がうまくこの辺とってきてくれてたりするのかな...🤔)
Twitterでこの記事を投げていたら社内のサーバーサイドエンジニアさん(@shogo82148)が救済してくれました🎉🙇♂️
StripeSDKにプリセットされているカード情報入力UIの表示(STPCustomerContext)にはEphemeralKey
(ワンタイムトークン)が必要です。
let customerContext = STPCustomerContext(keyProvider: MyAPIClient()) self.paymentContext = STPPaymentContext(customerContext: customerContext) self.paymentContext.delegate = self self.paymentContext.hostViewController = self self.paymentContext.paymentAmount = 5000 // This is in cents, i.e. $50 USD
基本的に上記の公式ドキュメント通り進めていたのですが...
まずサーバーサイド側。こちらはGolangのAWSLambdaにある関数をAPIGatewayで発行しています。
package main import ( "fmt" "github.com/aws/aws-lambda-go/lambda" "github.com/stripe/stripe-go" "github.com/stripe/stripe-go/ephemeralkey" "log" ) type EphemeralKeysRequest struct { CustomerId string `json:"customer_id"` ApiVersion string `json:"api_version"` } func EphemeralKey(request EphemeralKeysRequest) (*stripe.EphemeralKey, error) { stripe.Key = "sk_test_xxxxxxxxxxxx" params := &stripe.EphemeralKeyParams{ Customer: stripe.String(request.CustomerId), StripeVersion: stripe.String(request.ApiVersion), } key, error := ephemeralkey.New(params) if error != nil { log.Fatal(error) } return key, nil } func main() { lambda.Start(EphemeralKey) }
これのレスポンスが以下の通りです。
{ "associated_objects": [ { "id": "cus_xxxxxxxxxxx", "type": "customer" } ], "created": 1000000000, "expires": 1000000000, "id": "ephkey_xxxxxxxxxxxxxxxx", "livemode": false }
これを公式ドキュメントにもあるとおりクライアント側(今回はiOS側)でSerializeすると、、、
Could not parse the ephemeral key response following protocol STPCustomerEphemeralKeyProvider. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app. For more info, see https://stripe.com/docs/mobile/ios/standard#prepare-your-api
とエラーとなります。
これの箇所が、SDK内のSTPEphemeralKeyManager.m
内の(void)_createKey
の以下の箇所です。
STPJSONResponseCompletionBlock jsonCompletion = ^(NSDictionary *jsonResponse, NSError *error) { STPEphemeralKey *key = [STPEphemeralKey decodedObjectFromAPIResponse:jsonResponse]; if (key) { [self.createKeyPromise succeed:key]; } else { // the API request failed if (error) { [self.createKeyPromise fail:error]; } else { // the ephemeral key could not be decoded [self.createKeyPromise fail:[NSError stp_ephemeralKeyDecodingError]]; if ([self.keyProvider conformsToProtocol:@protocol(STPCustomerEphemeralKeyProvider)]) { NSAssert(NO, @"Could not parse the ephemeral key response following protocol STPCustomerEphemeralKeyProvider. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app. For more info, see https://stripe.com/docs/mobile/ios/standard#prepare-your-api"); } else if ([self.keyProvider conformsToProtocol:@protocol(STPIssuingCardEphemeralKeyProvider)]) { NSAssert(NO, @"Could not parse the ephemeral key response following protocol STPIssuingCardEphemeralKeyProvider. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app. For more info, see https://stripe.com/docs/mobile/ios/standard#prepare-your-api"); } NSAssert(NO, @"Could not parse the ephemeral key response. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app. For more info, see https://stripe.com/docs/mobile/ios/standard#prepare-your-api"); } }
STPEphemeralKey
は手前でインスタンス化することは不可能で必ずdecodedObjectFromAPIResponse
を経由する必要があります。
最大の落とし穴
サーバーサイド側のstripe.EphemeralKey
クラスにRawJSONというプロパティがあります。
これが、
RawJSON is provided so that it may be passed back to the frontend unchanged. Ephemeral keys are issued on behalf of another client which may be running a different version of the bindings and thus expect a different JSON structure. This ensures that if the structure differs from the version of these bindings, we can still pass back a compatible key.
なんて記述があります。しかしこれを返却したとしてもクライアント側で[String: Any]にキャストできません。
解決(正しいかはわからない)
STPEphemeralKey
の中(SDK内)を覗くと、
@property (nonatomic, readonly) NSString *stripeID; @property (nonatomic, readonly) NSDate *created; @property (nonatomic, readonly) BOOL livemode; @property (nonatomic, readonly) NSString *secret; @property (nonatomic, readonly) NSDate *expires; @property (nonatomic, readonly, nullable) NSString *customerID; @property (nonatomic, readonly, nullable) NSString *issuingCardID;
とあります。
サーバーサイド側で返却しているプロパティにstripeID
とsecret
に該当するものがないのが原因でした。
なのでひとまずLambda側のStructを返却するように変更しました。
package main import ( "fmt" "github.com/aws/aws-lambda-go/lambda" "github.com/stripe/stripe-go" "github.com/stripe/stripe-go/ephemeralkey" "log" ) type EphemeralKeysRequest struct { CustomerId string `json:"customer_id"` ApiVersion string `json:"api_version"` } type Response struct { //EphemeralKeys []byte `json:"ephemeralKeys"` AssociatedObjects []struct { ID string `json:"id"` Type string `json:"type"` } `json:"associated_objects"` Created int64 `json:"created"` Expires int64 `json:"expires"` ID string `json:"id"` Livemode bool `json:"livemode"` StripeID string `json:"stripeID"` Secret string `json:"secret"` } func EphemeralKey(request EphemeralKeysRequest) (Response, error) { stripe.Key = "sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" params := &stripe.EphemeralKeyParams{ Customer: stripe.String(request.CustomerId), StripeVersion: stripe.String(request.ApiVersion), } key, error := ephemeralkey.New(params) if error != nil { log.Fatal(error) } return Response { key.AssociatedObjects, key.Created, key.Expires, key.ID, key.Livemode, "pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxx", stripe.Key, }, nil } func main() { lambda.Start(EphemeralKey) }
Sandbox環境だからかなんなのか、issueにでも書いてみます。
決済インフラ入門〔2020年版〕 仮想通貨、ブロックチェーンから新日銀ネット、次なる改革まで
- 作者:宿輪 純一
- 発売日: 2018/07/20
- メディア: 単行本