如何在现有React PayPal结账代码中集成月度定期支付功能?
如何在现有PayPal React代码中集成月度订阅支付功能
当然可以集成月度固定金额的捐款功能,PayPal的**订阅(Subscriptions)**功能正好适配这类定期扣款场景。下面是针对你现有代码的具体修改方案:
1. 添加订阅状态管理
首先在Redux的inputMoneyAmount reducer中新增状态字段,区分用户选择的是一次性支付还是月度订阅:
// reducer初始状态 const initialState = { amount: 0, flowAmount: 0, isMonthlySubscription: false // 新增订阅状态字段 }; // 新增对应的action export const setIsMonthlySubscription = (isMonthly) => ({ type: 'SET_IS_MONTHLY_SUBSCRIPTION', payload: isMonthly }); // reducer处理逻辑 case 'SET_IS_MONTHLY_SUBSCRIPTION': return { ...state, isMonthlySubscription: action.payload };
2. 添加订阅切换UI
在组件的金额选择区域下方,添加切换按钮让用户选择是否开启月度捐款:
<div className="mt-6 mb-4"> <button onClick={() => dispatch(setIsMonthlySubscription(!isMonthly))} className={`px-6 py-2 rounded-lg font-medium ${ isMonthly ? 'bg-blue-600 hover:bg-blue-700' : 'bg-gray-600 hover:bg-gray-700' }`} > {isMonthly ? '✅ 当前为月度自动捐款' : '🔄 切换为月度自动捐款'} </button> </div>
同时在组件中获取该状态:
const isMonthly = useSelector(state => state.inputMoneyAmount.isMonthlySubscription);
3. 修改PayPal按钮逻辑,支持双模式切换
修改ButtonWrapper中的PayPalButtons组件,根据isMonthly状态切换一次性支付/订阅逻辑:
<PayPalButtons style={style} disabled={false} forceReRender={[amount, currency, style, isMonthly]} fundingSource={undefined} // 原有一次性支付逻辑 createOrder={!isMonthly ? (data, actions) => { return actions.order .create({ purchase_units: [ { amount: { currency_code: currency, value: amount, }, }, ], }) .then((orderId) => { return orderId; }); } : undefined} // 新增订阅创建逻辑 createSubscription={isMonthly ? (data, actions) => { return actions.subscription.create({ // 推荐在PayPal后台预先创建订阅计划,直接填写plan_id plan_id: 'YOUR_PAYPAL_SUBSCRIPTION_PLAN_ID', // 若需前端动态生成计划(不推荐,建议后台创建更安全),可替换为以下配置: /* application_context: { brand_name: "Forward Ukraine Ministries", user_action: "SUBSCRIBE_NOW" }, plan: { product_id: "YOUR_PAYPAL_PRODUCT_ID", // 需先在PayPal后台创建产品 name: "月度捐款", description: "每月固定金额支持乌克兰人道主义救援", billing_cycles: [ { frequency: { interval_unit: "MONTH", interval_count: 1 }, tenure_type: "REGULAR", sequence: 1, total_cycles: 0, // 0表示无限期扣款,直到用户取消 pricing_scheme: { fixed_price: { currency_code: currency, value: amount } } } ], payment_preferences: { auto_bill_outstanding: true, setup_fee: { currency_code: currency, value: "0" }, setup_fee_failure_action: "CONTINUE", payment_failure_threshold: 3 } } */ }).then(subscriptionId => { // 将订阅ID同步到后端,用于后续管理 console.log('订阅ID:', subscriptionId); return subscriptionId; }); } : undefined} // 一次性支付确认逻辑(原有) onApprove={!isMonthly ? (data, actions) => { return actions.order.capture().then(function (details) { console.log('一次性支付成功:', details); }); } : (data, actions) => { // 订阅确认逻辑(新增) return actions.subscription.get().then(details => { console.log('订阅创建成功:', details); }); }} />
关键注意事项
- PayPal后台配置:需在PayPal开发者平台(沙盒/生产环境)创建产品和订阅计划,获取对应的
product_id和plan_id。 - Webhook配置:必须配置PayPal Webhook接收订阅状态通知(如扣款成功、失败、用户取消订阅等),需后端配合处理。
- 客户端ID替换:将代码中的
"client-id": "test"替换为你在PayPal平台获取的真实客户端ID(测试用沙盒ID,生产用正式ID)。 - 金额验证:确保月度捐款金额为合法数值,与一次性支付的金额验证逻辑保持一致。
修改后的完整组件代码
import React, { useEffect } from "react"; import { PayPalScriptProvider, PayPalButtons, usePayPalScriptReducer, } from "@paypal/react-paypal-js"; import imgLogo from "../../../images/FUMlogo.png"; import imgColageDesk from "../../../images/Donate/colageDesk.png"; import DonateBTN from "../../UI/Button/DonateBTN/DonateBTN"; import DonateOther from "../../UI/Button/DonateBTN/DonateOther"; import { useDispatch, useSelector } from "react-redux"; import { setAmountMoney, setFlowAmountMoney, setIsMonthlySubscription } from "../../../Store/Reducers/inputMoneyAmount"; export default function Donate() { const amountDonate = useSelector((state) => state.inputMoneyAmount.amount); const flowAmountDonate = useSelector( (state) => state.inputMoneyAmount.flowAmount ); const isMonthly = useSelector(state => state.inputMoneyAmount.isMonthlySubscription); const dispatch = useDispatch(); const amount50 = () => { dispatch(setFlowAmountMoney(false)); dispatch(setAmountMoney(50)); }; const amount100 = () => { dispatch(setFlowAmountMoney(false)); dispatch(setAmountMoney(100)); }; const amount200 = () => { dispatch(setFlowAmountMoney(false)); dispatch(setAmountMoney(200)); }; const amount = amountDonate ? amountDonate : flowAmountDonate + ""; const currency = "USD"; const style = { layout: "vertical", color: "blue" }; const ButtonWrapper = ({ currency, showSpinner }) => { const [{ options, isPending }, dispatch] = usePayPalScriptReducer(); useEffect(() => { dispatch({ type: "resetOptions", value: { ...options, currency: currency, }, }); }, [currency, showSpinner]); return ( <> {showSpinner && isPending && <div className="spinner" />} <PayPalButtons style={style} disabled={false} forceReRender={[amount, currency, style, isMonthly]} fundingSource={undefined} createOrder={!isMonthly ? (data, actions) => { return actions.order .create({ purchase_units: [ { amount: { currency_code: currency, value: amount, }, }, ], }) .then((orderId) => { return orderId; }); } : undefined} createSubscription={isMonthly ? (data, actions) => { return actions.subscription.create({ plan_id: 'YOUR_PAYPAL_SUBSCRIPTION_PLAN_ID', }).then(subscriptionId => { console.log('订阅ID:', subscriptionId); return subscriptionId; }); } : undefined} onApprove={!isMonthly ? (data, actions) => { return actions.order.capture().then(function (details) { console.log('一次性支付成功:', details); }); } : (data, actions) => { return actions.subscription.get().then(details => { console.log('订阅创建成功:', details); }); }} /> </> ); }; return ( <main className="flex flex-col items-center w-full h-screen sm:h-auto text-white bg-[#1b1d33] pt-[8vh] sm:pt-[9vw] lg:pt-[7vw] xl:pt-[5vw] relative "> <section className=" flex flex-col items-center justify-center px-[3vw] "> <img className=" w-full sm:w-[70vw] md:w-[60vw] lg:w-[40vw] xl:w-[30vw]" src={imgColageDesk} alt="colage" /> <img className="w-[10vw] lg:w-[8vw] xl:w-[5vw] h-[10vw] lg:h-[8vw] xl:h-[5vw] mt-10" src={imgLogo} alt="logo" /> <h3 className="text-xs sm:text-sm md:text-base lg:text-lg mt-5"> Donate to </h3> <h1 className="uppercase mt-3 text-sm sm:text-base md:text-lg lg:text-xl font-bold "> forward ukraine ministries </h1> <p className="w-full sm:w-[70vw] md:w-[60vw] lg:w-[40vw] xl:w-[30vw] text-center text-xs sm:text-sm md:text-base lg:text-lg mt-2"> A humanitarian catastrophe is unfolding in Ukraine as attacks of war put millions of lives at risk. Support Military & Refugee </p> <div className="w-[80vw] sm:w-[50vw] md:w-[30vw] flex justify-around mt-14 mb-4 sm:mt-15 text-black"> <DonateBTN setAmount={amount50} amount="$50" /> <DonateBTN setAmount={amount100} amount="$100" /> <DonateBTN setAmount={amount200} amount="$200" /> <DonateOther name="Other" /> </div> {/* 月度订阅切换按钮 */} <div className="mt-6 mb-4"> <button onClick={() => dispatch(setIsMonthlySubscription(!isMonthly))} className={`px-6 py-2 rounded-lg font-medium ${ isMonthly ? 'bg-blue-600 hover:bg-blue-700' : 'bg-gray-600 hover:bg-gray-700' }`} > {isMonthly ? '✅ 当前为月度自动捐款' : '🔄 切换为月度自动捐款'} </button> </div> <input value={flowAmountDonate} onChange={(e) => dispatch(setFlowAmountMoney(e.target.value))} onKeyDown={(e) => ["ArrowUp", "ArrowDown", "e", "E", "+", "-", ".", ","].includes( e.key ) && e.preventDefault() } className={`${ flowAmountDonate ? "block font-bold w-[80%] sm:w-3/4 md:w-[85%] h-[7vw] sm:h-[6vw] md:h-[4vw] mb-4 sm:mb-8 text-black rounded-lg bg-gradient-to-r from-blue-500 to-yellow-500 focus:from-pink-500 focus:to-yellow-500 hover:from-pink-500 hover:to-yellow-500" : "hidden" }`} type="number" name="" /> <PayPalScriptProvider options={{ "client-id": "YOUR_PAYPAL_CLIENT_ID", components: "buttons", currency: "USD", }} > <ButtonWrapper currency={currency} showSpinner={false} /> </PayPalScriptProvider> </section> </main> ); }
内容的提问来源于stack exchange,提问作者Artem Kosharnyi
相关产品推荐
相关产品推荐

