Yuanchieh's Blog

Yuanchieh's Blog

生命是長期而持續的累積

17 Nov 2018

Stripe 串金流教學 (上)

Stripe 是一間國際的金流支付公司,提供 client (Web / Android / iOS等)支付介面與 server-side API,用最短的時間就可以讓服務接上金流;
支援支付方式有Visa/Master Card等多間信用卡支付、Google Pay 、Apple Pay等等;
其中金流交易服務:

  1. 入門會員服務Payment(交易)、Billing(開立發票)、Connect(中間商,向A收錢並轉給B多少錢);
  2. 其他需要多花錢的加值服務:
    Sigma(支援SQL產生報表)、Altas(美國開公司)、Radar(金融詐騙偵測,預設有支援但付費有進階功能)
  3. 邀請制的加值服務:
    Terminal(整合硬體Reader,限定持有該硬體才能交易)、Issuing(可以自己發卡,Stripe 提供虛擬卡與實體卡發送)

這次主要嘗試 Payment / Billing 的串接,內容參考自官方文件,包含 client-side(採用 React) 與 server-side(採用 Nodejs SDK),不得不說 Stripe對於開發者非常友善,文件非常好懂且各式語言與插件SDK都支援完整,所以只需要將SDK換掉我想以下的概念都是相同的。

2018/11: 全球地區都可以付款,只是要開通 stripe帳戶只有26個國家,目前不支援台灣

主要介紹 Payment / Billing 的概念,以及試圖理解 Stripe背後的運行機制

Payment

分成兩步驟,在client-side 置入 stripe 付費元件(如 html form),用戶輸入後會產生 token轉交給 server,接著 server 用此 token驗證並實際扣款

在Stripe 中,它定義很多物件,每個物件都有各自參數與封裝的用法,所以文件非常的 OOP,所以對開發者來說很好理解與上手

Checkout

Checkout 是一個 Stripe 提供簡化過後的 Payment方式, 開發者只要接上 Stripe 提供的 Client SDK,用戶的交易細節就會直接丟給 Stripe 處理並以 Token方式回傳,開發者再去 Server Side 拿 Token做後續的應用。

整合 Stripe Client-side有兩種模式 SimpleCustom

Simple

用一個 <form/> 定義如何轉交給token給 server,接著內部嵌入 stripe script 並定義參數,最基本的幾個參數

<form action="your-server-side-code" method="POST">  <script    src="https://checkout.stripe.com/checkout.js" class="stripe-button"    data-key="YOUR_KEY"    data-amount="999"    data-name="COMPANY"    data-description="Example charge"    data-image="https://stripe.com/img/documentation/checkout/marketplace.png"    data-locale="auto"    data-currency="hkd">  </script></form>

對,就這麼簡單就完成了,接著就是到 form定義的 server api 收 token資料

Custom

如果希望有更細緻的用戶體驗,例如客製化自己的付費表單、顯示錯誤訊息等,就需要自己客製化,這部分也十分的簡單,用 vanilla js 也可以輕鬆完成

這部分程式碼可以參考文件

參數設定

以上的 Simple/Custome 就是一個基本的 Element,裡頭定義的 data-* 屬性則會決定產生的 Checkout 物件屬性,常見的屬性有

  1. data-key [required] 當帳號成功激活後,會有兩組 key / secret 組合,分別是 live / test ,對應就是正式機與測試機的概念
  2. token / source [required in custom mode]
    這兩個對應 callback function,分別對應收到 Token / Source,Token主要是server可以取得用戶的部分信用卡資料,Source則是代表用戶的其他付款方式(這些資料的取得要透過後續的參數設定)
  3. data-name / data-description / data-image / data-locale / data-amount [highly recommend]
    對應會顯示在 stripe 付費頁面的資訊,locale是設定表單語言
  4. data-zip-code / data-billing-address [highly recommend] 用戶的地址相關,Stripe推薦向用戶索取 zip-code,zip-code可以在後台設定當作 Radar的驗證條件,禁止可疑的Payment
  5. data-email / open / close [optional] 
    還有一些欄位可以定義就翻文件了,open / close則是 callback function會在表單開啟與關閉時候呼叫,同樣只用於 custom mode

特別注意,開發者完全不需要接觸到用戶的信用卡資料,在沒有謹慎評估之前,不要妄想儲存用戶信用卡資料!
因為如果要「合格」儲存用戶信用卡資料,必須過 PCI-DSS 國際信用卡組織聯合規範的支付安全認證,但這部分規範嚴格,而且必須定期做漏洞掃描
所以 Stripe 也提到他們為了讓開發者便利,所以信用卡資料都還是通過他們,僅回傳 Token形式供後續扣款使用,不用再多煩惱這些資安上的問題

Server-side

前端取得 Token 後,就可以往 server 丟,這個 Token是一次性扣款使用,此外就等同於 Source的效力,也就是用於實際扣款的支付方式,後續會詳細介紹什麼是 Source

以上是最基本的 Checkout 信用卡付款,因為信用卡是立即扣款就能知道結果,但如果需要支援如銀行轉帳等其他需要用戶額外授權與支付的流程,就需要以下更複雜的設計

多元支付方式

Stripe透過產生 Source 物件代表不同的支付方式,支付概念上可以分成
付款是同步與非同步完成(async vs sync) 錢如何從用戶轉出(push vs pull) 是否可重複使用(reusable)

例如說信用卡就是 sync + pull ,當用戶輸入信用卡後,就會立刻執行扣款動作(sync),就直接從用戶帳戶扣款或是產生支付紀錄(pull)

而像銀行產生虛擬帳號提供ATM付款是 async + push,用戶可能在過幾天才去付款(async),而用戶本身需要主動去產生支付的動作(走到ATM前面,也就是 push)

其他還有多種地域性的支付方式,因為不熟悉就不再多敘述,可以參考文件

Source 物件建立初始化為 source.pending,有 webhook 可以接收狀態的改變,當用戶授權後會觸發source.chargeable 、用戶拒絕授權source.failed 、過期source.canceled

Source 此時僅代表支付方式,實際的支付要透過 Source 建立 Charge 物件,Charge 物件同樣有幾個 webhook 可以串接,非同步支付初始化會是等待用戶支付charge.pending 、成功收到用戶支付charge.succeeded 、與支付失敗charge.failed

webhook 部分則是在後台設定。

以下擷取官方案例,僅作於理解使用

stripe.createSource({  type: 'ach_credit_transfer',  currency: 'usd',  owner: {    email: 'jenny.rosen@example.com',  },}).then(function(result) {  
  // 這裡會回傳 Source基本資料如 id等  
  // 還有用戶需要知道的轉帳資訊如銀行帳號
});


// 掛webhook,指定source.chargeable  
app.post("/webhook/to/source.chargable", (req, res) => {  
    console.log(req.body.read)  
})

某些支付方式是可以多次扣款,例如信用卡,Stripe 提供 Customer 概念,也就是可以創建一個 Customer 代表用戶,接著將 Source 綁定到該用戶上,一個用戶可以綁定多個 Source,到時候如果要扣款可以從 Source中選擇其中一個,相當的方便
需注意 Source 必須是 chargable 才可以扣款

// 建立 customer
stripe.customers.create({
    email: "paying.user@example.com",
    source: "src_18eYalAHEMiOZZp1l9ZTjSU0",
}, function(err, customer) { // asynchronously called
});

// 如果要扣款的話
stripe.charges.create({
        amount: 1099,
        currency: "eur",
        customer: "cus_AFGbOSiITuJVDs",
        source: "src_18eYalAHEMiOZZp1l9ZTjSU0",
        // 可以不指定 source,會自動找 customer 預設的 source
    },
    function(err, charge) {
        // asynchronously called
    });

小結!

看到是不是有點暈頭轉向了呢,說好的 Stripe 很簡單呢? 
或許這也是種 simplicity but not simple,要提供多元的支付方式勢必帶來邏輯的複雜性,但是我覺得 Stripe 透過 OOP梳理整個金融支付的過程,帶來極棒的 Developer Experience與 User Experience,接過台灣的紅藍綠你就會知道Stripe 有多棒了

廢話不多說,讓我來重新整理一翻

最一開始提的 Checkout 信用卡支付取得的Token,算是 Source的簡化版,我猜目的是為了讓最快速接串好金流的作法,而且信用卡的支付行為算是最簡單的。

Billings

用於重複性扣款與開立發票

發票狀態流程

發票流程

Invoice,代表發票的物件,一張發票可以多筆款項 InvoiceItems,因為不同的財務規劃而有比較多的狀態可以設

  1. 初始化為 draft ,此時發票的設定都還可以做調整,確定後或是預設一個小時後會變成 open
    刪除就變成 deleted
  2. open 則代表發票確認了,如果用戶付款了可以調整為 paid ,可以選擇觸發後續的發票寄送等流程;
    如果發現用戶破產之類無法支付,可以在後台將此筆發票設定為 uncollectible
    如果發票有誤需要作廢,則設為 Void

一次性開立發票 one-off invoices

var stripe = require("stripe")("sk_test_AyGgRZ5ZZkIETGtHDI3f1GAE");
// 先建立發票
stripe.invoiceItems.create({
    customer: "cus_DqZrTNCO4puf2p",
    amount: 2500,
    currency: "usd",
    description: "One-time setup fee"
}, function(err, invoiceItem) {
    // 針對某個用戶底下所有的InvoiceItems開立發票  
    stripe.invoices.create({
        customer: "cus_4fdAW5ftNQow1a",
        auto_advance: true
        // auto-finalize this draft after ~1 hour  
    }, function(err, invoice) { // asynchronously called  
    });
});

auto_advance 算是很重要的參數,Stripe 預設在 Invoice 創建之後會有自動化的動作,如一小時後自動轉成 open**,並會向用戶自動扣款 (預設 Source)接著轉成** paid,並Email 發送 Receipt 與 Invoice;
如果不想要記得設為 false

在開立發票上 Stripe 靈活性頗高,每一筆 InvoiceItem 可以設定費用/折扣/稅率,InvoiceItem 可以指定所屬的 Invoice,或是預設歸類到該用戶的下筆 open 的Invoice中

訂閱制扣款服務 subscription

有些雲端服務都是以年或是月的訂閱收費制度,Stripe 中可以定義 Product 與 Plan,例如說有一個 Product 是 SaaS服務

const product = stripe.products.create({
    name: 'My SaaS Platform',
    type: 'service',
    metadata: {
        // store anything you want
    }
});

針對這個 Product,可能會有許多的收費方式 Plan,可能是月繳/季繳/年繳或是有不同的適用期限、也可能有多種國家的不同定價等設定

const plan = stripe.plans.create({
    product: 'prod_CbvTFuXWh7BPJH',
    nickname: 'SaaS Platform USD',
    currency: 'usd',
    interval: 'month',
    amount: 10000,
});

Stripe 支援分層收費,可以指定多種模式,例如 當超過一個額度,每個商品變多少錢 / 又或是分多階層,超過部分算該價格的階梯形收費

當先前的 Custome想要購買時,會創建一個訂閱 Subscription 的物件,並決定要訂閱的 Plan,如果要一個訂閱多個 Plan,這些 Plan必須幣別相同且收費區間一致;

const subscription = stripe.subscriptions.create({  customer: 'cus_4fdAW5ftNQow1a',    coupon: 'free-period',  tax_percent: 6.34,  trial_end: 1542721841,  items: [{      plan: 'plan_CBXbz9i7AIOTzr'    }, {      plan: 'plan_IFuCu48Snc02bc',      quantity: 2,    }],});

此外,還有Coupon 可以設定,支援一次性 / 永久性 / 每次扣款用,還有 %跟數量的設定,相當有彈性

const coupon = stripe.coupons.create({  duration: 'once',  id: 'free-period',  percent_off: 100,});

建立 subscription 在沒有停止之前,Stripe 會自動依照時間定期扣款,並自動開立發票(可關閉),相當的方便。

升級或降級

如果用戶訂閱了普通版,在月中時突然想升級到專業版,該怎麼處理呢?
可以選擇將該用戶的 subscription 物件修改,更新訂閱的 Plan

stripe.subscriptions.update('sub_49ty4767H20z6a', {
    cancel_at_period_end: false,
    items: [{
        id: subscription.items.data[0].id,
        plan: 'plan_CBb6IXqvTLXp3f',
    }],
    proration_date: proration_date // Optional,詳看下方流程
});

更新容易,但是實際的扣款流程必須對應業務邏輯的處理,也就是收費的方式;

例如幾種案例

  1. 1/1開始訂閱 PlanA $30/mo,接著再 1/15 升級到 PlanB $45/mo
    因為都是每月扣款,所以 Stripe 預設會在 2/1 開始收 PlanB 也就是 $45元,所以理論上應該在 2/1才將用戶升級到 PlanB的服務
  2. 同上,但是用戶希望 1/15就升級到PlanB
    這時候有兩種做法
    a. 自己手動開立 Invoice,開立Invoice當下就會扣款並產生發票
    b. 設立 Proration,這是 Stripe 提供的方法,也就是在 update subscription 時指定 proration_data ,Stripe 會在下一次收費時間多開立這部分的金額
  3. 如果用戶從月付費改成年付費,會立即扣款
  4. 如果1/1已經扣款 PlanA的錢,用戶 1/15取消,Stripe 預設不會退款;
    也可以設定 cancel_at_period_end,讓用戶在月底才取消資格;
    如果有額外產生的代付款項服,必須要手動清除 Invoice才不會在月底多扣款一次

小結二

同整一下 Invoice / Invoice Item / Product / Plan / Subscription 關係

總結

如果要一次性付費,則可以使用 Payment,且 Payment 不需要綁定 Customer,同樣會開立發票;
以下更複雜的扣款方式方式要先在 Stripe 建立 Customer,可以利用 Invoice,分次建立 Invoice Item,最後再一次開在一張 Invoice 下完成扣款;
又或是建立 Product 與對應的付費機制 Plan,並用 Customer 角色訂閱 Subscription, Stripe會處理定期扣款等機制;
結合 Coupon 可以提供彈性的折價機制。

總體上 Stripe 最讓我覺得方便的是 Dashboard 可以設定,這樣就可以少蓋一個後台自己麻煩,而且把Customer / Payment / Billing 等分門別類,以及個別子項目,非常好管理金流服務。

Categories