Explorar el Código

feat: add stripe topup page

wzxjohn hace 8 meses
padre
commit
e8ae980104
Se han modificado 2 ficheros con 220 adiciones y 2 borrados
  1. 0 1
      controller/topup_stripe.go
  2. 220 1
      web/src/pages/TopUp/index.js

+ 0 - 1
controller/topup_stripe.go

@@ -28,7 +28,6 @@ var stripeAdaptor = &StripeAdaptor{}
 type StripePayRequest struct {
 type StripePayRequest struct {
 	Amount        int64  `json:"amount"`
 	Amount        int64  `json:"amount"`
 	PaymentMethod string `json:"payment_method"`
 	PaymentMethod string `json:"payment_method"`
-	TopUpCode     string `json:"top_up_code"`
 }
 }
 
 
 type StripeAdaptor struct {
 type StripeAdaptor struct {

+ 220 - 1
web/src/pages/TopUp/index.js

@@ -59,6 +59,13 @@ const TopUp = () => {
     statusState?.status?.enable_online_topup || false,
     statusState?.status?.enable_online_topup || false,
   );
   );
   const [priceRatio, setPriceRatio] = useState(statusState?.status?.price || 1);
   const [priceRatio, setPriceRatio] = useState(statusState?.status?.price || 1);
+
+  const [stripeAmount, setStripeAmount] = useState(0.0);
+  const [stripeMinTopUp, setStripeMinTopUp] = useState(statusState?.status?.stripe_min_topup || 1);
+  const [stripeTopUpCount, setStripeTopUpCount] = useState(statusState?.status?.stripe_min_topup || 1);
+  const [enableStripeTopUp, setEnableStripeTopUp] = useState(statusState?.status?.enable_stripe_topup || false);
+  const [stripeOpen, setStripeOpen] = useState(false);
+
   const [userQuota, setUserQuota] = useState(0);
   const [userQuota, setUserQuota] = useState(0);
   const [isSubmitting, setIsSubmitting] = useState(false);
   const [isSubmitting, setIsSubmitting] = useState(false);
   const [open, setOpen] = useState(false);
   const [open, setOpen] = useState(false);
@@ -231,6 +238,65 @@ const TopUp = () => {
     }
     }
   };
   };
 
 
+  const stripePreTopUp = async () => {
+    if (!enableStripeTopUp) {
+      showError(t('管理员未开启在线充值!'));
+      return;
+    }
+    setPayWay('stripe')
+    setPaymentLoading(true);
+    try {
+      await getStripeAmount();
+      if (stripeTopUpCount < stripeMinTopUp) {
+        showError(t('充值数量不能小于') + stripeMinTopUp);
+        return;
+      }
+      setStripeOpen(true);
+    } catch (error) {
+      showError(t('获取金额失败'));
+    } finally {
+      setPayWay('')
+      setPaymentLoading(false);
+    }
+  };
+
+  const onlineStripeTopUp = async () => {
+    if (stripeAmount === 0) {
+      await getStripeAmount();
+    }
+    if (stripeTopUpCount < stripeMinTopUp) {
+      showError(t('充值数量不能小于') + stripeMinTopUp);
+      return;
+    }
+    setConfirmLoading(true);
+    try {
+      const res = await API.post('/api/user/stripe/pay', {
+        amount: parseInt(stripeTopUpCount),
+        payment_method: 'stripe',
+      });
+      if (res !== undefined) {
+        const { message, data } = res.data;
+        if (message === 'success') {
+          processStripeCallback(data)
+        } else {
+          showError(data);
+        }
+      } else {
+        showError(res);
+      }
+    } catch (err) {
+      console.log(err);
+      showError(t('支付请求失败'));
+    } finally {
+      setStripeOpen(false);
+      setConfirmLoading(false);
+    }
+  }
+
+  const processStripeCallback = (data) => {
+    location.href = data.pay_link;
+  };
+
   const getUserQuota = async () => {
   const getUserQuota = async () => {
     setUserDataLoading(true);
     setUserDataLoading(true);
     let res = await API.get(`/api/user/self`);
     let res = await API.get(`/api/user/self`);
@@ -327,6 +393,10 @@ const TopUp = () => {
       setTopUpLink(statusState.status.top_up_link || '');
       setTopUpLink(statusState.status.top_up_link || '');
       setEnableOnlineTopUp(statusState.status.enable_online_topup || false);
       setEnableOnlineTopUp(statusState.status.enable_online_topup || false);
       setPriceRatio(statusState.status.price || 1);
       setPriceRatio(statusState.status.price || 1);
+
+      setStripeMinTopUp(statusState.status.stripe_min_topup || 1);
+      setStripeTopUpCount(statusState.status.stripe_min_topup || 1);
+      setEnableStripeTopUp(statusState.status.enable_stripe_topup || false);
     }
     }
   }, [statusState?.status]);
   }, [statusState?.status]);
 
 
@@ -334,6 +404,10 @@ const TopUp = () => {
     return amount + ' ' + t('元');
     return amount + ' ' + t('元');
   };
   };
 
 
+  const renderStripeAmount = () => {
+    return stripeAmount + ' ' + t('元');
+  };
+
   const getAmount = async (value) => {
   const getAmount = async (value) => {
     if (value === undefined) {
     if (value === undefined) {
       value = topUpCount;
       value = topUpCount;
@@ -361,10 +435,40 @@ const TopUp = () => {
     setAmountLoading(false);
     setAmountLoading(false);
   };
   };
 
 
+  const getStripeAmount = async (value) => {
+    if (value === undefined) {
+      value = stripeTopUpCount
+    }
+    try {
+      const res = await API.post('/api/user/stripe/amount', {
+        amount: parseFloat(value),
+      });
+      if (res !== undefined) {
+        const { message, data } = res.data;
+        // showInfo(message);
+        if (message === 'success') {
+          setStripeAmount(parseFloat(data))
+        } else {
+          setStripeAmount( 0)
+          Toast.error({ content: '错误:' + data, id: 'getAmount' });
+        }
+      } else {
+        showError(res);
+      }
+    } catch (err) {
+      console.log(err);
+    } finally {
+    }
+  }
+
   const handleCancel = () => {
   const handleCancel = () => {
     setOpen(false);
     setOpen(false);
   };
   };
 
 
+  const handleStripeCancel = () => {
+    setStripeOpen(false);
+  };
+
   const handleTransferCancel = () => {
   const handleTransferCancel = () => {
     setOpenTransfer(false);
     setOpenTransfer(false);
   };
   };
@@ -374,6 +478,9 @@ const TopUp = () => {
     setTopUpCount(preset.value);
     setTopUpCount(preset.value);
     setSelectedPreset(preset.value);
     setSelectedPreset(preset.value);
     setAmount(preset.value * priceRatio);
     setAmount(preset.value * priceRatio);
+
+    setStripeTopUpCount(preset.value);
+    setStripeAmount(preset.value);
   };
   };
 
 
   // 格式化大数字显示
   // 格式化大数字显示
@@ -496,6 +603,25 @@ const TopUp = () => {
         </div>
         </div>
       </Modal>
       </Modal>
 
 
+      <Modal
+          title={t('确定要充值吗')}
+          visible={stripeOpen}
+          onOk={onlineStripeTopUp}
+          onCancel={handleStripeCancel}
+          maskClosable={false}
+          size='small'
+          centered
+          confirmLoading={confirmLoading}
+      >
+        <p>
+          {t('充值数量')}:{stripeTopUpCount}
+        </p>
+        <p>
+          {t('实付金额')}:{renderStripeAmount()}
+        </p>
+        <p>{t('是否确认充值?')}</p>
+      </Modal>
+
       <div className='grid grid-cols-1 lg:grid-cols-12 gap-6'>
       <div className='grid grid-cols-1 lg:grid-cols-12 gap-6'>
         {/* 左侧充值区域 */}
         {/* 左侧充值区域 */}
         <div className='lg:col-span-7 space-y-6 w-full'>
         <div className='lg:col-span-7 space-y-6 w-full'>
@@ -798,7 +924,7 @@ const TopUp = () => {
                 </>
                 </>
               )}
               )}
 
 
-              {!enableOnlineTopUp && (
+              {!enableOnlineTopUp && !enableStripeTopUp && (
                 <Banner
                 <Banner
                   type='warning'
                   type='warning'
                   description={t(
                   description={t(
@@ -809,6 +935,99 @@ const TopUp = () => {
                 />
                 />
               )}
               )}
 
 
+              {enableStripeTopUp && (
+                  <>
+                    {/* 桌面端显示的自定义金额和支付按钮 */}
+                    <div className='hidden md:block space-y-4'>
+                      {!enableOnlineTopUp?(
+                          <Divider style={{ margin: '24px 0' }}>
+                            <Text className='text-sm font-medium'>
+                              {t('或输入自定义金额')}
+                            </Text>
+                          </Divider>
+                      ) : (
+                          <Divider style={{ margin: '24px 0' }}>
+                            <Text className='text-sm font-medium'>
+                              {t('Stripe')}
+                            </Text>
+                          </Divider>
+                      )
+                      }
+
+
+                      <div>
+                        <div className='flex justify-between mb-2'>
+                          <Text strong>{t('充值数量')}</Text>
+                          {amountLoading ? (
+                              <Skeleton.Title
+                                  style={{ width: '80px', height: '16px' }}
+                              />
+                          ) : (
+                              <Text type='tertiary'>
+                                {t('实付金额:') + renderStripeAmount()}
+                              </Text>
+                          )}
+                        </div>
+                        <InputNumber
+                            disabled={!enableOnlineTopUp}
+                            placeholder={
+                                t('充值数量,最低 ') + renderQuotaWithAmount(stripeMinTopUp)
+                            }
+                            value={stripeTopUpCount}
+                            min={stripeMinTopUp}
+                            max={999999999}
+                            step={1}
+                            precision={0}
+                            onChange={async (value) => {
+                              if (value && value >= 1) {
+                                setStripeTopUpCount(value);
+                                setSelectedPreset(null);
+                                await getStripeAmount(value);
+                              }
+                            }}
+                            onBlur={(e) => {
+                              const value = parseInt(e.target.value);
+                              if (!value || value < 1) {
+                                setStripeTopUpCount(1);
+                                getStripeAmount(1);
+                              }
+                            }}
+                            size='large'
+                            className='w-full'
+                            formatter={(value) => (value ? `${value}` : '')}
+                            parser={(value) =>
+                                value ? parseInt(value.replace(/[^\d]/g, '')) : 0
+                            }
+                        />
+                      </div>
+
+                      <div>
+                        <Text strong className='block mb-3'>
+                          {t('选择支付方式')}
+                        </Text>
+                          <div className='grid grid-cols-1 gap-3'>
+                            <Button
+                                key='stripe'
+                                type='primary'
+                                onClick={() => stripePreTopUp()}
+                                size='large'
+                                disabled={!enableStripeTopUp}
+                                loading={paymentLoading && payWay === 'stripe'}
+                                icon={<CreditCard size={16} />}
+                                style={{
+                                  height: '40px',
+                                  color: '#b161fe',
+                                }}
+                                className='transition-all hover:shadow-md w-full'
+                            >
+                              <span className='ml-1'>Stripe</span>
+                            </Button>
+                          </div>
+                      </div>
+                    </div>
+                  </>
+              )}
+
               <Divider style={{ margin: '24px 0' }}>
               <Divider style={{ margin: '24px 0' }}>
                 <Text className='text-sm font-medium'>{t('兑换码充值')}</Text>
                 <Text className='text-sm font-medium'>{t('兑换码充值')}</Text>
               </Divider>
               </Divider>