package wechatrepoimpl import ( "context" "encoding/json" "fmt" "github.com/go-kratos/kratos/v2/log" "github.com/wechatpay-apiv3/wechatpay-go/core" "github.com/wechatpay-apiv3/wechatpay-go/services/cashcoupons" "io" "net/http" "strings" "time" err2 "voucher/api/err" "voucher/internal/biz/bo" "voucher/internal/biz/businesserr" "voucher/internal/biz/vo" "voucher/internal/biz/wechatrepo" "voucher/internal/conf" "voucher/internal/data" "voucher/internal/pkg/helper" "voucher/internal/pkg/request" "voucher/internal/pkg/supplier/qixing" ) // CpnRepoImpl . // @link https://pay.weixin.qq.com/doc/v3/merchant/4012463767 type CpnRepoImpl struct { bc *conf.Bootstrap Server data.Server } func NewCpnRepoImpl(bc *conf.Bootstrap) (wechatrepo.WechatCpnRepo, error) { server := data.Server{ MchID: bc.Wechat.MchID, MchCertificateSerialNumber: bc.Wechat.MchCertificateSerialNumber, WechatPayPublicKeyID: bc.Wechat.WechatPayPublicKeyID, } if err := server.Validate(); err != nil { return nil, err } return &CpnRepoImpl{bc: bc, Server: server}, nil } func (c *CpnRepoImpl) GetClient(ctx context.Context) (*core.Client, error) { client, err := data.GetClient(ctx, c.Server) if err != nil { return nil, err2.ErrorWechatFAIL(err.Error()) } return client, err } func (c *CpnRepoImpl) bodyErr(_ context.Context, result *core.APIResult) error { // // 格式:{"code":"INVALID_REQUEST","message":"对应单号已超出重试期;请查单确认后决定是否换单请求"} bodyBytes, err := io.ReadAll(result.Response.Body) if err != nil { return err2.ErrorWechatFAIL(fmt.Sprintf("读取微信错误返回body报错:%s", err.Error())) } var beer *businesserr.BusinessErr if err = json.Unmarshal(bodyBytes, &beer); err != nil { log.Errorf("微信错误返回body解析报错,body:%s,err:%s", string(bodyBytes), err.Error()) return err2.ErrorWechatFAIL(fmt.Sprintf("微信错误返回内容解析错误:%s", err.Error())) } return beer } func (c *CpnRepoImpl) Order(ctx context.Context, order *bo.OrderBo) (string, error) { req := cashcoupons.SendCouponRequest{ OutRequestNo: core.String(order.OrderNo), Appid: core.String(order.AppID), Openid: core.String(order.Account), StockId: core.String(order.BatchNo), StockCreatorMchid: core.String(order.MerchantNo), } client, err := c.GetClient(ctx) if err != nil { return "", err } svc := cashcoupons.CouponApiService{Client: client} resp, result, err := svc.SendCoupon(ctx, req) if err != nil { if result.Response != nil && result.Response.Body != nil { return "", c.bodyErr(ctx, result) } return "", err } return *resp.CouponId, nil } func (c *CpnRepoImpl) Query(ctx context.Context, order *bo.OrderBo) (vo.OrderStatus, error) { // todo 确认下,多笔立减金用普通立减金的接口查询也能查,结果是准确的吗 // 福州启蒙 - 启星 if order.MerchantNo == "1715349578" { //return c.QxQuery(ctx, order) } return c.LsxdQuery(ctx, order) } func (c *CpnRepoImpl) QxQuery(ctx context.Context, order *bo.OrderBo) (vo.OrderStatus, error) { if order.ActivityId == "" { return 0, fmt.Errorf("商户号 %s 只支持多笔立减金查询", order.MerchantNo) } b := qixing.QxQueryReq{ OrderNo: order.OrderNo, CouponId: order.VoucherNo, OpenId: order.Account, } var strToBeSigned strings.Builder kvRows := helper.SortStructJsonTag(b) for _, kv := range kvRows { if kv.Key == "sign" || kv.Value == "" { continue } strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value)) } s := strToBeSigned.String() + "config.AppKey" b.Sign = helper.Md5(s) body, err := json.Marshal(b) if err != nil { return 0, err } isSuccess := func(code int) bool { return code == http.StatusOK } h := http.Header{ "Content-Type": []string{"application/json"}, } _, body, err = request.Post( ctx, c.bc.WechatNotifyMQ.RegisterTagUrl, body, request.WithHeaders(h), request.WithStatusCodeFunc(isSuccess), request.WithTimeout(time.Second*10), ) if err != nil { return 0, err } var resp qixing.QxQueryResp if err := json.Unmarshal(body, &resp); err != nil { return 0, err } // 统一返回状态类型 return resp.Data.CouponState.GetStatus() } func (c *CpnRepoImpl) LsxdQuery(ctx context.Context, order *bo.OrderBo) (vo.OrderStatus, error) { // 需要判断是多笔立减金查询还是普通立减金查询,此处需要更正查询方式 req := cashcoupons.QueryCouponRequest{ CouponId: core.String(order.VoucherNo), Appid: core.String(order.AppID), Openid: core.String(order.Account), } client, err := c.GetClient(ctx) if err != nil { return 0, err } svc := cashcoupons.CouponApiService{Client: client} resp, result, err := svc.QueryCoupon(ctx, req) if err != nil { if result.Response != nil && result.Response.Body != nil { return 0, c.bodyErr(ctx, result) } return 0, err } cpnStatus := CpnStatus(*resp.Status) if cpnStatus.IsRevoked() { } return CpnStatus(*resp.Status).GetStatus() } func (c *CpnRepoImpl) QueryCoupon(ctx context.Context, orderWechat *bo.OrderBo) (*cashcoupons.Coupon, error) { req := cashcoupons.QueryCouponRequest{ CouponId: core.String(orderWechat.VoucherNo), Appid: core.String(orderWechat.AppID), Openid: core.String(orderWechat.Account), } client, err := c.GetClient(ctx) if err != nil { return nil, err } svc := cashcoupons.CouponApiService{Client: client} resp, result, err := svc.QueryCoupon(ctx, req) if err != nil { if result.Response != nil && result.Response.Body != nil { return nil, c.bodyErr(ctx, result) } return nil, err } return resp, nil } func (c *CpnRepoImpl) QueryProduct(ctx context.Context, stockCreatorMchId, stockId string) (*cashcoupons.Stock, error) { if stockCreatorMchId == "" || stockId == "" { return nil, err2.ErrorWechatFAIL("商户号或批次号不能为空") } client, err := c.GetClient(ctx) if err != nil { return nil, err } req := cashcoupons.QueryStockRequest{ StockId: core.String(stockId), StockCreatorMchid: core.String(stockCreatorMchId), } svc := cashcoupons.StockApiService{Client: client} response, result, err := svc.QueryStock(ctx, req) if err != nil { if result.Response != nil && result.Response.Body != nil { return nil, c.bodyErr(ctx, result) } return nil, err } return response, nil } func (c *CpnRepoImpl) QueryCallback(ctx context.Context) (*cashcoupons.Callback, error) { client, err := c.GetClient(ctx) if err != nil { return nil, err } svc := cashcoupons.CallBackUrlApiService{Client: client} response, result, err := svc.QueryCallback(ctx, cashcoupons.QueryCallbackRequest{ Mchid: core.String(c.bc.Wechat.MchID), }) if err != nil { if result.Response != nil && result.Response.Body != nil { return nil, c.bodyErr(ctx, result) } return nil, err } return response, nil } func (c *CpnRepoImpl) SetCallback(ctx context.Context, url string) (*cashcoupons.SetCallbackResponse, error) { client, err := c.GetClient(ctx) if err != nil { return nil, err } svc := cashcoupons.CallBackUrlApiService{Client: client} response, result, err := svc.SetCallback(ctx, cashcoupons.SetCallbackRequest{ Mchid: core.String(c.bc.Wechat.MchID), NotifyUrl: core.String(url), Switch: core.Bool(true), }) if err != nil { if result.Response != nil && result.Response.Body != nil { return nil, c.bodyErr(ctx, result) } return nil, err } return response, nil } func (c *CpnRepoImpl) RegisterNotifyTag(ctx context.Context, stockID string) error { // 注册“刚哥那边”回调中心tag,一个批次只能注册一次,消费时根据不同的tag消费(区分测试/正式注册tag处理) h := http.Header{ "Content-Type": []string{"application/json"}, } b := struct { TagName string `json:"tag_name"` StockID string `json:"stock_id"` }{ TagName: c.bc.WechatNotifyMQ.Tag, StockID: stockID, } body, err := json.Marshal(b) if err != nil { return err } isSuccess := func(code int) bool { return code == http.StatusOK } _, _, err = request.Post( ctx, c.bc.WechatNotifyMQ.RegisterTagUrl, body, request.WithHeaders(h), request.WithStatusCodeFunc(isSuccess), request.WithTimeout(time.Second*20), ) if err != nil { return err } return nil }