stripe google pay and webhook
google pay 主要前端 后台配合
<div class="col-sm-6 col-sm-offset-3" id="payment-request-button"></div> <div id="not-supported-error" style="display: none;">Unfortunately, this payment method is not supported by your device and/or browser...</div> <script type="text/javascript"> console.log('One Click Order ver.', '{{ payment_oneclickorder_version }}'); if (!window.Stripe) { $.getScript("https://js.stripe.com/v3/"); } </script> <script type="text/javascript"> var stripe = null; function finishPayment(clientSecret, successUrl, failureUrl) { stripe.handleCardPayment(clientSecret).then(function(result) { console.log(result); if (result.error) { var status = 'errored'; } else { var status = 'succeeded'; } $.ajax({ url: 'index.php?route=extension/payment/oneclickorder/send', type: 'post', data: {status:status,msg:result}, dataType: 'json', complete: function() { }, success: function(json) { if (json['success']) { location = successUrl; }else{ location = failureUrl; } }, error: function (xhr, ajaxOptions, thrownError) { console.log('>>>> one click order error'); console.log(xhr.responseText); console.log('<<<< one click order error'); } }); }); } function initButton() { var elements = stripe.elements(); var paymentRequest = stripe.paymentRequest({ country: 'US', requestPayerName: true, requestPayerEmail: true, // requestPayerPhone: true, currency: '{{ payment_oneclickorder_currency }}'.toLowerCase(), total: { label: 'Total', amount: parseInt('{{ payment_oneclickorder_amount }}'), } }); paymentRequest.canMakePayment().then(function (result) { console.log('canMakePayment', result); if (result) { var prButton = elements.create('paymentRequestButton', { paymentRequest: paymentRequest, }); prButton.mount('#payment-request-button'); } else { // apple pay is not available document.getElementById('payment-request-button').style.display = 'none'; document.getElementById('not-supported-error').style.display = 'block'; } }); //支付完成才能触发 paymenthod paymentRequest.on('paymentmethod', function (ev) { $.ajax({ url: 'index.php?route=extension/payment/oneclickorder/intentCheckout', type: 'post', dataType: 'json', data: { 'amount': '{{ payment_oneclickorder_amount }}', 'currency': '{{ payment_oneclickorder_currency }}', 'product_id': '{{ product_id }}', }, complete: function() { }, success: function(json) { var clientSecret = json['intent']['client_secret']; stripe.confirmPaymentIntent(clientSecret, { payment_method: ev.paymentMethod.id, }).then(function (confirmResult) { console.log(confirmResult); if (confirmResult.error) { ev.complete('fail'); } else { ev.complete('success'); console.log(clientSecret); finishPayment(clientSecret, json['success_url'], json['failure_url']); } }); }, error: function (xhr, ajaxOptions, thrownError) { console.log(xhr.responseText); alert('Payment error 405'); } }); }); } function initStripe() { if (window.Stripe) { stripe = Stripe('{{ payment_oneclickorder_public_key }}'); initButton(); } else { setTimeout(function() { initStripe() }, 50); } } initStripe(); </script>
后台
public function intentCheckout() { $this->load->language('extension/payment/oneclickorder'); $this->response->addHeader('Content-Type: application/json'); $this->load->model('checkout/order'); $order_info = $this->model_checkout_order->getOrder($this->session->data['order_id']); $json = array(); require_once('./system/library/stripe-php-7.82.0/init.php'); if($this->config->get('payment_oneclickorder_transaction_mode')) { \Stripe\Stripe::setApiKey($this->config->get('payment_oneclickorder_live_private')); } else { \Stripe\Stripe::setApiKey($this->config->get('payment_oneclickorder_test_private')); } $amount = (int)($this->currency->format($order_info['total'], $order_info['currency_code'], $order_info['currency_value'], false) * 100); $currency = $order_info['currency_code']; $intent = \Stripe\PaymentIntent::create([ "amount" => $amount, "currency" => $currency, "setup_future_usage" => "off_session", "payment_method_types" => ["card"], "capture_method" => 'automatic', "description" => 'OpenCart order #' . $order_info['order_id'], "metadata" => [ "source" => 'oneclickorder_checkout', "version" => $this->language->get('version'), "order_id" => $order_info['order_id'], "firstname" => $order_info['firstname'], "lastname" => $order_info['lastname'], "email" => $order_info['email'], ] ]); $json['intent'] = $intent; $json['success_url'] = $this->url->link('checkout/success', '', 'SSL'); $json['failure_url'] = $this->url->link('checkout/failure', '', 'SSL'); $this->response->setOutput(json_encode($json)); } public function webhook() { require_once('./system/library/stripe-php-7.82.0/init.php'); $this->response->addHeader('Content-Type: application/json'); $payload = @file_get_contents('php://input'); $this->logmsg('webhook back msg1 '.$payload); $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE']; $this->logmsg('webhook back time'.$sig_header); $event = null; try { $event = \Stripe\Webhook::constructEvent($payload, $sig_header, $this->config->get('payment_oneclickorder_signing_secret')); // $event = \Stripe\Webhook::constructEvent($payload, $sig_header, 'whsec_QkYVcixAn3cDHaOoXsg81sxngPPg1mIK'); $this->logmsg('webhook event msg'.$event); } catch(\UnexpectedValueException $e) { $this->logmsg('test 1'); // Invalid payload http_response_code(202); // PHP 5.4 or greater return; } catch(\Stripe\Error\SignatureVerification $e) { $this->logmsg('test 2'); // Invalid signature http_response_code(202); // PHP 5.4 or greater return; } if ($event->type != "payment_intent.succeeded") { $this->logmsg('test 3'); http_response_code(202); $json = array('skip' => 'Event type not correct'); $this->response->setOutput(json_encode($json)); return; } $this->logmsg('test 4'); if(isset($event->data->object->metadata->order_id)){ $orderId = $event->data->object->metadata->order_id; }elseif(isset($event->data->object->metadata->OrderId)){ $orderId = $event->data->object->metadata->OrderId; } $message = 'Payment Intent ID: '.$event->data->object->id. PHP_EOL .'Status: '. $event->data->object->type; $this->model_checkout_order->addOrderHistory($orderId, $this->config->get('payment_stripe_order_success_status_id'), $message, false); http_response_code(200); } /** * 修改订单状态 */ public function send() { $json = array(); // Save order in OC //1.18 start $this->logmsg('post '.$this->session->data['order_id'].json_encode($this->request->post)); $this->load->model('checkout/order'); if($this->request->post['status'] == 'succeeded' || $this->request->post['msg']['paymentIntent']['status'] == 'succeeded'){ $message = 'Payment Intent ID: '.$this->request->post['msg']['paymentIntent']['id']. PHP_EOL .'Status: '. $this->request->post['msg']['paymentIntent']['status']; $this->model_checkout_order->addOrderHistory($this->session->data['order_id'], $this->config->get('payment_stripe_order_success_status_id'), $message, false); $json['success'] = $this->url->link('checkout/success', '', 'SSL'); } else { $message = 'error:'.$this->request->post['msg']['error']; $this->model_checkout_order->addOrderHistory($this->session->data['order_id'], $this->config->get('payment_stripe_order_failed_status_id'), $message, false); } //end // todo order status id $this->response->addHeader('Content-Type: application/json'); $this->response->setOutput(json_encode($json)); } /** * 创建日志 */ public function logmsg($data = null) { if (!$data) { return; } $log = new Log('stripelog-googlepay-' . date('Ymd') . '.log'); if (is_array($data)) { $log->write(json_encode($data).'\r\n'); } else { $log->write($data); } }
第 1 步:安装 Stripe CLI
wget https://github.com/stripe/stripe-cli/releases/download/v1.7.9/stripe_1.7.9_linux_x86_64.tar.gz
yuminstall stripe/stripe-cli/stripe
数据返回:
$.ajax({ url: 'index.php?route=extension/payment/oneclickorder/send', type: 'post', data: {status:status,msg:result}, dataType: 'json', complete: function() { }, success: function(json) { if (json['success']) { location = successUrl; }else{ location = failureUrl; } }, error: function (xhr, ajaxOptions, thrownError) { console.log('>>>> one click order error'); console.log(xhr.responseText); console.log('<<<< one click order error'); } }); result:{"status":"succeeded","msg":{"paymentIntent":{"id":"pi_3KKGUALlDdeSga8u1SxgdtNO","object":"payment_intent","amount":"7445","automatic_payment_methods":"","canceled_at":"","cancellation_reason":"","capture_method":"automatic","client_secret":"pi_3KKGUALlDdeSga8u1SxgdtNO_secret_NlnCeiEzIxmm02lx4sJbc0urf","confirmation_method":"automatic","created":"1642746034","currency":"usd","description":"OpenCart order #16319","last_payment_error":"","livemode":"true","next_action":"","payment_method":"pm_1KKGU9LlDdeSga8u91641mEK","payment_method_types":["card"],"processing":"","receipt_email":"","setup_future_usage":"off_session","shipping":"","source":"","status":"succeeded"}}}
$payload = @file_get_contents('php://input');
返回数据: { "id": "evt_3KKDyvLlDdeSga8u0ourH91J", "object": "event", "api_version": "2020-08-27", "created": 1642736411, "data": { "object": { "id": "pi_3KKDyvLlDdeSga8u0NHENJtA", "object": "payment_intent", "amount": 2000, "amount_capturable": 0, "amount_received": 2000, "application": null, "application_fee_amount": null, "automatic_payment_methods": null, "canceled_at": null, "cancellation_reason": null, "capture_method": "automatic", "charges": { "object": "list", "data": [ { "id": "ch_3KKDyvLlDdeSga8u0SovpLUG", "object": "charge", "amount": 2000, "amount_captured": 2000, "amount_refunded": 0, "application": null, "application_fee": null, "application_fee_amount": null, "balance_transaction": "txn_3KKDyvLlDdeSga8u0xGY2Bl2", "billing_details": { "address": { "city": null, "country": null, "line1": null, "line2": null, "postal_code": null, "state": null }, "email": null, "name": null, "phone": null }, "calculated_statement_descriptor": "ELECBEE PTE. LTD.", "captured": true, "created": 1642736410, "currency": "usd", "customer": null, "description": "(created by Stripe CLI)", "destination": null, "dispute": null, "disputed": false, "failure_code": null, "failure_message": null, "fraud_details": { }, "invoice": null, "livemode": false, "metadata": { }, "on_behalf_of": null, "order": null, "outcome": { "network_status": "approved_by_network", "reason": null, "risk_level": "normal", "risk_score": 47, "seller_message": "Payment complete.", "type": "authorized" }, "paid": true, "payment_intent": "pi_3KKDyvLlDdeSga8u0NHENJtA", "payment_method": "pm_1KKDyvLlDdeSga8u3Dc8MPFl", "payment_method_details": { "card": { "brand": "visa", "checks": { "address_line1_check": null, "address_postal_code_check": null, "cvc_check": null }, "country": "US", "exp_month": 1, "exp_year": 2023, "fingerprint": "T6xnSOemGs7Dt9sM", "funding": "credit", "installments": null, "last4": "4242", "network": "visa", "three_d_secure": null, "wallet": null }, "type": "card" }, "receipt_email": null, "receipt_number": null, "receipt_url": "https://pay.stripe.com/receipts/acct_1IbJcpLlDdeSga8u/ch_3KKDyvLlDdeSga8u0SovpLUG/rcpt_L0ERKJeWPSOCsCYrCYzt0aBxuuUGuh5", "refunded": false, "refunds": { "object": "list", "data": [ ], "has_more": false, "total_count": 0, "url": "/v1/charges/ch_3KKDyvLlDdeSga8u0SovpLUG/refunds" }, "review": null, "shipping": { "address": { "city": "San Francisco", "country": "US", "line1": "510 Townsend St", "line2": null, "postal_code": "94103", "state": "CA" }, "carrier": null, "name": "Jenny Rosen", "phone": null, "tracking_number": null }, "source": null, "source_transfer": null, "statement_descriptor": null, "statement_descriptor_suffix": null, "status": "succeeded", "transfer_data": null, "transfer_group": null } ], "has_more": false, "total_count": 1, "url": "/v1/charges?payment_intent=pi_3KKDyvLlDdeSga8u0NHENJtA" }, "client_secret": "pi_3KKDyvLlDdeSga8u0NHENJtA_secret_3AP6QiC3wKAGVwUcCGfIDrCrp", "confirmation_method": "automatic", "created": 1642736409, "currency": "usd", "customer": null, "description": "(created by Stripe CLI)", "invoice": null, "last_payment_error": null, "livemode": false, "metadata": { }, "next_action": null, "on_behalf_of": null, "payment_method": "pm_1KKDyvLlDdeSga8u3Dc8MPFl", "payment_method_options": { "card": { "installments": null, "network": null, "request_three_d_secure": "automatic" } }, "payment_method_types": [ "card" ], "processing": null, "receipt_email": null, "review": null, "setup_future_usage": null, "shipping": { "address": { "city": "San Francisco", "country": "US", "line1": "510 Townsend St", "line2": null, "postal_code": "94103", "state": "CA" }, "carrier": null, "name": "Jenny Rosen", "phone": null, "tracking_number": null }, "source": null, "statement_descriptor": null, "statement_descriptor_suffix": null, "status": "succeeded", "transfer_data": null, "transfer_group": null } }, "livemode": false, "pending_webhooks": 2, "request": { "id": "req_fGASHL5gaZKKyn", "idempotency_key": "dae06fc0-11a2-4884-a8f1-5f0fb5222bcd" }, "type": "payment_intent.succeeded" }
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE']; t=1642736411,v1=e0f0f26770c43f8448a5d8ebc187a5cf6dd5fb07f554d4eb15c6d29f3116ccb5,v0=0133c67013048e67f219da15cfebbbe1afd576cea4392e49288e989354d8033b
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构