如何从 Stripe Payment Element 获取支付数据

How to capture payment data from Stripe Payment Element

我终于通过支付意图 API 在 Laravel 中实现了新的 Stripe 支付元素。但是,我现在需要捕获有关付款的信息并将它们存储在我的数据库中 - 具体来说,我需要以下数据:

所有这些信息似乎都可以在 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