feat: add system configration switch option for order pay service, support sandbox env for alipay

This commit is contained in:
RockYang 2023-11-09 18:28:56 +08:00
parent 7ca4dfe09b
commit 9dc9a6923e
11 changed files with 89 additions and 25 deletions

View File

@ -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},
}
}

View File

@ -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 模型
}

View File

@ -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"`

View File

@ -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) {

View File

@ -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)
}

View File

@ -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>

View File

@ -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(() => {

View File

@ -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>

View File

@ -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">

View File

@ -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">

View File

@ -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 = () => {