This commit is contained in:
Mrx
2026-06-06 17:03:08 +08:00
parent a85436950e
commit 35e9191981
28 changed files with 666 additions and 286 deletions

View File

@@ -23,10 +23,10 @@ import (
)
const (
xpaySessionKeyFmt = "qnc:wx:xpay:session:%s"
xpayNotifiedKeyFmt = "qnc:xpay:notified:%s"
xpayAccessTokenKey = "qnc:wx:xpay:access_token"
xpayMode = "short_series_goods"
xpaySessionKeyFmt = "qnc:wx:xpay:session:%s"
xpayNotifiedKeyFmt = "qnc:xpay:notified:%s"
xpayAccessTokenKey = "qnc:wx:xpay:access_token"
xpayMode = "short_series_goods"
xpayVirtualPaymentURI = "requestVirtualPayment"
)
@@ -115,12 +115,13 @@ func (s *XpayService) BuildPayParams(ctx context.Context, userID, orderNo, produ
}
goodsPrice := lzUtils.ToWechatAmount(amount)
xpayProductID := ResolveXpayProductId(productEn)
payload := signDataPayload{
OfferId: s.config.WechatXpay.OfferId,
BuyQuantity: 1,
Env: s.Env(),
CurrencyType: "CNY",
ProductId: productEn,
ProductId: xpayProductID,
GoodsPrice: goodsPrice,
OutTradeNo: orderNo,
Attach: fmt.Sprintf("query:%s", orderNo),
@@ -134,8 +135,8 @@ func (s *XpayService) BuildPayParams(ctx context.Context, userID, orderNo, produ
paySig := hmacSHA256Hex(appKey, xpayVirtualPaymentURI+"&"+signData)
signature := hmacSHA256Hex(sessionKey, signData)
logx.WithContext(ctx).Infof("[xpay] create user=%s order_no=%s env=%d productId=%s goodsPrice=%d",
userID, orderNo, s.Env(), productEn, goodsPrice)
logx.WithContext(ctx).Infof("[xpay] create user=%s order_no=%s env=%d product_en=%s xpay_product_id=%s goodsPrice=%d",
userID, orderNo, s.Env(), productEn, xpayProductID, goodsPrice)
return &XpayPrepayData{
Provider: "xpay",
@@ -187,12 +188,15 @@ type xpayAPIResp struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
Data json.RawMessage `json:"data"`
Order json.RawMessage `json:"order"` // query_order 官方返回 order 字段
}
type xpayOrderStatus struct {
Status int `json:"status"`
PaidFee int64 `json:"paid_fee"`
OrderID string `json:"order_id"`
Status int `json:"status"`
PaidFee int64 `json:"paid_fee"`
LeftFee int64 `json:"left_fee"`
OrderID string `json:"order_id"`
WxOrderID string `json:"wx_order_id"`
}
func (s *XpayService) callAPI(ctx context.Context, uri, bodyJSON, sessionKey string, needSignature bool) (*xpayAPIResp, error) {
@@ -243,7 +247,8 @@ func (s *XpayService) QueryOrder(ctx context.Context, openid, orderNo, sessionKe
return nil, err
}
apiResp, err := s.callAPI(ctx, "/xpay/query_order", bodyJSON, sessionKey, true)
// 官方文档query_order 仅需 pay_sig不需用户态 signature
apiResp, err := s.callAPI(ctx, "/xpay/query_order", bodyJSON, "", false)
if err != nil {
return nil, err
}
@@ -251,13 +256,65 @@ func (s *XpayService) QueryOrder(ctx context.Context, openid, orderNo, sessionKe
return nil, fmt.Errorf("query_order errcode=%d errmsg=%s", apiResp.ErrCode, apiResp.ErrMsg)
}
orderRaw := apiResp.Order
if len(orderRaw) == 0 {
orderRaw = apiResp.Data
}
var status xpayOrderStatus
if len(apiResp.Data) > 0 {
_ = json.Unmarshal(apiResp.Data, &status)
if len(orderRaw) > 0 {
_ = json.Unmarshal(orderRaw, &status)
}
return &status, nil
}
// RefundOrder 启动 xpay 退款任务(全额退)
func (s *XpayService) RefundOrder(ctx context.Context, openid, orderNo, refundOrderID string, refundFeeFen int64) error {
if refundFeeFen <= 0 {
return fmt.Errorf("退款金额无效")
}
status, err := s.QueryOrder(ctx, openid, orderNo, "")
if err != nil {
return fmt.Errorf("退款前查单失败: %w", err)
}
leftFee := status.LeftFee
if leftFee <= 0 {
leftFee = status.PaidFee
}
if leftFee <= 0 {
leftFee = refundFeeFen
}
if refundFeeFen > leftFee {
refundFeeFen = leftFee
}
bodyObj := map[string]interface{}{
"openid": openid,
"env": s.Env(),
"order_id": orderNo,
"refund_order_id": refundOrderID,
"left_fee": leftFee,
"refund_fee": refundFeeFen,
"biz_meta": fmt.Sprintf("refund:%s", orderNo),
"refund_reason": 5,
"req_from": 3,
}
bodyJSON, err := compactJSON(bodyObj)
if err != nil {
return err
}
apiResp, err := s.callAPI(ctx, "/xpay/refund_order", bodyJSON, "", false)
if err != nil {
return err
}
if apiResp.ErrCode != 0 && apiResp.ErrCode != 268490004 && apiResp.ErrCode != 268490014 {
return fmt.Errorf("refund_order errcode=%d errmsg=%s", apiResp.ErrCode, apiResp.ErrMsg)
}
logx.WithContext(ctx).Infof("[xpay] refund_order OK order_no=%s refund_order_id=%s fee=%d", orderNo, refundOrderID, refundFeeFen)
return nil
}
// NotifyProvideGoods 主动通知微信已发货
func (s *XpayService) NotifyProvideGoods(ctx context.Context, openid, orderNo, wxOrderID, sessionKey string) error {
bodyObj := map[string]interface{}{
@@ -305,10 +362,10 @@ func (s *XpayService) VerifyPushSignature(signature, timestamp, nonce string) bo
// XpayDeliverNotify 推送消息体(明文 JSON
type XpayDeliverNotify struct {
Event string `json:"Event"`
OpenId string `json:"OpenId"`
OutTradeNo string `json:"OutTradeNo"`
Env int `json:"Env"`
Event string `json:"Event"`
OpenId string `json:"OpenId"`
OutTradeNo string `json:"OutTradeNo"`
Env int `json:"Env"`
WeChatPayInfo struct {
TransactionId string `json:"TransactionId"`
PaidTime int64 `json:"PaidTime"`