1、新增微信退款通知(迪为)接口;

2、商城订单明慧帐户收费改为微信帐户收费;
3、新增微信退款接口(暂时未使用);
This commit is contained in:
chenlin 2025-05-28 16:53:07 +08:00
parent f11bf3409c
commit e1cb2b7662
5 changed files with 295 additions and 12 deletions

View File

@ -3382,9 +3382,9 @@ type WxPayRefundPlaintext struct {
UserReceivedAccount string `json:"user_received_account"`
}
// 0 元购 微信推送支付通知
// PushWXPayRefundNotice 微信退款消息通知(明慧)
func PushWXPayRefundNotice(c *gin.Context) {
fmt.Println("微信推送支付通知")
fmt.Println("微信推送退款通知")
//body, err := ioutil.ReadAll(c.Request.Body)
//if err != nil {
// logger.Error(err)
@ -3393,11 +3393,103 @@ func PushWXPayRefundNotice(c *gin.Context) {
mchID := "1609877389"
mchAPIv3Key := "DeovoMingHuiRengTianTang45675123" // 商户APIv3密钥
mchCertificateSerialNumber := "7540301D8FD52CCF7D6267DCF7CD2BC0AB467EFF" // 商户证书序列号
mchPrivateKey, err := wechatpayutils.LoadPrivateKeyWithPath("./configs/merchant/apiclient_key.pem")
if err != nil {
log.Print("load merchant private key error")
}
ctx := context.Background()
// 1. 使用 `RegisterDownloaderWithPrivateKey` 注册下载器
err = downloader.MgrInstance().RegisterDownloaderWithPrivateKey(ctx, mchPrivateKey, mchCertificateSerialNumber, mchID, mchAPIv3Key)
if err != nil {
fmt.Println(err)
return
}
// 2. 获取商户号对应的微信支付平台证书访问器
certVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
// 3. 使用证书访问器初始化 `notify.Handler`
handler := notify.NewNotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certVisitor))
transaction := new(payments.Transaction)
notifyReq, err := handler.ParseNotifyRequest(context.Background(), c.Request, transaction)
// 如果验签未通过,或者解密失败
if err != nil {
fmt.Println(err)
return
}
// 处理通知内容
//fmt.Println(notifyReq.Summary)
//fmt.Println(transaction.TransactionId)
//transactionJson, _ := json.Marshal(transaction)
//fmt.Println("transactionJson:", string(transactionJson))
//notifyReqJson, _ := json.Marshal(notifyReq)
//fmt.Println("notifyReqJson:", string(notifyReqJson))
if notifyReq.EventType == "REFUND.SUCCESS" {
plaintext := new(WxPayRefundPlaintext)
err = json.Unmarshal([]byte(notifyReq.Resource.Plaintext), plaintext)
if err != nil {
logger.Error("unmarshal plaintext err:", err)
return
}
count, err := model.NewFundRecordQuerySet(model.DB).RefundIdEq(plaintext.RefundId).Count()
if err != nil {
logger.Error("count refund id err:", err)
return
}
plaintextJson, _ := json.Marshal(plaintext)
fmt.Println("plaintextJson:", string(plaintextJson))
if count == 0 {
openMemberRecord := new(model.UserOpenMemberRecord)
err = model.NewUserOpenMemberRecordQuerySet(model.DB).OpenNoEq(plaintext.OutRefundNo).One(openMemberRecord)
if err != nil {
logger.Error("user open member record err:", err)
return
}
//fundType := model.FundTypeExpressFeeRefund
//if openMemberRecord.OrderType == 6 {
// fundType = model.FundTypeBuyGoodsRefund
//}
fundRecord := &model.FundRecord{
Uid: openMemberRecord.Uid,
FundType: GetFundRecordFundType(openMemberRecord.OrderType),
Amount: int64(plaintext.Amount.Refund) * (-1),
TransactionId: plaintext.TransactionId,
OutTradeNo: plaintext.OutTradeNo,
RefundId: plaintext.RefundId,
Status: 2,
Remark: GetFundRecordRemark(openMemberRecord.OrderType),
}
err = model.DB.Create(fundRecord).Error
if err != nil {
logger.Error("create fund record err:", err)
return
}
}
}
RespNotice(c, "SUCCESS", "成功")
return
//logger.Error("xml Request.Body1:", string(body))
}
// DwPushWXPayRefundNotice 微信退款消息通知(迪为)
func DwPushWXPayRefundNotice(c *gin.Context) {
fmt.Println("微信推送退款通知")
//body, err := ioutil.ReadAll(c.Request.Body)
//if err != nil {
// logger.Error(err)
//}
mchID := "1494954322"
mchAPIv3Key := "hTCTqF9jHsWlFOO8ZuL05BDo2UlrJwVv" // 商户APIv3密钥
mchCertificateSerialNumber := "5D98B7F99C24BFD8649E2045635AFBBCDD5B29C1" // 商户证书序列号
mchPrivateKey, err := wechatpayutils.LoadPrivateKeyWithPath("./configs/dw_merchant/apiclient_key.pem")
if err != nil {
log.Print("load merchant private key error")
}
ctx := context.Background()
// 1. 使用 `RegisterDownloaderWithPrivateKey` 注册下载器
err = downloader.MgrInstance().RegisterDownloaderWithPrivateKey(ctx, mchPrivateKey, mchCertificateSerialNumber, mchID, mchAPIv3Key)

View File

@ -481,7 +481,7 @@ func MallOrderCreate(c *gin.Context) {
RespOK(c, ret)
} else { // 其他则默认明慧账户收费
webPay, err := wxpay.HmJsPayUnifiedOrderForBuyGoods(order.SerialNo, order.Rm, user.WxOpenID, configInfo.NotifyUrl)
webPay, err := wxpay.WebPay(order.SerialNo, order.Rm, user.WxOpenID, "N", wxpay.WxPayBuyGoods, configInfo.NotifyUrl, false)
if err != nil {
logger.Error(errors.New("WebPay err"))
RespJson(c, status.InternalServerError, nil)
@ -495,6 +495,21 @@ func MallOrderCreate(c *gin.Context) {
}
RespOK(c, ret)
//webPay, err := wxpay.HmJsPayUnifiedOrderForBuyGoods(order.SerialNo, order.Rm, user.WxOpenID, configInfo.NotifyUrl)
//if err != nil {
// logger.Error(errors.New("WebPay err"))
// RespJson(c, status.InternalServerError, nil)
// return
//}
//
//ret := map[string]interface{}{
// "web_pay": webPay,
// "order_id": order.ID,
// "order": order,
//}
//
//RespOK(c, ret)
}
return
@ -643,6 +658,7 @@ func MallOrderRefund(c *gin.Context) {
RespJson(c, status.InternalServerError, nil)
return
}
if goodsOrder.Amount == 0 && goodsOrder.DeliverStoreId == 0 { // 新机预售券
// 查询新机预售券状态,只有未使用才能退
var newMachineCoupon model.UserCoupon
@ -697,6 +713,7 @@ func MallOrderRefund(c *gin.Context) {
RespJson(c, status.InternalServerError, nil)
return
}
RespOK(c, goodsOrder)
return
}

View File

@ -27,7 +27,6 @@ import (
mathrand "math/rand"
"mh-server/config"
"mh-server/lib/utils"
"net/http"
"sort"
"strconv"
@ -70,7 +69,7 @@ const (
TestHmPubKeySwitchPubFp = "/Users/max/Documents/code/deovo/mh_server/pack/configs/hm_pay/switch_pub_private_key.pem"
)
// web 微信支付
// WebPay web 微信支付
func WebPay(orderId string, totalFee uint32, openId, profitSharing, attach, notifyUrl string, flag bool) (*Sextuple, error) {
now := time.Now().Local()
strTime := fmt.Sprintf("%04d%02d%02d%02d%02d%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
@ -176,9 +175,69 @@ func WebPay(orderId string, totalFee uint32, openId, profitSharing, attach, noti
return &sextuple, nil
}
// WechatRefund 微信退款
func WechatRefund(orderId, refundId string, totalFee uint32, attach, notifyUrl string, flag bool) (*RefundResp, error) {
if notifyUrl == "" {
logger.Error("NotifyUrl is null")
return nil, errors.New("NotifyUrl is null")
}
fmt.Println("MchId:", config.AppConfig.WxMchID)
fmt.Println("AppId:", config.AppConfig.WxAppId)
fmt.Println("MchSecret:", config.AppConfig.WxMchSecret)
nonce := utils.GenRandStr(NonceStringLength)
refundReq := RefundReq{
AppId: config.AppConfig.WxAppId,
MchId: config.AppConfig.WxMchID,
NonceStr: nonce,
Sign: "",
SignType: "MD5",
OutTradeNo: orderId,
OutRefundNo: refundId,
TotalFee: int(totalFee),
RefundFee: int(totalFee),
RefundFeeType: "CNY",
RefundDesc: attach,
NotifyUrl: notifyUrl,
}
if flag {
refundReq.MchId = config.AppConfig.WxDwMchID
}
fmt.Println("OutTradeNo:", refundReq.OutTradeNo)
m, err := struct2Map(refundReq)
if err != nil {
logger.Error(err)
return nil, err
}
payKey := config.AppConfig.WxMchSecret
if flag {
payKey = config.AppConfig.WxDwMchSecret
}
sign, err := GenWxPaySign(m, payKey)
if err != nil {
logger.Error(err)
return nil, err
}
refundReq.Sign = strings.ToUpper(sign)
refundResp, err := WxRefund(refundReq)
if err != nil {
logger.Errorf("WxUnifiedOrder unified order error %#v", err)
return nil, err
}
return &refundResp, nil
}
const (
NonceStringLength = 32
UnifiedOrderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"
RefundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund"
)
type (
@ -312,6 +371,57 @@ type (
Timestamp string `json:"timestamp,omitempty"`
Sign string `json:"sign,omitempty"`
}
RefundReq struct {
AppId string `xml:"appid" json:"appid"` //微信分配的小程序ID必须
MchId string `xml:"mch_id" json:"mch_id"` //微信支付分配的商户号,必须
NonceStr string `xml:"nonce_str" json:"nonce_str"` //随机字符串,必须
Sign string `xml:"sign" json:"sign"` //签名,必须
SignType string `xml:"sign_type" json:"sign_type"` //"HMAC-SHA256"或者"MD5"非必须默认MD5
TransactionId string `xml:"transaction_id" json:"transaction_id"` //微信支付订单号
OutTradeNo string `xml:"out_trade_no" json:"out_trade_no"` //商户系统内部订单号,transaction_id、out_trade_no二选一
OutRefundNo string `xml:"out_refund_no" json:"out_refund_no"` //商户退款单号,必须
TotalFee int `xml:"total_fee" json:"total_fee"` //订单金额,单位分,必须
RefundFee int `xml:"refund_fee" json:"refund_fee"` //退款金额,单位分,必须
RefundFeeType string `xml:"refund_fee_type" json:"refund_fee_type"` //退款货币种类,非必须
RefundDesc string `xml:"refund_desc" json:"refund_desc"` //退款原因,非必须
RefundAccount string `xml:"refund_account" json:"refund_account"` //退款资金来源,非必须
NotifyUrl string `xml:"notify_url" json:"notify_url"` //退款结果通知url非必须
}
RefundResp struct {
ReturnCode string `xml:"return_code"`
ReturnMsg string `xml:"return_msg"`
ResultCode string `xml:"result_code"`
ErrCode string `xml:"err_code"`
ErrCodeDes string `xml:"err_code_des"`
AppId string `xml:"appid"` //微信分配的小程序ID必须
MchId string `xml:"mch_id"` //微信支付分配的商户号,必须
NonceStr string `xml:"nonce_str"` //随机字符串,必须
Sign string `xml:"sign"` //签名,必须
TransactionId string `xml:"transaction_id"` //微信支付订单号,必须
OutTradeNo string `xml:"out_trade_no"` //商户订单号,必须
OutRefundNo string `xml:"out_refund_no"` //商户退款单号,必须
RefundId string `xml:"refund_id"` //微信退款单号,必须
RefundFee int `xml:"refund_fee"` //退款金额,单位分,必须
SettlementRefundFee int `xml:"settlement_refund_fee"` //应结退款金额,非必须
TotalFee int `xml:"total_fee"` //标价金额,单位分,必须
SettlementTotalFee int `xml:"settlement_total_fee"` //应结订单金额,非必须
FeeType string `xml:"fee_type,omitempty"` // 标价币种
CashFee int `xml:"cash_fee"` // 现金支付金额
CashFeeType string `xml:"cash_fee_type,omitempty"` // 现金支付币种
CashRefundFee int `xml:"cash_refund_fee,omitempty"` // 现金退款金额
CouponRefundFee int `xml:"coupon_refund_fee,omitempty"` // 代金券退款总金额
CouponRefundCount int `xml:"coupon_refund_count,omitempty"` // 退款代金券使用数量
Coupons []RefundCoupon `xml:"coupons,omitempty"` // 代金券明细
}
// RefundCoupon 表示单个代金券的退款明细(可多个)
RefundCoupon struct {
CouponType string `xml:"coupon_type"` // 代金券类型CASH/NO_CASH
CouponID string `xml:"coupon_refund_id"` // 退款代金券ID
CouponAmount int `xml:"coupon_refund_fee"` // 单个代金券退款金额
}
)
type T struct {
@ -543,6 +653,51 @@ func WxUnifiedOrder(r UnifiedOrderReq) (UnifiedOrderResp, error) {
return payResp, nil
}
func WxRefund(r RefundReq) (RefundResp, error) {
var rResp RefundResp
data, err := xml.Marshal(r)
if err != nil {
logger.Error(err)
return rResp, err
}
logger.Error("xml:", string(data))
client := http.Client{}
req, err := http.NewRequest("POST", RefundUrl, bytes.NewBuffer(data))
if err != nil {
logger.Error(err)
return rResp, err
}
req.Header.Set("Content-Type", "application/xml; charset=utf-8")
resp, err := client.Do(req)
if err != nil {
logger.Error(err)
return rResp, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logger.Error(err)
return rResp, err
}
fmt.Println("body:", string(body))
defer resp.Body.Close()
err = xml.Unmarshal(body, &rResp)
if err != nil {
logger.Error(err)
return rResp, err
}
if rResp.ReturnCode != "SUCCESS" {
return rResp, errors.New(rResp.ReturnMsg)
}
return rResp, nil
}
func WxPayTransactionOrderClose(outTradeNo, mchid string) error {
// url := fmt.Sprintf("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s/close", outTradeNo)
// para := map[string]interface{}{

View File

@ -7,6 +7,7 @@ import (
"github.com/codinl/go-logger"
"github.com/jinzhu/gorm"
"github.com/rs/zerolog/log"
"math/rand"
"mh-server/lib/utils"
"mh-server/lib/wxpay"
"sort"
@ -884,6 +885,23 @@ func (m *UserInviteListReq) InviteUserList() (*UserInviteListResp, error) {
return resp, nil
}
// GenerateRefundOrderNo 生成退款订单号例如R202505271545301234567890
func GenerateRefundOrderNo() string {
// 设置随机种子
rand.Seed(time.Now().UnixNano())
// 当前时间年月日时分秒14位
timestamp := time.Now().Format("20060102150405")
// 生成 10 位随机数:范围是 1000000000 ~ 9999999999
randomNum := rand.Int63n(9000000000) + 1000000000 // int63 支持更大范围
// 拼接最终退款订单号
refundOrderNo := fmt.Sprintf("R%s%d", timestamp, randomNum)
return refundOrderNo
}
func GetOrderSn() string {
var orderSn string
for {

View File

@ -32,12 +32,13 @@ func ConfigAppRouter(r gin.IRouter) {
// //api.POST("upload_user_info", controller.UploadUserInfo) // 上传用户信息
//api.POST("wxpay/notice", controller.PushWXPayNotice) // 微信推送支付通知
// TODO两边都改
api.GET("wxpay/notice", controller.HmPushWXPayNotice) // 河马付推送支付通知
api.POST("wxpay/notice", controller.PushWXPayNotice) // 微信推送支付通知
api.POST("wxpay_refund/notice", controller.PushWXPayRefundNotice) // 微信推送支付退款通知
api.POST("aliyun/sts_token", controller.AliyunStsTokenGet) // 阿里云上传图片token
api.POST("auto_reply/focus", controller.AutoReplyFocusMsg) // 自动回复
api.GET("auto_reply/focus", controller.CustomerServiceMessageCheck) // 客服校验
api.GET("wxpay/notice", controller.HmPushWXPayNotice) // 河马付推送支付通知
api.POST("wxpay/notice", controller.PushWXPayNotice) // 微信推送支付通知
api.POST("wxpay_refund/notice", controller.PushWXPayRefundNotice) // 微信推送支付退款通知(明慧)
api.POST("wxpay_refund/dw/notice", controller.DwPushWXPayRefundNotice) // 微信推送支付退款通知(迪为)
api.POST("aliyun/sts_token", controller.AliyunStsTokenGet) // 阿里云上传图片token
api.POST("auto_reply/focus", controller.AutoReplyFocusMsg) // 自动回复
api.GET("auto_reply/focus", controller.CustomerServiceMessageCheck) // 客服校验
// api.GET("wx_cs/message", controller.CustomerServiceMessageCheck) // 客服校验
// api.POST("wx_cs/message", controller.CustomerServiceMessage) // 客服