如何在React Native与Laravel后端的Stripe支付中添加iDEAL支付方式
为Laravel+React Native支付系统添加iDEAL支付方式
现有实现代码
React Native 前端代码
try { const response = await axios.post( `${API_BASE_URL}/store-order`, { company_id: cart[0].company_id, name: name, address: address, phone: phone, email: email, company: company, products: cart, price: getTotalPrice(), // delivery_at: delivery_at, }, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, } ); if (response.data) { const { clientSecret, orderId } = response.data; const { error } = await initPaymentSheet({ paymentIntentClientSecret: clientSecret, }); if (!error) { // Handle the confirmation of payment here handlePayment(clientSecret, orderId); } } } const handlePayment = async (clientSecret, orderId) => { const { error } = await presentPaymentSheet({ clientSecret }); if (error) { const newClientSecret = await fetchNewClientSecret(orderId); if (newClientSecret) { // Re-initialize the PaymentSheet with the new client secret const { error: initError } = await initPaymentSheet({ paymentIntentClientSecret: newClientSecret, }); if (initError) { console.log( `PaymentSheet re-initialization failed: ${initError.message}` ); } else { // Try the payment again await handlePayment(newClientSecret, orderId); } } else { console.log("Failed to fetch a new client secret"); } } else { const response = await axios.post( `${API_BASE_URL}/pay-order`, { order_id: orderId, }, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, } ); if (response.data.message == "Order paid.") { navigation.navigate("DrawerNavigation"); } else { Alert.alert("Payment Failed", "Do you want to retry?", [ { text: "No", style: "cancel" }, { text: "Yes", onPress: () => handlePayment(clientSecret, orderId) }, ]); } }
Laravel 后端代码
store-order 接口
Stripe::setApiKey(env('STRIPE_SECRET')); $paymentIntent = PaymentIntent::create([ 'amount' => $price * 100, // amount should be in cents 'currency' => 'eur', ]);
pay-order 接口
Stripe::setApiKey(env('STRIPE_SECRET')); $paymentIntent = PaymentIntent::retrieve($order->payment_intent);
实现iDEAL支付的修改步骤
1. Laravel后端调整
1.1 更新PaymentIntent创建逻辑
iDEAL属于异步跳转类支付,需要在创建PaymentIntent时指定支持的支付方式、关联订单元数据,并配置用户支付完成后的跳转地址:
Stripe::setApiKey(env('STRIPE_SECRET')); // 先创建订单实例,保存用户提交的基础订单信息 $order = // 你的订单创建逻辑 $paymentIntent = PaymentIntent::create([ 'amount' => $price * 100, // 金额以分为单位 'currency' => 'eur', 'payment_method_types' => ['card', 'ideal'], // 同时支持信用卡和iDEAL 'metadata' => [ 'order_id' => $order->id, // 关联订单ID,用于webhook处理 ], 'return_url' => 'your-app-deep-link://payment-complete?orderId=' . $order->id, // 替换为你的App Deep Link ]); // 将PaymentIntent ID保存到订单 $order->payment_intent_id = $paymentIntent->id; $order->save(); // 返回客户端所需参数 return response()->json([ 'clientSecret' => $paymentIntent->client_secret, 'orderId' => $order->id ]);
1.2 配置Stripe Webhook处理异步状态
iDEAL支付结果由Stripe通过webhook异步通知,需实现payment_intent.succeeded和payment_intent.payment_failed事件的处理:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Stripe\Stripe; use Stripe\Webhook; class StripeWebhookController extends Controller { public function handleWebhook(Request $request) { Stripe::setApiKey(env('STRIPE_SECRET')); $payload = $request->getContent(); $sigHeader = $request->header('Stripe-Signature'); $webhookSecret = env('STRIPE_WEBHOOK_SECRET'); try { $event = Webhook::constructEvent( $payload, $sigHeader, $webhookSecret ); } catch(\UnexpectedValueException $e) { return response()->json(['error' => 'Invalid payload'], 400); } catch(\Stripe\Exception\SignatureVerificationException $e) { return response()->json(['error' => 'Invalid signature'], 400); } // 处理支付成功事件 if ($event->type === 'payment_intent.succeeded') { $paymentIntent = $event->data->object; $order = \App\Models\Order::find($paymentIntent->metadata['order_id']); if ($order) { $order->status = 'paid'; $order->save(); } } // 处理支付失败事件 if ($event->type === 'payment_intent.payment_failed') { $paymentIntent = $event->data->object; $order = \App\Models\Order::find($paymentIntent->metadata['order_id']); if ($order) { $order->status = 'payment_failed'; $order->save(); } } return response()->json(['status' => 'success']); } }
1.3 修改pay-order接口逻辑
由于iDEAL支付是异步的,需改为检查PaymentIntent或订单的实际状态:
Stripe::setApiKey(env('STRIPE_SECRET')); $order = \App\Models\Order::find(request()->order_id); if (!$order) { return response()->json(['message' => 'Order not found'], 404); } $paymentIntent = PaymentIntent::retrieve($order->payment_intent_id); if ($paymentIntent->status === 'succeeded') { if ($order->status !== 'paid') { $order->status = 'paid'; $order->save(); } return response()->json(['message' => 'Order paid.']); } elseif ($paymentIntent->status === 'requires_action') { return response()->json(['message' => 'Payment requires action', 'clientSecret' => $paymentIntent->client_secret]); } else { return response()->json(['message' => 'Payment not completed yet.']); }
2. React Native前端调整
2.1 完善PaymentSheet初始化配置
添加商家名称和返回地址,确保iDEAL支付流程正常:
const { error } = await initPaymentSheet({ paymentIntentClientSecret: clientSecret, merchantDisplayName: '你的商家名称', // 显示在支付界面 returnURL: 'your-app-deep-link://payment-complete', // 和后端配置一致 });
2.2 监听Deep Link并轮询订单状态
用户完成iDEAL支付后会通过Deep Link返回App,需监听该链接并轮询订单状态:
import { Linking, useEffect } from 'react-native'; // 组件挂载时监听Deep Link useEffect(() => { const subscription = Linking.addEventListener('url', handleDeepLink); return () => subscription.remove(); }, []); const handleDeepLink = async (event) => { const url = event.url; if (url.includes('payment-complete')) { const params = new URLSearchParams(new URL(url).search); const orderId = params.get('orderId'); await pollOrderStatus(orderId); } }; // 轮询订单状态函数 const pollOrderStatus = async (orderId) => { const interval = setInterval(async () => { try { const response = await axios.post( `${API_BASE_URL}/pay-order`, { order_id: orderId }, { headers: { Authorization: `Bearer ${token}` } } ); if (response.data.message === 'Order paid.') { clearInterval(interval); navigation.navigate("DrawerNavigation"); } else if (response.data.message.includes('Payment failed')) { clearInterval(interval); Alert.alert("Payment Failed", "Do you want to retry?", [ { text: "No", style: "cancel" }, { text: "Yes", onPress: () => handlePayment(clientSecret, orderId) }, ]); } } catch (err) { console.error('Error checking order status:', err); } }, 3000); // 每3秒查询一次 };
2.3 调整handlePayment函数逻辑
移除支付完成后立即调用pay-order的逻辑,改为启动状态轮询:
const handlePayment = async (clientSecret, orderId) => { const { error } = await presentPaymentSheet({ clientSecret }); if (error) { // 保留原有错误处理逻辑 const newClientSecret = await fetchNewClientSecret(orderId); if (newClientSecret) { const { error: initError } = await initPaymentSheet({ paymentIntentClientSecret: newClientSecret, }); if (initError) { console.log(`PaymentSheet re-initialization failed: ${initError.message}`); } else { await handlePayment(newClientSecret, orderId); } } else { console.log("Failed to fetch a new client secret"); } } else { // 启动订单状态轮询 await pollOrderStatus(orderId); } };
注意事项
- 需在Stripe Dashboard中启用iDEAL支付方式。
- 测试iDEAL可使用Stripe提供的测试银行账户(如
NL91ABNA0417164300)。 - 需完成React Native的Deep Link配置(iOS修改Info.plist,Android修改AndroidManifest.xml),确保用户支付后能正确跳转回App。
内容的提问来源于stack exchange,提问作者Laurens Smid
相关产品推荐
相关产品推荐

