voucher/internal/data/wechatrepoimpl/cpn.go

255 lines
6.1 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"
"time"
err2 "voucher/api/err"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/biz/wechatrepo"
"voucher/internal/conf"
"voucher/internal/data"
"voucher/internal/pkg/request"
)
// 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 {
bodyBytes, err := io.ReadAll(result.Response.Body)
if err != nil {
return err2.ErrorWechatFAIL(fmt.Sprintf("读取微信错误返回body报错:%s", err.Error()))
}
var errBody ErrBody
if err = json.Unmarshal(bodyBytes, &errBody); err != nil {
log.Errorf("微信错误返回body解析报错,body:%s,err:%s", string(bodyBytes), err.Error())
return err2.ErrorWechatFAIL(fmt.Sprintf("微信错误返回内容解析错误:%s", err.Error()))
}
return errBody.GetWechatError()
}
func (c *CpnRepoImpl) Order(ctx context.Context, order *bo.OrderBo) (string, error) {
req := cashcoupons.SendCouponRequest{
OutRequestNo: core.String(order.OrderNo),
// 微信为发券方商户分配的公众账号ID接口传入的所有appid应该为公众号的appid在mp.weixin.qq.com申请的不能为APP的appid在open.weixin.qq.com申请的
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 {
return "", c.bodyErr(ctx, result)
}
return *resp.CouponId, nil
}
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 {
return nil, c.bodyErr(ctx, result)
}
return resp, nil
}
func (c *CpnRepoImpl) Query(ctx context.Context, orderWechat *bo.OrderBo) (vo.OrderStatus, 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 0, err
}
svc := cashcoupons.CouponApiService{Client: client}
resp, result, err := svc.QueryCoupon(ctx, req)
if err != nil {
return 0, c.bodyErr(ctx, result)
}
return CpnStatus(*resp.Status).GetStatus()
}
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 {
return nil, c.bodyErr(ctx, result)
}
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 {
return nil, c.bodyErr(ctx, result)
}
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 {
return nil, c.bodyErr(ctx, result)
}
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
}