2024-09-14 10:48:09 +08:00
|
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
|
|
|
"github.com/smartwalle/alipay/v3"
|
|
|
|
|
wxcore "github.com/wechatpay-apiv3/wechatpay-go/core"
|
|
|
|
|
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
|
|
|
|
|
"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
|
|
|
|
|
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
|
|
|
|
|
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
|
|
|
|
|
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
|
|
|
|
|
wxutils "github.com/wechatpay-apiv3/wechatpay-go/utils"
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
"log"
|
2024-12-25 11:59:33 +08:00
|
|
|
|
"math"
|
2024-09-14 10:48:09 +08:00
|
|
|
|
"net/http"
|
|
|
|
|
"qnc-server/config"
|
|
|
|
|
"qnc-server/db"
|
|
|
|
|
"qnc-server/global"
|
|
|
|
|
"qnc-server/model/model"
|
|
|
|
|
"qnc-server/model/request"
|
|
|
|
|
"qnc-server/model/response"
|
|
|
|
|
"qnc-server/service"
|
|
|
|
|
"qnc-server/utils"
|
|
|
|
|
"strconv"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Pay struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ctx = context.Background()
|
|
|
|
|
var productService service.ProductService
|
|
|
|
|
|
|
|
|
|
// 注册路由
|
|
|
|
|
func InitPay(group *gin.RouterGroup) {
|
|
|
|
|
var p Pay
|
|
|
|
|
{
|
|
|
|
|
payPublicGroup := group.Group("pay")
|
|
|
|
|
payPublicGroup.POST("callback/:platform", p.Callback) // 微信支付支付回调
|
|
|
|
|
payPublicGroup.GET("refund_details/:id", p.RefundDetailsHTML) // 内部退款订单详情页面
|
|
|
|
|
payPublicGroup.POST("refund/:id", p.Refund) // 内部退款按钮
|
|
|
|
|
payPublicGroup.POST("refund_callback/:platform", p.RefundCallback) // 微信退款回调
|
2024-12-25 11:59:33 +08:00
|
|
|
|
payPublicGroup.POST("ali_callback", p.AlipayCallback) // 阿里回调
|
2024-09-14 10:48:09 +08:00
|
|
|
|
payPublicGroup.POST("complaint_callback/:platform", p.WxPayComplaintCallback)
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
payPrivateGroup := group.Group("pay")
|
|
|
|
|
payPrivateGroup.Use(JWTAuth())
|
2024-12-25 11:59:33 +08:00
|
|
|
|
payPrivateGroup.POST("prepay", p.Prepay) // 创建微信支付订单
|
|
|
|
|
payPrivateGroup.GET("order_list", p.GetOrderList) // 获取订单列表
|
|
|
|
|
payPrivateGroup.GET("promotion_list", p.GetPromotionList) // 获取订单列表
|
|
|
|
|
payPrivateGroup.POST("ali_prepay", p.AliPrepay) // 创建支付宝支付订单
|
|
|
|
|
payPrivateGroup.GET("get_query_cache", p.GetQueryCache) // 获取网页的查询缓存
|
2024-09-14 10:48:09 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
func (p *Pay) GetOrderList(c *gin.Context) {
|
|
|
|
|
// 从查询参数中获取分页参数
|
|
|
|
|
pageSizeStr := c.DefaultQuery("page_size", "10")
|
|
|
|
|
pageNumStr := c.DefaultQuery("page_num", "1")
|
|
|
|
|
userId := utils.GetUserID(c)
|
|
|
|
|
pageSize, err := strconv.Atoi(pageSizeStr)
|
|
|
|
|
if err != nil {
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pageNum, err := strconv.Atoi(pageNumStr)
|
|
|
|
|
if err != nil {
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list, err := orderService.GetList(pageSize, pageNum, userId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println("get order list error:", err)
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
response.OkWithData(list, c)
|
|
|
|
|
}
|
2024-12-25 11:59:33 +08:00
|
|
|
|
func (p *Pay) GetPromotionList(c *gin.Context) {
|
|
|
|
|
userId := utils.GetUserID(c)
|
|
|
|
|
user, err := userService.GetUserByUserid(userId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println("get user info error:", err)
|
|
|
|
|
response.FailWithMessage("系统开小差啦, 请稍后再试~", c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if user.Promotion == "" {
|
|
|
|
|
response.FailWithMessage("您不是代理推广人哦", c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// 从查询参数中获取分页参数
|
|
|
|
|
pageSizeStr := c.DefaultQuery("page_size", "10")
|
|
|
|
|
pageNumStr := c.DefaultQuery("page_num", "1")
|
|
|
|
|
pageSize, err := strconv.Atoi(pageSizeStr)
|
|
|
|
|
if err != nil {
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pageNum, err := strconv.Atoi(pageNumStr)
|
|
|
|
|
if err != nil {
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list, err := orderService.GetListByPromotion(pageSize, pageNum, user.Promotion)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println("get order list error:", err)
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// 获取总单数和总金额
|
|
|
|
|
totalCount, totalAmount, err := orderService.GetSummaryByPromotion(user.Promotion)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println("get order summary error:", err)
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// 计算总金额的 30%,并保留两位小数
|
|
|
|
|
totalAmount30 := math.Round((totalAmount/30.0)*100) / 100
|
|
|
|
|
response.OkWithData(gin.H{
|
|
|
|
|
"list": list,
|
|
|
|
|
"points": 30,
|
|
|
|
|
"total_count": totalCount,
|
|
|
|
|
"total_amount": totalAmount30, // 总金额的30%
|
|
|
|
|
}, c)
|
|
|
|
|
}
|
2024-09-14 10:48:09 +08:00
|
|
|
|
func (p *Pay) Prepay(c *gin.Context) {
|
|
|
|
|
Claims, err := utils.GetClaims(c)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println("get claims error:", err)
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var reqBody request.PrepayReq
|
|
|
|
|
err = c.ShouldBindJSON(&reqBody)
|
|
|
|
|
if err != nil {
|
|
|
|
|
response.FailWithMessage("Failed to read request body, need product", c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
userid = Claims.Userid
|
|
|
|
|
openid string
|
|
|
|
|
appid string
|
|
|
|
|
mchID string
|
|
|
|
|
)
|
2024-12-25 11:59:33 +08:00
|
|
|
|
log.Printf("Claims, %+v", Claims)
|
|
|
|
|
log.Printf("reqBody, %+v", reqBody)
|
|
|
|
|
|
2024-09-14 10:48:09 +08:00
|
|
|
|
// 获取用户ip
|
|
|
|
|
clientIP := c.ClientIP()
|
|
|
|
|
switch reqBody.Platform {
|
|
|
|
|
case model.PlatformMPWEIXIN:
|
|
|
|
|
openid = Claims.AuthIdentifiers.OpenID
|
|
|
|
|
appid = config.ConfigData.System.WxAppId
|
|
|
|
|
mchID = config.ConfigData.WxPay.MchID
|
|
|
|
|
case model.PlatformMPH5:
|
|
|
|
|
openid = Claims.AuthIdentifiers.OpenID
|
|
|
|
|
appid = config.ConfigData.System.WxH5AppId
|
|
|
|
|
mchID = config.ConfigData.WxPay.MchH5ID
|
|
|
|
|
case model.PlatformH5:
|
|
|
|
|
appid = config.ConfigData.System.WxH5AppId
|
|
|
|
|
mchID = config.ConfigData.WxPay.MchH5ID
|
2024-12-25 11:59:33 +08:00
|
|
|
|
case model.PlatformTYDATA:
|
|
|
|
|
openid = Claims.AuthIdentifiers.OpenID
|
|
|
|
|
appid = config.ConfigData.System.WxTyDataAppId
|
|
|
|
|
mchID = config.ConfigData.WxPay.MchH5ID
|
2024-09-14 10:48:09 +08:00
|
|
|
|
default:
|
|
|
|
|
response.FailWithMessage("ProductName Must be wx or mp-h5 or h5", c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
product, err := productService.GetProduct(reqBody.ProductName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println(err)
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 查看用户是否内部号
|
|
|
|
|
user, err := userService.GetUserByUserid(Claims.Userid)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println(err)
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var amount int64
|
|
|
|
|
if user.Inside {
|
|
|
|
|
amount = int64(1)
|
|
|
|
|
} else {
|
|
|
|
|
amount = int64(product.SellPrice)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var outTradeNo = fmt.Sprintf("wx_%s", utils.GenerateOrderNumber())
|
|
|
|
|
|
|
|
|
|
var resp interface{}
|
|
|
|
|
|
|
|
|
|
if reqBody.Platform == model.PlatformMPWEIXIN {
|
|
|
|
|
resp, err = orderService.WechatJSAPIPrepay(appid, mchID, product, outTradeNo, amount, openid, model.PlatformMPWEIXIN, global.GlobalData.PayClient)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("【创建微信支付订单】创建支付失败,系统错误: %v", err)
|
|
|
|
|
response.FailWithMessage("创建支付失败,系统错误", c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
} else if reqBody.Platform == model.PlatformMPH5 {
|
2024-12-25 11:59:33 +08:00
|
|
|
|
resp, err = orderService.WechatJSAPIPrepay(appid, mchID, product, outTradeNo, amount, openid, model.PlatformMPH5, global.GlobalData.PayH5Client)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("【创建微信支付订单】创建支付失败,系统错误: %v", err)
|
|
|
|
|
response.FailWithMessage("创建支付失败,系统错误", c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
} else if reqBody.Platform == model.PlatformTYDATA {
|
|
|
|
|
resp, err = orderService.WechatJSAPIPrepay(appid, mchID, product, outTradeNo, amount, openid, model.PlatformTYDATA, global.GlobalData.PayH5Client)
|
2024-09-14 10:48:09 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("【创建微信支付订单】创建支付失败,系统错误: %v", err)
|
|
|
|
|
response.FailWithMessage("创建支付失败,系统错误", c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
resp, err = orderService.WechatH5Prepay(appid, mchID, product, outTradeNo, amount, clientIP, global.GlobalData.PayH5Client)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("【创建微信支付订单】创建支付失败,系统错误: %v", err)
|
|
|
|
|
response.FailWithMessage("创建支付失败,系统错误", c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var payOrder = model.PayOrder{
|
|
|
|
|
OutTradeNo: outTradeNo,
|
|
|
|
|
Amount: amount,
|
|
|
|
|
Userid: userid,
|
|
|
|
|
PayStatus: model.PayStatusNotPay,
|
|
|
|
|
ProductID: product.ID,
|
|
|
|
|
Product: &product,
|
|
|
|
|
Platform: reqBody.Platform,
|
|
|
|
|
PaymentMethod: model.PaymentMethod_WECHAT,
|
2024-12-25 11:59:33 +08:00
|
|
|
|
Promotion: reqBody.Promotion,
|
2024-09-14 10:48:09 +08:00
|
|
|
|
}
|
|
|
|
|
err = db.DB.Create(&payOrder).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("create payOrder error:%v", err)
|
|
|
|
|
}
|
|
|
|
|
response.OkWithData(resp, c)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Pay) Callback(c *gin.Context) {
|
|
|
|
|
|
|
|
|
|
ctx := c //这个参数是context.Background()
|
|
|
|
|
cRequest := c.Request //这个值是*http.Request
|
|
|
|
|
platform := c.Param("platform")
|
|
|
|
|
var mchID string
|
|
|
|
|
var mchCertificateSerialNumber string
|
|
|
|
|
var mchAPIv3Key string
|
|
|
|
|
var privateKeyPath string
|
|
|
|
|
switch platform {
|
|
|
|
|
case model.PlatformMPWEIXIN:
|
|
|
|
|
mchID = config.ConfigData.WxPay.MchID
|
|
|
|
|
mchCertificateSerialNumber = config.ConfigData.WxPay.MchCertificateSerialNumber
|
|
|
|
|
mchAPIv3Key = config.ConfigData.WxPay.MchAPIv3Key
|
|
|
|
|
privateKeyPath = "merchant/mp/apiclient_key.pem"
|
2024-12-25 11:59:33 +08:00
|
|
|
|
case model.PlatformH5, model.PlatformMPH5, model.PlatformTYDATA:
|
2024-09-14 10:48:09 +08:00
|
|
|
|
mchID = config.ConfigData.WxPay.MchH5ID
|
|
|
|
|
mchCertificateSerialNumber = config.ConfigData.WxPay.MchH5CertificateSerialNumber
|
|
|
|
|
mchAPIv3Key = config.ConfigData.WxPay.MchH5APIv3Key
|
|
|
|
|
privateKeyPath = "merchant/mph5/apiclient_key.pem"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mchPrivateKey, err := wxutils.LoadPrivateKeyWithPath(privateKeyPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("load merchant private key error")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// 1. 使用 `RegisterDownloaderWithPrivateKey` 注册下载器
|
|
|
|
|
err = downloader.MgrInstance().RegisterDownloaderWithPrivateKey(ctx, mchPrivateKey, mchCertificateSerialNumber, mchID, mchAPIv3Key)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("register downloader with private key error")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// 2. 获取商户号对应的微信支付平台证书访问器
|
|
|
|
|
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
|
|
|
|
|
// 3. 使用证书访问器初始化 `notify.Handler`
|
|
|
|
|
handler := notify.NewNotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor))
|
|
|
|
|
transaction := new(payments.Transaction)
|
|
|
|
|
notifyReq, err := handler.ParseNotifyRequest(context.Background(), cRequest, transaction)
|
|
|
|
|
// 如果验签未通过,或者解密失败
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("parse notify request error")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
log.Printf("微信支付回调响应: %+v", notifyReq)
|
|
|
|
|
var status string
|
|
|
|
|
if notifyReq.Summary == "支付成功" {
|
|
|
|
|
status = model.PayStatusSuccess
|
|
|
|
|
} else {
|
|
|
|
|
status = model.PayStatusPayError
|
|
|
|
|
}
|
|
|
|
|
err = db.DB.Model(&model.PayOrder{}).Where("out_trade_no = ?", transaction.OutTradeNo).Updates(model.PayOrder{PayStatus: status, TransactionId: *transaction.TransactionId}).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("订单回调处理错误%v", err)
|
|
|
|
|
}
|
|
|
|
|
c.String(http.StatusOK, "success")
|
|
|
|
|
}
|
|
|
|
|
func (p *Pay) RefundDetailsHTML(c *gin.Context) {
|
|
|
|
|
encryptedOrderID := c.Param("id")
|
|
|
|
|
|
|
|
|
|
// 解密订单ID
|
|
|
|
|
decryptedOrderID, err := utils.IDDecrypt(encryptedOrderID, "njbh287yfbuyh18suygbhd98")
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("解密订单ID(%s)失败:%v", decryptedOrderID, err)
|
|
|
|
|
c.String(http.StatusNotFound, "")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将字符串转换为 uint64
|
|
|
|
|
u64, err := strconv.ParseUint(decryptedOrderID, 10, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("uint64转换失败: %v", err)
|
|
|
|
|
c.String(http.StatusNotFound, "")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将 uint64 转换为 uint
|
|
|
|
|
orderID := uint(u64)
|
|
|
|
|
|
|
|
|
|
order, err := orderService.GetOrderByid(orderID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("订单(%d)获取失败:%v", orderID, err)
|
|
|
|
|
c.String(http.StatusNotFound, "")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
type RenderOrder struct {
|
|
|
|
|
ID string
|
|
|
|
|
OutTradeNo string
|
|
|
|
|
TransactionId string
|
|
|
|
|
Userid uint
|
|
|
|
|
CreatedAt string
|
|
|
|
|
Product string
|
|
|
|
|
Amount float64
|
|
|
|
|
PayStatus string
|
|
|
|
|
}
|
|
|
|
|
renderOrder := RenderOrder{
|
|
|
|
|
ID: encryptedOrderID,
|
|
|
|
|
OutTradeNo: order.OutTradeNo,
|
|
|
|
|
TransactionId: order.TransactionId,
|
|
|
|
|
Userid: order.Userid,
|
|
|
|
|
CreatedAt: order.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
|
|
|
Product: order.Product.ProductName,
|
|
|
|
|
Amount: float64(order.Amount) / 100,
|
|
|
|
|
PayStatus: order.PayStatus,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.HTML(http.StatusOK, "refund.html", renderOrder)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Pay) Refund(c *gin.Context) {
|
|
|
|
|
encryptedOrderID := c.Param("id")
|
|
|
|
|
|
|
|
|
|
// 解密订单ID
|
|
|
|
|
decryptedOrderID, err := utils.IDDecrypt(encryptedOrderID, "njbh287yfbuyh18suygbhd98")
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("解密订单ID(%s)失败:%v", decryptedOrderID, err)
|
|
|
|
|
c.String(http.StatusNotFound, "")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将字符串转换为 uint64
|
|
|
|
|
u64, err := strconv.ParseUint(decryptedOrderID, 10, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("uint64转换失败: %v", err)
|
|
|
|
|
c.String(http.StatusNotFound, "")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将 uint64 转换为 uint
|
|
|
|
|
orderID := uint(u64)
|
|
|
|
|
|
|
|
|
|
order, err := orderService.GetOrderByid(orderID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("订单(%d)获取失败:%v", orderID, err)
|
|
|
|
|
c.String(http.StatusNotFound, "")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var payClient *wxcore.Client
|
|
|
|
|
switch order.Platform {
|
|
|
|
|
case model.PlatformMPWEIXIN:
|
|
|
|
|
payClient = global.GlobalData.PayClient
|
2024-12-25 11:59:33 +08:00
|
|
|
|
case model.PlatformH5, model.PlatformMPH5, model.PlatformTYDATA:
|
2024-09-14 10:48:09 +08:00
|
|
|
|
payClient = global.GlobalData.PayH5Client
|
|
|
|
|
}
|
|
|
|
|
outRefundNo := utils.GenerateOrderRefundNumber()
|
|
|
|
|
svc := refunddomestic.RefundsApiService{Client: payClient}
|
|
|
|
|
resp, result, err := svc.Create(ctx,
|
|
|
|
|
refunddomestic.CreateRequest{
|
|
|
|
|
OutTradeNo: wxcore.String(order.OutTradeNo),
|
|
|
|
|
OutRefundNo: wxcore.String(outRefundNo), //退款单号
|
|
|
|
|
NotifyUrl: wxcore.String(fmt.Sprintf("%s/%s", config.ConfigData.WxPay.RefundNotifyURL, order.Platform)),
|
|
|
|
|
Amount: &refunddomestic.AmountReq{
|
|
|
|
|
Currency: wxcore.String("CNY"),
|
|
|
|
|
Refund: wxcore.Int64(order.Amount),
|
|
|
|
|
Total: wxcore.Int64(order.Amount),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// 处理错误
|
|
|
|
|
log.Printf("微信订单申请退款错误:%s", err)
|
|
|
|
|
response.Fail(c)
|
|
|
|
|
} else {
|
|
|
|
|
// 处理返回结果
|
|
|
|
|
log.Printf("微信订单申请退款 response status=%d resp=%s", result.Response.StatusCode, resp)
|
|
|
|
|
order.PayStatus = model.PayStatusUnderRefund
|
|
|
|
|
err = db.DB.Save(order).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("微信订单退款状态修改失败 underRefund Error:%v", err)
|
|
|
|
|
}
|
|
|
|
|
response.Ok(c)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
func (p *Pay) RefundCallback(c *gin.Context) {
|
|
|
|
|
ctx := c //这个参数是context.Background()
|
|
|
|
|
cRequest := c.Request //这个值是*http.Request
|
|
|
|
|
platform := c.Param("platform")
|
|
|
|
|
var mchID string
|
|
|
|
|
var mchCertificateSerialNumber string
|
|
|
|
|
var mchAPIv3Key string
|
|
|
|
|
var privateKeyPath string
|
|
|
|
|
switch platform {
|
|
|
|
|
case model.PlatformMPWEIXIN:
|
|
|
|
|
mchID = config.ConfigData.WxPay.MchID
|
|
|
|
|
mchCertificateSerialNumber = config.ConfigData.WxPay.MchCertificateSerialNumber
|
|
|
|
|
mchAPIv3Key = config.ConfigData.WxPay.MchAPIv3Key
|
|
|
|
|
privateKeyPath = "merchant/mp/apiclient_key.pem"
|
2024-12-25 11:59:33 +08:00
|
|
|
|
case model.PlatformH5, model.PlatformMPH5, model.PlatformTYDATA:
|
2024-09-14 10:48:09 +08:00
|
|
|
|
mchID = config.ConfigData.WxPay.MchH5ID
|
|
|
|
|
mchCertificateSerialNumber = config.ConfigData.WxPay.MchH5CertificateSerialNumber
|
|
|
|
|
mchAPIv3Key = config.ConfigData.WxPay.MchH5APIv3Key
|
|
|
|
|
privateKeyPath = "merchant/mph5/apiclient_key.pem"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mchPrivateKey, err := wxutils.LoadPrivateKeyWithPath(privateKeyPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("load merchant private key error")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// 1. 使用 `RegisterDownloaderWithPrivateKey` 注册下载器
|
|
|
|
|
err = downloader.MgrInstance().RegisterDownloaderWithPrivateKey(ctx, mchPrivateKey, mchCertificateSerialNumber, mchID, mchAPIv3Key)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("register downloader with private key error")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// 2. 获取商户号对应的微信支付平台证书访问器
|
|
|
|
|
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
|
|
|
|
|
// 3. 使用证书访问器初始化 `notify.Handler`
|
|
|
|
|
handler := notify.NewNotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor))
|
|
|
|
|
transaction := new(payments.Transaction)
|
|
|
|
|
notifyReq, err := handler.ParseNotifyRequest(context.Background(), cRequest, transaction)
|
|
|
|
|
// 如果验签未通过,或者解密失败
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("parse notify request error")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
log.Printf("微信退款回调响应 notifyReq: %+v", notifyReq)
|
|
|
|
|
log.Printf("微信退款回调响应 transaction: %+v", transaction)
|
|
|
|
|
|
|
|
|
|
order := model.PayOrder{}
|
|
|
|
|
err = db.DB.Preload("Product").Where("out_trade_no = ?", transaction.OutTradeNo).First(&order).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
log.Printf("【微信退款回调响应】找不到回调相关的订单。OutTradeNo:%s", transaction.OutTradeNo)
|
|
|
|
|
return
|
|
|
|
|
} else {
|
|
|
|
|
log.Printf("退款回调处理错误%v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if notifyReq.EventType == "REFUND.SUCCESS" {
|
|
|
|
|
order.PayStatus = model.PayStatusRefund
|
|
|
|
|
err = db.DB.Save(&order).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("退款回调处理错误%v", err)
|
|
|
|
|
} else {
|
|
|
|
|
notifyService.SendNotification("退款成功", order.Product.ProductName, order.Userid, order.ID)
|
|
|
|
|
}
|
|
|
|
|
} else if notifyReq.EventType == "REFUND.ABNORMAL" {
|
|
|
|
|
order.PayStatus = model.PayStatusRefundError
|
|
|
|
|
err = db.DB.Save(&order).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("退款回调处理错误%v", err)
|
|
|
|
|
} else {
|
|
|
|
|
notifyService.SendNotification("退款异常,请及时处理", order.Product.ProductName, order.Userid, order.ID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//log.Printf("微信退款回调响应: %+v", transaction)
|
|
|
|
|
c.String(http.StatusOK, "success")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AliPrepay 阿里支付
|
|
|
|
|
func (p *Pay) AliPrepay(c *gin.Context) {
|
|
|
|
|
var reqBody request.H5PrepayReq
|
|
|
|
|
err := c.ShouldBindJSON(&reqBody)
|
|
|
|
|
if err != nil {
|
|
|
|
|
response.FailWithMessage("Failed to read request body, need product", c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
Claims, err := utils.GetClaims(c)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println("get claims error:", err)
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取商品信息
|
|
|
|
|
product, err := productService.GetProduct(reqBody.ProductName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println(err)
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成单号
|
|
|
|
|
outTradeNo := fmt.Sprintf("ali_%s", utils.GenerateOrderNumber())
|
|
|
|
|
// 查看用户是否内部号
|
|
|
|
|
user, err := userService.GetUserByUserid(Claims.Userid)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println(err)
|
|
|
|
|
response.FailWithMessage(err.Error(), c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var amount string
|
|
|
|
|
var orderAmount int64
|
|
|
|
|
log.Printf("is inside%+v,is %t", user, user.Inside)
|
|
|
|
|
if user.Inside {
|
|
|
|
|
amount = "0.01"
|
|
|
|
|
orderAmount = 1
|
|
|
|
|
} else {
|
|
|
|
|
amount = utils.ConvertCentsToYuan(product.SellPrice)
|
|
|
|
|
orderAmount = int64(product.SellPrice)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pay := alipay.TradeWapPay{}
|
|
|
|
|
pay.NotifyURL = config.ConfigData.AliPay.NotifyURL // 替换为您的回调地址
|
|
|
|
|
pay.ReturnURL = fmt.Sprintf("%s%s?callback=true", config.ConfigData.Server.Domain, reqBody.Href) // 支付成功后跳转的地址
|
|
|
|
|
pay.Subject = product.ProductName // 订单标题
|
|
|
|
|
pay.OutTradeNo = outTradeNo // 生成的唯一订单号
|
|
|
|
|
pay.TotalAmount = amount // 支付金额,单位元
|
2024-12-25 11:59:33 +08:00
|
|
|
|
pay.ProductCode = "QUICK_WAP_PAY" // WAP支付的产品代码w
|
2024-09-14 10:48:09 +08:00
|
|
|
|
|
|
|
|
|
payURL, err := global.GlobalData.AliPayClient.TradeWapPay(pay)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("【阿里支付创建】创建订单错误:%v", err)
|
|
|
|
|
response.Fail(c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
expiration := 5 * time.Minute // 5分钟超时
|
|
|
|
|
err = db.RedisClient.Set(ctx, fmt.Sprintf("alipay_%s", outTradeNo), reqBody.QueryData, expiration).Err()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("【阿里支付创建】前端请求数据缓存错误:%v", err)
|
|
|
|
|
response.Fail(c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var payOrder = model.PayOrder{
|
|
|
|
|
OutTradeNo: outTradeNo,
|
|
|
|
|
Amount: orderAmount,
|
|
|
|
|
Userid: Claims.Userid,
|
|
|
|
|
PayStatus: model.PayStatusNotPay,
|
|
|
|
|
ProductID: product.ID,
|
|
|
|
|
Product: &product,
|
|
|
|
|
Platform: reqBody.Platform,
|
|
|
|
|
PaymentMethod: model.PaymentMethod_ALIPAY,
|
|
|
|
|
}
|
|
|
|
|
err = db.DB.Create(&payOrder).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("【阿里支付创建】订单保存错误:%v", err)
|
|
|
|
|
response.Fail(c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
response.OkWithData(gin.H{
|
|
|
|
|
"PayUrl": payURL.String(),
|
|
|
|
|
}, c)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AlipayCallback 阿里支付回调
|
|
|
|
|
func (p *Pay) AlipayCallback(c *gin.Context) {
|
|
|
|
|
// 解析表单
|
|
|
|
|
err := c.Request.ParseForm()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("ali pay callback解析请求表单失败:%v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DecodeNotification 内部已调用 VerifySign 方法验证签名
|
|
|
|
|
noti, err := global.GlobalData.AliPayClient.DecodeNotification(c.Request.Form)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("【阿里支付回调】通知解码失败失败:%v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Printf("【阿里支付回调】接收到支付宝回调,商户订单号:%s", noti.OutTradeNo)
|
|
|
|
|
log.Printf("【阿里支付回调】接收到支付宝回调,支付宝订单号:%s", noti.OutTradeNo)
|
|
|
|
|
|
|
|
|
|
log.Printf("【阿里支付回调】交易状态:%s", noti.TradeStatus)
|
|
|
|
|
if noti.TradeStatus == alipay.TradeStatusSuccess {
|
|
|
|
|
log.Printf("【阿里支付回调】交易成功")
|
|
|
|
|
err = db.DB.Model(&model.PayOrder{}).Where("out_trade_no = ?", noti.OutTradeNo).Updates(model.PayOrder{PayStatus: model.PayStatusSuccess, AliTradeNo: noti.TradeNo}).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("【阿里支付回调】订单回调处理错误%v", err)
|
|
|
|
|
}
|
|
|
|
|
// 确认收到通知消息,不然支付宝后续会继续推送相同的消息
|
|
|
|
|
alipay.ACKNotification(c.Writer)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
func (p *Pay) GetQueryCache(c *gin.Context) {
|
|
|
|
|
var reqBody request.QueryDataReq
|
|
|
|
|
err := c.ShouldBindQuery(&reqBody)
|
|
|
|
|
if err != nil {
|
|
|
|
|
response.FailWithMessage("Failed to read request body, need out_trade_no", c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
key := fmt.Sprintf("alipay_%s", reqBody.OutTradeNo)
|
|
|
|
|
result, err := db.RedisClient.Get(ctx, key).Result()
|
|
|
|
|
if errors.Is(err, redis.Nil) {
|
|
|
|
|
response.FailWithMessage("超时,请重新手动输入数据查询", c)
|
|
|
|
|
return
|
|
|
|
|
} else if err != nil {
|
|
|
|
|
log.Printf("【阿里支付】获取缓存表单数据错误%v", err)
|
|
|
|
|
response.FailWithMessage("请重新手动输入数据", c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
response.OkWithData(result, c)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Pay) WxPayComplaintCallback(c *gin.Context) {
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
cRequest := c.Request
|
|
|
|
|
|
|
|
|
|
platform := c.Param("platform")
|
|
|
|
|
|
|
|
|
|
var mchID string
|
|
|
|
|
var mchCertificateSerialNumber string
|
|
|
|
|
var mchAPIv3Key string
|
|
|
|
|
var privateKeyPath string
|
|
|
|
|
var platformString string
|
|
|
|
|
switch platform {
|
|
|
|
|
case model.PlatformMPWEIXIN:
|
|
|
|
|
platformString = "微信小程序"
|
|
|
|
|
mchID = config.ConfigData.WxPay.MchID
|
|
|
|
|
mchCertificateSerialNumber = config.ConfigData.WxPay.MchCertificateSerialNumber
|
|
|
|
|
mchAPIv3Key = config.ConfigData.WxPay.MchAPIv3Key
|
|
|
|
|
privateKeyPath = "merchant/mp/apiclient_key.pem"
|
2024-12-25 11:59:33 +08:00
|
|
|
|
case model.PlatformH5, model.PlatformMPH5, model.PlatformTYDATA:
|
2024-09-14 10:48:09 +08:00
|
|
|
|
platformString = "公众号"
|
|
|
|
|
mchID = config.ConfigData.WxPay.MchH5ID
|
|
|
|
|
mchCertificateSerialNumber = config.ConfigData.WxPay.MchH5CertificateSerialNumber
|
|
|
|
|
mchAPIv3Key = config.ConfigData.WxPay.MchH5APIv3Key
|
|
|
|
|
privateKeyPath = "merchant/mph5/apiclient_key.pem"
|
|
|
|
|
}
|
|
|
|
|
notifyService.SendComplaintNotification(fmt.Sprintf("%s投诉", platformString), "", platformString, "")
|
|
|
|
|
mchPrivateKey, err := wxutils.LoadPrivateKeyWithPath(privateKeyPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("【%s投诉通知回调】加载商户私钥失败: %v", platformString, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 注册下载器
|
|
|
|
|
err = downloader.MgrInstance().RegisterDownloaderWithPrivateKey(ctx, mchPrivateKey, mchCertificateSerialNumber, mchID, mchAPIv3Key)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("【%s投诉通知回调】注册下载器失败: %v", platformString, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取微信支付平台证书访问器
|
|
|
|
|
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
|
|
|
|
|
|
|
|
|
|
// 初始化 notify.Handler
|
|
|
|
|
handler := notify.NewNotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor))
|
|
|
|
|
|
|
|
|
|
// 解析并验证通知请求
|
|
|
|
|
content := make(map[string]interface{})
|
|
|
|
|
notifyReq, err := handler.ParseNotifyRequest(ctx, cRequest, &content)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("【%s投诉通知回调】解析通知请求失败: %v", platformString, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 打印通知摘要和解析后的内容
|
|
|
|
|
log.Printf("【%s投诉通知回调】通知信息: %s", platformString, notifyReq.Summary)
|
|
|
|
|
log.Printf("【%s投诉通知回调】通知数据: %s", platformString, content)
|
|
|
|
|
|
|
|
|
|
// 根据解析后的数据处理通知
|
|
|
|
|
actionType, ok := content["action_type"].(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
log.Printf("【%s投诉通知回调】通知内容中缺少action_type字段", platformString)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
complaintID, ok := content["complaint_id"].(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
log.Printf("【%s投诉通知回调】通知内容中缺少complaint_id字段", platformString)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch actionType {
|
|
|
|
|
case "CREATE_COMPLAINT":
|
|
|
|
|
message := "用户提交了新的投诉"
|
|
|
|
|
notifyService.SendComplaintNotification(message, "新投诉", platformString, complaintID)
|
|
|
|
|
|
|
|
|
|
case "CONTINUE_COMPLAINT":
|
|
|
|
|
message := "用户继续投诉"
|
|
|
|
|
notifyService.SendComplaintNotification(message, "继续投诉", platformString, complaintID)
|
|
|
|
|
|
|
|
|
|
case "USER_RESPONSE":
|
|
|
|
|
message := "用户在投诉单中添加了新留言"
|
|
|
|
|
notifyService.SendComplaintNotification(message, "用户留言", platformString, complaintID)
|
|
|
|
|
|
|
|
|
|
case "RESPONSE_BY_PLATFORM":
|
|
|
|
|
message := "平台在投诉单中添加了新留言"
|
|
|
|
|
notifyService.SendComplaintNotification(message, "平台留言", platformString, complaintID)
|
|
|
|
|
|
|
|
|
|
case "SELLER_REFUND":
|
|
|
|
|
message := "商户发起了全额退款"
|
|
|
|
|
notifyService.SendComplaintNotification(message, "全额退款", platformString, complaintID)
|
|
|
|
|
|
|
|
|
|
case "MERCHANT_RESPONSE":
|
|
|
|
|
message := "商户对投诉进行了回复"
|
|
|
|
|
notifyService.SendComplaintNotification(message, "商户回复", platformString, complaintID)
|
|
|
|
|
|
|
|
|
|
case "MERCHANT_CONFIRM_COMPLETE":
|
|
|
|
|
message := "商户标记投诉处理完成"
|
|
|
|
|
notifyService.SendComplaintNotification(message, "投诉处理完成", platformString, complaintID)
|
|
|
|
|
|
|
|
|
|
case "MERCHANT_APPROVE_REFUND":
|
|
|
|
|
message := "商户同意了退款"
|
|
|
|
|
notifyService.SendComplaintNotification(message, "同意退款", platformString, complaintID)
|
|
|
|
|
|
|
|
|
|
case "MERCHANT_REJECT_REFUND":
|
|
|
|
|
message := "商户拒绝了退款"
|
|
|
|
|
notifyService.SendComplaintNotification(message, "拒绝退款", platformString, complaintID)
|
|
|
|
|
|
|
|
|
|
case "REFUND_SUCCESS":
|
|
|
|
|
message := "退款已成功到账"
|
|
|
|
|
notifyService.SendComplaintNotification(message, "退款到账", platformString, complaintID)
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
message := "未知投诉类型"
|
|
|
|
|
notifyService.SendComplaintNotification(message, "未知投诉类型", platformString, complaintID)
|
|
|
|
|
log.Printf("【%s投诉通知回调】收到未知类型的投诉通知: %s", platformString, actionType)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 返回成功响应
|
|
|
|
|
c.String(http.StatusOK, "success")
|
|
|
|
|
}
|