Slide 1

Slide 1 text

Stripe Search APIを利用した、LINE とStripeの顧客情報連携 LINE de 決済勉強会!LINE API×Stripeエキスパート秘伝のノウハウを共有! @hide__dev May 2022 1 #LINEDC #JP_Stripes

Slide 2

Slide 2 text

岡本 秀高 ( @hide__dev ) ● Stripe Developer Advocate (ex-developer in Digitalcube) ● JavaScript / TypeScript developer ● AWS / Next.js / WordPress / etc… ● WordCamp Kyoto 2017 / JP_Stripes Connect 2019 / AWS Samurai 2017 / etc… ● 🐈(猫フラご容赦󰢛) 2 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 #LINEDC #JP_Stripes

Slide 3

Slide 3 text

npm install use-line-liff 3 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 #jamjamjamstack import { LiffProvider } from 'use-line-liff' {() => { const { liff } = useLiff() }}

Slide 4

Slide 4 text

Agenda ● Search APIを使わずにLINEのユーザーとStripeのCustomerを連携 ● Stripe Search API入門 ● Search APIを使った顧客情報連携方法 ○ Checkout編 ○ Payment Links編 ● Search APIを顧客管理やプロモーションに活用する 4 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 #LINEDC #JP_Stripes

Slide 5

Slide 5 text

Agenda ● Search APIを使わずにLINEのユーザーとStripeのCustomerを連携 ● Stripe Search API入門 ● Search APIを使った顧客情報連携方法 ○ Checkout編 ○ Payment Links編 ● Search APIを顧客管理やプロモーションに活用する 5 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 #LINEDC #JP_Stripes

Slide 6

Slide 6 text

LINEとStripeの連携には、DBが必要(だった) ● 現状、LINE APIでユーザーにmetadataをつけれない・取得できない ● Stripe APIはEmailかIDでしか取得ができなかった ○ LINEにメールアドレスはないので、実質IDだけ ● LINEのユーザーIDとStripe Customer IDを紐づけるDBが必要だった ○ 取得・更新だけなら、RDBMSまでいかずとも、 Amazon DynamoDBやRedisなどのKVS系でOK ○ GET / PUT操作のみなので、 Alexa SDKのようにAmazon S3にJSON配置でもいけたかも 6 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 #LINEDC #JP_Stripes

Slide 7

Slide 7 text

余談: Alexaのask-sdkは、データをJSONで管理できる ● DynamoDB(KVS)と S3(Objct Storage)が選べる ● Key - Value形式のデータを、 ユーザーIDなどをキーに 取得・更新・削除 ● S3は結果整合性なので、 頻繁に更新するケースでは 古いデータが残るリスクがある 7 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 public async saveAttributes(userId: string, attributes: {[key: string]: string}): Promise { const putParams: S3.PutObjectRequest = { Bucket : this.bucketName, Key : userId, Body : JSON.stringify(attributes), }; try { await (new AWS.DynamoDB())).putObject(putParams).promise(); } catch (err) { throw Error(err); ); } } #LINEDC #JP_Stripes

Slide 8

Slide 8 text

Agenda ● Search APIを使わずにLINEのユーザーとStripeのCustomerを連携 ● Stripe Search API入門 ● Search APIを使った顧客情報連携方法 ○ Checkout編 ○ Payment Links編 ● Search APIを顧客管理やプロモーションに活用する 8 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 #LINEDC #JP_Stripes

Slide 9

Slide 9 text

Stripe Search API (2022春GA) ● データを検索するためのAPI ● Metadataなどへのクエリや、 AND / ORなどを利用した検索に 対応したAPI ● 対応リソース: ○ Payment Intents (支払) ○ Customers (顧客) ○ Invoices (請求書) ○ Prices (料金) ○ Products (商品) ○ Subscriptions (サブスク) ○ etc… 9 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 export const getMonthlyCustomerSucceededPayment = async (customerId) => { const targetMonth = dayjs() const result = await stripe.paymentIntents.search({ query: [ `created>${targetMonth.startOf('month').unix()}`, 'AND', `created<${targetMonth.endOf('month').unix()}`, 'AND', `customer:"${customerId}"`, 'AND', `status:"succeeded"`, 'AND', 'currency:"JPY"', ].join(' '), }) return result } #LINEDC #JP_Stripes

Slide 10

Slide 10 text

Search APIとLINEの連携例 ● LINEのユーザーIDを customers.metadataに保存 ● Customerに対するSearchで、 metadataを引っ掛けるクエリを書く ● `-field:null`で、 「NOT NULL」データの検索も可能 ● CRMやマーケティング用途で、 「LINEにメッセージを送れる顧客」の 検索も可能 10 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 // 特定のIDをmetadataに持つCustomerを検索 await stripe.customers.search({ query: `metadata['userId']:'${userId}'` }) // IDをmetadataに持つCustomerだけを検索 await stripe.customers.search({ query: `-metadata['userId']:null`, limit: 100, }) #LINEDC #JP_Stripes

Slide 11

Slide 11 text

Stripe Search APIの注意事項 ● ANDとORの併用はエラーになる ● API versionが 2020-08-27より古いと使えない ● データを更新した時、 検索結果に反映されるまで 少しラグがある(通常は 1分未満) ● Rate limitに注意 ○ 20 read operations per second ○ セールやプロモーションで 顧客が殺到すると・・・ ● ユーザー数が増加してきたら、 KVS系DBへの切り替えも要検討 11 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 await stripe.paymentIntents.search({ query: [ `status:"succeeded"`, 'AND', 'currency:"JPY"', 'OR', 'currency:"USD"', ].join(' '), }) // 実行結果 { type: 'StripeInvalidRequestError', raw: { message: 'Queries cannot have a mix of AND and OR.', type: 'invalid_request_error', #LINEDC #JP_Stripes

Slide 12

Slide 12 text

Agenda ● Search APIを使わずにLINEのユーザーとStripeのCustomerを連携 ● Stripe Search API入門 ● Search APIを使った顧客情報連携方法 ○ Checkout編 ○ Payment Links編 ● Search APIを顧客管理やプロモーションに活用する 12 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 #LINEDC #JP_Stripes

Slide 13

Slide 13 text

Checkoutの注文にLINEのユーザー情報を連携する ● Checkoutのセッション作成時に、 Customerを作るのが確実 ● Search APIでユーザーが すでに存在するかを確認 ● 存在しない場合は、データを作る ● 取得・生成したCustomer IDを、 Checkoutセッション作成時の引数へ 13 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 const putAndGetStripeCustomerIdByUserId = async (userId) => { const {data} = await stripe.customers.search({ query: `metadata['userId']:'${userId}'` }) if (data.length < 1) { const customer = await stripe.customers.create({ metadata: { userId, } }) return customer.id } return data[0].id } #LINEDC #JP_Stripes

Slide 14

Slide 14 text

Webhookを使えば、Payment Linksでも ● Client_reference_idに LINEのUser IDをセットする ● Checkoutのセッション完了時に、 Stripe Customerにデータを登録する ● Payment Linksの場合、 Customerが複数作成されることに注意 ● LIFFなどで コードを書いて実装する場合、 Checkoutの利用を推奨 14 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 liff.init({ liffId: import.meta.env.LIFF_ID, }).then(() => { // ログインしていない if (!liff.isLoggedIn()) { return liff.login() } return liff.getProfile() .then(profile => { window.open(`https://buy.stripe.com/test_xxxxx?client_reference_id=${profile.userI d}`) }) }).catch(e => { console.log(e) }) #LINEDC #JP_Stripes

Slide 15

Slide 15 text

Webhookを使って「決済後」にIDをセットする例 ● Customerの作成を後でやるタイプ ● カゴ落ちした顧客の Customerデータが作られない ● Payment Linksでの注文時にも、 顧客データを入れたい場合も使える ● カゴ落ちした顧客に リカバーメッセージなどを 出したい場合は不向き 15 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 if (( ['checkout.session.completed', 'checkout.session.async_payment_succeeded' ].includes(event.type)) && data.payment_status === 'paid' ) { const userId = data.metadata?.user_id if (userId) { // put user id to customer const customer = await stripe.customers.retrieve(data.customer) if (!customer.deleted && !customer.metadata.userId) { await stripe.customers.update(customer.id, { metadata: { userId } }) } } #LINEDC #JP_Stripes

Slide 16

Slide 16 text

CheckoutもPayment Linksもまとめて処理する場合 ● どちらもCheckoutの Webhookイベントを利用する ● Payment LinksのIDがあるかないかで 分岐が可能 ● LINEのUser IDを取得する場所の差を 吸収する ○ Plinks: data.client_reference_id ○ Checkout: data.metadata(など) 16 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 const data = event.data.object as any if ( ([ 'checkout.session.completed', 'checkout.session.async_payment_succeeded' ].includes(event.type)) ) { // Payment Links if (data.payment_link) { } else { // Checkout Sessions } } #LINEDC #JP_Stripes

Slide 17

Slide 17 text

Agenda ● Search APIを使わずにLINEのユーザーとStripeのCustomerを連携 ● Stripe Search API入門 ● Search APIを使った顧客情報連携方法 ○ Checkout編 ○ Payment Links編 ● Search APIを顧客管理やプロモーションに活用する 17 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 #LINEDC #JP_Stripes

Slide 18

Slide 18 text

特定の顧客にLINEでプロモメッセージ ● `-field:null`を応用して、 「LINE IDを持つ」顧客を検索 ● PaymentIntentsに検索をかけて、 「先月X円以上注文した顧客」を 絞り込み ● LINEのbot-sdkで、pushメッセージ ○ Stripeのクーポンコードを送付 ○ 特別価格のPayment Links URL ○ Checkout Sessionを使った 限定セールURL ○ etc… 18 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 await stripe.customers.search({ query: `-metadata['userId']:null` }) const {data: intents} = await stripe.paymentIntents.search({ query: [ `created>${targetMonth.startOf('month').unix()}`, 'AND', `created<${targetMonth.endOf('month').unix()}`, 'AND', `customer:"${customerId}"`, 'AND', `status:"succeeded"`, 'AND', 'currency:"JPY"', ].join(' '), }) #LINEDC #JP_Stripes

Slide 19

Slide 19 text

注文履歴を表示するLINE botの例 ● Search APIで顧客と注文を特定 ● CheckoutとInvoiceのデータを取得し、 注文詳細を取得 ○ pi.invoiceで請求書かを判定 ○ Checkout APIに pi.idを渡して検索 ○ レスポンスの型を揃えれば、 両方の履歴をまとめて出せる ● LINEのbot-sdkで、replyメッセージ ○ Flex Messageでリッチな表示に ○ 再注文ボタンなどがあると、 より便利かも 19 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 if (req.method?.toLocaleLowerCase() === 'post') { await runMiddleware(req, res, middleware); await Promise.all(req.body.events.map(async event => { if (event.type === 'message') { if (event.message.type !== 'text') return; if (!/注文履歴/.test(event.message.text)) return; const userId = event.source.userId; const {data: customers} = await stripe.customers.search({ query: `metadata['userId']:"${userId}"`, limit: 100, }) const histories = await listCheckoutHistory(customers[0].id) const orderedProducts = await listOrderedProducts(histories) await Promise.all(histories.map(async (history) => { await sendOrderHistory(history, event.replyToken, orderedProducts) })) Full: https://zenn.dev/link/comments/b5319e4143c0ab

Slide 20

Slide 20 text

[注文履歴]と入れると、注文履歴がでる 20 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 #LINEDC #JP_Stripes

Slide 21

Slide 21 text

まとめ ● Stripe Search APIで、LINEのUserとStripe Customerの連携が楽になる ● 注文履歴や、顧客ごとの月間決済高の検索もより手軽に ● 秒単位とはいえ、Rate Limitがある点には要注意 ○ MVPではSearch APIを使った紐付けをしつつ、 どこかでKVS系のデータストアへの移行も要検討 ● bot-sdk / Webhookと組み合わせて、よりリッチなチャットコマースを! 21 Stripe Search APIを利用した、LINEとStripeの顧客情報連携 #LINEDC #JP_Stripes