如何使用Unity在遊戲之中結合Stripe的API進行金流付款和訂閱制的服務的教學流程。會使用到UnityWebRequest去呼叫Stripe 的API取得付款連結,和BuildShip進行快速的後端Json驗證。
Stripe是什麼?

Stripe 是一個提供金流付費服務的的平台。
它提供各種支付方式、一次性和訂閱的付款服務
讓你可以輕鬆管理線上支付業務。
幾乎所有知名的網站
如 ChatGPT, Google, Netflix
都選擇使用Stripe進行訂閱付款。
Stripe API 付款流程

- 在遊戲中點擊購買按鈕
- 呼叫Stripe API → 取得付款連結
- 自動打開瀏覽器 → 用戶進行付款
- 遊戲在後台循環偵測付款狀態
- 處理付款完成後的邏輯
串接 Stripe API 的準備
注意: 註冊Stripe帳號需要有一間公司
這篇文章預設你已經註冊好Stripe帳號了。
1. 打開測試模式

登入Stripe網站後
右上角可以打開測試模式
在開發測試階段打開測試模式
就不會收取真錢。
在測試模式下
可以輸入測試信用卡 4242-4242-4242 來付款
2. 取得Stripe API密鑰

點擊開發人員→API密鑰→顯示密鑰。
API Key 會在呼叫Stripe API時用到
可以先保存起來。
3. 新增產品目錄
到產品目錄的頁面,點擊添加產品。

我們要新增兩個產品,第一個是一次性購買的

第二個是訂閱模式的:

4. 取得商品的價錢ID

把創建的產品點開 → 找到價格(再點開)

右上角有一個 price_xxx 的價錢ID
這個過後帶入參數會用到,先複製保存下來。
訂閱的產品也是一樣的操作。
5. 總結
操作完上面的步驟之後,你會得到
- Stripe API Key
- 一次性商品的 Price ID
- 訂閱模式的 Price ID
需要的 Stripe API

Stripe API的說明文件: https://docs.stripe.com/api
很多,但我們一共要知道的只有4個API:
1. Create Session API – 創建付款連結的API
Endpoint: https://api.stripe.com/v1/checkout/sessions
類型: POST
參數 | 解釋 |
payment_method_types[] | card |
mode | payment // 一次性 subscription // 訂閱制 |
success_url | 成功後跳轉的URL |
cancel_url | 失敗/取消後跳轉的URL |
customer_email | 用戶的Email |
line_items[0][price] | 價格的 Price ID // price_xxx |
line_items[0][quantity] | 購買數量 // 1 |
回傳的Json:
{
"id": "cs_test_a11YYufWQzNY63zpQ6QSNRQhkUpVph4WRmzW0zWJO2znZKdVujZ0N0S22u",
"object": "checkout.session",
"after_expiration": null,
"allow_promotion_codes": null,
"amount_subtotal": 2198,
"amount_total": 2198,
"automatic_tax": {
"enabled": false,
"liability": null,
"status": null
},
"billing_address_collection": null,
"cancel_url": null,
"client_reference_id": null,
"consent": null,
"consent_collection": null,
"created": 1679600215,
"currency": "usd",
"custom_fields": [],
"custom_text": {
"shipping_address": null,
"submit": null
},
"customer": null,
"customer_creation": "if_required",
"customer_details": null,
"customer_email": null,
"expires_at": 1679686615,
"invoice": null,
"invoice_creation": {
"enabled": false,
"invoice_data": {
"account_tax_ids": null,
"custom_fields": null,
"description": null,
"footer": null,
"issuer": null,
"metadata": {},
"rendering_options": null
}
},
"livemode": false,
"locale": null,
"metadata": {},
"mode": "payment",
"payment_intent": null,
"payment_link": null,
"payment_method_collection": "always",
"payment_method_options": {},
"payment_method_types": [
"card"
],
"payment_status": "unpaid",
"phone_number_collection": {
"enabled": false
},
"recovered_from": null,
"setup_intent": null,
"shipping_address_collection": null,
"shipping_cost": null,
"shipping_details": null,
"shipping_options": [],
"status": "open",
"submit_type": null,
"subscription": null,
"success_url": "https://example.com/success",
"total_details": {
"amount_discount": 0,
"amount_shipping": 0,
"amount_tax": 0
},
"url": "https://checkout.stripe.com/c/pay/cs_test_a11YYufWQzNY63zpQ6QSNRQhkUpVph4WRmzW0zWJO2znZKdVujZ0N0S22u#fidkdWxOYHwnPyd1blpxYHZxWjA0SDdPUW5JbmFMck1wMmx9N2BLZjFEfGRUNWhqTmJ%2FM2F8bUA2SDRySkFdUV81T1BSV0YxcWJcTUJcYW5rSzN3dzBLPUE0TzRKTTxzNFBjPWZEX1NKSkxpNTVjRjN8VHE0YicpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl"
}
JSON
我們需要用到的是最後一行的付款URL
2. Check Session API – 取得付款狀態的API3. Check Subscribe Status API – 檢查訂閱狀態的API4. Unsubscribe API – 取消訂閱的API
呼叫API的時候需要在Header 帶入Bearer Token – API Key
實作Unity C# 代碼
1. 先把Interface需要的功能列出來
public interface IPaymentServices
{
public void Initialize(string apiKey, Action<object> callback);
public void Pay(object sessionModel);
public void Subscribe(object sessionModel);
public void GetPaymentStatus(string sessionId, Action<object> callback);
public void GetSubscribeStatus(string subscribeId, Action<object> callback);
public void Unsubscribe(string subscribeId);
}
C#
2. 實作這個Interface
StripeServices.cs
public class StripeServices : IPaymentServices{
//定義 Stripe API 的 Endpoint
private string SessionRoute = "https://api.stripe.com/v1/checkout/sessions";
private string SubscribeRoute = "https://api.stripe.com/v1/subscriptions";
private string ApiKey = "sk_stripe_secret_key";
private Action<object> sessionIdCallback;
// 一開始使用的時候要把API Key帶進來
public void Initialize(string key, Action<object> callback)
{
ApiKey = key;
sessionIdCallback = callback;
}
// 一次性付款的時候呼叫
public void Pay(object sessionModel)
{
var session = sessionModel as StripeSessionRequestModel;
if (session == null)
{
Debug.Log("Session Info Is Null");
return;
}
session.mode = "payment";
CoroutineManager.StartStaticCoroutine(CreateCheckoutSession(session));
}
// 訂閱模式
public void Subscribe(object sessionModel)
{
var session = sessionModel as StripeSessionRequestModel;
if (session == null)
{
Debug.Log("Session Info Is Null");
return;
}
session.mode = "subscription";
session.successUrl += "?type=vip&subscribeId=null";
CoroutineManager.StartStaticCoroutine(CreateCheckoutSession(session));
}
// 取得付款狀況
public void GetPaymentStatus(string sessionId, Action<object> callback)
{
CoroutineManager.StartStaticCoroutine(GetSessionStatus(sessionId, callback));
}
// 取得訂閱狀況
public void GetSubscribeStatus(string subscribeId, Action<object> callback)
{
CoroutineManager.StartStaticCoroutine(GetSubscribeStatusCoroutine(subscribeId, callback));
}
// 取消訂閱
public void Unsubscribe(string subscribeId)
{
CoroutineManager.StartStaticCoroutine(CancelSubscribeCoroutine(subscribeId,null));
}
// 使用Unity的 Webrequest 呼叫Stripe 的API
private IEnumerator CreateCheckoutSession(StripeSessionRequestModel sessionModel)
{
var form = new WWWForm();
form.AddField("mode", sessionModel.mode);
form.AddField("customer_email", sessionModel.customerEmail);
form.AddField("line_items[0][price]", sessionModel.priceId);
form.AddField("line_items[0][quantity]", sessionModel.quantity);
form.AddField("success_url", sessionModel.successUrl);
form.AddField("cancel_url", sessionModel.cancelUrl);
form.AddField("payment_method_types[0]", "card");
using var www = UnityWebRequest.Post(SessionRoute, form);
www.SetRequestHeader("Authorization", "Bearer " + ApiKey);
www.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded");
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
Debug.LogError("Stripe Call Error: " + www.error);
}
else
{
Debug.Log("Stripe Response: " + www.downloadHandler.text);
var response = JsonConvert.DeserializeObject<StripeSessionResponseModel>(www.downloadHandler.text);
sessionIdCallback?.Invoke(response.id);
Application.OpenURL(response.url);
}
}
private IEnumerator GetSessionStatus(string sessionId, Action<object> callback)
{
using var www = UnityWebRequest.Get(SessionRoute + $"/{sessionId}");
www.SetRequestHeader("Authorization", "Bearer " + ApiKey);
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
Debug.LogError("Stripe Call Error: " + www.error);
}
else
{
Debug.Log("Stripe Response: " + www.downloadHandler.text);
var response = JsonConvert.DeserializeObject<StripeSessionStatusModel>(www.downloadHandler.text);
callback?.Invoke(response);
}
}
private IEnumerator GetSubscribeStatusCoroutine(string subscribeId, Action<object> callback)
{
using var www = UnityWebRequest.Get(SessionRoute + $"/{subscribeId}");
www.SetRequestHeader("Authorization", "Bearer " + ApiKey);
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
Debug.LogError("Stripe Call Error: " + www.error);
}
else
{
Debug.Log("Stripe Response: " + www.downloadHandler.text);
var response = JsonConvert.DeserializeObject<StripeSubscribeStatusModel>(www.downloadHandler.text);
callback?.Invoke(response);
}
}
private IEnumerator CancelSubscribeCoroutine(string subscribeId, Action<object> callback)
{
using var www = UnityWebRequest.Delete(SubscribeRoute + $"/{subscribeId}");
www.SetRequestHeader("Authorization", "Bearer " + ApiKey);
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
Debug.LogError("Stripe Call Error: " + www.error);
}
else
{
Debug.Log("Stripe Response: " + www.downloadHandler.text);
var response = JsonConvert.DeserializeObject<StripeSubscribeStatusModel>(www.downloadHandler.text);
callback?.Invoke(response);
}
}
}
C#CoroutineManager.csStripe API Model Class.cs
Unity UI Scene

1 – 製作一個簡單的UI介面:
- 付款按鈕
- 訂閱按鈕
- 取消訂閱按鈕
- 客戶的Member狀態
- 金幣數量的顯示
2 – 調用代碼:
StripeView.cs
public class StripeLabsView : MonoBehaviour
{
public string StripeSecretApiKey;
public string CustomerEmail;
[Header("One Time Pay Info")]
public string PayPriceId;
public string PayPriceQuantity;
public string PaySuccessUrl;
public string PayCancelUrl;
[Header("Subscribe Info")]
public string SubscribePriceId;
public string SubscribePriceQuantity;
public string SubscribeSuccessUrl;
public string SubscribeCancelUrl;
public Button PayBtn;
public Button SubscribeBtn;
public Button UnsubscribeBtn;
public Text MemberTypeText;
public Text CoinText;
public GameObject LoadingObj;
public Button CancelLoadingBtn;
public string GetCoinRoute = "https://bll2sj.buildship.run/GetCoin";
public string GetMemberRoute = "https://bll2sj.buildship.run/GetType";
private IPaymentServices PaymentServices = new StripeServices();
private string currentSessionId;
private StripeSessionStatusModel currentSessionStatus;
private void Start()
{
PayBtn.onClick.AddListener(PayBtnOnClick);
SubscribeBtn.onClick.AddListener(SubscribeBtnOnClick);
PaymentServices.Initialize(StripeSecretApiKey, sessionIdCallback);
CancelLoadingBtn.onClick.AddListener(() => LoadingObj.SetActive(false));
CoroutineManager.Instance.StartCoroutine(GetCoin());
CoroutineManager.Instance.StartCoroutine(GetMemberType());
}
private void sessionIdCallback(object obj)
{
currentSessionId = obj as string;
}
private void PayBtnOnClick()
{
var sessionInfo = new StripeSessionRequestModel()
{
mode = "payment",
customerEmail = CustomerEmail,
priceId = PayPriceId,
quantity = PayPriceQuantity,
successUrl = PaySuccessUrl,
cancelUrl = PayCancelUrl
};
PaymentServices.Pay(sessionInfo);
LoadingObj.SetActive(true);
CoroutineManager.Instance.StartCoroutine(CheckSessionStatusLoop());
}
private void SubscribeBtnOnClick()
{
var sessionInfo = new StripeSessionRequestModel()
{
mode = "subscription",
customerEmail = CustomerEmail,
priceId = SubscribePriceId,
quantity = SubscribePriceQuantity,
successUrl = SubscribeSuccessUrl,
cancelUrl = SubscribeCancelUrl
};
PaymentServices.Subscribe(sessionInfo);
LoadingObj.SetActive(true);
CoroutineManager.Instance.StartCoroutine(CheckSessionStatusLoop());
}
private IEnumerator GetCoin()
{
using var www = UnityWebRequest.Get(GetCoinRoute);
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
Debug.LogError("Get Coin Call Error: " + www.error);
}
else
{
CoinText.text = www.downloadHandler.text;
}
}
private IEnumerator GetMemberType()
{
using var www = UnityWebRequest.Get(GetMemberRoute);
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
Debug.LogError("Get Member Call Error: " + www.error);
}
else
{
MemberTypeText.text = www.downloadHandler.text;
}
}
private void SessionStatusCallBack(object obj)
{
currentSessionStatus = obj as StripeSessionStatusModel;
}
private IEnumerator CheckSessionStatusLoop()
{
currentSessionStatus = new StripeSessionStatusModel();
currentSessionStatus.payment_status = "waiting";
yield return new WaitForSeconds(5f);
while (currentSessionStatus.payment_status != "paid")
{
Debug.Log(currentSessionStatus.payment_status);
PaymentServices.GetPaymentStatus(currentSessionId, SessionStatusCallBack);
yield return new WaitForSeconds(3f);
}
LoadingObj.SetActive(false);
CoroutineManager.Instance.StartCoroutine(GetCoin());
CoroutineManager.Instance.StartCoroutine(GetMemberType());
}
}
3 – 在Inspector把需要的參數帶入:

BuildShip 後端架設

後端的實作邏輯我是用 Buildship
快速架設一個可以回傳資料庫的Json數值
主要用了幾個簡單的API
可以取得和設定金幣,Member Type:

它背後的資料庫表單長這樣:

Demo影片
總結
以上就是在Unity中實作Stripe API付款的流程
一些注意事項:
- 上線時要把Stripe API Key換成正式模式
- 付款後的邏輯你可以寫在後端,用付款跳轉後的連結去處理也可以寫在Unity裡面,判斷付款成功之後處理遊戲邏輯(但這樣要考慮好會不會有安全風險)
- 不想要從遊戲跳轉到網頁的話,可以在遊戲裡面是做一個嵌入式的瀏覽器,在遊戲內完成付款。
Discussion