voucher/internal/data/wechatrepoimpl/cpn.go

361 lines
8.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}