佛系程序员
首页
栏目
标签
归档
关于
友链
佛系程序员
首页
栏目
标签
归档
友链
关于
充值页面 recharge.html 增加 PayPal
学习笔记
网站开发
发布日期: 2025-05-06 18:36:48
喜欢量: 6 个
阅读次数:
100
### 场景: 会员充值有日会员,月会员,年会员,不同价格。 在会员...
### 场景: 会员充值有日会员,月会员,年会员,不同价格。 在会员页面,有一个表格,然后不同会员不同价格,选择某一种会员,点订阅后,跳转到对应类型的充值页面。 现在我们要在充值页面recharge.html ,看到PayPal 按钮,并实现支付功能。 为了测试回调,我们需要安装ngork ### 一. PayPal 按钮配置里,调用顺序是: 1. 用户点击 PayPal 支付按钮 2. PayPal SDK 调用我们定义的 createOrder 函数 3. 该函数创建 PayPal 订单 4. 同时调用我们的后端 API /api/create-order 记录订单信息 5. 后端创建订单记录并返回订单 ID 6. PayPal 弹出支付窗口让用户完成支付 ### 二.主要代码: ```html <hr> <div class="text-center"> <div id="paypal-button-container"></div> </div> ``` ```html <!-- PayPal SDK --> {% if webconfigs.paypal_client_id %} <div id="paypal-loading" class="alert alert-info"> 正在加载支付模块... </div> <script src="https://www.paypal.com/sdk/js?client-id={{ webconfigs.paypal_client_id }}¤cy={{ webconfigs.paypal_currency }}" data-sdk-integration-source="button-factory"></script> <script> document.addEventListener('DOMContentLoaded', function () { const loadingDiv = document.getElementById('paypal-loading'); const buttonContainer = document.getElementById('paypal-button-container'); // 检查PayPal SDK是否成功加载 const checkPayPalSDK = setInterval(function () { if (typeof paypal !== 'undefined') { clearInterval(checkPayPalSDK); loadingDiv.style.display = 'none'; paypal.Buttons({ style: { layout: 'vertical', color: 'blue', shape: 'rect', label: 'paypal' }, createOrder: async function (data, actions) { // 1. 先创建 PayPal 订单 const order = await actions.order.create({ purchase_units: [{ amount: { currency_code: "USD", value: '{{ subscription.price }}' } }] }); // 2. 创建成功后,调用我们的 API 保存订单信息 try { console.log('PayPal订单创建成功,ID:', order); const response = await fetch('/{{ current_language }}/api/create-order', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: '{{ subscription.type }}', price: {{ subscription.price }}, orderId: order // 传递 PayPal orderId }) }); const orderData = await response.json(); console.log('订单创建结果:', orderData); if (!orderData.success) { alert('创建订单失败:' + orderData.message); return null; } return order; // 返回 PayPal 的订单号,供 PayPal 继续处理 return orderData.id; } catch (error) { console.error('创建订单错误:', error); alert('创建订单时发生错误'); return null; } }, onApprove: async function(data, actions) { try { console.log('支付已批准,订单ID:', data.orderID); const response = await fetch('/{{ current_language }}/api/confirm-payment', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ orderId: data.orderID }) }); const result = await response.json(); console.log('确认支付结果:', result); if (result.success) { // 显示成功 Modal 而不是 alert const successModal = new bootstrap.Modal(document.getElementById('successModal')); successModal.show(); } else { alert('支付确认失败:' + result.message); } } catch (error) { console.error('确认支付错误:', error); alert('确认支付时发生错误'); } }, onError: function(err) { console.error('PayPal 错误:', err); alert('支付过程中发生错误,请稍后重试'); } }).render('#paypal-button-container'); } }, 100); // 超时处理 setTimeout(function () { clearInterval(checkPayPalSDK); if (typeof paypalCheckout === 'undefined') { loadingDiv.className = 'alert alert-danger'; loadingDiv.innerHTML = 'PayPal支付模块加载失败,请刷新页面重试'; } }, 10000); // 10秒超时 }); </script> {% else %} <div class="alert alert-danger"> PayPal配置未完成,请联系管理员。 </div> {% endif %} ``` create_order 是在用户点击 PayPal 支付按钮时触发的。让我们看一下调用流程: 点击后,会调起: https://www.sandbox.paypal.com/v2/checkout/orders ```{"purchase_units":[{"amount":{"currency_code":"USD","value":"9.90"}}],"intent":"CAPTURE","application_context":{}} { "id": "4GF652603E990650R", "intent": "CAPTURE", "status": "CREATED", "purchase_units": [ { "reference_id": "default", "amount": { "currency_code": "USD", "value": "9.90" }, "payee": { "email_address": "sb-ashw330796726@business.example.com", "merchant_id": "UTZFMUHGSY4QJ" } } ], "create_time": "2025-05-05T19:26:08Z", "links": [ { "href": "https://api.sandbox.paypal.com/v2/checkout/orders/4GF652603E990650R", "rel": "self", "method": "GET" }, { "href": "https://www.sandbox.paypal.com/checkoutnow?token=4GF652603E990650R", "rel": "approve", "method": "GET" }, { "href": "https://api.sandbox.paypal.com/v2/checkout/orders/4GF652603E990650R", "rel": "update", "method": "PATCH" }, { "href": "https://api.sandbox.paypal.com/v2/checkout/orders/4GF652603E990650R/capture", "rel": "capture", "method": "POST" } ] } ``` http://127.0.0.1:8000/zh/api/confirm-payment `{"orderId":"64M72484L8900410S"}` 注意这个:"64M72484L8900410S" 是Paypal的订单号。 我们需要在 PayPal 的 Webhook 中处理支付回调。首先需要添加一个新的路由来处理 PayPal 的 Webhook 通知: ``` python:/Users/mac/data/pythonProjects/yijing/web/routers/payment.py from fastapi import Header @router.post("/api/paypal-webhook") async def paypal_webhook( request: Request, paypal_transmission_id: str = Header(...), paypal_transmission_time: str = Header(...), paypal_transmission_sig: str = Header(...), paypal_cert_url: str = Header(...), paypal_auth_algo: str = Header(...), db: Session = Depends(get_db) ): try: #获取请求体数据 payload = await request.json() logging.info(f"Received PayPal webhook: {payload}") #验证事件类型 event_type = payload.get('event_type') if event_type == 'PAYMENT.CAPTURE.COMPLETED': #获取订单信息 resource = payload.get('resource', {}) paypal_order_id = resource.get('supplementary_data', {}).get('related_ids', {}).get('order_id') if not paypal_order_id: logging.error("PayPal order ID not found in webhook payload") return {"success": False} #查找并更新订单 recharge = db.query(UserRechargeModel).filter( UserRechargeModel.trade_no == paypal_order_id, UserRechargeModel.status == 0 ).first() if recharge: #更新订单状态 recharge.status = 1 recharge.pay_time = datetime.now() #更新用户订阅信息 user = db.query(UserModel).filter(UserModel.id == recharge.user_id).first() subscription = db.query(SubscriptionTypeModel).filter( SubscriptionTypeModel.id == recharge.subscription_type_id ).first() if user and subscription: user.subscription_type = subscription.type user.subscription_expired_at = datetime.now() + timedelta(days=subscription.duration) user.divination_times = subscription.divination_times db.commit() logging.info(f"Successfully processed PayPal webhook for order {paypal_order_id}") return {"success": True} except Exception as e: db.rollback() logging.error(f"Error processing PayPal webhook: {str(e)}") return {"success": False} ``` ### 配置 Webhook 然后需要在 PayPal 开发者平台上配置 Webhook: 1. 登录 PayPal 开发者平台 2. 进入应用设置 3. 添加 Webhook URL:`https://你的域名/api/paypal-webhook` 4. 选择需要监听的事件类型:`PAYMENT.CAPTURE.COMPLETED` 这样即使用户在支付成功后关闭了网页,PayPal 也会通过 Webhook 通知我们支付状态,我们可以在后台完成订单处理和用户订阅更新。 注意事项: 1. Webhook URL 必须是 HTTPS 2. 建议添加 PayPal 的签名验证 3. 需要正确处理重复通知的情况 4. 建议添加日志记录以便追踪问题 ### 使用 ngrok 在开发测试阶段,我们可以使用 ngrok 来创建一个临时的公网地址,这样 PayPal 就可以访问到我们的本地开发环境。 1. 首先安装 ngrok: ```bash brew install ngrok ``` 2. 启动 ngrok 隧道(假设你的 FastAPI 运行在 8000 端口): ```bash ngrok http 8000 ``` 3. ngrok 会生成一个公网地址,类似:`https://xxxx.ngrok.io`,将这个地址配置到 PayPal 的 Webhook URL: `https://xxxx.ngrok.io/api/paypal-webhook` 这样在本地开发时就可以接收到 PayPal 的 Webhook 通知了。 注意事项: 1. 每次重启 ngrok 都会生成新的地址,需要在 PayPal 开发者平台更新 2. 免费版的 ngrok 每次重启会变更地址 3. 建议在日志中记录完整的 webhook 请求数据,方便调试 这种方式可以在本地开发环境中完整测试 PayPal 支付流程,包括异步通知。 ### 验证邮箱 这个错误表明需要先验证 ngrok 账号的邮箱地址。请按以下步骤操作: 1. 先登录或注册 ngrok 账号: ```bash ngrok config add-authtoken <your-auth-token> ``` 2. 访问 https://dashboard.ngrok.com/user/settings 3. 在设置页面找到邮箱验证部分,点击发送验证邮件 4. 登录邮箱,点击验证链接完成验证 5. 验证完成后,再次运行: ```bash ngrok http 8000 ``` 完成邮箱验证后,就可以正常使用 ngrok 了。这是一个一次性的设置,验证后就不会再遇到这个错误。
文章作者:
yyii
文章链接:
https://amtb.info/show/MTk1
版权声明:
本博客所有文章除特別声明外,转载请注明来源
佛系程序员
!
分享:
搜索