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 // 支付宝公钥文件路径
|
AlipayPublicKey string // 支付宝公钥文件路径
|
||||||
RootCert string // Root 秘钥路径
|
RootCert string // Root 秘钥路径
|
||||||
NotifyURL string // 异步通知回调
|
NotifyURL string // 异步通知回调
|
||||||
|
ReturnURL string // 支付成功返回地址
|
||||||
}
|
}
|
||||||
|
|
||||||
type HuPiPayConfig struct { //虎皮椒第四方支付配置
|
type HuPiPayConfig struct { //虎皮椒第四方支付配置
|
||||||
@ -90,6 +91,7 @@ type HuPiPayConfig struct { //虎皮椒第四方支付配置
|
|||||||
AppSecret string // app 密钥
|
AppSecret string // app 密钥
|
||||||
ApiURL string // 支付网关
|
ApiURL string // 支付网关
|
||||||
NotifyURL string // 异步通知回调
|
NotifyURL string // 异步通知回调
|
||||||
|
ReturnURL string // 支付成功返回地址
|
||||||
}
|
}
|
||||||
|
|
||||||
// JPayConfig PayJs 支付配置
|
// JPayConfig PayJs 支付配置
|
||||||
@ -100,6 +102,7 @@ type JPayConfig struct {
|
|||||||
PrivateKey string // 私钥
|
PrivateKey string // 私钥
|
||||||
ApiURL string // API 网关
|
ApiURL string // API 网关
|
||||||
NotifyURL string // 异步回调地址
|
NotifyURL string // 异步回调地址
|
||||||
|
ReturnURL string // 支付成功返回地址
|
||||||
}
|
}
|
||||||
|
|
||||||
type XXLConfig struct { // XXL 任务调度配置
|
type XXLConfig struct { // XXL 任务调度配置
|
||||||
|
@ -279,7 +279,6 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
|||||||
|
|
||||||
// Mobile 移动端支付
|
// Mobile 移动端支付
|
||||||
func (h *PaymentHandler) Mobile(c *gin.Context) {
|
func (h *PaymentHandler) Mobile(c *gin.Context) {
|
||||||
|
|
||||||
var data struct {
|
var data struct {
|
||||||
PayWay string `json:"pay_way"` // 支付方式
|
PayWay string `json:"pay_way"` // 支付方式
|
||||||
ProductId uint `json:"product_id"`
|
ProductId uint `json:"product_id"`
|
||||||
@ -309,18 +308,61 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
|
||||||
var payWay string
|
var payWay string
|
||||||
var notifyURL string
|
var notifyURL, returnURL string
|
||||||
|
var payURL string
|
||||||
switch data.PayWay {
|
switch data.PayWay {
|
||||||
case "hupi":
|
case "hupi":
|
||||||
payWay = PayWayXunHu
|
payWay = PayWayXunHu
|
||||||
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
|
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":
|
case "payjs":
|
||||||
payWay = PayWayJs
|
payWay = PayWayJs
|
||||||
notifyURL = h.App.Config.JPayConfig.NotifyURL
|
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
|
payWay = PayWayAlipay
|
||||||
notifyURL = h.App.Config.AlipayConfig.NotifyURL
|
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{
|
remark := types.OrderRemark{
|
||||||
@ -331,7 +373,6 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
|||||||
Discount: product.Discount,
|
Discount: product.Discount,
|
||||||
}
|
}
|
||||||
|
|
||||||
amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
|
|
||||||
order := model.Order{
|
order := model.Order{
|
||||||
UserId: user.Id,
|
UserId: user.Id,
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
@ -349,54 +390,7 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// PayJs 单独处理,只能用官方生成的二维码
|
resp.SUCCESS(c, payURL)
|
||||||
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})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步通知回调公共逻辑
|
// 异步通知回调公共逻辑
|
||||||
|
@ -324,6 +324,7 @@ func main() {
|
|||||||
group.GET("payWays", h.GetPayWays)
|
group.GET("payWays", h.GetPayWays)
|
||||||
group.POST("query", h.OrderQuery)
|
group.POST("query", h.OrderQuery)
|
||||||
group.POST("qrcode", h.PayQrcode)
|
group.POST("qrcode", h.PayQrcode)
|
||||||
|
group.POST("mobile", h.Mobile)
|
||||||
group.POST("alipay/notify", h.AlipayNotify)
|
group.POST("alipay/notify", h.AlipayNotify)
|
||||||
group.POST("hupipay/notify", h.HuPiPayNotify)
|
group.POST("hupipay/notify", h.HuPiPayNotify)
|
||||||
group.POST("payjs/notify", h.PayJsNotify)
|
group.POST("payjs/notify", h.PayJsNotify)
|
||||||
|
@ -29,16 +29,18 @@ type JPayReq struct {
|
|||||||
OutTradeNo string `json:"out_trade_no"`
|
OutTradeNo string `json:"out_trade_no"`
|
||||||
Subject string `json:"body"`
|
Subject string `json:"body"`
|
||||||
NotifyURL string `json:"notify_url"`
|
NotifyURL string `json:"notify_url"`
|
||||||
|
ReturnURL string `json:"callback_url"`
|
||||||
}
|
}
|
||||||
type JPayReps struct {
|
type JPayReps struct {
|
||||||
CodeUrl string `json:"code_url"`
|
|
||||||
OutTradeNo string `json:"out_trade_no"`
|
OutTradeNo string `json:"out_trade_no"`
|
||||||
OrderId string `json:"payjs_order_id"`
|
OrderId string `json:"payjs_order_id"`
|
||||||
Qrcode string `json:"qrcode"`
|
|
||||||
ReturnCode int `json:"return_code"`
|
ReturnCode int `json:"return_code"`
|
||||||
ReturnMsg string `json:"return_msg"`
|
ReturnMsg string `json:"return_msg"`
|
||||||
Sign string `json:"Sign"`
|
Sign string `json:"Sign"`
|
||||||
TotalFee string `json:"total_fee"`
|
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 {
|
func (r JPayReps) IsOK() bool {
|
||||||
@ -78,6 +80,39 @@ func (js *PayJS) Pay(param JPayReq) JPayReps {
|
|||||||
return data
|
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 {
|
func (js *PayJS) sign(params url.Values) string {
|
||||||
params.Del(`sign`)
|
params.Del(`sign`)
|
||||||
var keys = make([]string, 0, 0)
|
var keys = make([]string, 0, 0)
|
||||||
|
@ -21,23 +21,18 @@
|
|||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
|
|
||||||
<van-field label="剩余对话次数">
|
<van-field label="剩余算力">
|
||||||
<template #input>
|
<template #input>
|
||||||
<van-tag type="primary">{{ form.calls }}</van-tag>
|
<van-tag type="primary">{{ form.power }}</van-tag>
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
|
|
||||||
<van-field label="剩余绘图次数">
|
<van-field label="VIP到期时间" v-if="form.expired_time > 0">
|
||||||
<template #input>
|
<template #input>
|
||||||
<van-tag type="primary">{{ form.img_calls }}</van-tag>
|
<van-tag type="warning">{{ dateFormat(form.expired_time) }}</van-tag>
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
|
|
||||||
<van-field label="累计算力消耗">
|
|
||||||
<template #input>
|
|
||||||
<van-tag type="primary">{{ form.total_tokens }}</van-tag>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
</van-form>
|
</van-form>
|
||||||
|
|
||||||
@ -50,7 +45,19 @@
|
|||||||
<div class="item" v-for="item in products" :key="item.id">
|
<div class="item" v-for="item in products" :key="item.id">
|
||||||
<h4 class="title">
|
<h4 class="title">
|
||||||
<span>{{ item.name }}</span>
|
<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>
|
</h4>
|
||||||
|
|
||||||
<van-cell-group>
|
<van-cell-group>
|
||||||
@ -111,7 +118,10 @@ import {onMounted, ref} from "vue";
|
|||||||
import {showFailToast, showNotify, showSuccessToast} from "vant";
|
import {showFailToast, showNotify, showSuccessToast} from "vant";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet, httpPost} from "@/utils/http";
|
||||||
import Compressor from 'compressorjs';
|
import Compressor from 'compressorjs';
|
||||||
|
import {dateFormat} from "@/utils/libs";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
|
import {checkSession} from "@/action/session";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
const title = ref('用户设置')
|
const title = ref('用户设置')
|
||||||
const form = ref({
|
const form = ref({
|
||||||
@ -132,8 +142,13 @@ const fileList = ref([
|
|||||||
const products = ref([])
|
const products = ref([])
|
||||||
const vipMonthCalls = ref(0)
|
const vipMonthCalls = ref(0)
|
||||||
const vipMonthImgCalls = ref(0)
|
const vipMonthImgCalls = ref(0)
|
||||||
|
const payWays = ref({})
|
||||||
|
const router = useRouter()
|
||||||
|
const loginUser = ref(null)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
checkSession().then(user => {
|
||||||
|
loginUser.value = user
|
||||||
httpGet('/api/user/profile').then(res => {
|
httpGet('/api/user/profile').then(res => {
|
||||||
form.value = res.data
|
form.value = res.data
|
||||||
fileList.value[0].url = form.value.avatar
|
fileList.value[0].url = form.value.avatar
|
||||||
@ -155,6 +170,17 @@ onMounted(() => {
|
|||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
showFailToast("获取系统配置失败:" + e.message)
|
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) => {
|
const afterRead = (file) => {
|
||||||
@ -222,6 +248,18 @@ const updatePass = () => {
|
|||||||
showPasswordDialog.value = false
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
@ -249,10 +287,15 @@ const updatePass = () => {
|
|||||||
padding 0 12px
|
padding 0 12px
|
||||||
position relative
|
position relative
|
||||||
|
|
||||||
.van-button {
|
.buy-btn {
|
||||||
position absolute
|
position absolute
|
||||||
top -5px
|
top -5px
|
||||||
right 10px
|
right 10px
|
||||||
|
|
||||||
|
.van-button {
|
||||||
|
font-size 14px
|
||||||
|
margin-left 10px
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user