f
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"qnc-server/app/main/api/internal/config"
|
||||
tianyuanapi "qnc-server/app/main/api/internal/service/tianyuanapi_sdk"
|
||||
|
||||
@@ -184,8 +185,9 @@ func (r *VerificationService) GetWechatH5OpenID(ctx context.Context, code string
|
||||
func (r *VerificationService) GetWechatMiniOpenID(ctx context.Context, code string) (string, error) {
|
||||
appID := r.c.WechatMini.AppID
|
||||
appSecret := r.c.WechatMini.AppSecret
|
||||
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", appID, appSecret, code)
|
||||
resp, err := http.Get(url)
|
||||
apiURL := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
||||
url.QueryEscape(appID), url.QueryEscape(appSecret), url.QueryEscape(code))
|
||||
resp, err := http.Get(apiURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"qnc-server/app/main/api/internal/config"
|
||||
"qnc-server/app/main/model"
|
||||
"qnc-server/common/ctxdata"
|
||||
@@ -446,9 +447,10 @@ func (w *WechatPayService) GenerateOutTradeNo() string {
|
||||
func (w *WechatPayService) getWechatMiniOpenID(ctx context.Context, code string) (string, error) {
|
||||
appID := w.config.WechatMini.AppID
|
||||
appSecret := w.config.WechatMini.AppSecret
|
||||
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", appID, appSecret, code)
|
||||
apiURL := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
||||
url.QueryEscape(appID), url.QueryEscape(appSecret), url.QueryEscape(code))
|
||||
|
||||
resp, err := http.Get(url)
|
||||
resp, err := http.Get(apiURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("请求微信API失败: %v", err)
|
||||
}
|
||||
|
||||
37
app/main/api/internal/service/xpayProductMapping.go
Normal file
37
app/main/api/internal/service/xpayProductMapping.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package service
|
||||
|
||||
// XpayProductIdMap 微信道具 productId(QCXG*)→ 本系统 product_en(toc_*)
|
||||
// mp 后台道具 id 须与左侧 QCXG 编码一致;支付签名 productId 使用 QCXG,业务订单仍用 product_en。
|
||||
var XpayProductIdMap = map[string]string{
|
||||
"QCXG6B4E": "toc_VehicleClaimVerify",
|
||||
"QCXGP00W": "toc_VehicleClaimDetail",
|
||||
"QCXG3Z3L": "toc_VehicleMaintenanceDetail",
|
||||
"QCXG3Y6B": "toc_VehicleMaintenanceSimple",
|
||||
"QCXG4I1Z": "toc_VehicleTransferDetail",
|
||||
"QCXG1H7Y": "toc_VehicleTransferSimple",
|
||||
"QCXGY7F2": "toc_VehicleVinValuation",
|
||||
"QCXG1U4U": "toc_VehicleMileageMixed",
|
||||
"QCXG5U0Z": "toc_VehicleStaticInfo",
|
||||
"QCXG4D2E": "toc_VehiclesUnderNameCount",
|
||||
"QCXG9P1C": "toc_VehiclesUnderNamePlate",
|
||||
"QCXGYTS2": "toc_PersonVehicleVerificationDetail",
|
||||
"QCXGGB2Q": "toc_PersonVehicleVerification",
|
||||
"QCXG7A2B": "toc_VehiclesUnderName",
|
||||
}
|
||||
|
||||
var xpayProductEnToId map[string]string
|
||||
|
||||
func init() {
|
||||
xpayProductEnToId = make(map[string]string, len(XpayProductIdMap))
|
||||
for xpayID, productEn := range XpayProductIdMap {
|
||||
xpayProductEnToId[productEn] = xpayID
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveXpayProductId 将 product_en 转为 mp 道具 id;未配置时回退 product_en 本身。
|
||||
func ResolveXpayProductId(productEn string) string {
|
||||
if id, ok := xpayProductEnToId[productEn]; ok {
|
||||
return id
|
||||
}
|
||||
return productEn
|
||||
}
|
||||
@@ -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"`
|
||||
|
||||
Reference in New Issue
Block a user