mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat: add system configration switch option for order pay service, support sandbox env for alipay
This commit is contained in:
		@@ -36,7 +36,7 @@ func NewDefaultConfig() *types.AppConfig {
 | 
			
		||||
		MjConfig:     types.MidJourneyConfig{Enabled: false},
 | 
			
		||||
		SdConfig:     types.StableDiffusionConfig{Enabled: false, Txt2ImgJsonPath: "res/text2img.json"},
 | 
			
		||||
		WeChatBot:    false,
 | 
			
		||||
		AlipayConfig: types.AlipayConfig{Enabled: false},
 | 
			
		||||
		AlipayConfig: types.AlipayConfig{Enabled: false, SandBox: false},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,7 @@ type AliYunSmsConfig struct {
 | 
			
		||||
 | 
			
		||||
type AlipayConfig struct {
 | 
			
		||||
	Enabled         bool   // 是否启用该服务
 | 
			
		||||
	SandBox         bool   // 是否沙盒环境
 | 
			
		||||
	Company         string // 公司名称
 | 
			
		||||
	UserId          string // 支付宝用户 ID
 | 
			
		||||
	AppId           string // 支付宝 AppID
 | 
			
		||||
@@ -69,7 +70,6 @@ type AlipayConfig struct {
 | 
			
		||||
	PublicKey       string // 用户公钥文件路径
 | 
			
		||||
	AlipayPublicKey string // 支付宝公钥文件路径
 | 
			
		||||
	RootCert        string // Root 秘钥路径
 | 
			
		||||
	ReturnURL       string // 支付成功返回 URL
 | 
			
		||||
	NotifyURL       string // 异步通知回调
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -144,5 +144,6 @@ type SystemConfig struct {
 | 
			
		||||
	RewardImg       string   `json:"reward_img"`       // 众筹收款二维码地址
 | 
			
		||||
	EnabledFunction bool     `json:"enabled_function"` // 启用 API 函数功能
 | 
			
		||||
	EnabledReward   bool     `json:"enabled_reward"`   // 启用众筹功能
 | 
			
		||||
	EnabledAlipay   bool     `json:"enabled_alipay"`   // 是否启用支付宝支付通道
 | 
			
		||||
	DefaultModels   []string `json:"default_models"`   // 默认开通的 AI 模型
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ func (h *PaymentHandler) Alipay(c *gin.Context) {
 | 
			
		||||
	h.db.Model(&order).UpdateColumn("status", types.OrderScanned)
 | 
			
		||||
	// 生成支付链接
 | 
			
		||||
	notifyURL := h.App.Config.AlipayConfig.NotifyURL
 | 
			
		||||
	returnURL := h.App.Config.AlipayConfig.ReturnURL
 | 
			
		||||
	returnURL := "" // 关闭同步回跳
 | 
			
		||||
	amount := fmt.Sprintf("%.2f", order.Amount)
 | 
			
		||||
 | 
			
		||||
	uri, err := h.alipayService.PayUrlMobile(order.OrderNo, notifyURL, returnURL, amount, order.Subject)
 | 
			
		||||
@@ -113,6 +113,11 @@ func (h *PaymentHandler) OrderQuery(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
// AlipayQrcode 生成支付宝支付 URL 二维码
 | 
			
		||||
func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
 | 
			
		||||
	if !h.App.SysConfig.EnabledAlipay {
 | 
			
		||||
		resp.ERROR(c, "当前支付通道已经关闭,请联系管理员开通!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var data struct {
 | 
			
		||||
		ProductId uint `json:"product_id"`
 | 
			
		||||
		UserId    int  `json:"user_id"`
 | 
			
		||||
 
 | 
			
		||||
@@ -232,6 +232,7 @@ type userProfile struct {
 | 
			
		||||
	TotalTokens int64                `json:"total_tokens"`
 | 
			
		||||
	Tokens      int64                `json:"tokens"`
 | 
			
		||||
	ExpiredTime int64                `json:"expired_time"`
 | 
			
		||||
	Vip         bool                 `json:"vip"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *UserHandler) Profile(c *gin.Context) {
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ func NewAlipayService(appConfig *types.AppConfig) (*AlipayService, error) {
 | 
			
		||||
		return nil, fmt.Errorf("error with read App Private key: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	xClient, err := alipay.New(config.AppId, priKey, false)
 | 
			
		||||
	xClient, err := alipay.New(config.AppId, priKey, !config.SandBox)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("error with initialize alipay service: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,14 @@
 | 
			
		||||
          <el-input v-model="form.mobile"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="手机验证码">
 | 
			
		||||
          <el-input v-model.number="form.code" maxlength="6" style="max-width: 200px; margin-right: 10px;"/>
 | 
			
		||||
          <send-msg size="" :mobile="form.mobile"/>
 | 
			
		||||
          <el-row :gutter="20">
 | 
			
		||||
            <el-col :span="16">
 | 
			
		||||
              <el-input v-model.number="form.code" maxlength="6"/>
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="8">
 | 
			
		||||
              <send-msg size="" :mobile="form.mobile"/>
 | 
			
		||||
            </el-col>
 | 
			
		||||
          </el-row>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -81,6 +87,12 @@ const close = function () {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
#bind-mobile-form {
 | 
			
		||||
  .el-form-item__content {
 | 
			
		||||
    .el-row {
 | 
			
		||||
      width 100%
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -16,6 +16,14 @@
 | 
			
		||||
      </el-row>
 | 
			
		||||
      <el-form-item label="账户">
 | 
			
		||||
        <span>{{ user.mobile }}</span>
 | 
			
		||||
        <el-tooltip
 | 
			
		||||
            class="box-item"
 | 
			
		||||
            effect="light"
 | 
			
		||||
            content="您已经是 VIP 会员"
 | 
			
		||||
            placement="right"
 | 
			
		||||
        >
 | 
			
		||||
          <el-image v-if="user.vip" :src="vipImg" style="height: 25px;margin-left: 10px"/>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="剩余对话次数">
 | 
			
		||||
        <el-tag>{{ user['calls'] }}</el-tag>
 | 
			
		||||
@@ -60,6 +68,7 @@ import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
 | 
			
		||||
const user = ref({
 | 
			
		||||
  vip: false,
 | 
			
		||||
  username: '',
 | 
			
		||||
  nickname: '',
 | 
			
		||||
  avatar: '',
 | 
			
		||||
@@ -68,6 +77,7 @@ const user = ref({
 | 
			
		||||
  tokens: 0,
 | 
			
		||||
  chat_config: {api_keys: {OpenAI: "", Azure: "", ChatGLM: ""}}
 | 
			
		||||
})
 | 
			
		||||
const vipImg = ref("/images/vip.png")
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  checkSession().then(() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -194,8 +194,7 @@
 | 
			
		||||
      </el-main>
 | 
			
		||||
    </el-container>
 | 
			
		||||
 | 
			
		||||
    <config-dialog v-if="isLogin" :show="showConfigDialog" :models="models" @hide="showConfigDialog = false"
 | 
			
		||||
                   @update-user="updateUser"/>
 | 
			
		||||
    <config-dialog v-if="isLogin" :show="showConfigDialog" :models="models" @hide="showConfigDialog = false"/>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,16 +10,20 @@
 | 
			
		||||
 | 
			
		||||
        <el-row class="user-opt" :gutter="20">
 | 
			
		||||
          <el-col :span="12">
 | 
			
		||||
            <el-button type="primary">修改密码</el-button>
 | 
			
		||||
            <el-button type="primary" @click="showPasswordDialog = true">修改密码</el-button>
 | 
			
		||||
          </el-col>
 | 
			
		||||
          <el-col :span="12">
 | 
			
		||||
            <el-button type="primary">绑定手机号</el-button>
 | 
			
		||||
            <el-button type="primary" @click="showBindMobileDialog = true">绑定手机号</el-button>
 | 
			
		||||
          </el-col>
 | 
			
		||||
          <el-col :span="12">
 | 
			
		||||
            <el-button type="primary">加入众筹</el-button>
 | 
			
		||||
            <el-button type="primary" v-if="enableReward" @click="showRewardDialog = true">加入众筹</el-button>
 | 
			
		||||
          </el-col>
 | 
			
		||||
          <el-col :span="12">
 | 
			
		||||
            <el-button type="primary">众筹核销</el-button>
 | 
			
		||||
            <el-button type="primary" v-if="enableReward" @click="showRewardVerifyDialog = true">众筹核销</el-button>
 | 
			
		||||
          </el-col>
 | 
			
		||||
 | 
			
		||||
          <el-col :span="24" style="padding-top: 30px">
 | 
			
		||||
            <el-button type="danger" round @click="logout">退出登录</el-button>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
      </div>
 | 
			
		||||
@@ -67,7 +71,7 @@
 | 
			
		||||
    <password-dialog v-if="isLogin" :show="showPasswordDialog" @hide="showPasswordDialog = false"
 | 
			
		||||
                     @logout="logout"/>
 | 
			
		||||
 | 
			
		||||
    <bind-mobile v-if="isLogin" :show="showBindMobileDialog" :mobile="loginUser.mobile"
 | 
			
		||||
    <bind-mobile v-if="isLogin" :show="showBindMobileDialog" :mobile="user.mobile"
 | 
			
		||||
                 @hide="showBindMobileDialog = false"/>
 | 
			
		||||
 | 
			
		||||
    <reward-verify v-if="isLogin" :show="showRewardVerifyDialog" @hide="showRewardVerifyDialog = false"/>
 | 
			
		||||
@@ -81,7 +85,7 @@
 | 
			
		||||
      <el-alert type="info" :closable="false">
 | 
			
		||||
        <div style="font-size: 14px">您好,众筹 9.9元,就可以兑换 100 次对话,以此来覆盖我们的 OpenAI
 | 
			
		||||
          账单和服务器的费用。<strong
 | 
			
		||||
              style="color: #f56c6c">由于本人没有开通微信支付,付款后请凭借转账单号进入核销【众筹核销】菜单手动核销。</strong>
 | 
			
		||||
              style="color: #f56c6c">由于本人没有开通微信支付,付款后请凭借转账单号,点击【众筹核销】按钮手动核销。</strong>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-alert>
 | 
			
		||||
      <div style="text-align: center;padding-top: 10px;">
 | 
			
		||||
@@ -94,7 +98,7 @@
 | 
			
		||||
        :close-on-click-modal="false"
 | 
			
		||||
        :show-close="true"
 | 
			
		||||
        :width="400"
 | 
			
		||||
        title="用户登录">
 | 
			
		||||
        title="充值订单支付">
 | 
			
		||||
      <div class="pay-container">
 | 
			
		||||
        <div class="pay-qrcode">
 | 
			
		||||
          <el-image :src="qrcode"/>
 | 
			
		||||
@@ -130,6 +134,7 @@ import PasswordDialog from "@/components/PasswordDialog.vue";
 | 
			
		||||
import BindMobile from "@/components/BindMobile.vue";
 | 
			
		||||
import RewardVerify from "@/components/RewardVerify.vue";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {removeUserToken} from "@/store/session";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = window.innerHeight - 97
 | 
			
		||||
const list = ref([])
 | 
			
		||||
@@ -201,6 +206,15 @@ const queryOrder = (orderNo) => {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const logout = function () {
 | 
			
		||||
  httpGet('/api/user/logout').then(() => {
 | 
			
		||||
    removeUserToken();
 | 
			
		||||
    router.push('/login');
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    ElMessage.error('注销失败!');
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 
 | 
			
		||||
@@ -90,6 +90,19 @@
 | 
			
		||||
            </template>
 | 
			
		||||
          </el-input>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="启用支付宝" prop="enabled_alipay">
 | 
			
		||||
          <el-switch v-model="system['enabled_alipay']"/>
 | 
			
		||||
          <el-tooltip
 | 
			
		||||
              effect="dark"
 | 
			
		||||
              content="是否启用支付宝支付功能,<br />请先在 config.toml 配置文件配置支付秘钥"
 | 
			
		||||
              raw-content
 | 
			
		||||
              placement="right"
 | 
			
		||||
          >
 | 
			
		||||
            <el-icon>
 | 
			
		||||
              <InfoFilled/>
 | 
			
		||||
            </el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="默认AI模型" prop="default_models">
 | 
			
		||||
          <template #default>
 | 
			
		||||
            <div class="tip-input">
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,12 @@
 | 
			
		||||
      <el-table :data="users.items" border class="table" :row-key="row => row.id"
 | 
			
		||||
                @selection-change="handleSelectionChange" table-layout="auto">
 | 
			
		||||
        <el-table-column type="selection" width="38"/>
 | 
			
		||||
        <el-table-column prop="mobile" label="账号"/>
 | 
			
		||||
        <el-table-column prop="mobile" label="账号">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ scope.row.mobile }}</span>
 | 
			
		||||
            <el-image v-if="scope.row.vip" :src="vipImg" style="height: 20px;position: relative; top:5px; left: 5px"/>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="calls" label="剩余对话次数"/>
 | 
			
		||||
        <el-table-column prop="img_calls" label="剩余绘图次数"/>
 | 
			
		||||
        <el-table-column prop="total_tokens" label="累计消耗tokens"/>
 | 
			
		||||
@@ -74,7 +79,7 @@
 | 
			
		||||
          <el-input v-model.number="user.calls" autocomplete="off" placeholder="0"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="绘图次数:" prop="img_calls">
 | 
			
		||||
          <el-input v-model.number="user.img_calls" autocomplete="off" placeholder="0"/>
 | 
			
		||||
          <el-input v-model.number="user['img_calls']" autocomplete="off" placeholder="0"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="有效期:" prop="expired_time">
 | 
			
		||||
@@ -124,6 +129,10 @@
 | 
			
		||||
        <el-form-item label="启用状态">
 | 
			
		||||
          <el-switch v-model="user.status"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="开通VIP">
 | 
			
		||||
          <el-switch v-model="user.vip"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
 | 
			
		||||
      <template #footer>
 | 
			
		||||
@@ -140,8 +149,8 @@
 | 
			
		||||
        width="50%"
 | 
			
		||||
    >
 | 
			
		||||
      <el-form label-width="100px" ref="userEditFormRef">
 | 
			
		||||
        <el-form-item label="用户名:">
 | 
			
		||||
          <el-input v-model="pass.username" autocomplete="off" readonly disabled/>
 | 
			
		||||
        <el-form-item label="账户:">
 | 
			
		||||
          <el-input v-model="pass.mobile" autocomplete="off" readonly disabled/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="新密码:">
 | 
			
		||||
@@ -168,18 +177,18 @@ import {Plus, Search} from "@element-plus/icons-vue";
 | 
			
		||||
 | 
			
		||||
// 变量定义
 | 
			
		||||
const users = ref({page: 1, page_size: 15, items: []})
 | 
			
		||||
const query = ref({username: '', mobile: '', page: 1, page_size: 15})
 | 
			
		||||
const query = ref({mobile: '', page: 1, page_size: 15})
 | 
			
		||||
 | 
			
		||||
const title = ref('添加用户')
 | 
			
		||||
const vipImg = ref("/images/vip.png")
 | 
			
		||||
const add = ref(true)
 | 
			
		||||
const user = ref({chat_roles: [], chat_models: []})
 | 
			
		||||
const pass = ref({username: '', password: '', id: 0})
 | 
			
		||||
const pass = ref({mobile: '', password: '', id: 0})
 | 
			
		||||
const roles = ref([])
 | 
			
		||||
const models = ref([])
 | 
			
		||||
const showUserEditDialog = ref(false)
 | 
			
		||||
const showResetPassDialog = ref(false)
 | 
			
		||||
const rules = reactive({
 | 
			
		||||
  username: [{required: true, message: '请输入用户名', trigger: 'change',}],
 | 
			
		||||
  nickname: [{required: true, message: '请输入昵称', trigger: 'change',}],
 | 
			
		||||
  password: [{required: true, message: '请输入密码', trigger: 'change',}],
 | 
			
		||||
  mobile: [{required: true, message: '请输入手机号码', trigger: 'change',}],
 | 
			
		||||
@@ -300,7 +309,7 @@ const handleSelectionChange = function (rows) {
 | 
			
		||||
const resetPass = (row) => {
 | 
			
		||||
  showResetPassDialog.value = true
 | 
			
		||||
  pass.value.id = row.id
 | 
			
		||||
  pass.value.username = row.username
 | 
			
		||||
  pass.value.mobile = row.mobile
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const doResetPass = () => {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user