当前位置: 首页 > news >正文

ThinkPHP框架接入Stripe支付

我的框架是ThinkPHP5.0,后台是fastadmin

1.安装composer包,包名是 stripe/stripe-php

2.下单代码

<?php
namespace app\common\service;
use app\common\controller\Api;
use Stripe\Stripe;
use Stripe\Checkout\Session;
use think\Config;class StripeService  extends Api {protected $noNeedLogin = ['*'];protected $noNeedRight = ['*'];/*** 创建支付会话(APP调用)* @param string $orderNo 你的业务订单号* @param float  $amount  金额(英镑)* @return array*/public function createPayment($orderNo, $amount,$type) {Stripe::setApiKey(Config::get('stripe.secret_key'));try {$paymentIntent = \Stripe\PaymentIntent::create(['amount' => $amount * 100,'currency' => 'gbp','payment_method_types' => ['card'], // 指定卡支付方式,包含Apple Pay'metadata' => ['order_no' => $orderNo,'business_type' => $type]]);return ['paymentIntent' => $paymentIntent->client_secret, // 核心参数'merchantName' => Config::get('stripe.merchantName'),'publishKey'=>Config::get('stripe.publishKey')];} catch (\Exception $e) {throw new \Exception("支付创建失败: " . $e->getMessage());}}/*** 创建H5支付会话(网页端调用)* @param string $orderNo 业务订单号* @param float  $amount  金额(英镑)* @param string $type    业务类型* @return array*/public function createH5Payment($orderNo, $amount, $type) {Stripe::setApiKey(Config::get('stripe.secret_key'));try {$session = Session::create(['payment_method_types' => ['card'],'line_items' => [['price_data' => ['currency' => 'gbp','product_data' => ['name' => 'Order ' . $orderNo, // 可根据业务需求修改商品名称],'unit_amount' => $amount * 100, // 转换为分],'quantity' => 1,]],'mode' => 'payment','success_url' => request()->domain().'/api/notify/activity_stripe?session_id={CHECKOUT_SESSION_ID}','cancel_url' => request()->domain().'/api/notify/activity_cancel_stripe?session_id={CHECKOUT_SESSION_ID}','metadata' => ['order_no' => $orderNo,'business_type' => $type]]);return ['sessionId' => $session->id,'redirectUrl' => $session->url, // 前端需跳转到这个URL完成支付'publishKey' => Config::get('stripe.publishKey')];} catch (\Exception $e) {throw new \Exception('H5支付创建失败: ' . $e->getMessage());}}
}

2.处理webhook异步通知修改订单的支付状态。需要去Stripe后台设置接收webhook通知的url,Stripe的通知是所有的事件通过一个接口通知,所以在处理的时候要判断事件类型.

<?phpnamespace app\api\controller;use app\common\controller\Api;use app\common\model\ActivityOrder;
use app\common\model\GroupOrder;
use app\common\model\GroupSettle;
use think\Config;
use think\Log;
use Stripe\Webhook;
use Stripe\Exception\SignatureVerificationException;
class Stripenotify  extends Api
{protected $noNeedLogin = '*';protected $noNeedRight = '*';public function handle() {$payload = file_get_contents('php://input');$sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';try {// 验证事件签名$event = Webhook::constructEvent($payload,$sigHeader,Config::get('stripe.webhook_secret'));// 只处理checkout.session.completed事件if ($event->type !== 'checkout.session.completed') {Log::info("忽略非支付完成事件: " . $event->type);return json(['code' => 200, 'msg' => 'Event ignored']);}$session = $event->data->object;Log::info('____Stripe Session Data: ' . json_encode($session));// 提取业务类型$businessType = $session->metadata['business_type'] ?? 'unknown';Log::info("业务类型识别: " . $businessType);switch ($businessType) {case 'activity':$this->handleActivityPayment($session);break;case 'group':$this->handleGroupPayment($session);break;case 'settle':$this->handleSettlePayment($session);break;default:Log::warning("未知业务类型: " . $businessType);return json(['code' => 400, 'msg' => 'Unknown business type']);}return json(['code' => 200, 'msg' => '处理成功']);} catch (SignatureVerificationException $e) {Log::error("Webhook签名验证失败: " . $e->getMessage());return json(['code' => 403, 'msg' => 'Invalid signature'], 403);} catch (\Exception $e) {Log::error("Webhook处理异常: " . $e->getMessage());return json(['code' => 500, 'msg' => 'Server error'], 500);}}/*** 处理活动订单支付* @param \Stripe\Checkout\Session $session*/protected function handleActivityPayment($session) {try {// 1. 提取关键数据$metadata = $session->metadata;$orderNo = $metadata['order_no'] ?? null;$paidAmount = $session->amount_total / 100; // GBP$paymentId = $session->payment_intent;Log::info("开始处理活动订单 | 订单号: {$orderNo} | 支付金额: {$paidAmount}");// 2. 基础验证if (empty($orderNo)) {throw new \Exception("订单号缺失");}// 3. 获取订单数据$order = ActivityOrder::where('order_num', $orderNo)->find();if (!$order) {throw new \Exception("订单不存在: {$orderNo}");}// 4. 金额验证(防止篡改)
//            if (bccomp($paidAmount, $order->total_price, 2) !== 0) {
//                throw new \Exception("金额不一致 | 订单: {$orderNo} | 支付金额: {$paidAmount} | 订单金额: {$order->total_price}");
//            }// 5. 状态验证(防止重复处理)if ($order->status == 2) {Log::info("订单已支付,跳过处理: {$orderNo}");return;}// 6. 开启事务$order->startTrans();// 7. 更新订单状态$updateData = ['status' => '2',       // 假设2表示已支付'pay_order_num' => $paymentId,'pay_time' => time(),'pay_way' => '7'      // 支付方式标识];if (!$order->save($updateData)) {throw new \Exception("订单状态更新失败: {$orderNo}");}// 8. 提交事务$order->commit();Log::info("活动订单支付成功 | 订单号: {$orderNo}");} catch (\Exception $e) {// 回滚事务isset($order) && $order->rollback();Log::error("活动订单处理失败 | 订单号: {$orderNo} | 错误: " . $e->getMessage());throw $e; // 向上抛出以触发500响应}}/*** 团购订单逻辑* @param $session* @return void*/protected function handleGroupPayment($session){try {// 1. 提取关键数据$metadata = $session->metadata;$orderNo = $metadata['order_no'] ?? null;$paidAmount = $session->amount_total / 100; // GBP$paymentId = $session->payment_intent;Log::info("开始处理团购订单 | 订单号: {$orderNo} | 支付金额: {$paidAmount}");// 2. 基础验证if (empty($orderNo)) {throw new \Exception("团购订单号缺失");}// 3. 获取订单数据$order = GroupOrder::where('order_num', $orderNo)->find();if (!$order) {throw new \Exception("团购订单不存在: {$orderNo}");}// 4. 金额验证(防止篡改)
//            if (bccomp($paidAmount, $order->total_price, 2) !== 0) {
//                throw new \Exception("金额不一致 | 订单: {$orderNo} | 支付金额: {$paidAmount} | 订单金额: {$order->total_price}");
//            }// 5. 状态验证(防止重复处理)if ($order->status == 2) {Log::info("团购订单已支付,跳过处理: {$orderNo}");return;}// 6. 开启事务$order->startTrans();// 7. 更新订单状态$updateData = ['status' => '2',       // 假设2表示已支付'pay_order_num' => $paymentId,'pay_time' => time(),'pay_way' => '7'      // 支付方式标识];if (!$order->save($updateData)) {throw new \Exception("团购订单状态更新失败: {$orderNo}");}// 8. 提交事务$order->commit();Log::info("团购订单支付成功 | 订单号: {$orderNo}");} catch (\Exception $e) {// 回滚事务isset($order) && $order->rollback();Log::error("团购订单处理失败 | 订单号: {$orderNo} | 错误: " . $e->getMessage());throw $e; // 向上抛出以触发500响应}}/*** 团购订单逻辑* @param $session* @return void*/protected function handleSettlePayment($session){try {// 1. 提取关键数据$metadata = $session->metadata;$orderNo = $metadata['order_no'] ?? null;$paidAmount = $session->amount_total / 100; // GBP$paymentId = $session->payment_intent;Log::info("开始处理线下买单订单 | 订单号: {$orderNo} | 支付金额: {$paidAmount}");// 2. 基础验证if (empty($orderNo)) {throw new \Exception("线下买单订单号缺失");}// 3. 获取订单数据$order = GroupSettle::where('order_num', $orderNo)->find();if (!$order) {throw new \Exception("线下买单不存在: {$orderNo}");}// 4. 金额验证(防止篡改)
//            if (bccomp($paidAmount, $order->total_price, 2) !== 0) {
//                throw new \Exception("金额不一致 | 订单: {$orderNo} | 支付金额: {$paidAmount} | 订单金额: {$order->total_price}");
//            }// 5. 状态验证(防止重复处理)if ($order->status == 2) {Log::info("线下买单已支付,跳过处理: {$orderNo}");return;}// 6. 开启事务$order->startTrans();// 7. 更新订单状态$updateData = ['status' => '2',       // 假设2表示已支付'pay_order_num' => $paymentId,'pay_time' => time(),'pay_way' => '7'      // 支付方式标识];if (!$order->save($updateData)) {throw new \Exception("订单状态更新失败: {$orderNo}");}// 8. 提交事务$order->commit();Log::info("线下买单订单支付成功 | 订单号: {$orderNo}");} catch (\Exception $e) {// 回滚事务isset($order) && $order->rollback();Log::error("线下买单订单处理失败 | 订单号: {$orderNo} | 错误: " . $e->getMessage());throw $e; // 向上抛出以触发500响应}}
}

3.相关配置文件。建议测试的时候使用测试模式,换成测试的密钥,用测试银行卡模拟付款。

<?php
return ['secret_key'         => 'sk_test_51QTOQRGaWuPD3************IMFFHEyAzCFdPRlgUG007tpHxjwo', // 替换为你的测试密钥'webhook_secret'     => 'whsec_9m***********md9',   // Webhook 密钥'currency'           => 'gbp',         // 默认货币 英镑'merchantName'           => 'g***********y',         // 商户号'publishKey'=>'pk_test_51Q**************KpiYR'
];

4.调试程序的时候可以通过后台的控制台手动发布webhook推送

相关文章:

  • 【贪心】C++ 活动安排问题
  • 三角形分类程序黑盒实验(三)(包含完整源码)
  • 深入解析C++中的队列(queue)容器:原理、应用与最佳实践
  • 16613/16614/16615系列噪声源
  • 线上创业协会小程序源码介绍
  • 52常用控件_QCheckBox的使用
  • Vue.js:现代前端开发的轻量级框架
  • 蓝桥杯 6. k倍区间
  • 来个去照片背景的GUI程序
  • git 根据http url设置账号密码
  • MySQL之text字段详细分类说明
  • 2025年C#人力外包趋势与价值分析
  • android studio 运行java main报错
  • 环状双向链表创建,删除,插入,遍历详细讲解
  • DDoS本地防御与绕线防御的区别
  • 三轴云台之开源算法篇
  • ubuntu配置网卡为AP模式
  • Spring Boot 中配置 Redis 连接池的详细
  • 台账自动统计——餐饮物资管理台账——仙盟共创平台——未来之窗
  • 软件测试之测试数据生成(Excel版)
  • 做慕课的网站/性价比高的seo网站优化
  • 公司名高端大气不重名/优化设计答案
  • 网站域名space/网络营销的主要方式
  • 网站开发试验报告/广告资源网
  • 做网站架构需要什么工具/四平网络推广
  • 青岛找网站建设公司/营销客户管理系统