mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-17 16:56:38 +08:00
Geek Pay notify is ready
This commit is contained in:
parent
59d9ae96ac
commit
e9ac58b1ef
@ -219,10 +219,6 @@ func needLogin(c *gin.Context) bool {
|
|||||||
c.Request.URL.Path == "/api/product/list" ||
|
c.Request.URL.Path == "/api/product/list" ||
|
||||||
c.Request.URL.Path == "/api/menu/list" ||
|
c.Request.URL.Path == "/api/menu/list" ||
|
||||||
c.Request.URL.Path == "/api/markMap/client" ||
|
c.Request.URL.Path == "/api/markMap/client" ||
|
||||||
c.Request.URL.Path == "/api/payment/alipay/notify" ||
|
|
||||||
c.Request.URL.Path == "/api/payment/hupipay/notify" ||
|
|
||||||
c.Request.URL.Path == "/api/payment/payjs/notify" ||
|
|
||||||
c.Request.URL.Path == "/api/payment/wechat/notify" ||
|
|
||||||
c.Request.URL.Path == "/api/payment/doPay" ||
|
c.Request.URL.Path == "/api/payment/doPay" ||
|
||||||
c.Request.URL.Path == "/api/payment/payWays" ||
|
c.Request.URL.Path == "/api/payment/payWays" ||
|
||||||
c.Request.URL.Path == "/api/suno/client" ||
|
c.Request.URL.Path == "/api/suno/client" ||
|
||||||
@ -231,6 +227,7 @@ func needLogin(c *gin.Context) bool {
|
|||||||
c.Request.URL.Path == "/api/download" ||
|
c.Request.URL.Path == "/api/download" ||
|
||||||
c.Request.URL.Path == "/api/video/client" ||
|
c.Request.URL.Path == "/api/video/client" ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
|
||||||
|
strings.HasPrefix(c.Request.URL.Path, "/api/payment/notify/") ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/config/") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/config/") ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
|
||||||
|
@ -57,8 +57,7 @@ type AlipayConfig struct {
|
|||||||
PublicKey string // 用户公钥文件路径
|
PublicKey string // 用户公钥文件路径
|
||||||
AlipayPublicKey string // 支付宝公钥文件路径
|
AlipayPublicKey string // 支付宝公钥文件路径
|
||||||
RootCert string // Root 秘钥路径
|
RootCert string // Root 秘钥路径
|
||||||
NotifyURL string // 异步通知回调
|
NotifyHost string // 通知回调地址
|
||||||
ReturnURL string // 支付成功返回地址
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WechatPayConfig struct {
|
type WechatPayConfig struct {
|
||||||
@ -68,8 +67,7 @@ type WechatPayConfig struct {
|
|||||||
SerialNo string // 商户证书的证书序列号
|
SerialNo string // 商户证书的证书序列号
|
||||||
PrivateKey string // 用户私钥文件路径
|
PrivateKey string // 用户私钥文件路径
|
||||||
ApiV3Key string // API V3 秘钥
|
ApiV3Key string // API V3 秘钥
|
||||||
NotifyURL string // 异步通知回调
|
NotifyHost string // 通知回调地址
|
||||||
ReturnURL string // 支付成功返回地址
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type HuPiPayConfig struct { //虎皮椒第四方支付配置
|
type HuPiPayConfig struct { //虎皮椒第四方支付配置
|
||||||
@ -78,8 +76,7 @@ type HuPiPayConfig struct { //虎皮椒第四方支付配置
|
|||||||
AppId string // App ID
|
AppId string // App ID
|
||||||
AppSecret string // app 密钥
|
AppSecret string // app 密钥
|
||||||
ApiURL string // 支付网关
|
ApiURL string // 支付网关
|
||||||
NotifyURL string // 异步通知回调
|
NotifyHost string // 通知回调地址
|
||||||
ReturnURL string // 支付成功返回地址
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeekPayConfig GEEK支付配置
|
// GeekPayConfig GEEK支付配置
|
||||||
@ -88,8 +85,7 @@ type GeekPayConfig struct {
|
|||||||
AppId string // 商户 ID
|
AppId string // 商户 ID
|
||||||
PrivateKey string // 私钥
|
PrivateKey string // 私钥
|
||||||
ApiURL string // API 网关
|
ApiURL string // API 网关
|
||||||
NotifyURL string // 异步回调地址
|
NotifyHost string // 通知回调地址
|
||||||
ReturnURL string // 支付成功返回地址
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type XXLConfig struct { // XXL 任务调度配置
|
type XXLConfig struct { // XXL 任务调度配置
|
||||||
|
@ -9,7 +9,6 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"geekai/core"
|
"geekai/core"
|
||||||
"geekai/core/types"
|
"geekai/core/types"
|
||||||
@ -20,7 +19,6 @@ import (
|
|||||||
"geekai/utils/resp"
|
"geekai/utils/resp"
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -71,62 +69,94 @@ func NewPaymentHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *PaymentHandler) DoPay(c *gin.Context) {
|
func (h *PaymentHandler) Pay(c *gin.Context) {
|
||||||
orderNo := h.GetTrim(c, "order_no")
|
payWay := c.Query("pay_way")
|
||||||
t := h.GetInt(c, "t", 0)
|
payType := c.Query("pay_type")
|
||||||
sign := h.GetTrim(c, "sign")
|
productId := c.Query("pid")
|
||||||
signStr := fmt.Sprintf("%s-%d-%s", orderNo, t, h.signKey)
|
device := c.Query("device")
|
||||||
newSign := utils.Sha256(signStr)
|
userId := c.Query("user_id")
|
||||||
if newSign != sign {
|
|
||||||
resp.ERROR(c, "订单签名错误!")
|
var product model.Product
|
||||||
|
err := h.DB.First(&product, productId).Error
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, "Product not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查二维码是否过期
|
orderNo, err := h.snowflake.Next(false)
|
||||||
if time.Now().Unix()-int64(t) > int64(h.App.SysConfig.OrderPayTimeout) {
|
if err != nil {
|
||||||
resp.ERROR(c, "支付二维码已过期,请重新生成!")
|
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var user model.User
|
||||||
|
err = h.DB.Where("id", userId).First(&user).Error
|
||||||
|
if err != nil {
|
||||||
|
resp.NotAuth(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 创建订单
|
||||||
|
remark := types.OrderRemark{
|
||||||
|
Days: product.Days,
|
||||||
|
Power: product.Power,
|
||||||
|
Name: product.Name,
|
||||||
|
Price: product.Price,
|
||||||
|
Discount: product.Discount,
|
||||||
|
}
|
||||||
|
|
||||||
|
amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
|
||||||
|
|
||||||
|
order := model.Order{
|
||||||
|
UserId: user.Id,
|
||||||
|
Username: user.Username,
|
||||||
|
ProductId: product.Id,
|
||||||
|
OrderNo: orderNo,
|
||||||
|
Subject: product.Name,
|
||||||
|
Amount: amount,
|
||||||
|
Status: types.OrderNotPaid,
|
||||||
|
PayWay: payWay,
|
||||||
|
PayType: payType,
|
||||||
|
Remark: utils.JsonEncode(remark),
|
||||||
|
}
|
||||||
|
err = h.DB.Create(&order).Error
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, "error with create order: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if orderNo == "" {
|
var payURL string
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
if payWay == "alipay" { // 支付宝
|
||||||
return
|
money := fmt.Sprintf("%.2f", order.Amount)
|
||||||
|
notifyURL := fmt.Sprintf("%s/api/payment/notify/alipay", h.App.Config.AlipayConfig.NotifyHost)
|
||||||
|
returnURL := fmt.Sprintf("%s/member", h.App.Config.AlipayConfig.NotifyHost)
|
||||||
|
if device == "mobile" {
|
||||||
|
payURL, err = h.alipayService.PayMobile(payment.AlipayParams{
|
||||||
|
OutTradeNo: orderNo,
|
||||||
|
Subject: product.Name,
|
||||||
|
TotalFee: money,
|
||||||
|
ReturnURL: returnURL,
|
||||||
|
NotifyURL: notifyURL,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
payURL, err = h.alipayService.PayPC(payment.AlipayParams{
|
||||||
|
OutTradeNo: orderNo,
|
||||||
|
Subject: product.Name,
|
||||||
|
TotalFee: money,
|
||||||
|
ReturnURL: returnURL,
|
||||||
|
NotifyURL: notifyURL,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var order model.Order
|
|
||||||
res := h.DB.Where("order_no = ?", orderNo).First(&order)
|
|
||||||
if res.Error != nil {
|
|
||||||
resp.ERROR(c, "Order not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// fix: 这里先检查一下订单状态,如果已经支付了,就直接返回
|
|
||||||
if order.Status == types.OrderPaidSuccess {
|
|
||||||
resp.ERROR(c, "订单已支付成功,无需重复支付!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新扫码状态
|
|
||||||
h.DB.Model(&order).UpdateColumn("status", types.OrderScanned)
|
|
||||||
|
|
||||||
if order.PayWay == "alipay" { // 支付宝
|
|
||||||
amount := fmt.Sprintf("%.2f", order.Amount)
|
|
||||||
uri, err := h.alipayService.PayUrlMobile(order.OrderNo, amount, order.Subject)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, "error with generate pay url: "+err.Error())
|
resp.ERROR(c, "error with generate pay url: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(302, uri)
|
|
||||||
return
|
|
||||||
} else if order.PayWay == "hupi" { // 虎皮椒支付
|
} else if order.PayWay == "hupi" { // 虎皮椒支付
|
||||||
params := payment.HuPiPayReq{
|
params := payment.HuPiPayReq{
|
||||||
Version: "1.1",
|
Version: "1.1",
|
||||||
TradeOrderId: orderNo,
|
TradeOrderId: orderNo,
|
||||||
TotalFee: fmt.Sprintf("%f", order.Amount),
|
TotalFee: fmt.Sprintf("%f", order.Amount),
|
||||||
Title: order.Subject,
|
Title: order.Subject,
|
||||||
NotifyURL: h.App.Config.HuPiPayConfig.NotifyURL,
|
NotifyURL: fmt.Sprintf("%s/api/payment/notify/hupi", h.App.Config.HuPiPayConfig.NotifyHost),
|
||||||
WapName: "极客学长",
|
WapName: "GeekAI助手",
|
||||||
}
|
}
|
||||||
r, err := h.huPiPayService.Pay(params)
|
r, err := h.huPiPayService.Pay(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -136,7 +166,8 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
|
|||||||
|
|
||||||
c.Redirect(302, r.URL)
|
c.Redirect(302, r.URL)
|
||||||
} else if order.PayWay == "wechat" {
|
} else if order.PayWay == "wechat" {
|
||||||
uri, err := h.wechatPayService.PayUrlNative(order.OrderNo, int(order.Amount*100), order.Subject)
|
//uri, err := h.wechatPayService.PayUrlNative(order.OrderNo, int(order.Amount*100), order.Subject)
|
||||||
|
uri, err := h.wechatPayService.PayUrlNative(payment.WechatPayParams{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, err.Error())
|
||||||
return
|
return
|
||||||
@ -144,261 +175,28 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
|
|||||||
|
|
||||||
c.Redirect(302, uri)
|
c.Redirect(302, uri)
|
||||||
} else if order.PayWay == "geek" {
|
} else if order.PayWay == "geek" {
|
||||||
|
notifyURL := fmt.Sprintf("%s/api/payment/notify/geek", h.App.Config.GeekPayConfig.NotifyHost)
|
||||||
|
returnURL := fmt.Sprintf("%s/member", h.App.Config.GeekPayConfig.NotifyHost)
|
||||||
params := payment.GeekPayParams{
|
params := payment.GeekPayParams{
|
||||||
OutTradeNo: orderNo,
|
OutTradeNo: orderNo,
|
||||||
Method: "web",
|
Method: "web",
|
||||||
Name: order.Subject,
|
Name: order.Subject,
|
||||||
Money: fmt.Sprintf("%f", order.Amount),
|
Money: fmt.Sprintf("%f", order.Amount),
|
||||||
ClientIP: c.ClientIP(),
|
ClientIP: c.ClientIP(),
|
||||||
Device: "pc",
|
Device: device,
|
||||||
Type: "alipay",
|
Type: payType,
|
||||||
}
|
|
||||||
|
|
||||||
s, err := h.geekPayService.Pay(params)
|
|
||||||
if err != nil {
|
|
||||||
resp.ERROR(c, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp.SUCCESS(c, s)
|
|
||||||
}
|
|
||||||
//resp.ERROR(c, "Invalid operations")
|
|
||||||
}
|
|
||||||
|
|
||||||
// PayQrcode 生成支付 URL 二维码
|
|
||||||
func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
|
||||||
var data struct {
|
|
||||||
PayWay string `json:"pay_way"` // 支付方式
|
|
||||||
PayType string `json:"pay_type"` // 支付类别:wechat,alipay,qq...
|
|
||||||
ProductId uint `json:"product_id"` // 支付产品ID
|
|
||||||
}
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var product model.Product
|
|
||||||
res := h.DB.First(&product, data.ProductId)
|
|
||||||
if res.Error != nil {
|
|
||||||
resp.ERROR(c, "Product not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
orderNo, err := h.snowflake.Next(false)
|
|
||||||
if err != nil {
|
|
||||||
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, err := h.GetLoginUser(c)
|
|
||||||
if err != nil {
|
|
||||||
resp.NotAuth(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var notifyURL string
|
|
||||||
switch data.PayWay {
|
|
||||||
case "hupi":
|
|
||||||
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
|
|
||||||
break
|
|
||||||
case "geek":
|
|
||||||
notifyURL = h.App.Config.GeekPayConfig.NotifyURL
|
|
||||||
break
|
|
||||||
case "alipay": // 支付宝商户支付
|
|
||||||
notifyURL = h.App.Config.AlipayConfig.NotifyURL
|
|
||||||
break
|
|
||||||
case "wechat": // 微信商户支付
|
|
||||||
notifyURL = h.App.Config.WechatPayConfig.NotifyURL
|
|
||||||
default:
|
|
||||||
resp.ERROR(c, "Invalid pay way")
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
// 创建订单
|
|
||||||
remark := types.OrderRemark{
|
|
||||||
Days: product.Days,
|
|
||||||
Power: product.Power,
|
|
||||||
Name: product.Name,
|
|
||||||
Price: product.Price,
|
|
||||||
Discount: product.Discount,
|
|
||||||
}
|
|
||||||
|
|
||||||
amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
|
|
||||||
order := model.Order{
|
|
||||||
UserId: user.Id,
|
|
||||||
Username: user.Username,
|
|
||||||
ProductId: product.Id,
|
|
||||||
OrderNo: orderNo,
|
|
||||||
Subject: product.Name,
|
|
||||||
Amount: amount,
|
|
||||||
Status: types.OrderNotPaid,
|
|
||||||
PayWay: data.PayWay,
|
|
||||||
PayType: data.PayType,
|
|
||||||
Remark: utils.JsonEncode(remark),
|
|
||||||
}
|
|
||||||
res = h.DB.Create(&order)
|
|
||||||
if res.Error != nil || res.RowsAffected == 0 {
|
|
||||||
resp.ERROR(c, "error with create order: "+res.Error.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var logo string
|
|
||||||
switch data.PayType {
|
|
||||||
case "alipay":
|
|
||||||
logo = "res/img/alipay.jpg"
|
|
||||||
break
|
|
||||||
case "wechat":
|
|
||||||
logo = "res/img/wechat-pay.jpg"
|
|
||||||
break
|
|
||||||
case "qq":
|
|
||||||
logo = "res/img/qq-pay.jpg"
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
logo = "res/img/geek-pay.jpg"
|
|
||||||
|
|
||||||
}
|
|
||||||
if data.PayType == "alipay" {
|
|
||||||
logo = "res/img/alipay.jpg"
|
|
||||||
} else if data.PayType == "wechat" {
|
|
||||||
logo = "res/img/wechat-pay.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
|
|
||||||
}
|
|
||||||
timestamp := time.Now().Unix()
|
|
||||||
signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, data.PayWay, timestamp, h.signKey)
|
|
||||||
sign := utils.Sha256(signStr)
|
|
||||||
payUrl := fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s&pay_type=%s&t=%d&sign=%s", parse.Scheme, parse.Host, orderNo, data.PayWay, data.PayType, timestamp, sign)
|
|
||||||
imgData, err := utils.GenQrcode(payUrl, 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": payUrl})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mobile 移动端支付
|
|
||||||
func (h *PaymentHandler) Mobile(c *gin.Context) {
|
|
||||||
var data struct {
|
|
||||||
PayWay string `json:"pay_way"` // 支付方式
|
|
||||||
PayType string `json:"pay_type"` // 支付类别:wechat,alipay,qq...
|
|
||||||
ProductId uint `json:"product_id"`
|
|
||||||
}
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var product model.Product
|
|
||||||
res := h.DB.First(&product, data.ProductId)
|
|
||||||
if res.Error != nil {
|
|
||||||
resp.ERROR(c, "Product not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
orderNo, err := h.snowflake.Next(false)
|
|
||||||
if err != nil {
|
|
||||||
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, err := h.GetLoginUser(c)
|
|
||||||
if err != nil {
|
|
||||||
resp.NotAuth(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
|
|
||||||
var notifyURL, returnURL string
|
|
||||||
var payURL string
|
|
||||||
switch data.PayWay {
|
|
||||||
case "hupi":
|
|
||||||
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
|
|
||||||
returnURL = h.App.Config.HuPiPayConfig.ReturnURL
|
|
||||||
parse, _ := url.Parse(h.App.Config.HuPiPayConfig.ReturnURL)
|
|
||||||
baseURL := fmt.Sprintf("%s://%s", parse.Scheme, parse.Host)
|
|
||||||
params := payment.HuPiPayReq{
|
|
||||||
Version: "1.1",
|
|
||||||
TradeOrderId: orderNo,
|
|
||||||
TotalFee: fmt.Sprintf("%f", amount),
|
|
||||||
Title: product.Name,
|
|
||||||
NotifyURL: notifyURL,
|
|
||||||
ReturnURL: returnURL,
|
ReturnURL: returnURL,
|
||||||
CallbackURL: returnURL,
|
NotifyURL: notifyURL,
|
||||||
WapName: "极客学长",
|
|
||||||
WapUrl: baseURL,
|
|
||||||
Type: "WAP",
|
|
||||||
}
|
|
||||||
r, err := h.huPiPayService.Pay(params)
|
|
||||||
if err != nil {
|
|
||||||
errMsg := "error with generating Pay Hupi URL: " + err.Error()
|
|
||||||
logger.Error(errMsg)
|
|
||||||
resp.ERROR(c, errMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
payURL = r.URL
|
|
||||||
case "geek":
|
|
||||||
//totalFee := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Mul(decimal.NewFromInt(100)).IntPart()
|
|
||||||
//params := url.Values{}
|
|
||||||
//params.Add("total_fee", fmt.Sprintf("%d", totalFee))
|
|
||||||
//params.Add("out_trade_no", orderNo)
|
|
||||||
//params.Add("body", product.Name)
|
|
||||||
//params.Add("notify_url", notifyURL)
|
|
||||||
//params.Add("auto", "0")
|
|
||||||
//payURL = h.geekPayService.Pay(params)
|
|
||||||
case "alipay":
|
|
||||||
payURL, err = h.alipayService.PayUrlMobile(orderNo, fmt.Sprintf("%.2f", amount), product.Name)
|
|
||||||
if err != nil {
|
|
||||||
errMsg := "error with generating Alipay URL: " + err.Error()
|
|
||||||
resp.ERROR(c, errMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "wechat":
|
|
||||||
payURL, err = h.wechatPayService.PayUrlH5(orderNo, int(amount*100), product.Name, c.ClientIP())
|
|
||||||
if err != nil {
|
|
||||||
errMsg := "error with generating Wechat URL: " + err.Error()
|
|
||||||
logger.Error(errMsg)
|
|
||||||
resp.ERROR(c, errMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
resp.ERROR(c, "Unsupported pay way: "+data.PayWay)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 创建订单
|
|
||||||
remark := types.OrderRemark{
|
|
||||||
Days: product.Days,
|
|
||||||
Power: product.Power,
|
|
||||||
Name: product.Name,
|
|
||||||
Price: product.Price,
|
|
||||||
Discount: product.Discount,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
order := model.Order{
|
res, err := h.geekPayService.Pay(params)
|
||||||
UserId: user.Id,
|
if err != nil {
|
||||||
Username: user.Username,
|
resp.ERROR(c, err.Error())
|
||||||
ProductId: product.Id,
|
|
||||||
OrderNo: orderNo,
|
|
||||||
Subject: product.Name,
|
|
||||||
Amount: amount,
|
|
||||||
Status: types.OrderNotPaid,
|
|
||||||
PayWay: data.PayWay,
|
|
||||||
PayType: data.PayType,
|
|
||||||
Remark: utils.JsonEncode(remark),
|
|
||||||
}
|
|
||||||
res = h.DB.Create(&order)
|
|
||||||
if res.Error != nil || res.RowsAffected == 0 {
|
|
||||||
resp.ERROR(c, "error with create order: "+res.Error.Error())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
payURL = res.PayURL
|
||||||
resp.SUCCESS(c, gin.H{"url": payURL, "order_no": orderNo})
|
}
|
||||||
|
resp.SUCCESS(c, payURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步通知回调公共逻辑
|
// 异步通知回调公共逻辑
|
||||||
@ -505,14 +303,14 @@ func (h *PaymentHandler) GetPayWays(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
if h.App.Config.GeekPayConfig.Enabled {
|
if h.App.Config.GeekPayConfig.Enabled {
|
||||||
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "alipay"})
|
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "alipay"})
|
||||||
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "wechat"})
|
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "wxpay"})
|
||||||
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "qq"})
|
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "qqpay"})
|
||||||
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "jd"})
|
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "jdpay"})
|
||||||
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "douyin"})
|
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "douyin"})
|
||||||
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "paypal"})
|
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "paypal"})
|
||||||
}
|
}
|
||||||
if h.App.Config.WechatPayConfig.Enabled {
|
if h.App.Config.WechatPayConfig.Enabled {
|
||||||
payWays = append(payWays, gin.H{"pay_way": "wechat", "pay_type": "wechat"})
|
payWays = append(payWays, gin.H{"pay_way": "wechat", "pay_type": "wxpay"})
|
||||||
}
|
}
|
||||||
resp.SUCCESS(c, payWays)
|
resp.SUCCESS(c, payWays)
|
||||||
}
|
}
|
||||||
@ -570,32 +368,28 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
|||||||
c.String(http.StatusOK, "success")
|
c.String(http.StatusOK, "success")
|
||||||
}
|
}
|
||||||
|
|
||||||
// PayJsNotify PayJs 支付异步回调
|
// GeekPayNotify 支付异步回调
|
||||||
func (h *PaymentHandler) PayJsNotify(c *gin.Context) {
|
func (h *PaymentHandler) GeekPayNotify(c *gin.Context) {
|
||||||
err := c.Request.ParseForm()
|
var params = make(map[string]string)
|
||||||
if err != nil {
|
for k := range c.Request.URL.Query() {
|
||||||
|
params[k] = c.Query(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("收到GeekPay订单支付回调:%+v", params)
|
||||||
|
// 检查支付状态
|
||||||
|
if params["trade_status"] != "TRADE_SUCCESS" {
|
||||||
|
c.String(http.StatusOK, "success")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sign := h.geekPayService.Sign(params)
|
||||||
|
if sign != c.Query("sign") {
|
||||||
|
logger.Errorf("签名验证失败, %s, %s", sign, c.Query("sign"))
|
||||||
c.String(http.StatusOK, "fail")
|
c.String(http.StatusOK, "fail")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
orderNo := c.Request.Form.Get("out_trade_no")
|
err := h.notify(params["out_trade_no"], params["trade_no"])
|
||||||
returnCode := c.Request.Form.Get("return_code")
|
|
||||||
logger.Infof("收到PayJs订单支付回调,订单 NO:%s,支付结果代码:%v", orderNo, returnCode)
|
|
||||||
// 支付失败
|
|
||||||
if returnCode != "1" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 校验订单支付状态
|
|
||||||
tradeNo := c.Request.Form.Get("payjs_order_id")
|
|
||||||
//err = h.geekPayService.TradeVerify(tradeNo)
|
|
||||||
//if err != nil {
|
|
||||||
// logger.Error("订单校验失败:", err)
|
|
||||||
// c.String(http.StatusOK, "fail")
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
|
|
||||||
err = h.notify(orderNo, tradeNo)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.String(http.StatusOK, "fail")
|
c.String(http.StatusOK, "fail")
|
||||||
return
|
return
|
||||||
|
11
api/main.go
11
api/main.go
@ -372,14 +372,11 @@ func main() {
|
|||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) {
|
fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) {
|
||||||
group := s.Engine.Group("/api/payment/")
|
group := s.Engine.Group("/api/payment/")
|
||||||
group.GET("doPay", h.DoPay)
|
group.GET("doPay", h.Pay)
|
||||||
group.GET("payWays", h.GetPayWays)
|
group.GET("payWays", h.GetPayWays)
|
||||||
group.POST("qrcode", h.PayQrcode)
|
group.POST("notify/alipay", h.AlipayNotify)
|
||||||
group.POST("mobile", h.Mobile)
|
group.GET("notify/geek", h.GeekPayNotify)
|
||||||
group.POST("alipay/notify", h.AlipayNotify)
|
group.POST("notify/wechat", h.WechatPayNotify)
|
||||||
group.POST("hupipay/notify", h.HuPiPayNotify)
|
|
||||||
group.POST("payjs/notify", h.PayJsNotify)
|
|
||||||
group.POST("wechat/notify", h.WechatPayNotify)
|
|
||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) {
|
fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) {
|
||||||
group := s.Engine.Group("/api/admin/product/")
|
group := s.Engine.Group("/api/admin/product/")
|
||||||
|
@ -44,9 +44,7 @@ func NewAlipayService(appConfig *types.AppConfig) (*AlipayService, error) {
|
|||||||
//client.DebugSwitch = gopay.DebugOn // 开启调试模式
|
//client.DebugSwitch = gopay.DebugOn // 开启调试模式
|
||||||
client.SetLocation(alipay.LocationShanghai). // 设置时区,不设置或出错均为默认服务器时间
|
client.SetLocation(alipay.LocationShanghai). // 设置时区,不设置或出错均为默认服务器时间
|
||||||
SetCharset(alipay.UTF8). // 设置字符编码,不设置默认 utf-8
|
SetCharset(alipay.UTF8). // 设置字符编码,不设置默认 utf-8
|
||||||
SetSignType(alipay.RSA2). // 设置签名类型,不设置默认 RSA2
|
SetSignType(alipay.RSA2) // 设置签名类型,不设置默认 RSA2
|
||||||
SetReturnUrl(config.ReturnURL). // 设置返回URL
|
|
||||||
SetNotifyUrl(config.NotifyURL)
|
|
||||||
|
|
||||||
if err = client.SetCertSnByPath(config.PublicKey, config.RootCert, config.AlipayPublicKey); err != nil {
|
if err = client.SetCertSnByPath(config.PublicKey, config.RootCert, config.AlipayPublicKey); err != nil {
|
||||||
return nil, fmt.Errorf("error with load payment public key: %v", err)
|
return nil, fmt.Errorf("error with load payment public key: %v", err)
|
||||||
@ -55,23 +53,33 @@ func NewAlipayService(appConfig *types.AppConfig) (*AlipayService, error) {
|
|||||||
return &AlipayService{config: &config, client: client}, nil
|
return &AlipayService{config: &config, client: client}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AlipayService) PayUrlMobile(outTradeNo string, amount string, subject string) (string, error) {
|
type AlipayParams struct {
|
||||||
|
OutTradeNo string `json:"out_trade_no"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
TotalFee string `json:"total_fee"`
|
||||||
|
ReturnURL string `json:"return_url"`
|
||||||
|
NotifyURL string `json:"notify_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AlipayService) PayMobile(params AlipayParams) (string, error) {
|
||||||
bm := make(gopay.BodyMap)
|
bm := make(gopay.BodyMap)
|
||||||
bm.Set("subject", subject)
|
bm.Set("subject", params.Subject)
|
||||||
bm.Set("out_trade_no", outTradeNo)
|
bm.Set("out_trade_no", params.OutTradeNo)
|
||||||
bm.Set("quit_url", s.config.ReturnURL)
|
bm.Set("quit_url", params.ReturnURL)
|
||||||
bm.Set("total_amount", amount)
|
bm.Set("total_amount", params.TotalFee)
|
||||||
|
bm.Set("return_url", params.ReturnURL)
|
||||||
|
bm.Set("notify_url", params.NotifyURL)
|
||||||
bm.Set("product_code", "QUICK_WAP_WAY")
|
bm.Set("product_code", "QUICK_WAP_WAY")
|
||||||
return s.client.TradeWapPay(context.Background(), bm)
|
return s.client.TradeWapPay(context.Background(), bm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AlipayService) PayUrlPc(outTradeNo string, amount string, subject string) (string, error) {
|
func (s *AlipayService) PayPC(params AlipayParams) (string, error) {
|
||||||
bm := make(gopay.BodyMap)
|
bm := make(gopay.BodyMap)
|
||||||
bm.Set("subject", subject)
|
bm.Set("subject", params.Subject)
|
||||||
bm.Set("out_trade_no", outTradeNo)
|
bm.Set("out_trade_no", params.OutTradeNo)
|
||||||
bm.Set("total_amount", amount)
|
bm.Set("total_amount", params.TotalFee)
|
||||||
bm.Set("product_code", "FAST_INSTANT_TRADE_PAY")
|
bm.Set("product_code", "FAST_INSTANT_TRADE_PAY")
|
||||||
return s.client.TradePagePay(context.Background(), bm)
|
return s.client.SetNotifyUrl(params.NotifyURL).SetReturnUrl(params.ReturnURL).TradePagePay(context.Background(), bm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TradeVerify 交易验证
|
// TradeVerify 交易验证
|
||||||
|
@ -8,15 +8,11 @@ package payment
|
|||||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"encoding/json"
|
||||||
"crypto/rand"
|
"errors"
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"geekai/core/types"
|
"geekai/core/types"
|
||||||
|
"geekai/utils"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -46,41 +42,37 @@ type GeekPayParams struct {
|
|||||||
ClientIP string `json:"clientip"` //用户IP地址
|
ClientIP string `json:"clientip"` //用户IP地址
|
||||||
SubOpenId string `json:"sub_openid"` // 微信用户 openid,仅小程序支付需要
|
SubOpenId string `json:"sub_openid"` // 微信用户 openid,仅小程序支付需要
|
||||||
SubAppId string `json:"sub_appid"` // 小程序 AppId,仅小程序支付需要
|
SubAppId string `json:"sub_appid"` // 小程序 AppId,仅小程序支付需要
|
||||||
|
NotifyURL string `json:"notify_url"`
|
||||||
|
ReturnURL string `json:"return_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pay 支付订单
|
// Pay 支付订单
|
||||||
func (s *GeekPayService) Pay(params GeekPayParams) (string, error) {
|
func (s *GeekPayService) Pay(params GeekPayParams) (*GeekPayResp, error) {
|
||||||
if params.Type == "wechat" {
|
|
||||||
params.Type = "wxpay"
|
|
||||||
}
|
|
||||||
p := map[string]string{
|
p := map[string]string{
|
||||||
"pid": s.config.AppId,
|
"pid": s.config.AppId,
|
||||||
"method": params.Method,
|
//"method": params.Method,
|
||||||
"device": params.Device,
|
"device": params.Device,
|
||||||
"type": params.Type,
|
"type": params.Type,
|
||||||
"out_trade_no": params.OutTradeNo,
|
"out_trade_no": params.OutTradeNo,
|
||||||
"name": params.Name,
|
"name": params.Name,
|
||||||
"money": params.Money,
|
"money": params.Money,
|
||||||
"clientip": params.ClientIP,
|
"clientip": params.ClientIP,
|
||||||
"sub_openid": params.SubOpenId,
|
"notify_url": params.NotifyURL,
|
||||||
"sub_appid": params.SubAppId,
|
"return_url": params.ReturnURL,
|
||||||
"notify_url": s.config.NotifyURL,
|
|
||||||
"return_url": s.config.ReturnURL,
|
|
||||||
"timestamp": fmt.Sprintf("%d", time.Now().Unix()),
|
"timestamp": fmt.Sprintf("%d", time.Now().Unix()),
|
||||||
}
|
}
|
||||||
sign, err := s.Sign(p)
|
p["sign"] = s.Sign(p)
|
||||||
if err != nil {
|
p["sign_type"] = "MD5"
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
p["sign"] = sign
|
|
||||||
p["sign_type"] = "RSA"
|
|
||||||
return s.sendRequest(s.config.ApiURL, p)
|
return s.sendRequest(s.config.ApiURL, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GeekPayService) Sign(params map[string]string) (string, error) {
|
func (s *GeekPayService) Sign(params map[string]string) string {
|
||||||
// 按字母顺序排序参数
|
// 按字母顺序排序参数
|
||||||
var keys []string
|
var keys []string
|
||||||
for k := range params {
|
for k := range params {
|
||||||
|
if params[k] == "" || k == "sign" || k == "sign_type" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
keys = append(keys, k)
|
keys = append(keys, k)
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
@ -93,44 +85,48 @@ func (s *GeekPayService) Sign(params map[string]string) (string, error) {
|
|||||||
signStr.WriteString(params[k])
|
signStr.WriteString(params[k])
|
||||||
signStr.WriteString("&")
|
signStr.WriteString("&")
|
||||||
}
|
}
|
||||||
signString := strings.TrimSuffix(signStr.String(), "&")
|
signString := strings.TrimSuffix(signStr.String(), "&") + s.config.PrivateKey
|
||||||
|
|
||||||
// 使用RSA私钥签名
|
return utils.Md5(signString)
|
||||||
block, _ := pem.Decode([]byte(s.config.PrivateKey))
|
|
||||||
if block == nil {
|
|
||||||
return "", fmt.Errorf("failed to decode private key")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
type GeekPayResp struct {
|
||||||
if err != nil {
|
Code int `json:"code"`
|
||||||
return "", fmt.Errorf("failed to parse private key: %v", err)
|
Msg string `json:"msg"`
|
||||||
|
TradeNo string `json:"trade_no"`
|
||||||
|
PayURL string `json:"payurl"`
|
||||||
|
QrCode string `json:"qrcode"`
|
||||||
|
UrlScheme string `json:"urlscheme"` // 小程序跳转支付链接
|
||||||
}
|
}
|
||||||
|
|
||||||
hashed := sha256.Sum256([]byte(signString))
|
func (s *GeekPayService) sendRequest(endpoint string, params map[string]string) (*GeekPayResp, error) {
|
||||||
signature, err := rsa.SignPKCS1v15(rand.Reader, priKey, crypto.SHA256, hashed[:])
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("failed to sign: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return base64.StdEncoding.EncodeToString(signature), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *GeekPayService) sendRequest(apiEndpoint string, params map[string]string) (string, error) {
|
|
||||||
form := url.Values{}
|
form := url.Values{}
|
||||||
for k, v := range params {
|
for k, v := range params {
|
||||||
form.Add(k, v)
|
form.Add(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.PostForm(apiEndpoint, form)
|
apiURL := fmt.Sprintf("%s/mapi.php", endpoint)
|
||||||
|
logger.Infof(apiURL)
|
||||||
|
|
||||||
|
resp, err := http.PostForm(apiURL, form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
logger.Debugf(string(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(body), nil
|
var r GeekPayResp
|
||||||
|
err = json.Unmarshal(body, &r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("当前支付渠道暂不支持")
|
||||||
|
}
|
||||||
|
if r.Code != 1 {
|
||||||
|
return nil, errors.New(r.Msg)
|
||||||
|
}
|
||||||
|
return &r, nil
|
||||||
}
|
}
|
||||||
|
@ -46,18 +46,28 @@ func NewWechatService(appConfig *types.AppConfig) (*WechatPayService, error) {
|
|||||||
return &WechatPayService{config: &config, client: client}, nil
|
return &WechatPayService{config: &config, client: client}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WechatPayService) PayUrlNative(outTradeNo string, amount int, subject string) (string, error) {
|
type WechatPayParams struct {
|
||||||
|
OutTradeNo string `json:"out_trade_no"`
|
||||||
|
TotalFee int `json:"total_fee"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
ClientIP string `json:"client_ip"`
|
||||||
|
ReturnURL string `json:"return_url"`
|
||||||
|
NotifyURL string `json:"notify_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WechatPayService) PayUrlNative(params WechatPayParams) (string, error) {
|
||||||
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
|
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
|
||||||
// 初始化 BodyMap
|
// 初始化 BodyMap
|
||||||
bm := make(gopay.BodyMap)
|
bm := make(gopay.BodyMap)
|
||||||
bm.Set("appid", s.config.AppId).
|
bm.Set("appid", s.config.AppId).
|
||||||
Set("mchid", s.config.MchId).
|
Set("mchid", s.config.MchId).
|
||||||
Set("description", subject).
|
Set("description", params.Subject).
|
||||||
Set("out_trade_no", outTradeNo).
|
Set("out_trade_no", params.OutTradeNo).
|
||||||
Set("time_expire", expire).
|
Set("time_expire", expire).
|
||||||
Set("notify_url", s.config.NotifyURL).
|
Set("notify_url", params.NotifyURL).
|
||||||
|
Set("return_url", params.ReturnURL).
|
||||||
SetBodyMap("amount", func(bm gopay.BodyMap) {
|
SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||||
bm.Set("total", amount).
|
bm.Set("total", params.TotalFee).
|
||||||
Set("currency", "CNY")
|
Set("currency", "CNY")
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -71,22 +81,23 @@ func (s *WechatPayService) PayUrlNative(outTradeNo string, amount int, subject s
|
|||||||
return wxRsp.Response.CodeUrl, nil
|
return wxRsp.Response.CodeUrl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WechatPayService) PayUrlH5(outTradeNo string, amount int, subject string, ip string) (string, error) {
|
func (s *WechatPayService) PayUrlH5(params WechatPayParams) (string, error) {
|
||||||
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
|
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
|
||||||
// 初始化 BodyMap
|
// 初始化 BodyMap
|
||||||
bm := make(gopay.BodyMap)
|
bm := make(gopay.BodyMap)
|
||||||
bm.Set("appid", s.config.AppId).
|
bm.Set("appid", s.config.AppId).
|
||||||
Set("mchid", s.config.MchId).
|
Set("mchid", s.config.MchId).
|
||||||
Set("description", subject).
|
Set("description", params.Subject).
|
||||||
Set("out_trade_no", outTradeNo).
|
Set("out_trade_no", params.OutTradeNo).
|
||||||
Set("time_expire", expire).
|
Set("time_expire", expire).
|
||||||
Set("notify_url", s.config.NotifyURL).
|
Set("notify_url", params.NotifyURL).
|
||||||
|
Set("return_url", params.ReturnURL).
|
||||||
SetBodyMap("amount", func(bm gopay.BodyMap) {
|
SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||||
bm.Set("total", amount).
|
bm.Set("total", params.TotalFee).
|
||||||
Set("currency", "CNY")
|
Set("currency", "CNY")
|
||||||
}).
|
}).
|
||||||
SetBodyMap("scene_info", func(bm gopay.BodyMap) {
|
SetBodyMap("scene_info", func(bm gopay.BodyMap) {
|
||||||
bm.Set("payer_client_ip", ip).
|
bm.Set("payer_client_ip", params.ClientIP).
|
||||||
SetBodyMap("h5_info", func(bm gopay.BodyMap) {
|
SetBodyMap("h5_info", func(bm gopay.BodyMap) {
|
||||||
bm.Set("type", "Wap")
|
bm.Set("type", "Wap")
|
||||||
})
|
})
|
||||||
|
@ -181,6 +181,27 @@
|
|||||||
|
|
||||||
.el-button {
|
.el-button {
|
||||||
margin 10px 5px 0 5px
|
margin 10px 5px 0 5px
|
||||||
|
padding 0
|
||||||
|
|
||||||
|
.icon-alipay,.icon-wechat-pay {
|
||||||
|
color #ffffff
|
||||||
|
}
|
||||||
|
.icon-qq {
|
||||||
|
color #15A6E8
|
||||||
|
font-size 24px
|
||||||
|
}
|
||||||
|
.icon-jd-pay {
|
||||||
|
color #ffffff
|
||||||
|
font-size 24px
|
||||||
|
}
|
||||||
|
.icon-douyin {
|
||||||
|
color #0a0a0a
|
||||||
|
font-size 22px
|
||||||
|
}
|
||||||
|
.icon-paypal {
|
||||||
|
font-size 14px
|
||||||
|
color #009CDE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 4125778 */
|
font-family: "iconfont"; /* Project id 4125778 */
|
||||||
src: url('iconfont.woff2?t=1726612860394') format('woff2'),
|
src: url('iconfont.woff2?t=1726622198991') format('woff2'),
|
||||||
url('iconfont.woff?t=1726612860394') format('woff'),
|
url('iconfont.woff?t=1726622198991') format('woff'),
|
||||||
url('iconfont.ttf?t=1726612860394') format('truetype');
|
url('iconfont.ttf?t=1726622198991') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@ -13,12 +13,12 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-douyin:before {
|
.icon-paypal:before {
|
||||||
content: "\e852";
|
content: "\e666";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-paypal:before {
|
.icon-douyin:before {
|
||||||
content: "\e60f";
|
content: "\e8db";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-qq:before {
|
.icon-qq:before {
|
||||||
|
File diff suppressed because one or more lines are too long
@ -6,18 +6,18 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
{
|
{
|
||||||
"icon_id": "22174321",
|
"icon_id": "7443846",
|
||||||
"name": "抖音支付",
|
"name": "PayPal",
|
||||||
"font_class": "douyin",
|
"font_class": "paypal",
|
||||||
"unicode": "e852",
|
"unicode": "e666",
|
||||||
"unicode_decimal": 59474
|
"unicode_decimal": 58982
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "1238433",
|
"icon_id": "18166694",
|
||||||
"name": "social-paypal",
|
"name": "抖音",
|
||||||
"font_class": "paypal",
|
"font_class": "douyin",
|
||||||
"unicode": "e60f",
|
"unicode": "e8db",
|
||||||
"unicode_decimal": 58895
|
"unicode_decimal": 59611
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "1244217",
|
"icon_id": "1244217",
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="user-bill" v-loading="loading">
|
<div class="user-bill" v-loading="loading" element-loading-background="rgba(255,255,255,.3)">
|
||||||
<el-row v-if="items.length > 0">
|
<el-row v-if="items.length > 0">
|
||||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
|
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
|
||||||
style="--el-table-border-color:#373C47;
|
style="--el-table-border-color:#373C47;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="member custom-scroll">
|
<div class="member custom-scroll" v-loading="loading" element-loading-background="rgba(255,255,255,.3)" :element-loading-text="loadingText">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<div class="user-profile">
|
<div class="user-profile">
|
||||||
<user-profile :key="profileKey"/>
|
<user-profile :key="profileKey"/>
|
||||||
@ -22,10 +22,6 @@
|
|||||||
<el-button type="primary" @click="showRedeemVerifyDialog = true">卡密兑换
|
<el-button type="primary" @click="showRedeemVerifyDialog = true">卡密兑换
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :span="24" style="padding-top: 30px" v-if="isLogin">
|
|
||||||
<el-button type="danger" round @click="logout">退出登录</el-button>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -67,23 +63,23 @@
|
|||||||
|
|
||||||
<div class="pay-way">
|
<div class="pay-way">
|
||||||
|
|
||||||
<span type="primary" v-for="payWay in payWays" @click="genPayQrcode(item,payWay)" :key="payWay">
|
<span type="primary" v-for="payWay in payWays" @click="pay(item,payWay)" :key="payWay">
|
||||||
<el-button v-if="payWay.pay_type==='alipay'" color="#409eff" circle>
|
<el-button v-if="payWay.pay_type==='alipay'" color="#15A6E8" circle>
|
||||||
<i class="iconfont icon-alipay" ></i>
|
<i class="iconfont icon-alipay" ></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-else-if="payWay.pay_type==='qq'" class="qq" circle>
|
<el-button v-else-if="payWay.pay_type==='qqpay'" circle>
|
||||||
<i class="iconfont icon-qq"></i>
|
<i class="iconfont icon-qq"></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-else-if="payWay.pay_type==='paypal'" class="paypal" circle>
|
<el-button v-else-if="payWay.pay_type==='paypal'" class="paypal" round>
|
||||||
<i class="iconfont icon-paypal"></i>
|
<i class="iconfont icon-paypal"></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-else-if="payWay.pay_type==='jd'" class="jd" circle>
|
<el-button v-else-if="payWay.pay_type==='jdpay'" color="#E1251B" circle>
|
||||||
<i class="iconfont icon-jd-pay"></i>
|
<i class="iconfont icon-jd-pay"></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-else-if="payWay.pay_type==='douyin'" class="douyin" circle>
|
<el-button v-else-if="payWay.pay_type==='douyin'" class="douyin" circle>
|
||||||
<i class="iconfont icon-douyin"></i>
|
<i class="iconfont icon-douyin"></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-else circle class="wechat">
|
<el-button v-else circle class="wechat" color="#67C23A">
|
||||||
<i class="iconfont icon-wechat-pay"></i>
|
<i class="iconfont icon-wechat-pay"></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
@ -92,7 +88,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-empty description="暂无数据" />
|
<el-empty description="暂无数据" v-else />
|
||||||
|
|
||||||
<h2 class="headline">消费账单</h2>
|
<h2 class="headline">消费账单</h2>
|
||||||
|
|
||||||
@ -102,47 +98,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<password-dialog v-if="isLogin" :show="showPasswordDialog" @hide="showPasswordDialog = false"
|
<password-dialog v-if="isLogin" :show="showPasswordDialog" @hide="showPasswordDialog = false"/>
|
||||||
@logout="logout"/>
|
|
||||||
|
|
||||||
<bind-mobile v-if="isLogin" :show="showBindMobileDialog" @hide="showBindMobileDialog = false"/>
|
<bind-mobile v-if="isLogin" :show="showBindMobileDialog" @hide="showBindMobileDialog = false"/>
|
||||||
|
|
||||||
<bind-email v-if="isLogin" :show="showBindEmailDialog" @hide="showBindEmailDialog = false"/>
|
<bind-email v-if="isLogin" :show="showBindEmailDialog" @hide="showBindEmailDialog = false"/>
|
||||||
<third-login v-if="isLogin" :show="showThirdLoginDialog" @hide="showThirdLoginDialog = false"/>
|
<third-login v-if="isLogin" :show="showThirdLoginDialog" @hide="showThirdLoginDialog = false"/>
|
||||||
|
|
||||||
<redeem-verify v-if="isLogin" :show="showRedeemVerifyDialog" @hide="redeemCallback"/>
|
<redeem-verify v-if="isLogin" :show="showRedeemVerifyDialog" @hide="redeemCallback"/>
|
||||||
|
|
||||||
<el-dialog
|
|
||||||
v-model="showPayDialog"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
:show-close="true"
|
|
||||||
:width="400"
|
|
||||||
@close="closeOrder"
|
|
||||||
:title="'实付金额:¥'+amount">
|
|
||||||
<div class="pay-container">
|
|
||||||
<div class="count-down">
|
|
||||||
<count-down :second="orderTimeout" @timeout="refreshPayCode" ref="countDownRef"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pay-qrcode" v-loading="loading">
|
|
||||||
<el-image :src="qrcode"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tip success" v-if="text !== ''">
|
|
||||||
<el-icon>
|
|
||||||
<SuccessFilled/>
|
|
||||||
</el-icon>
|
|
||||||
<span class="text">{{ text }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="tip" v-else>
|
|
||||||
<el-icon>
|
|
||||||
<InfoFilled/>
|
|
||||||
</el-icon>
|
|
||||||
<span class="text">请打开手机扫码支付</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -151,46 +112,34 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, ref} from "vue"
|
import {onMounted, ref} from "vue"
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet} from "@/utils/http";
|
||||||
import {InfoFilled, SuccessFilled} from "@element-plus/icons-vue";
|
|
||||||
import {checkSession, getSystemInfo} from "@/store/cache";
|
import {checkSession, getSystemInfo} from "@/store/cache";
|
||||||
import UserProfile from "@/components/UserProfile.vue";
|
import UserProfile from "@/components/UserProfile.vue";
|
||||||
import PasswordDialog from "@/components/PasswordDialog.vue";
|
import PasswordDialog from "@/components/PasswordDialog.vue";
|
||||||
import BindMobile from "@/components/BindMobile.vue";
|
import BindMobile from "@/components/BindMobile.vue";
|
||||||
import RedeemVerify from "@/components/RedeemVerify.vue";
|
import RedeemVerify from "@/components/RedeemVerify.vue";
|
||||||
import {useRouter} from "vue-router";
|
|
||||||
import {removeUserToken} from "@/store/session";
|
|
||||||
import UserOrder from "@/components/UserOrder.vue";
|
import UserOrder from "@/components/UserOrder.vue";
|
||||||
import CountDown from "@/components/CountDown.vue";
|
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import {useSharedStore} from "@/store/sharedata";
|
||||||
import BindEmail from "@/components/BindEmail.vue";
|
import BindEmail from "@/components/BindEmail.vue";
|
||||||
import ThirdLogin from "@/components/ThirdLogin.vue";
|
import ThirdLogin from "@/components/ThirdLogin.vue";
|
||||||
|
|
||||||
const list = ref([])
|
const list = ref([])
|
||||||
const showPayDialog = ref(false)
|
|
||||||
const vipImg = ref("/images/vip.png")
|
const vipImg = ref("/images/vip.png")
|
||||||
const enableReward = ref(false) // 是否启用众筹功能
|
const enableReward = ref(false) // 是否启用众筹功能
|
||||||
const rewardImg = ref('/images/reward.png')
|
const rewardImg = ref('/images/reward.png')
|
||||||
const qrcode = ref("")
|
|
||||||
const showPasswordDialog = ref(false)
|
const showPasswordDialog = ref(false)
|
||||||
const showBindMobileDialog = ref(false)
|
const showBindMobileDialog = ref(false)
|
||||||
const showBindEmailDialog = ref(false)
|
const showBindEmailDialog = ref(false)
|
||||||
const showRedeemVerifyDialog = ref(false)
|
const showRedeemVerifyDialog = ref(false)
|
||||||
const showThirdLoginDialog = ref(false)
|
const showThirdLoginDialog = ref(false)
|
||||||
const text = ref("")
|
|
||||||
const user = ref(null)
|
const user = ref(null)
|
||||||
const isLogin = ref(false)
|
const isLogin = ref(false)
|
||||||
const router = useRouter()
|
|
||||||
const activeOrderNo = ref("")
|
|
||||||
const countDownRef = ref(null)
|
|
||||||
const orderTimeout = ref(1800)
|
const orderTimeout = ref(1800)
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
|
const loadingText = ref("加载中...")
|
||||||
const orderPayInfoText = ref("")
|
const orderPayInfoText = ref("")
|
||||||
|
|
||||||
const payWays = ref([])
|
const payWays = ref([])
|
||||||
const amount = ref(0)
|
|
||||||
const curPayProduct = ref(null)
|
|
||||||
const curPayWay = ref({pay_way: "", pay_type:""})
|
|
||||||
const vipInfoText = ref("")
|
const vipInfoText = ref("")
|
||||||
const store = useSharedStore()
|
const store = useSharedStore()
|
||||||
const profileKey = ref(0)
|
const profileKey = ref(0)
|
||||||
@ -206,6 +155,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
httpGet("/api/product/list").then((res) => {
|
httpGet("/api/product/list").then((res) => {
|
||||||
list.value = res.data
|
list.value = res.data
|
||||||
|
loading.value = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
ElMessage.error("获取产品套餐失败:" + e.message)
|
ElMessage.error("获取产品套餐失败:" + e.message)
|
||||||
})
|
})
|
||||||
@ -229,74 +179,30 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// refresh payment qrcode
|
const pay = (product, payWay) => {
|
||||||
const refreshPayCode = () => {
|
|
||||||
genPayQrcode(curPayProduct.value, curPayWay.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const genPayQrcode = (product, payWay) => {
|
|
||||||
if (!isLogin.value) {
|
if (!isLogin.value) {
|
||||||
store.setShowLoginDialog(true)
|
store.setShowLoginDialog(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
text.value = ""
|
loadingText.value = "正在生成支付订单..."
|
||||||
curPayProduct.value = product
|
httpGet(`${process.env.VUE_APP_API_HOST}/api/payment/doPay`, {
|
||||||
curPayWay.value = payWay
|
product_id: product.id,
|
||||||
amount.value = (product.price - product.discount).toFixed(2)
|
|
||||||
httpPost("/api/payment/qrcode", {
|
|
||||||
pay_way: payWay.pay_way,
|
pay_way: payWay.pay_way,
|
||||||
pay_type: payWay.pay_type,
|
pay_type: payWay.pay_type,
|
||||||
product_id: curPayProduct.value.id,
|
user_id: user.value.id,
|
||||||
|
device: "jump"
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
showPayDialog.value = true
|
window.open(res.data, '_blank');
|
||||||
qrcode.value = res.data['image']
|
|
||||||
activeOrderNo.value = res.data['order_no']
|
|
||||||
queryOrder(activeOrderNo.value)
|
|
||||||
loading.value = false
|
loading.value = false
|
||||||
// 重置计数器
|
|
||||||
if (countDownRef.value) {
|
|
||||||
countDownRef.value.resetTimer()
|
|
||||||
}
|
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
setTimeout(() => {
|
||||||
ElMessage.error("生成支付订单失败:" + e.message)
|
ElMessage.error("生成支付订单失败:" + e.message)
|
||||||
|
loading.value = false
|
||||||
|
}, 500)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const queryOrder = (orderNo) => {
|
|
||||||
httpGet("/api/order/query", {order_no: orderNo}).then(res => {
|
|
||||||
if (res.data.status === 1) {
|
|
||||||
text.value = "扫码成功,请在手机上进行支付!"
|
|
||||||
queryOrder(orderNo)
|
|
||||||
} else if (res.data.status === 2) {
|
|
||||||
text.value = "支付成功,正在刷新页面"
|
|
||||||
setTimeout(() => location.reload(), 500)
|
|
||||||
} else {
|
|
||||||
// 如果当前订单没有过期,继续等待订单的下一个状态
|
|
||||||
if (activeOrderNo.value === orderNo) {
|
|
||||||
queryOrder(orderNo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).catch(e => {
|
|
||||||
ElMessage.error("查询支付状态失败:" + e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const logout = function () {
|
|
||||||
httpGet('/api/user/logout').then(() => {
|
|
||||||
removeUserToken();
|
|
||||||
router.push('/login');
|
|
||||||
}).catch(() => {
|
|
||||||
ElMessage.error('注销失败!');
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeOrder = () => {
|
|
||||||
activeOrderNo.value = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const redeemCallback = (success) => {
|
const redeemCallback = (success) => {
|
||||||
showRedeemVerifyDialog.value = false
|
showRedeemVerifyDialog.value = false
|
||||||
if (success) {
|
if (success) {
|
||||||
|
Loading…
Reference in New Issue
Block a user