package paymentService import ( "PaymentCenter/app/constants/common" "PaymentCenter/app/constants/errorcode" "PaymentCenter/app/services/thirdpay/thirdpay_notify" "PaymentCenter/app/third/paymentService/payCommon" "PaymentCenter/config" "context" "encoding/json" "errors" "fmt" "github.com/go-pay/gopay" "github.com/go-pay/gopay/wechat/v3" "github.com/qit-team/snow-core/log/logger" "net/url" "strconv" "time" ) // InitClient 使用提供的支付请求参数初始化微信客户端 func InitClient(wxConfig WxPay) (*wechat.ClientV3, error) { // NewClientV3 初始化微信客户端 v3 // mchid:商户ID 或者服务商模式的 sp_mchid // serialNo:商户证书的证书序列号 // apiV3Key:apiV3Key,商户平台获取 // privateKey:私钥 apiclient_key.pem 读取后的内容 wxClient, clientErr := wechat.NewClientV3( wxConfig.MchId, wxConfig.SerialNo, wxConfig.ApiV3Key, wxConfig.PrivateKey, ) if clientErr != nil { return nil, clientErr } // 启用自动同步返回验签,并定时更新微信平台API证书(开启自动验签时,无需单独设置微信平台API证书和序列号) clientErr = wxClient.AutoVerifySign() if wxClient == nil { return nil, errors.New("client not initialized") } // 自定义配置http请求接收返回结果body大小,默认 10MB wxClient.SetBodySize(10) // 没有特殊需求,可忽略此配置 // 打开Debug开关,输出日志,默认是关闭的 wxClient.DebugSwitch = gopay.DebugOn return wxClient, nil } // WxH5PayInfo 微信H5支付 func WxH5PayInfo(c context.Context, payOrderRequest PayOrderRequest) (string, error) { // 初始化微信客户端 wxClient, err := InitClient(payOrderRequest.Wx) if err != nil { return "", err } expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339) // 初始化 BodyMap envConfig := config.GetConf() bm := make(gopay.BodyMap) bm.Set("appid", payOrderRequest.Wx.AppId). Set("mchid", payOrderRequest.Wx.MchId). Set("description", payOrderRequest.Description). Set("out_trade_no", strconv.FormatInt(payOrderRequest.OrderId, 10)). Set("time_expire", expire). Set("notify_url", fmt.Sprintf(envConfig.PayService.Host+payCommon.WX_NOTIFY_URL_TEST+"%d", payOrderRequest.PayChannelId)). SetBodyMap("amount", func(bm gopay.BodyMap) { bm.Set("total", payOrderRequest.Amount). Set("currency", "CNY") }). SetBodyMap("scene_info", func(bm gopay.BodyMap) { bm.Set("payer_client_ip", payOrderRequest.PayerClientIp). SetBodyMap("h5_info", func(bm gopay.BodyMap) { bm.Set("type", "common") }) }) wxRsp, err := wxClient.V3TransactionH5(c, bm) if err != nil { return "", err } if wxRsp.Code != wechat.Success || wxRsp.Response.H5Url == "" { logger.Error(c, "WxH5PayInfo 发生错误", fmt.Sprintf("错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error)) return "", errors.New(fmt.Sprintf("发起支付失败,失败状态码:%d, 失败原因:%s", wxRsp.Code, wxRsp.Error)) } if payOrderRequest.ReturnUrl != "" { values := url.Values{} values.Set("redirect_url", payOrderRequest.ReturnUrl) encodedStr := values.Encode() wxRsp.Response.H5Url = wxRsp.Response.H5Url + "&" + encodedStr } return wxRsp.Response.H5Url, nil } // 微信JSAPI支付 func WxJsApiPayInfo(c context.Context, payOrderRequest PayOrderRequest) (string, error) { // 初始化微信客户端 wxClient, err := InitClient(payOrderRequest.Wx) if err != nil { return "", err } expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339) // 初始化 BodyMap envConfig := config.GetConf() bm := make(gopay.BodyMap) bm.Set("appid", payOrderRequest.Wx.AppId). Set("mchid", payOrderRequest.Wx.MchId). Set("description", payOrderRequest.Description). Set("out_trade_no", strconv.FormatInt(payOrderRequest.OrderId, 10)). Set("time_expire", expire). Set("notify_url", fmt.Sprintf(envConfig.PayService.Host+payCommon.WX_NOTIFY_URL_TEST+"%d", payOrderRequest.PayChannelId)). SetBodyMap("amount", func(bm gopay.BodyMap) { bm.Set("total", payOrderRequest.Amount). Set("currency", "CNY") }). SetBodyMap("payer", func(bm gopay.BodyMap) { bm.Set("openid", payOrderRequest.OpenId) }) wxRsp, err := wxClient.V3TransactionJsapi(c, bm) if err != nil { return "", err } if wxRsp.Code != wechat.Success || wxRsp.Response.PrepayId == "" { //logger.Error(c, "WxJsApiPayInfo 发生错误", fmt.Sprintf("错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error)) return "", errors.New(fmt.Sprintf("发起支付失败,失败状态码:%d, 失败原因:%s", wxRsp.Code, wxRsp.Error)) } //if payOrderRequest.ReturnUrl != "" { // values := url.Values{} // values.Set("redirect_url", payOrderRequest.ReturnUrl) // encodedStr := values.Encode() // //wxRsp.Response.H5Url = wxRsp.Response.H5Url + "&" + encodedStr //} return wxRsp.Response.PrepayId, nil } // WxPayCallBack 微信支付回调 func WxPayCallBack(notifyReq *wechat.V3NotifyReq, wxConfig WxPay) error { // 初始化微信客户端 var orderStatus int wxClient, err := InitClient(wxConfig) if err != nil { return err } // 获取微信平台证书 certMap := wxClient.WxPublicKeyMap() // 验证异步通知的签名 err = notifyReq.VerifySignByPKMap(certMap) if err != nil { return err } var CallBackInfo struct { AppId string `json:"appid"` // 应用ID Mchid string `json:"mchid"` // 商户号 OutTradeNo string `json:"out_trade_no"` // 商户系统内部订单号 TransactionId string `json:"transaction_id"` // 微信系统订单号 TradeType string `json:"trade_type"` // JSAPI:公众号支付 NATIVE:扫码支付 App:App支付 MICROPAY:付款码支付 MWEB:H5支付 FACEPAY:刷脸支付 TradeState string `json:"trade_state"` // SUCCESS:支付成功 REFUND:转入退款 NOTPAY:未支付 CLOSED:已关闭 REVOKED:已撤销(付款码支付) USERPAYING:用户支付中(付款码支付) PAYERROR:支付失败(其他原因,如银行返回失败) TradeStateDesc string `json:"trade_state_desc"` // 交易状态描述 SuccessTime string `json:"success_time"` // 支付完成时间 Amount struct { Total int64 `json:"total"` PayerTotal int64 `json:"payer_total"` } `json:"amount"` } // 通用通知解密(推荐此方法) err = notifyReq.DecryptCipherTextToStruct(wxConfig.ApiV3Key, &CallBackInfo) if err != nil { return err } errCode := 0 msg := "" // 订单状态 switch CallBackInfo.TradeState { case "SUCCESS": orderStatus = common.ORDER_STATUS_PAYED errCode = errorcode.Success msg = "支付成功" case "CLOSED": errCode = errorcode.ParamError orderStatus = common.ORDER_STATUS_CLOSE msg = "已关闭" case "REVOKED": errCode = errorcode.ParamError orderStatus = common.ORDER_STATUS_FAILED msg = "已撤销(付款码支付)" case "PAYERROR": errCode = errorcode.ParamError msg = "支付失败(其他原因,如银行返回失败)" orderStatus = common.ORDER_STATUS_FAILED } // 触发下游回调的格式 orderId, _ := strconv.Atoi(CallBackInfo.OutTradeNo) res := thirdpay_notify.NewOrderNotifyWithHandle(int64(orderId), orderStatus, errCode, int(CallBackInfo.Amount.PayerTotal), msg) merchantCallback, _ := json.Marshal(res) // 记录日志 go func() { payCallback, _ := json.Marshal(CallBackInfo) payParam := "{}" status := common.THIRD_ORDER_LOG_STATUS_SUCCESS SaveLog(int64(orderId), common.THIRD_ORDER_TYPE_CALL_BACK, string(payCallback), payParam, "{}", status) }() if res.ErrCode != errorcode.Success { logger.Error(context.Background(), "WxPayCallBack 发生错误", fmt.Sprintf("回调时,下游处理订单失败,返回数据为:%s", string(merchantCallback))) return errors.New("回调时,下游处理订单失败") } return nil } // WxOrderQuery 查询微信订单 func WxOrderQuery(ctx context.Context, wxConfig WxPay, orderNo string) (PayOrderQueryInfo, error) { // 初始化微信客户端 wxClient, err := InitClient(wxConfig) if err != nil { return PayOrderQueryInfo{}, err } wxRsp, err := wxClient.V3TransactionQueryOrder(ctx, payCommon.ORDER_NO_TYPE_ORDER_NO, orderNo) if err != nil { return PayOrderQueryInfo{}, err } if wxRsp.Code != wechat.Success { return PayOrderQueryInfo{}, errors.New(fmt.Sprintf("查询订单接口错误,错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error)) } // 映射交易状态 tradeState := "" switch wxRsp.Response.TradeState { case "SUCCESS": tradeState = "SUCCESS" case "REFUND": tradeState = "REFUND" case "NOTPAY": tradeState = "NOTPAY" case "CLOSED": tradeState = "CLOSED" } amountTotal := wxRsp.Response.Amount.Total payerTotal := wxRsp.Response.Amount.PayerTotal outTradeNo, _ := strconv.Atoi(wxRsp.Response.OutTradeNo) return PayOrderQueryInfo{ AppId: wxRsp.Response.Appid, OutTradeNo: int64(outTradeNo), TransactionId: wxRsp.Response.TransactionId, TradeState: tradeState, TradeStateDesc: wxRsp.Response.TradeStateDesc, SuccessTime: wxRsp.Response.SuccessTime, AmountTotal: int64(amountTotal), PayerTotal: int64(payerTotal), }, nil } // WxOrderRefund 微信退款申请 func WxOrderRefund(ctx context.Context, orderRefundRequest OrderRefundRequest) (OrderRefundInfo, error) { // 初始化微信客户端 wxClient, err := InitClient(orderRefundRequest.Wx) if err != nil { return OrderRefundInfo{}, err } // 初始化 BodyMap bm := make(gopay.BodyMap) bm.Set("out_trade_no", strconv.FormatInt(orderRefundRequest.OrderId, 10)). Set("sign_type", "MD5"). // 必填 退款订单号(程序员定义的) Set("out_refund_no", strconv.FormatInt(orderRefundRequest.RefundOrderId, 10)). // 选填 退款描述 Set("reason", orderRefundRequest.RefundReason). SetBodyMap("amount", func(bm gopay.BodyMap) { // 退款金额:单位是分 bm.Set("refund", orderRefundRequest.RefundAmount). //实际退款金额 Set("total", orderRefundRequest.RefundAmount). // 折扣前总金额(不是实际退款数) Set("currency", "CNY") }) // body:参数Body refund, err := wxClient.V3Refund(ctx, bm) if err != nil { return OrderRefundInfo{}, err } // 将非正常退款异常记录 if refund.Code != wechat.Success { logger.Error(ctx, "WxOrderRefund 发生错误", fmt.Sprintf("申请退款接口失败,错误状态码:%d, 错误信息:%s", refund.Code, refund.Error)) // 这里时对非正常退款的一些处理message,我们将code统一使用自定义的,然后把message抛出去 return OrderRefundInfo{}, errors.New(fmt.Sprintf("申请退款接口失败,错误状态码:%d, 错误信息:%s", refund.Code, refund.Error)) } outTradeNo, _ := strconv.Atoi(refund.Response.OutTradeNo) outRefundNo, _ := strconv.Atoi(refund.Response.OutRefundNo) refundStatus := payCommon.PAY_REFUND_STATU_COMMON switch refund.Response.Status { case "SUCCESS": refundStatus = payCommon.PAY_REFUND_STATU_SUCCESS case "CLOSED": refundStatus = payCommon.PAY_REFUND_STATU_FAIL case "PROCESSING": refundStatus = payCommon.PAY_REFUND_STATU_ING case "ABNORMAL": refundStatus = payCommon.PAY_REFUND_STATU_FAIL } return OrderRefundInfo{ OutTradeNo: int64(outTradeNo), TransactionId: refund.Response.TransactionId, RefundFee: int64(refund.Response.Amount.PayerRefund), RefundOrderId: int64(outRefundNo), RefundStatus: refundStatus, RefundSuccessTime: refund.Response.SuccessTime, }, nil } // WxOrderRefundQuery 微信订单退款查询 func WxOrderRefundQuery(ctx context.Context, orderRefundQueryRequest OrderRefundQueryRequest) (OrderRefundInfo, error) { // 初始化微信客户端 wxClient, err := InitClient(orderRefundQueryRequest.Wx) if err != nil { return OrderRefundInfo{}, err } wxRsp, err := wxClient.V3RefundQuery(ctx, strconv.FormatInt(orderRefundQueryRequest.RefundOrderId, 10), nil) if err != nil { return OrderRefundInfo{}, err } if wxRsp.Code != wechat.Success { return OrderRefundInfo{}, errors.New(fmt.Sprintf("查询订单接口错误,错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error)) } outTradeNo, _ := strconv.Atoi(wxRsp.Response.OutTradeNo) outRefundNo, _ := strconv.Atoi(wxRsp.Response.OutRefundNo) refundStatus := payCommon.PAY_REFUND_STATU_COMMON switch wxRsp.Response.Status { case "SUCCESS": refundStatus = payCommon.PAY_REFUND_STATU_SUCCESS case "CLOSED": refundStatus = payCommon.PAY_REFUND_STATU_FAIL case "PROCESSING": refundStatus = payCommon.PAY_REFUND_STATU_ING case "ABNORMAL": refundStatus = payCommon.PAY_REFUND_STATU_FAIL } return OrderRefundInfo{ OutTradeNo: int64(outTradeNo), TransactionId: wxRsp.Response.TransactionId, RefundFee: int64(wxRsp.Response.Amount.PayerRefund), RefundOrderId: int64(outRefundNo), RefundStatus: refundStatus, RefundSuccessTime: wxRsp.Response.SuccessTime, }, nil } // WxCloseOrder 微信订单关闭 func WxCloseOrder(ctx context.Context, orderCloseRequest OrderCloseRequest) (OrderCloseInfo, error) { // 初始化微信客户端 wxClient, err := InitClient(orderCloseRequest.Wx) if err != nil { return OrderCloseInfo{}, err } wxRsp, err := wxClient.V3TransactionCloseOrder(ctx, strconv.FormatInt(orderCloseRequest.OrderId, 10)) if err != nil { return OrderCloseInfo{}, err } if wxRsp.Code != wechat.Success { logger.Error(ctx, "WxCloseOrder 发生错误", fmt.Sprintf("查询订单接口错误,错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error)) return OrderCloseInfo{}, errors.New(fmt.Sprintf("查询订单接口错误,错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error)) } return OrderCloseInfo{ OutTradeNo: orderCloseRequest.OrderId, }, nil }