mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-17 16:56:38 +08:00
feat: payment for mobile pages is ready
This commit is contained in:
parent
e11c0a3633
commit
14f63a203d
@ -81,6 +81,7 @@ type AlipayConfig struct {
|
||||
AlipayPublicKey string // 支付宝公钥文件路径
|
||||
RootCert string // Root 秘钥路径
|
||||
NotifyURL string // 异步通知回调
|
||||
ReturnURL string // 支付成功返回地址
|
||||
}
|
||||
|
||||
type HuPiPayConfig struct { //虎皮椒第四方支付配置
|
||||
@ -90,6 +91,7 @@ type HuPiPayConfig struct { //虎皮椒第四方支付配置
|
||||
AppSecret string // app 密钥
|
||||
ApiURL string // 支付网关
|
||||
NotifyURL string // 异步通知回调
|
||||
ReturnURL string // 支付成功返回地址
|
||||
}
|
||||
|
||||
// JPayConfig PayJs 支付配置
|
||||
@ -100,6 +102,7 @@ type JPayConfig struct {
|
||||
PrivateKey string // 私钥
|
||||
ApiURL string // API 网关
|
||||
NotifyURL string // 异步回调地址
|
||||
ReturnURL string // 支付成功返回地址
|
||||
}
|
||||
|
||||
type XXLConfig struct { // XXL 任务调度配置
|
||||
|
@ -279,7 +279,6 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
|
||||
// Mobile 移动端支付
|
||||
func (h *PaymentHandler) Mobile(c *gin.Context) {
|
||||
|
||||
var data struct {
|
||||
PayWay string `json:"pay_way"` // 支付方式
|
||||
ProductId uint `json:"product_id"`
|
||||
@ -309,18 +308,61 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
|
||||
var payWay string
|
||||
var notifyURL string
|
||||
var notifyURL, returnURL string
|
||||
var payURL string
|
||||
switch data.PayWay {
|
||||
case "hupi":
|
||||
payWay = PayWayXunHu
|
||||
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
|
||||
returnURL = h.App.Config.HuPiPayConfig.ReturnURL
|
||||
params := payment.HuPiPayReq{
|
||||
Version: "1.1",
|
||||
TradeOrderId: orderNo,
|
||||
TotalFee: fmt.Sprintf("%f", amount),
|
||||
Title: product.Name,
|
||||
NotifyURL: notifyURL,
|
||||
ReturnURL: returnURL,
|
||||
CallbackURL: returnURL,
|
||||
WapName: "极客学长",
|
||||
}
|
||||
r, err := h.huPiPayService.Pay(params)
|
||||
if err != nil {
|
||||
logger.Error("error with generating Pay URL: ", err.Error())
|
||||
resp.ERROR(c, "error with generating Pay URL: "+err.Error())
|
||||
return
|
||||
}
|
||||
payURL = r.URL
|
||||
case "payjs":
|
||||
payWay = PayWayJs
|
||||
notifyURL = h.App.Config.JPayConfig.NotifyURL
|
||||
default:
|
||||
returnURL = h.App.Config.JPayConfig.ReturnURL
|
||||
params := payment.JPayReq{
|
||||
TotalFee: int(math.Ceil(amount * 100)),
|
||||
OutTradeNo: orderNo,
|
||||
Subject: product.Name,
|
||||
NotifyURL: notifyURL,
|
||||
ReturnURL: returnURL,
|
||||
}
|
||||
r := h.js.PayH5(params)
|
||||
if !r.IsOK() {
|
||||
resp.ERROR(c, "error with generating Pay URL: "+r.ReturnMsg)
|
||||
return
|
||||
}
|
||||
payURL = r.H5URL
|
||||
case "alipay":
|
||||
payWay = PayWayAlipay
|
||||
notifyURL = h.App.Config.AlipayConfig.NotifyURL
|
||||
returnURL = h.App.Config.AlipayConfig.ReturnURL
|
||||
payURL, err = h.alipayService.PayUrlMobile(orderNo, notifyURL, returnURL, fmt.Sprintf("%.2f", amount), product.Name)
|
||||
if err != nil {
|
||||
resp.ERROR(c, "error with generating Pay URL: "+err.Error())
|
||||
return
|
||||
}
|
||||
default:
|
||||
resp.ERROR(c, "Unsupported pay way: "+data.PayWay)
|
||||
return
|
||||
}
|
||||
// 创建订单
|
||||
remark := types.OrderRemark{
|
||||
@ -331,7 +373,6 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
||||
Discount: product.Discount,
|
||||
}
|
||||
|
||||
amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
|
||||
order := model.Order{
|
||||
UserId: user.Id,
|
||||
Username: user.Username,
|
||||
@ -349,54 +390,7 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// PayJs 单独处理,只能用官方生成的二维码
|
||||
if data.PayWay == "payjs" {
|
||||
params := payment.JPayReq{
|
||||
TotalFee: int(math.Ceil(order.Amount * 100)),
|
||||
OutTradeNo: order.OrderNo,
|
||||
Subject: product.Name,
|
||||
}
|
||||
r := h.js.Pay(params)
|
||||
if r.IsOK() {
|
||||
resp.SUCCESS(c, gin.H{"order_no": order.OrderNo, "image": r.Qrcode})
|
||||
return
|
||||
} else {
|
||||
resp.ERROR(c, "error with generating payment qrcode: "+r.ReturnMsg)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var logo string
|
||||
if data.PayWay == "alipay" {
|
||||
logo = "res/img/alipay.jpg"
|
||||
} else if data.PayWay == "hupi" {
|
||||
if h.App.Config.HuPiPayConfig.Name == "wechat" {
|
||||
logo = "res/img/wechat-pay.jpg"
|
||||
} else {
|
||||
logo = "res/img/alipay.jpg"
|
||||
}
|
||||
}
|
||||
|
||||
file, err := h.fs.Open(logo)
|
||||
if err != nil {
|
||||
resp.ERROR(c, "error with open qrcode log file: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
parse, err := url.Parse(notifyURL)
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
imageURL := fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s", parse.Scheme, parse.Host, orderNo, data.PayWay)
|
||||
imgData, err := utils.GenQrcode(imageURL, 400, file)
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
return
|
||||
}
|
||||
imgDataBase64 := base64.StdEncoding.EncodeToString(imgData)
|
||||
resp.SUCCESS(c, gin.H{"order_no": orderNo, "image": fmt.Sprintf("data:image/jpg;base64, %s", imgDataBase64), "url": imageURL})
|
||||
resp.SUCCESS(c, payURL)
|
||||
}
|
||||
|
||||
// 异步通知回调公共逻辑
|
||||
|
@ -324,6 +324,7 @@ func main() {
|
||||
group.GET("payWays", h.GetPayWays)
|
||||
group.POST("query", h.OrderQuery)
|
||||
group.POST("qrcode", h.PayQrcode)
|
||||
group.POST("mobile", h.Mobile)
|
||||
group.POST("alipay/notify", h.AlipayNotify)
|
||||
group.POST("hupipay/notify", h.HuPiPayNotify)
|
||||
group.POST("payjs/notify", h.PayJsNotify)
|
||||
|
@ -29,16 +29,18 @@ type JPayReq struct {
|
||||
OutTradeNo string `json:"out_trade_no"`
|
||||
Subject string `json:"body"`
|
||||
NotifyURL string `json:"notify_url"`
|
||||
ReturnURL string `json:"callback_url"`
|
||||
}
|
||||
type JPayReps struct {
|
||||
CodeUrl string `json:"code_url"`
|
||||
OutTradeNo string `json:"out_trade_no"`
|
||||
OrderId string `json:"payjs_order_id"`
|
||||
Qrcode string `json:"qrcode"`
|
||||
ReturnCode int `json:"return_code"`
|
||||
ReturnMsg string `json:"return_msg"`
|
||||
Sign string `json:"Sign"`
|
||||
TotalFee string `json:"total_fee"`
|
||||
CodeUrl string `json:"code_url,omitempty"`
|
||||
Qrcode string `json:"qrcode,omitempty"`
|
||||
H5URL string `json:"h5_url,omitempty"`
|
||||
}
|
||||
|
||||
func (r JPayReps) IsOK() bool {
|
||||
@ -78,6 +80,39 @@ func (js *PayJS) Pay(param JPayReq) JPayReps {
|
||||
return data
|
||||
}
|
||||
|
||||
func (js *PayJS) PayH5(param JPayReq) JPayReps {
|
||||
param.NotifyURL = js.config.NotifyURL
|
||||
var p = url.Values{}
|
||||
encode := utils.JsonEncode(param)
|
||||
m := make(map[string]interface{})
|
||||
_ = utils.JsonDecode(encode, &m)
|
||||
for k, v := range m {
|
||||
p.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
p.Add("mchid", js.config.AppId)
|
||||
|
||||
p.Add("sign", js.sign(p))
|
||||
|
||||
cli := http.Client{}
|
||||
apiURL := fmt.Sprintf("%s/api/h5", js.config.ApiURL)
|
||||
r, err := cli.PostForm(apiURL, p)
|
||||
if err != nil {
|
||||
return JPayReps{ReturnMsg: err.Error()}
|
||||
}
|
||||
defer r.Body.Close()
|
||||
bs, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return JPayReps{ReturnMsg: err.Error()}
|
||||
}
|
||||
|
||||
var data JPayReps
|
||||
err = utils.JsonDecode(string(bs), &data)
|
||||
if err != nil {
|
||||
return JPayReps{ReturnMsg: err.Error()}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (js *PayJS) sign(params url.Values) string {
|
||||
params.Del(`sign`)
|
||||
var keys = make([]string, 0, 0)
|
||||
|
@ -21,23 +21,18 @@
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<van-field label="剩余对话次数">
|
||||
<van-field label="剩余算力">
|
||||
<template #input>
|
||||
<van-tag type="primary">{{ form.calls }}</van-tag>
|
||||
<van-tag type="primary">{{ form.power }}</van-tag>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<van-field label="剩余绘图次数">
|
||||
<van-field label="VIP到期时间" v-if="form.expired_time > 0">
|
||||
<template #input>
|
||||
<van-tag type="primary">{{ form.img_calls }}</van-tag>
|
||||
<van-tag type="warning">{{ dateFormat(form.expired_time) }}</van-tag>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<van-field label="累计算力消耗">
|
||||
<template #input>
|
||||
<van-tag type="primary">{{ form.total_tokens }}</van-tag>
|
||||
</template>
|
||||
</van-field>
|
||||
</van-cell-group>
|
||||
</van-form>
|
||||
|
||||
@ -50,7 +45,19 @@
|
||||
<div class="item" v-for="item in products" :key="item.id">
|
||||
<h4 class="title">
|
||||
<span>{{ item.name }}</span>
|
||||
<van-button type="primary" size="small" icon="cart">立即购买</van-button>
|
||||
<div class="buy-btn">
|
||||
<van-button type="primary" @click="pay('alipay',item)" size="small" v-if="payWays['alipay']">
|
||||
<i class="iconfont icon-alipay"></i> 支付宝
|
||||
</van-button>
|
||||
<van-button type="success" @click="pay('hupi',item)" size="small" v-if="payWays['hupi']">
|
||||
<span v-if="payWays['hupi']['name'] === 'wechat'"><i class="iconfont icon-wechat-pay"></i> 微信</span>
|
||||
<span v-else><i class="iconfont icon-alipay"></i> 支付宝</span>
|
||||
</van-button>
|
||||
|
||||
<van-button type="success" @click="pay('payjs',item)" size="small" v-if="payWays['payjs']">
|
||||
<span><i class="iconfont icon-wechat-pay"></i> 微信</span>
|
||||
</van-button>
|
||||
</div>
|
||||
</h4>
|
||||
|
||||
<van-cell-group>
|
||||
@ -111,7 +118,10 @@ import {onMounted, ref} from "vue";
|
||||
import {showFailToast, showNotify, showSuccessToast} from "vant";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import Compressor from 'compressorjs';
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {checkSession} from "@/action/session";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
const title = ref('用户设置')
|
||||
const form = ref({
|
||||
@ -132,8 +142,13 @@ const fileList = ref([
|
||||
const products = ref([])
|
||||
const vipMonthCalls = ref(0)
|
||||
const vipMonthImgCalls = ref(0)
|
||||
const payWays = ref({})
|
||||
const router = useRouter()
|
||||
const loginUser = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
checkSession().then(user => {
|
||||
loginUser.value = user
|
||||
httpGet('/api/user/profile').then(res => {
|
||||
form.value = res.data
|
||||
fileList.value[0].url = form.value.avatar
|
||||
@ -155,6 +170,17 @@ onMounted(() => {
|
||||
}).catch(e => {
|
||||
showFailToast("获取系统配置失败:" + e.message)
|
||||
})
|
||||
|
||||
httpGet("/api/payment/payWays").then(res => {
|
||||
payWays.value = res.data
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取支付方式失败:" + e.message)
|
||||
})
|
||||
|
||||
}).catch(() => {
|
||||
router.push("/login")
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
const afterRead = (file) => {
|
||||
@ -222,6 +248,18 @@ const updatePass = () => {
|
||||
showPasswordDialog.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const pay = (payWay, item) => {
|
||||
httpPost("/api/payment/mobile", {
|
||||
pay_way: payWay,
|
||||
product_id: item.id,
|
||||
user_id: loginUser.value.id
|
||||
}).then(res => {
|
||||
location.href = res.data
|
||||
}).catch(e => {
|
||||
showFailToast("生成支付订单失败:" + e.message)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
@ -249,10 +287,15 @@ const updatePass = () => {
|
||||
padding 0 12px
|
||||
position relative
|
||||
|
||||
.van-button {
|
||||
.buy-btn {
|
||||
position absolute
|
||||
top -5px
|
||||
right 10px
|
||||
|
||||
.van-button {
|
||||
font-size 14px
|
||||
margin-left 10px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user