Stripe 是一个全球知名的支付处理平台,支持多种支付方式和多个国家/地区的货币。通过集成 Stripe 订阅支付,独立站可以轻松接受来自世界各地的订单,实现便捷的跨境支付。
Stripe 订阅 API 概述
Stripe 订阅 API 提供了一种在网站上集成定期付款的简单方法。如果您希望在网站项目中实现会员订阅功能,则需要通过订阅付费来进行定期计费。Stripe 支付网关 有助于将定期付款与计划和订阅 API 集成。Stripe 订阅是一种快速有效的方式,允许您的网站会员使用信用卡在线购买会员资格,支持多种全球信用卡支付方式,如 Visa、Mastercard、American Express 等。
在 Stripe 订阅付款中,买家需要根据特定的时间间隔定期付费。您的网站会员可以在不离开网站的情况下订阅计划并使用信用卡/借记卡付款。在本教程中,我将向您展示如何 使用 PHP 集成 Stripe 订阅支付。
功能实现
我将实现以下功能,以通过 PHP 中的 Stripe Payment Gateway 接受订阅付款:
- 创建 HTML 表单以选择订阅计划并提供信用卡信息。
- 使用 Stripe JS 库将 Stripe 卡元素附加到 HTML 表单。
- 使用 Stripe API 安全地传输卡详细信息、验证并创建订阅。
- 使用 Stripe API 检索 PaymentIntent 和订阅信息。
- 通过支持强客户身份验证 (SCA) 流程的 3D 安全身份验证确认卡付款。
- 验证卡并使用 Stripe API 创建订阅计划。
- 将交易数据和订阅详细信息存储在数据库中并显示付款状态。
文件结构
在开始使用 PHP 集成 Stripe 之前,我们先看一下文件结构。
stripe_subscription_payment_php/
├── config.php 【存放 Stripe API 和数据库配置】
├── dbConnect.php 【用于 PHP 和 MySQL 连接数据库】
├── index.php 【Stripe 订阅表格】
├── payment_init.php 【处理订阅付款】
├── payment-status.php 【付款状态】
├── stripe-php/
├── js/
| └── checkout.js 【订阅结帐处理程序脚本】
└── css/
└── style.css 【页面样式】
Stripe 测试 API 密钥
在 Stripe 订阅支付网关上线之前,需要检查订阅流程是否正常。您需要测试 API 密钥数据来检查订阅付款流程。
- 登录您的 Stripe 帐户 并导航至 开发人员 » API 密钥 页面。
- 在 测试数据 块中,您将看到 API 密钥(可发布密钥和秘密密钥)列在标准密钥部分下。要显示 密钥,请单击“展示 test 密钥”按钮。
请先收集 可发布密钥 和 秘密密钥 以供稍后在脚本中使用。
创建数据库表
我们需要在数据库中创建三个数据表,分别为 plans、users、user_subscriptions。
- 创建 plans 表 来保存订阅套餐信息。
- 创建 users 表 来保存会员信息。
- 创建 user_subscriptions 表 来保存订阅信息。
sql
CREATE TABLE user_subscriptions
(
id
int(11) NOT NULL AUTO_INCREMENT,
user_id
int(11) NOT NULL DEFAULT 0 COMMENT ‘foreign key of “users” table’,
plan_id
int(5) DEFAULT NULL COMMENT ‘foreign key of “plans” table’,
payment_method
enum(‘stripe’) COLLATE utf8_unicode_ci NOT NULL DEFAULT ‘stripe’,
stripe_subscription_id
varchar(50) COLLATE utf8_unicode_ci NOT NULL,
stripe_customer_id
varchar(50) COLLATE utf8_unicode_ci NOT NULL,
stripe_payment_intent_id
varchar(50) COLLATE utf8_unicode_ci NOT NULL,
paid_amount
float(10,2) NOT NULL,
paid_amount_currency
varchar(10) COLLATE utf8_unicode_ci NOT NULL,
plan_interval
varchar(10) COLLATE utf8_unicode_ci NOT NULL,
plan_interval_count
tinyint(2) NOT NULL DEFAULT 1,
customer_name
varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
customer_email
varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
created
datetime NOT NULL,
plan_period_start
datetime DEFAULT NULL,
plan_period_end
datetime DEFAULT NULL,
status
varchar(50) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
在继续之前,我们需要将一些数据添加到 plans
数据库的表中,以便用户可以选择订阅计划。
sql
INSERT INTO plans
(id
, name
, price
, interval
) VALUES
(1, ‘每周订阅’, 25.00, ‘周’),
(2, ‘每月订阅’, 75.00, ‘月’),
(3, ‘年度订阅’, 799.00, ‘年’);
Stripe API 和数据库配置 (config.php)
在该 config.php
文件中,为 Stripe API 和数据库设置定义了常量变量。
Stripe API 常量:
STRIPE_API_KEY
– 指定 API 密钥。STRIPE_PUBLISHABLE_KEY
– 指定 API 可发布密钥。STRIPE_CURRENCY
– 货币代码。
数据库常量:
DB_HOST
– 指定数据库主机。DB_USERNAME
– 指定数据库用户名。DB_PASSWORD
– 指定数据库密码。DB_NAME
– 指定数据库名称。
php
connect_errno) {
printf(“Connect failed: %s\n”, $db->connect_error);
exit();
}
## Stripe 订阅表格 (index.php)
包含配置文件并从数据库中获取订阅计划。
php
prepare($sqlQ);
$stmt->execute();
$stmt->store_result();
?>
Stripe JS 库:
包含 Stripe.js v3 库,可帮助直接从浏览器安全地将敏感信息发送到 Stripe。
html
Checkout JS 脚本:
包括自定义脚本 (checkout.js
),以使用 JavaScript 处理 Stripe API 的订阅。
- 该
STRIPE_PUBLISHABLE_KEY
属性用于将 Stripe API Key 传递给 JS 脚本。
html
HTML 订阅表单:
最初,所有计划都列在下拉选择框下,并定义一个 HTML 表单来收集用户信息(姓名和电子邮件)和卡详细信息(卡号、到期日期和 CVC 号码)。
html
使用 Stripe 订阅
订阅结帐处理程序脚本 (checkout.js)
以下 JavaScript 代码用于使用 Stripe JS v3 库处理订阅过程。
- 创建 Stripe 对象的实例并设置可发布的 API 密钥。
- 创建 card 元素并将 cardElement 安装到 HTML 元素 (
#card-element
)。 - 将事件处理程序 (handleSubscrSubmit) 附加到订阅表单。
javascript
let STRIPE_PUBLISHABLE_KEY = document.currentScript.getAttribute(‘STRIPE_PUBLISHABLE_KEY’);
const stripe = Stripe(STRIPE_PUBLISHABLE_KEY);
const subscrFrm = document.querySelector(“#subscrFrm”);
subscrFrm.addEventListener(“submit”, handleSubscrSubmit);
let elements = stripe.elements();
var style = {
base: {
lineHeight: “30px”,
fontSize: “16px”,
border: “1px solid #ced4da”,
}
};
let cardElement = elements.create(‘card’, { style: style });
cardElement.mount(‘#card-element’);
cardElement.on(‘change’, function (event) {
displayError(event);
});
function displayError(event) {
if (event.error) {
showMessage(event.error.message);
}
}
async function handleSubscrSubmit(e) {
e.preventDefault();
setLoading(true);
let subscr_plan_id = document.getElementById("subscr_plan").value;
let customer_name = document.getElementById("name").value;
let customer_email = document.getElementById("email").value;
fetch("payment_init.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ request_type:'create_customer_subscription', subscr_plan_id: subscr_plan_id, name: customer_name, email: customer_email }),
})
.then(response => response.json())
.then(data => {
if (data.subscriptionId && data.clientSecret) {
paymentProcess(data.subscriptionId, data.clientSecret, data.customerId);
} else {
showMessage(data.error);
}
setLoading(false);
})
.catch(console.error);
}
function paymentProcess(subscriptionId, clientSecret, customerId){
let subscr_plan_id = document.getElementById(“subscr_plan”).value;
let customer_name = document.getElementById(“name”).value;
stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: customer_name,
},
}
}).then((result) => {
if(result.error) {
showMessage(result.error.message);
setLoading(false);
} else {
fetch("payment_init.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ request_type:'payment_insert', subscription_id: subscriptionId, customer_id: customerId, subscr_plan_id: subscr_plan_id, payment_intent: result.paymentIntent }),
})
.then(response => response.json())
.then(data => {
if (data.payment_id) {
window.location.href = 'payment-status.php?sid='+data.payment_id;
} else {
showMessage(data.error);
setLoading(false);
}
})
.catch(console.error);
}
});
}
function showMessage(messageText) {
const messageContainer = document.querySelector(“#paymentResponse”);
messageContainer.classList.remove("hidden");
messageContainer.textContent = messageText;
setTimeout(function () {
messageContainer.classList.add("hidden");
messageText.textContent = "";
}, 5000);
}
function setLoading(isLoading) {
if (isLoading) {
document.querySelector(“#submitBtn”).disabled = true;
document.querySelector(“#spinner”).classList.remove(“hidden”);
document.querySelector(“#buttonText”).classList.add(“hidden”);
} else {
document.querySelector(“#submitBtn”).disabled = false;
document.querySelector(“#spinner”).classList.add(“hidden”);
document.querySelector(“#buttonText”).classList.remove(“hidden”);
}
}
Stripe PHP 库
Stripe PHP 库 用于访问 PHP 中的 Stripe API。它有助于使用 Stripe API 创建客户、计划和订阅。所有需要的库文件都包含在我们的源代码中,您无需单独下载。
处理订阅付款(payment_init.php)
该服务器端脚本由 JavaScript 代码中的客户端 Fetch API 访问,以使用 Stripe API PHP 库创建 PaymentIntent、订阅并处理信用卡支付。
- 包括 Stripe PHP 库。
- 使用 Stripe 类的 setApiKey() 方法设置 API 密钥。
- 使用
file_get_contents()
在 PHP 中使用和从 POST 正文中检索 JSONjson_decode()
。 - 从 SESSION 中获取登录用户 ID。
请求处理
如果 create_customer_subscription
提交请求:
- 使用 PHP
$_POST
方法从表单字段获取选定的计划 ID 和买家信息。 - 根据选定的计划 ID 从数据库中获取计划详细信息。
- 使用 Stripe Customer API 的方法创建客户。
- 使用 Stripe Price API 的方法创建价格。
- 使用 Stripe Subscription API 的方法创建订阅。
- 将 subscriptionId、clientSecret、customerId 返回给客户端。
如果 payment_insert
提交请求:
- 使用 PHP
$_POST
方法从表单字段获取 payment_intent、subscription_id 和 customer_id。 - 使用 Stripe Customer API 的 create() 方法创建客户。
- 使用 Payment Intents API 的 update() 方法用客户 ID 更新 PaymentIntent。
- 使用 Stripe Customer API 的 retrieve() 方法检索客户详细信息。
- 检查充值是否成功并验证订阅状态。
- 使用 MySQL 准备语句和 PHP 将事务数据插入数据库。
- 将 DB 插入 ID 作为支付 ID 返回给客户端。
php
request_type == ‘create_customer_subscription’){
$subscr_plan_id = !empty($jsonObj->subscr_plan_id) ? $jsonObj->subscr_plan_id : ”;
$name = !empty($jsonObj->name) ? $jsonObj->name : ”;
$email = !empty($jsonObj->email) ? $jsonObj->email : ”;
$sqlQ = “SELECT `name`,`price`,`interval` FROM plans WHERE id=?”;
$stmt = $db->prepare($sqlQ);
$stmt->bind_param(“i”, $subscr_plan_id);
$stmt->execute();
$stmt->bind_result($planName, $planPrice, $planInterval);
$stmt->fetch();
$planPriceCents = round($planPrice * 100);
try {
$customer = \Stripe\Customer::create([
‘name’ => $name,
’email’ => $email
]);
} catch(Exception $e) {
$api_error = $e->getMessage();
}
if(empty($api_error) && $customer){
try {
$price = \Stripe\Price::create([
‘unit_amount’ => $planPriceCents,
‘currency’ => STRIPE_CURRENCY,
‘recurring’ => [‘interval’ => $planInterval],
‘product_data’ => [‘name’ => $planName],
]);
} catch (Exception $e) {
$api_error = $e->getMessage();
}
if(empty($api_error) && $price){
try {
$subscription = \Stripe\Subscription::create([
‘customer’ => $customer->id,
‘items’ => [[
‘price’ => $price->id,
]],
‘payment_behavior’ => ‘default_incomplete’,
‘expand’ => [‘latest_invoice.payment_intent’],
]);
} catch(Exception $e) {
$api_error = $e->getMessage();
}
if(empty($api_error) && $subscription){
$output = [
‘subscriptionId’ => $subscription->id,
‘clientSecret’ => $subscription->latest_invoice->payment_intent->client_secret,
‘customerId’ => $customer->id
];
echo json_encode($output);
} else {
echo json_encode([‘error’ => $api_error]);
}
} else {
echo json_encode([‘error’ => $api_error]);
}
} else {
echo json_encode([‘error’ => $api_error]);
}
} elseif($jsonObj->request_type == ‘payment_insert’){
$payment_intent = !empty($jsonObj->payment_intent) ? $jsonObj->payment_intent : ”;
$subscription_id = !empty($jsonObj->subscription_id) ? $jsonObj->subscription_id : ”;
$customer_id = !empty($jsonObj->customer_id) ? $jsonObj->customer_id : ”;
$subscr_plan_id = !empty($jsonObj->subscr_plan_id) ? $jsonObj->subscr_plan_id : ”;
$sqlQ = “SELECT `interval` FROM plans WHERE id=?”;
$stmt = $db->prepare($sqlQ);
$stmt->bind_param(“i”, $subscr_plan_id);
$stmt->execute();
$stmt->bind_result($interval);
$stmt->fetch();
$planInterval = $interval;
$stmt->close();
try {
$customer = \Stripe\Customer::retrieve($customer_id);
} catch(Exception $e) {
$api_error = $e->getMessage();
}
if(!empty($payment_intent) && $payment_intent->status == ‘succeeded’){
try {
$subscriptionData = \Stripe\Subscription::retrieve($subscription_id);
} catch(Exception $e) {
$api_error = $e->getMessage();
}
$payment_intent_id = $payment_intent->id;
$paidAmount = $payment_intent->amount;
$paidAmount = ($paidAmount / 100);
$paidCurrency = $payment_intent->currency;
$payment_status = $payment_intent->status;
$created = date(“Y-m-d H:i:s”, $payment_intent->created);
$current_period_start = $current_period_end = ”;
if(!empty($subscriptionData)){
$created = date(“Y-m-d H:i:s”, $subscriptionData->created);
$current_period_start = date(“Y-m-d H:i:s”, $subscriptionData->current_period_start);
$current_period_end = date(“Y-m-d H:i:s”, $subscriptionData->current_period_end);
}
$customer_name = $customer_email = ”;
if(!empty($customer)){
$customer_name = !empty($customer->name) ? $customer->name : ”;
$customer_email = !empty($customer->email) ? $customer->email : ”;
if(!empty($customer_name)){
$name_arr = explode(‘ ‘, $customer_name);
$first_name = !empty($name_arr[0]) ? $name_arr[0] : ”;
$last_name = !empty($name_arr[1]) ? $name_arr[1] : ”;
}
if(empty($userID)){
$sqlQ = “INSERT INTO users (first_name, last_name, email) VALUES (?,?,?)”;
$stmt = $db->prepare($sqlQ);
$stmt->bind_param(“sss”, $first_name, $last_name, $customer_email);
$insertUser = $stmt->execute();
if($insertUser){
$userID = $stmt->insert_id;
}
}
}
$sqlQ = “SELECT id FROM user_subscriptions WHERE stripe_payment_intent_id = ?”;
$stmt = $db->prepare($sqlQ);
$stmt->bind_param(“s”, $payment_intent_id);
$stmt->execute();
$stmt->bind_result($id);
$stmt->fetch();
$prevPaymentID = $id;
$stmt->close();
$payment_id = 0;
if(!empty($prevPaymentID)){
$payment_id = $prevPaymentID;
} else {
$sqlQ = “INSERT INTO user_subscriptions (user_id, plan_id, stripe_subscription_id, stripe_customer_id, stripe_payment_intent_id, paid_amount, paid_amount_currency, plan_interval, customer_name, customer_email, created, plan_period_start, plan_period_end, status) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)”;
$stmt = $db->prepare($sqlQ);
$stmt->bind_param(“iisssdssssssss”, $userID, $subscr_plan_id, $subscription_id, $customer_id, $payment_intent_id, $paidAmount, $paidCurrency, $planInterval, $customer_name, $customer_email, $created, $current_period_start, $current_period_end, $payment_status);
$insert = $stmt->execute();
if($insert){
$payment_id = $stmt->insert_id;
$sqlQ = “UPDATE users SET subscription_id=? WHERE id=?”;
$stmt = $db->prepare($sqlQ);
$stmt->bind_param(“ii”, $payment_id, $userID);
$update = $stmt->execute();
}
}
$output = [
‘payment_id’ => base64_encode($payment_id)
];
echo json_encode($output);
} else {
echo json_encode([‘error’ => ‘事务处理失败!’]);
}
}
?>
付款状态(payment-status.php)
根据 paymentIntent.status 和 3D Secure 身份验证的付款确认,用户将被重定向到此页面。
- 使用 PHP 和 MySQL 从数据库中获取交易数据。
- 在网页上显示订阅状态和交易信息。
php
prepare($sqlQ);
$stmt->bind_param(“s”, $subscr_id);
$stmt->execute();
$stmt->store_result();
if($stmt->num_rows > 0){
$stmt->bind_result($subscription_id, $stripe_subscription_id, $paid_amount, $paid_amount_currency, $plan_interval, $plan_period_start, $plan_period_end, $customer_name, $customer_email, $subscr_status, $plan_name, $plan_amount);
$stmt->fetch();
$status = ‘success’;
$statusMsg = ‘您的订阅付款成功了!’;
} else {
$statusMsg = “交易失败了!”;
}
} else {
header(“Location: index.php”);
exit;
}
?>
<h1 class="<?php echo $status; ?>"><?php echo $statusMsg; ?></h1>
<h4>付款信息</h4>
<p><b>参考编号:</b> <?php echo $subscription_id; ?></p>
<p><b>订阅ID:</b> <?php echo $stripe_subscription_id; ?></p>
<p><b>支付金额:</b> <?php echo $paid_amount.' '.$paid_amount_currency; ?></p>
<p><b>支付状态:</b> <?php echo $subscr_status; ?></p>
<h4>订阅信息</h4>
<p><b>套餐名称:</b> <?php echo $plan_name; ?></p>
<p><b>数量:</b> <?php echo $plan_amount.' '.STRIPE_CURRENCY; ?></p>
<p><b>套餐期限:</b> <?php echo $plan_interval; ?></p>
<p><b>期限开始:</b> <?php echo $plan_period_start; ?></p>
<p><b>期限结束:</b> <?php echo $plan_period_end; ?></p>
<h4>顾客信息</h4>
<p><b>顾客名称:</b> <?php echo $customer_name; ?></p>
<p><b>顾客电子邮箱:</b> <?php echo $customer_email; ?></p>
<h1 class="error">您的交易失败了!</h1>
<p class="error"><?php echo $statusMsg; ?></p>
Stripe 订阅支付功能
测试卡详情
要测试 Stripe 订阅支付 API 集成,请使用以下测试卡号、有效的未来到期日期和任何随机 CVC 号。
- 4242424242424242 – 签证
- 4000056655665556 – Visa(借记卡)
- 5555555555554444 – 万事达卡
- 5200828282828210 – 万事达卡(借记卡)
- 378282246310005 – 美国运通
- 6011111111111117 – 发现
3D安全认证测试卡号:
- 4000002500003155 – 需要 3D 安全身份验证 (SCA)
- 4000002760003184 – 需要 3D 安全身份验证 (SCA)
启用 Stripe 支付网关
订阅 API 集成完成且支付流程正常运行后,请按照以下步骤启用 Stripe 支付网关。
- 登录您的 Stripe 帐户 并导航至 开发人员 » API 密钥页面。
- 从 实时数据 部分收集 API 密钥(可发布密钥和秘密密钥)。
- 在该
config.php
文件中,将测试 API 密钥(可发布密钥和秘密密钥)替换为实时 API 密钥(可发布密钥和秘密密钥)。
php
define(‘STRIPE_API_KEY’, ‘Insert_Stripe_Live_API_Secret_Key_HERE’);
define(‘STRIPE_PUBLISHABLE_KEY’, ‘Insert_Stripe_Live_API_Publishable_Key_HERE’);
总结
Stripe 订阅支付 API 是在线接受订阅信用支付的最简单选项。您可以使用 Stripe API 和 PHP 将定期计费功能添加到 Web 应用程序。我们的示例脚本可帮助您通过 Stripe 在特定时间间隔内定期向用户收费。此外,还集成了 3D 安全身份验证 功能,使该 Stripe 集成脚本可以用于 SCA(强客户身份验证)。可以根据您的需要增强此 Stripe 订阅支付脚本 的功能。