如何从 Stripe Payment Element 获取支付数据
How to capture payment data from Stripe Payment Element
我终于通过支付意图 API 在 Laravel 中实现了新的 Stripe 支付元素。但是,我现在需要捕获有关付款的信息并将它们存储在我的数据库中 - 具体来说,我需要以下数据:
- 交易编号
- 付款状态(failed/pending/successful等)
- 付款方式类型(card/Google Pay/Apple Pay/etc。)
- 实际向客户收取的金额
- 客户实际支付的币种
- 用户在付款表格中输入的邮政编码
所有这些信息似乎都可以在 Payment Intent object 中找到,但是几个 Stripe 指南的 none 指定了如何在服务器上捕获它们。我想避免使用 webhook,因为它们对于抓取和保存我已经检索到的数据来说似乎有点过分了。
这也无济于事,这要归功于 Stripe 文档的 AJAX/PHP 解决方案的设置方式,尝试在服务器端转储和终止任何变量会导致整个客户端流程中断,停止呈现支付表单并阻止任何调试信息。从本质上讲,这使得支付意图的整个实现 API 无法在服务器上进行调试。
以前来过这里的人知道我将如何捕获这些信息吗?
JavaScript/AJAX的相关部分:
const stripe = Stripe(<TEST_PUBLISHABLE_KEY>);
const fonts = [
{
cssSrc:
"https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&display=swap",
},
];
const appearance = {
theme: "stripe",
labels: "floating",
variables: {
colorText: "#2c2c2c",
fontFamily: "Open Sans, Segoe UI, sans-serif",
borderRadius: "4px",
},
};
let elements;
initialize();
checkStatus();
document
.querySelector("#payment-form")
.addEventListener("submit", handleSubmit);
// Fetches a payment intent and captures the client secret
async function initialize() {
const { clientSecret } = await fetch("/payment/stripe", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": document.querySelector('input[name="_token"]').value,
},
}).then((r) => r.json());
elements = stripe.elements({ fonts, appearance, clientSecret });
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost.rc/success"
},
});
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occured.");
}
setLoading(false);
}
// Fetches the payment intent status after payment submission
async function checkStatus() {
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
switch (paymentIntent.status) {
case "succeeded":
showMessage("Payment succeeded!");
break;
case "processing":
showMessage("Your payment is processing.");
break;
case "requires_payment_method":
showMessage("Your payment was not successful, please try again.");
break;
default:
showMessage("Something went wrong.");
break;
}
}
路由文件:
Route::post('/payment/stripe', [TransactionController::class, "stripe"]);
事务控制器:
public function stripe(Request $request) {
Stripe\Stripe::setApiKey(env(<TEST_SECRET_KEY>));
header('Content-Type: application/json');
try {
$paymentIntent = Stripe\PaymentIntent::create([
'amount' => 2.99,
'currency' => 'gbp',
'automatic_payment_methods' => [
'enabled' => true,
],
]);
$output = [
'clientSecret' => $paymentIntent->client_secret,
];
$this->storeStripe($paymentIntent, $output);
echo json_encode($output);
} catch (Stripe\Exception\CardException $e) {
echo 'Error code is:' . $e->getError()->code;
$paymentIntentId = $e->getError()->payment_intent->id;
$paymentIntent = Stripe\PaymentIntent::retrieve($paymentIntentId);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
}
如何从付款意向中获取上述信息以存储在我的数据库中?
我知道你不会喜欢这个,但我还是要说。老实说,我认为实施 webhook endpoint, listener and receiver function 是最好的选择,原因如下:
Stripe Payment Intent 在支付通过多个状态时捕获支付的生命周期。因为 Stripe 之外的各种支付网络不保证特定的响应时间,所以这些转换可以是异步的。
因此您无法确定什么时候是查询API您完成的付款意向的合适时间,除非您正在监听payment_intent.succeeded
事件.此外,在某些情况下,付款方式可能会在初始处理后很长时间内被拒绝(例如,疑似欺诈卡等)。使用 webhooks 方法可以让您了解这些更改。
最后,虽然您现在可能只关心将此数据存储在您的数据库中,但范围确实会增加并且尽早实施 webhook 侦听器意味着您将准备好解决方案,如果您需要采取其他措施,例如
- 向您的客户发送电子邮件通知
- 调整收入对帐
- 处理履行操作
- 其他内容......
在 RyanM 的推荐下,我选择了 webhook 解决方案,结果证明使用 Spatie's Stripe Webhooks 包比我预期的要容易(尽管它似乎是运行 比起修复潜在的 bug 更关心解决问题的人,所以选择 Stripe Cashier 可能会更容易和更愉快的开发人员体验。
请注意,默认情况下,Stripe webhooks return 事件对象本身包含与事件相关的其他对象,例如 payment_intent.succeeded 的 PaymentIntent,以及任何关联的 Charge 对象。因此,有必要深入了解一下以获得所需的所有信息。
$paymentIntent = $this->webhookCall->payload["data"]["object"];
$paymentID = $this->webhookCall->payload["data"]["object"]["id"]; // Transaction ID
$charge = $this->webhookCall->payload["data"]["object"]["charges"]["data"][0];
$transaction = Transaction::where("gateway_payment_id", $paymentID)->first();
$transaction->payment_status = strtoupper($paymentIntent["status"]); // Payment status
$transaction->payment_method = $charge["payment_method_details"]["type"]; // Payment method
$transaction->amount = ($paymentIntent["amount_received"]/100); // Amount charged, in pounds
$transaction->currency = strtoupper($paymentIntent["currency"]); // Currency charged in
$transaction->postcode = $charge["billing_details"]["address"]["postal_code"] ?? "N/A"; // Postcode if entered by the user - otherwise default to N/A
我终于通过支付意图 API 在 Laravel 中实现了新的 Stripe 支付元素。但是,我现在需要捕获有关付款的信息并将它们存储在我的数据库中 - 具体来说,我需要以下数据:
- 交易编号
- 付款状态(failed/pending/successful等)
- 付款方式类型(card/Google Pay/Apple Pay/etc。)
- 实际向客户收取的金额
- 客户实际支付的币种
- 用户在付款表格中输入的邮政编码
所有这些信息似乎都可以在 Payment Intent object 中找到,但是几个 Stripe 指南的 none 指定了如何在服务器上捕获它们。我想避免使用 webhook,因为它们对于抓取和保存我已经检索到的数据来说似乎有点过分了。
这也无济于事,这要归功于 Stripe 文档的 AJAX/PHP 解决方案的设置方式,尝试在服务器端转储和终止任何变量会导致整个客户端流程中断,停止呈现支付表单并阻止任何调试信息。从本质上讲,这使得支付意图的整个实现 API 无法在服务器上进行调试。
以前来过这里的人知道我将如何捕获这些信息吗?
JavaScript/AJAX的相关部分:
const stripe = Stripe(<TEST_PUBLISHABLE_KEY>);
const fonts = [
{
cssSrc:
"https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&display=swap",
},
];
const appearance = {
theme: "stripe",
labels: "floating",
variables: {
colorText: "#2c2c2c",
fontFamily: "Open Sans, Segoe UI, sans-serif",
borderRadius: "4px",
},
};
let elements;
initialize();
checkStatus();
document
.querySelector("#payment-form")
.addEventListener("submit", handleSubmit);
// Fetches a payment intent and captures the client secret
async function initialize() {
const { clientSecret } = await fetch("/payment/stripe", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": document.querySelector('input[name="_token"]').value,
},
}).then((r) => r.json());
elements = stripe.elements({ fonts, appearance, clientSecret });
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost.rc/success"
},
});
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occured.");
}
setLoading(false);
}
// Fetches the payment intent status after payment submission
async function checkStatus() {
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
switch (paymentIntent.status) {
case "succeeded":
showMessage("Payment succeeded!");
break;
case "processing":
showMessage("Your payment is processing.");
break;
case "requires_payment_method":
showMessage("Your payment was not successful, please try again.");
break;
default:
showMessage("Something went wrong.");
break;
}
}
路由文件:
Route::post('/payment/stripe', [TransactionController::class, "stripe"]);
事务控制器:
public function stripe(Request $request) {
Stripe\Stripe::setApiKey(env(<TEST_SECRET_KEY>));
header('Content-Type: application/json');
try {
$paymentIntent = Stripe\PaymentIntent::create([
'amount' => 2.99,
'currency' => 'gbp',
'automatic_payment_methods' => [
'enabled' => true,
],
]);
$output = [
'clientSecret' => $paymentIntent->client_secret,
];
$this->storeStripe($paymentIntent, $output);
echo json_encode($output);
} catch (Stripe\Exception\CardException $e) {
echo 'Error code is:' . $e->getError()->code;
$paymentIntentId = $e->getError()->payment_intent->id;
$paymentIntent = Stripe\PaymentIntent::retrieve($paymentIntentId);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
}
如何从付款意向中获取上述信息以存储在我的数据库中?
我知道你不会喜欢这个,但我还是要说。老实说,我认为实施 webhook endpoint, listener and receiver function 是最好的选择,原因如下:
Stripe Payment Intent 在支付通过多个状态时捕获支付的生命周期。因为 Stripe 之外的各种支付网络不保证特定的响应时间,所以这些转换可以是异步的。
因此您无法确定什么时候是查询API您完成的付款意向的合适时间,除非您正在监听payment_intent.succeeded
事件.此外,在某些情况下,付款方式可能会在初始处理后很长时间内被拒绝(例如,疑似欺诈卡等)。使用 webhooks 方法可以让您了解这些更改。
最后,虽然您现在可能只关心将此数据存储在您的数据库中,但范围确实会增加并且尽早实施 webhook 侦听器意味着您将准备好解决方案,如果您需要采取其他措施,例如
- 向您的客户发送电子邮件通知
- 调整收入对帐
- 处理履行操作
- 其他内容......
在 RyanM 的推荐下,我选择了 webhook 解决方案,结果证明使用 Spatie's Stripe Webhooks 包比我预期的要容易(尽管它似乎是运行 比起修复潜在的 bug 更关心解决问题的人,所以选择 Stripe Cashier 可能会更容易和更愉快的开发人员体验。
请注意,默认情况下,Stripe webhooks return 事件对象本身包含与事件相关的其他对象,例如 payment_intent.succeeded 的 PaymentIntent,以及任何关联的 Charge 对象。因此,有必要深入了解一下以获得所需的所有信息。
$paymentIntent = $this->webhookCall->payload["data"]["object"];
$paymentID = $this->webhookCall->payload["data"]["object"]["id"]; // Transaction ID
$charge = $this->webhookCall->payload["data"]["object"]["charges"]["data"][0];
$transaction = Transaction::where("gateway_payment_id", $paymentID)->first();
$transaction->payment_status = strtoupper($paymentIntent["status"]); // Payment status
$transaction->payment_method = $charge["payment_method_details"]["type"]; // Payment method
$transaction->amount = ($paymentIntent["amount_received"]/100); // Amount charged, in pounds
$transaction->currency = strtoupper($paymentIntent["currency"]); // Currency charged in
$transaction->postcode = $charge["billing_details"]["address"]["postal_code"] ?? "N/A"; // Postcode if entered by the user - otherwise default to N/A