feat: payment for mobile pages is ready

This commit is contained in:
RockYang 2024-03-20 16:14:02 +08:00
parent e11c0a3633
commit 14f63a203d
5 changed files with 160 additions and 84 deletions

View File

@ -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 任务调度配置

View File

@ -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})
} }
// 异步通知回调公共逻辑 // 异步通知回调公共逻辑

View File

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

View File

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

View File

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