diff --git a/api/core/app_server.go b/api/core/app_server.go index 9990ddba..efa63091 100644 --- a/api/core/app_server.go +++ b/api/core/app_server.go @@ -219,10 +219,6 @@ func needLogin(c *gin.Context) bool { c.Request.URL.Path == "/api/product/list" || c.Request.URL.Path == "/api/menu/list" || 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/payWays" || 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/video/client" || 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/config/") || strings.HasPrefix(c.Request.URL.Path, "/api/function/") || diff --git a/api/core/types/config.go b/api/core/types/config.go index f6e59727..dda50c00 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -57,8 +57,7 @@ type AlipayConfig struct { PublicKey string // 用户公钥文件路径 AlipayPublicKey string // 支付宝公钥文件路径 RootCert string // Root 秘钥路径 - NotifyURL string // 异步通知回调 - ReturnURL string // 支付成功返回地址 + NotifyHost string // 通知回调地址 } type WechatPayConfig struct { @@ -68,18 +67,16 @@ type WechatPayConfig struct { SerialNo string // 商户证书的证书序列号 PrivateKey string // 用户私钥文件路径 ApiV3Key string // API V3 秘钥 - NotifyURL string // 异步通知回调 - ReturnURL string // 支付成功返回地址 + NotifyHost string // 通知回调地址 } type HuPiPayConfig struct { //虎皮椒第四方支付配置 - Enabled bool // 是否启用该支付通道 - Name string // 支付名称,如:wechat/alipay - AppId string // App ID - AppSecret string // app 密钥 - ApiURL string // 支付网关 - NotifyURL string // 异步通知回调 - ReturnURL string // 支付成功返回地址 + Enabled bool // 是否启用该支付通道 + Name string // 支付名称,如:wechat/alipay + AppId string // App ID + AppSecret string // app 密钥 + ApiURL string // 支付网关 + NotifyHost string // 通知回调地址 } // GeekPayConfig GEEK支付配置 @@ -88,8 +85,7 @@ type GeekPayConfig struct { AppId string // 商户 ID PrivateKey string // 私钥 ApiURL string // API 网关 - NotifyURL string // 异步回调地址 - ReturnURL string // 支付成功返回地址 + NotifyHost string // 通知回调地址 } type XXLConfig struct { // XXL 任务调度配置 diff --git a/api/handler/payment_handler.go b/api/handler/payment_handler.go index 8a0112a9..0fba56b4 100644 --- a/api/handler/payment_handler.go +++ b/api/handler/payment_handler.go @@ -9,7 +9,6 @@ package handler import ( "embed" - "encoding/base64" "fmt" "geekai/core" "geekai/core/types" @@ -20,7 +19,6 @@ import ( "geekai/utils/resp" "github.com/shopspring/decimal" "net/http" - "net/url" "sync" "time" @@ -71,62 +69,94 @@ func NewPaymentHandler( } } -func (h *PaymentHandler) DoPay(c *gin.Context) { - orderNo := h.GetTrim(c, "order_no") - t := h.GetInt(c, "t", 0) - sign := h.GetTrim(c, "sign") - signStr := fmt.Sprintf("%s-%d-%s", orderNo, t, h.signKey) - newSign := utils.Sha256(signStr) - if newSign != sign { - resp.ERROR(c, "订单签名错误!") +func (h *PaymentHandler) Pay(c *gin.Context) { + payWay := c.Query("pay_way") + payType := c.Query("pay_type") + productId := c.Query("pid") + device := c.Query("device") + userId := c.Query("user_id") + + var product model.Product + err := h.DB.First(&product, productId).Error + if err != nil { + resp.ERROR(c, "Product not found") return } - // 检查二维码是否过期 - if time.Now().Unix()-int64(t) > int64(h.App.SysConfig.OrderPayTimeout) { - resp.ERROR(c, "支付二维码已过期,请重新生成!") + orderNo, err := h.snowflake.Next(false) + if err != nil { + 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 } - if orderNo == "" { - resp.ERROR(c, types.InvalidArgs) - return - } - - 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) + var payURL string + if payWay == "alipay" { // 支付宝 + 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, + }) + } if err != nil { resp.ERROR(c, "error with generate pay url: "+err.Error()) return } - - c.Redirect(302, uri) - return } else if order.PayWay == "hupi" { // 虎皮椒支付 params := payment.HuPiPayReq{ Version: "1.1", TradeOrderId: orderNo, TotalFee: fmt.Sprintf("%f", order.Amount), Title: order.Subject, - NotifyURL: h.App.Config.HuPiPayConfig.NotifyURL, - WapName: "极客学长", + NotifyURL: fmt.Sprintf("%s/api/payment/notify/hupi", h.App.Config.HuPiPayConfig.NotifyHost), + WapName: "GeekAI助手", } r, err := h.huPiPayService.Pay(params) if err != nil { @@ -136,7 +166,8 @@ func (h *PaymentHandler) DoPay(c *gin.Context) { c.Redirect(302, r.URL) } 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 { resp.ERROR(c, err.Error()) return @@ -144,261 +175,28 @@ func (h *PaymentHandler) DoPay(c *gin.Context) { c.Redirect(302, uri) } 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{ OutTradeNo: orderNo, Method: "web", Name: order.Subject, Money: fmt.Sprintf("%f", order.Amount), ClientIP: c.ClientIP(), - Device: "pc", - Type: "alipay", + Device: device, + Type: payType, + ReturnURL: returnURL, + NotifyURL: notifyURL, } - s, err := h.geekPayService.Pay(params) + res, err := h.geekPayService.Pay(params) if err != nil { resp.ERROR(c, err.Error()) return } - resp.SUCCESS(c, s) + payURL = res.PayURL } - //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, - CallbackURL: returnURL, - 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{ - 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 - } - - 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 { 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": "qq"}) - payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "jd"}) + payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "wxpay"}) + payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "qqpay"}) + 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": "paypal"}) } 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) } @@ -570,32 +368,28 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) { c.String(http.StatusOK, "success") } -// PayJsNotify PayJs 支付异步回调 -func (h *PaymentHandler) PayJsNotify(c *gin.Context) { - err := c.Request.ParseForm() - if err != nil { +// GeekPayNotify 支付异步回调 +func (h *PaymentHandler) GeekPayNotify(c *gin.Context) { + var params = make(map[string]string) + 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") return } - orderNo := c.Request.Form.Get("out_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) + err := h.notify(params["out_trade_no"], params["trade_no"]) if err != nil { c.String(http.StatusOK, "fail") return diff --git a/api/main.go b/api/main.go index 544abc1e..6d00d3fd 100644 --- a/api/main.go +++ b/api/main.go @@ -372,14 +372,11 @@ func main() { }), fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) { group := s.Engine.Group("/api/payment/") - group.GET("doPay", h.DoPay) + group.GET("doPay", h.Pay) group.GET("payWays", h.GetPayWays) - 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) - group.POST("wechat/notify", h.WechatPayNotify) + group.POST("notify/alipay", h.AlipayNotify) + group.GET("notify/geek", h.GeekPayNotify) + group.POST("notify/wechat", h.WechatPayNotify) }), fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) { group := s.Engine.Group("/api/admin/product/") diff --git a/api/service/payment/alipay_service.go b/api/service/payment/alipay_service.go index 8ab93eda..d6c8a139 100644 --- a/api/service/payment/alipay_service.go +++ b/api/service/payment/alipay_service.go @@ -44,9 +44,7 @@ func NewAlipayService(appConfig *types.AppConfig) (*AlipayService, error) { //client.DebugSwitch = gopay.DebugOn // 开启调试模式 client.SetLocation(alipay.LocationShanghai). // 设置时区,不设置或出错均为默认服务器时间 SetCharset(alipay.UTF8). // 设置字符编码,不设置默认 utf-8 - SetSignType(alipay.RSA2). // 设置签名类型,不设置默认 RSA2 - SetReturnUrl(config.ReturnURL). // 设置返回URL - SetNotifyUrl(config.NotifyURL) + SetSignType(alipay.RSA2) // 设置签名类型,不设置默认 RSA2 if err = client.SetCertSnByPath(config.PublicKey, config.RootCert, config.AlipayPublicKey); err != nil { 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 } -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.Set("subject", subject) - bm.Set("out_trade_no", outTradeNo) - bm.Set("quit_url", s.config.ReturnURL) - bm.Set("total_amount", amount) + bm.Set("subject", params.Subject) + bm.Set("out_trade_no", params.OutTradeNo) + bm.Set("quit_url", params.ReturnURL) + 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") 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.Set("subject", subject) - bm.Set("out_trade_no", outTradeNo) - bm.Set("total_amount", amount) + bm.Set("subject", params.Subject) + bm.Set("out_trade_no", params.OutTradeNo) + bm.Set("total_amount", params.TotalFee) 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 交易验证 diff --git a/api/service/payment/geekpay_service.go b/api/service/payment/geekpay_service.go index 2018f43f..7348c0d8 100644 --- a/api/service/payment/geekpay_service.go +++ b/api/service/payment/geekpay_service.go @@ -8,15 +8,11 @@ package payment // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import ( - "crypto" - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "crypto/x509" - "encoding/base64" - "encoding/pem" + "encoding/json" + "errors" "fmt" "geekai/core/types" + "geekai/utils" "io" "net/http" "net/url" @@ -46,41 +42,37 @@ type GeekPayParams struct { ClientIP string `json:"clientip"` //用户IP地址 SubOpenId string `json:"sub_openid"` // 微信用户 openid,仅小程序支付需要 SubAppId string `json:"sub_appid"` // 小程序 AppId,仅小程序支付需要 + NotifyURL string `json:"notify_url"` + ReturnURL string `json:"return_url"` } // Pay 支付订单 -func (s *GeekPayService) Pay(params GeekPayParams) (string, error) { - if params.Type == "wechat" { - params.Type = "wxpay" - } +func (s *GeekPayService) Pay(params GeekPayParams) (*GeekPayResp, error) { p := map[string]string{ - "pid": s.config.AppId, - "method": params.Method, + "pid": s.config.AppId, + //"method": params.Method, "device": params.Device, "type": params.Type, "out_trade_no": params.OutTradeNo, "name": params.Name, "money": params.Money, "clientip": params.ClientIP, - "sub_openid": params.SubOpenId, - "sub_appid": params.SubAppId, - "notify_url": s.config.NotifyURL, - "return_url": s.config.ReturnURL, + "notify_url": params.NotifyURL, + "return_url": params.ReturnURL, "timestamp": fmt.Sprintf("%d", time.Now().Unix()), } - sign, err := s.Sign(p) - if err != nil { - return "", err - } - p["sign"] = sign - p["sign_type"] = "RSA" + p["sign"] = s.Sign(p) + p["sign_type"] = "MD5" 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 for k := range params { + if params[k] == "" || k == "sign" || k == "sign_type" { + continue + } keys = append(keys, k) } sort.Strings(keys) @@ -93,44 +85,48 @@ func (s *GeekPayService) Sign(params map[string]string) (string, error) { signStr.WriteString(params[k]) signStr.WriteString("&") } - signString := strings.TrimSuffix(signStr.String(), "&") + signString := strings.TrimSuffix(signStr.String(), "&") + s.config.PrivateKey - // 使用RSA私钥签名 - block, _ := pem.Decode([]byte(s.config.PrivateKey)) - if block == nil { - return "", fmt.Errorf("failed to decode private key") - } - - priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return "", fmt.Errorf("failed to parse private key: %v", err) - } - - hashed := sha256.Sum256([]byte(signString)) - 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 + return utils.Md5(signString) } -func (s *GeekPayService) sendRequest(apiEndpoint string, params map[string]string) (string, error) { +type GeekPayResp struct { + Code int `json:"code"` + Msg string `json:"msg"` + TradeNo string `json:"trade_no"` + PayURL string `json:"payurl"` + QrCode string `json:"qrcode"` + UrlScheme string `json:"urlscheme"` // 小程序跳转支付链接 +} + +func (s *GeekPayService) sendRequest(endpoint string, params map[string]string) (*GeekPayResp, error) { form := url.Values{} for k, v := range params { 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 { - return "", err + return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) + logger.Debugf(string(body)) 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 } diff --git a/api/service/payment/wepay_service.go b/api/service/payment/wepay_service.go index b141a8e9..11554e9f 100644 --- a/api/service/payment/wepay_service.go +++ b/api/service/payment/wepay_service.go @@ -46,18 +46,28 @@ func NewWechatService(appConfig *types.AppConfig) (*WechatPayService, error) { 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) // 初始化 BodyMap bm := make(gopay.BodyMap) bm.Set("appid", s.config.AppId). Set("mchid", s.config.MchId). - Set("description", subject). - Set("out_trade_no", outTradeNo). + Set("description", params.Subject). + Set("out_trade_no", params.OutTradeNo). 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) { - bm.Set("total", amount). + bm.Set("total", params.TotalFee). Set("currency", "CNY") }) @@ -71,22 +81,23 @@ func (s *WechatPayService) PayUrlNative(outTradeNo string, amount int, subject s 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) // 初始化 BodyMap bm := make(gopay.BodyMap) bm.Set("appid", s.config.AppId). Set("mchid", s.config.MchId). - Set("description", subject). - Set("out_trade_no", outTradeNo). + Set("description", params.Subject). + Set("out_trade_no", params.OutTradeNo). 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) { - bm.Set("total", amount). + bm.Set("total", params.TotalFee). Set("currency", "CNY") }). 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) { bm.Set("type", "Wap") }) diff --git a/web/src/assets/css/member.styl b/web/src/assets/css/member.styl index 9dab8325..4dc2209f 100644 --- a/web/src/assets/css/member.styl +++ b/web/src/assets/css/member.styl @@ -181,6 +181,27 @@ .el-button { 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 + } } } } diff --git a/web/src/assets/iconfont/iconfont.css b/web/src/assets/iconfont/iconfont.css index a0959ced..e0dee322 100644 --- a/web/src/assets/iconfont/iconfont.css +++ b/web/src/assets/iconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 4125778 */ - src: url('iconfont.woff2?t=1726612860394') format('woff2'), - url('iconfont.woff?t=1726612860394') format('woff'), - url('iconfont.ttf?t=1726612860394') format('truetype'); + src: url('iconfont.woff2?t=1726622198991') format('woff2'), + url('iconfont.woff?t=1726622198991') format('woff'), + url('iconfont.ttf?t=1726622198991') format('truetype'); } .iconfont { @@ -13,12 +13,12 @@ -moz-osx-font-smoothing: grayscale; } -.icon-douyin:before { - content: "\e852"; +.icon-paypal:before { + content: "\e666"; } -.icon-paypal:before { - content: "\e60f"; +.icon-douyin:before { + content: "\e8db"; } .icon-qq:before { diff --git a/web/src/assets/iconfont/iconfont.js b/web/src/assets/iconfont/iconfont.js index 32973473..74291ab4 100644 --- a/web/src/assets/iconfont/iconfont.js +++ b/web/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4125778='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var h,t,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=h,o=a.document,z=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){z||(z=!0,i())}function s(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(s,50)}p()}})(window); \ No newline at end of file +window._iconfont_svg_string_4125778='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var h,t,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=h,o=a.document,z=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){z||(z=!0,i())}function s(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(s,50)}p()}})(window); \ No newline at end of file diff --git a/web/src/assets/iconfont/iconfont.json b/web/src/assets/iconfont/iconfont.json index 7dc3877f..a38cbab5 100644 --- a/web/src/assets/iconfont/iconfont.json +++ b/web/src/assets/iconfont/iconfont.json @@ -6,18 +6,18 @@ "description": "", "glyphs": [ { - "icon_id": "22174321", - "name": "抖音支付", - "font_class": "douyin", - "unicode": "e852", - "unicode_decimal": 59474 + "icon_id": "7443846", + "name": "PayPal", + "font_class": "paypal", + "unicode": "e666", + "unicode_decimal": 58982 }, { - "icon_id": "1238433", - "name": "social-paypal", - "font_class": "paypal", - "unicode": "e60f", - "unicode_decimal": 58895 + "icon_id": "18166694", + "name": "抖音", + "font_class": "douyin", + "unicode": "e8db", + "unicode_decimal": 59611 }, { "icon_id": "1244217", diff --git a/web/src/assets/iconfont/iconfont.ttf b/web/src/assets/iconfont/iconfont.ttf index 8b228359..e5b3d7de 100644 Binary files a/web/src/assets/iconfont/iconfont.ttf and b/web/src/assets/iconfont/iconfont.ttf differ diff --git a/web/src/assets/iconfont/iconfont.woff b/web/src/assets/iconfont/iconfont.woff index 426fce0c..c20755b7 100644 Binary files a/web/src/assets/iconfont/iconfont.woff and b/web/src/assets/iconfont/iconfont.woff differ diff --git a/web/src/assets/iconfont/iconfont.woff2 b/web/src/assets/iconfont/iconfont.woff2 index 906588c2..41f980f4 100644 Binary files a/web/src/assets/iconfont/iconfont.woff2 and b/web/src/assets/iconfont/iconfont.woff2 differ diff --git a/web/src/components/UserOrder.vue b/web/src/components/UserOrder.vue index dd8a6c59..13432f27 100644 --- a/web/src/components/UserOrder.vue +++ b/web/src/components/UserOrder.vue @@ -1,5 +1,5 @@