Procházet zdrojové kódy

feat: add stripe setting page

wzxjohn před 8 měsíci
rodič
revize
3568042cd9

+ 12 - 0
web/src/components/settings/PaymentSetting.js

@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
 import { Card, Spin } from '@douyinfe/semi-ui';
 import SettingsGeneralPayment from '../../pages/Setting/Payment/SettingsGeneralPayment.js';
 import SettingsPaymentGateway from '../../pages/Setting/Payment/SettingsPaymentGateway.js';
+import SettingsPaymentGatewayStripe from '../../pages/Setting/Payment/SettingsPaymentGatewayStripe.js';
 import { API, showError } from '../../helpers';
 import { useTranslation } from 'react-i18next';
 
@@ -17,6 +18,12 @@ const PaymentSetting = () => {
     TopupGroupRatio: '',
     CustomCallbackAddress: '',
     PayMethods: '',
+
+    StripeApiSecret: '',
+    StripeWebhookSecret: '',
+    StripePriceId: '',
+    StripeUnitPrice: 8.0,
+    StripeMinTopUp: 1,
   });
 
   let [loading, setLoading] = useState(false);
@@ -38,6 +45,8 @@ const PaymentSetting = () => {
             break;
           case 'Price':
           case 'MinTopUp':
+          case 'StripeUnitPrice':
+          case 'StripeMinTopUp':
             newInputs[item.key] = parseFloat(item.value);
             break;
           default:
@@ -80,6 +89,9 @@ const PaymentSetting = () => {
         <Card style={{ marginTop: '10px' }}>
           <SettingsPaymentGateway options={inputs} refresh={onRefresh} />
         </Card>
+        <Card style={{ marginTop: '10px' }}>
+          <SettingsPaymentGatewayStripe options={inputs} refresh={onRefresh} />
+        </Card>
       </Spin>
     </>
   );

+ 196 - 0
web/src/pages/Setting/Payment/SettingsPaymentGatewayStripe.js

@@ -0,0 +1,196 @@
+import React, { useEffect, useState, useRef } from 'react';
+import {
+  Banner,
+  Button,
+  Form,
+  Row,
+  Col,
+  Typography,
+  Spin,
+} from '@douyinfe/semi-ui';
+const { Text } = Typography;
+import {
+  API,
+  removeTrailingSlash,
+  showError,
+  showSuccess,
+  verifyJSON,
+} from '../../../helpers';
+import { useTranslation } from 'react-i18next';
+
+export default function SettingsPaymentGateway(props) {
+  const { t } = useTranslation();
+  const [loading, setLoading] = useState(false);
+  const [inputs, setInputs] = useState({
+    StripeApiSecret: '',
+    StripeWebhookSecret: '',
+    StripePriceId: '',
+    StripeUnitPrice: 8.0,
+    StripeMinTopUp: 1,
+  });
+  const [originInputs, setOriginInputs] = useState({});
+  const formApiRef = useRef(null);
+
+  useEffect(() => {
+    if (props.options && formApiRef.current) {
+      const currentInputs = {
+        StripeApiSecret: props.options.StripeApiSecret || '',
+        StripeWebhookSecret: props.options.StripeWebhookSecret || '',
+        StripePriceId: props.options.StripePriceId || '',
+        StripeUnitPrice: props.options.StripeUnitPrice !== undefined ? parseFloat(props.options.StripeUnitPrice) : 8.0,
+        StripeMinTopUp: props.options.StripeMinTopUp !== undefined ? parseFloat(props.options.StripeMinTopUp) : 1,
+      };
+      setInputs(currentInputs);
+      setOriginInputs({ ...currentInputs });
+      formApiRef.current.setValues(currentInputs);
+    }
+  }, [props.options]);
+
+  const handleFormChange = (values) => {
+    setInputs(values);
+  };
+
+  const submitStripeSetting = async () => {
+    if (props.options.ServerAddress === '') {
+      showError(t('请先填写服务器地址'));
+      return;
+    }
+
+    setLoading(true);
+    try {
+      const options = []
+
+      if (inputs.StripeApiSecret !== undefined && inputs.StripeApiSecret !== '') {
+        options.push({ key: 'StripeApiSecret', value: inputs.StripeApiSecret });
+      }
+      if (inputs.StripeWebhookSecret !== undefined && inputs.StripeWebhookSecret !== '') {
+        options.push({ key: 'StripeWebhookSecret', value: inputs.StripeWebhookSecret });
+      }
+      if (inputs.StripePriceId !== '') {
+        options.push({key: 'StripePriceId', value: inputs.StripePriceId,});
+      }
+      if (inputs.StripeUnitPrice !== '') {
+        options.push({ key: 'StripeUnitPrice', value: inputs.StripeUnitPrice.toString() });
+      }
+      if (inputs.StripeMinTopUp !== '') {
+        options.push({ key: 'StripeMinTopUp', value: inputs.StripeMinTopUp.toString() });
+      }
+
+      // 发送请求
+      const requestQueue = options.map(opt =>
+        API.put('/api/option/', {
+          key: opt.key,
+          value: opt.value,
+        })
+      );
+
+      const results = await Promise.all(requestQueue);
+
+      // 检查所有请求是否成功
+      const errorResults = results.filter(res => !res.data.success);
+      if (errorResults.length > 0) {
+        errorResults.forEach(res => {
+          showError(res.data.message);
+        });
+      } else {
+        showSuccess(t('更新成功'));
+        // 更新本地存储的原始值
+        setOriginInputs({ ...inputs });
+        props.refresh && props.refresh();
+      }
+    } catch (error) {
+      showError(t('更新失败'));
+    }
+    setLoading(false);
+  };
+
+  return (
+    <Spin spinning={loading}>
+      <Form
+        initValues={inputs}
+        onValueChange={handleFormChange}
+        getFormApi={(api) => (formApiRef.current = api)}
+      >
+        <Form.Section text={t('Stripe 设置')}>
+          <Text>
+            Stripe 密钥、Webhook 等设置请
+            <a
+                href='https://dashboard.stripe.com/developers'
+                target='_blank'
+                rel='noreferrer'
+            >
+              点击此处
+            </a>
+            进行设置,最好先在
+            <a
+                href='https://dashboard.stripe.com/test/developers'
+                target='_blank'
+                rel='noreferrer'
+            >
+              测试环境
+            </a>
+            进行测试。
+
+            <br />
+          </Text>
+          <Banner
+              type='info'
+              description={`Webhook 填:${props.options.ServerAddress ? props.options.ServerAddress : '网站地址'}/api/stripe/webhook`}
+          />
+          <Banner
+              type='warning'
+              description={`需要包含事件:checkout.session.completed 和 checkout.session.expired`}
+          />
+          <Row
+            gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
+          >
+            <Col xs={24} sm={24} md={8} lg={8} xl={8}>
+              <Form.Input
+                field='StripeApiSecret'
+                label={t('API 密钥')}
+                placeholder={t('sk_xxx 或 rk_xxx 的 Stripe 密钥,敏感信息不显示')}
+                type='password'
+              />
+            </Col>
+            <Col xs={24} sm={24} md={8} lg={8} xl={8}>
+              <Form.Input
+                field='StripeWebhookSecret'
+                label={t('Webhook 签名密钥')}
+                placeholder={t('whsec_xxx 的 Webhook 签名密钥,敏感信息不显示')}
+                type='password'
+              />
+            </Col>
+            <Col xs={24} sm={24} md={8} lg={8} xl={8}>
+              <Form.Input
+                field='StripePriceId'
+                label={t('商品价格 ID')}
+                placeholder={t('price_xxx 的商品价格 ID,新建产品后可获得')}
+              />
+            </Col>
+          </Row>
+          <Row
+            gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
+            style={{ marginTop: 16 }}
+          >
+            <Col xs={24} sm={24} md={8} lg={8} xl={8}>
+              <Form.InputNumber
+                field='StripeUnitPrice'
+                precision={2}
+                label={t('充值价格(x元/美金)')}
+                placeholder={t('例如:7,就是7元/美金')}
+              />
+            </Col>
+            <Col xs={24} sm={24} md={8} lg={8} xl={8}>
+              <Form.InputNumber
+                field='StripeMinTopUp'
+                label={t('最低充值美元数量')}
+                placeholder={t('例如:2,就是最低充值2$')}
+              />
+            </Col>
+          </Row>
+          <Button onClick={submitStripeSetting}>{t('更新 Stripe 设置')}</Button>
+        </Form.Section>
+      </Form>
+    </Spin>
+  );
+}