This commit is contained in:
parent
9c62904de5
commit
ad95880eef
|
|
@ -0,0 +1,69 @@
|
||||||
|
package biz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"voucher/internal/biz/bo"
|
||||||
|
"voucher/internal/biz/vo"
|
||||||
|
"voucher/internal/pkg/lock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *VoucherBiz) alarm(ctx context.Context, order *bo.OrderBo, errMsg string) error {
|
||||||
|
|
||||||
|
// 1小时 内 指定的批次号 发放 发生错误 预警
|
||||||
|
c := vo.OrderConsumeFailAlarmKey.BuildCache([]string{order.ProductNo})
|
||||||
|
|
||||||
|
_, err := v.rdb.Rdb.Get(ctx, c.Key).Result()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// 缓存存在,直接返回
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != redis.Nil {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("alarm 获取redis缓存%s异常:%v", c.Key, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
cl := vo.OrderConsumeFailAlarmLockKey.BuildCache([]string{order.ProductNo})
|
||||||
|
|
||||||
|
return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {
|
||||||
|
// 二次获取,判定处理,以免获取锁后又执行了一次
|
||||||
|
|
||||||
|
cacheValue, err3 := v.rdb.Rdb.Get(ctx, c.Key).Result()
|
||||||
|
|
||||||
|
if err3 != nil && err3 != redis.Nil {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("alarm 二次获取redis缓存%s异常:%v", c.Key, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cacheValue) > 0 {
|
||||||
|
return nil // 有直接返回
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知
|
||||||
|
if err = v.DingMixRepo.SendMarkdownMessage(ctx, "异常通知", v.alarmText(ctx, order, errMsg)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = v.rdb.Rdb.Set(ctx, c.Key, order.ProductNo, c.TTL).Err(); err != nil {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VoucherBiz) alarmText(_ context.Context, order *bo.OrderBo, errMsg string) string {
|
||||||
|
|
||||||
|
remarks := fmt.Sprintf("订单号:%s,商品编号:%s,原因:%s", order.OrderNo, order.ProductNo, errMsg)
|
||||||
|
|
||||||
|
msg := "# <font color='green'>" +
|
||||||
|
"<h1>立减金发放平台报警通知</h1>" +
|
||||||
|
"</font> \n" +
|
||||||
|
"<font color='black'>" +
|
||||||
|
"不好了,订单发放发生异常了" +
|
||||||
|
"[<font color='red'>%s</font>]请尽快处理@相关人员。" +
|
||||||
|
"</font>"
|
||||||
|
|
||||||
|
return fmt.Sprintf(msg, remarks)
|
||||||
|
}
|
||||||
|
|
@ -1,162 +1 @@
|
||||||
package biz
|
package biz
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
err2 "voucher/api/err"
|
|
||||||
v1 "voucher/api/v1"
|
|
||||||
"voucher/internal/biz/bo"
|
|
||||||
"voucher/internal/biz/vo"
|
|
||||||
"voucher/internal/pkg/lock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (v *VoucherBiz) GetByOutBizNo(ctx context.Context, req *bo.OrderCreateReqBo) (*bo.OrderBo, error) {
|
|
||||||
|
|
||||||
order, err := v.OrderRepo.GetByOutBizNo(ctx, vo.OrderTypeCmb, req.OutBizNo)
|
|
||||||
|
|
||||||
if err != nil && !err2.IsDbNotFound(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return order, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VoucherBiz) CmbOrder(ctx context.Context, req *bo.OrderCreateReqBo) (orderNo string, err error) {
|
|
||||||
|
|
||||||
order, err3 := v.GetByOutBizNo(ctx, req)
|
|
||||||
if err3 != nil {
|
|
||||||
return orderNo, err3
|
|
||||||
}
|
|
||||||
|
|
||||||
if order != nil {
|
|
||||||
|
|
||||||
if order.Status.IsFail() {
|
|
||||||
|
|
||||||
if err4 := v.orderRetry(ctx, order); err4 != nil {
|
|
||||||
return orderNo, err4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
orderNo = order.OrderNo
|
|
||||||
return orderNo, err
|
|
||||||
}
|
|
||||||
|
|
||||||
product, err3 := v.ProductRepo.GetByProductNo(ctx, req.ProductNo)
|
|
||||||
if err3 != nil {
|
|
||||||
return orderNo, err3
|
|
||||||
}
|
|
||||||
|
|
||||||
order, err3 = v.order(ctx, req, product)
|
|
||||||
if err3 != nil {
|
|
||||||
return orderNo, err3
|
|
||||||
}
|
|
||||||
|
|
||||||
orderNo = order.OrderNo
|
|
||||||
|
|
||||||
return orderNo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VoucherBiz) CmbQuery(ctx context.Context, orderNo string) (resp *v1.CmbQueryReply, err error) {
|
|
||||||
|
|
||||||
c := vo.CmbQueryLockKey.BuildCache([]string{orderNo})
|
|
||||||
|
|
||||||
err = lock.NewMutex(v.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
|
|
||||||
|
|
||||||
order, err3 := v.OrderRepo.GetByOrderNo(ctx, orderNo)
|
|
||||||
if err3 != nil {
|
|
||||||
return err3
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = v.Query(ctx, order); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err3 := order.Status.GetCmbStatusText()
|
|
||||||
if err3 != nil {
|
|
||||||
return err3
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = &v1.CmbQueryReply{
|
|
||||||
Ticket: order.OrderNo,
|
|
||||||
Status: status.GetValue(),
|
|
||||||
TransDate: time.Now().Format("20060102150405"),
|
|
||||||
OrgNo: v.bc.Cmb.OrgNo,
|
|
||||||
Ext: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VoucherBiz) CmbProductQuery(ctx context.Context, productNo string) (reps *v1.CmbQueryProductReply, err error) {
|
|
||||||
|
|
||||||
c := vo.CmbProductQueryLockKey.BuildCache([]string{productNo})
|
|
||||||
|
|
||||||
err = lock.NewMutex(v.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
|
|
||||||
|
|
||||||
product, err3 := v.ProductRepo.GetByProductNo(ctx, productNo)
|
|
||||||
if err3 != nil {
|
|
||||||
return err3
|
|
||||||
}
|
|
||||||
|
|
||||||
if !product.Channel.IsWeChat() {
|
|
||||||
return fmt.Errorf("只支持微信")
|
|
||||||
}
|
|
||||||
|
|
||||||
wechatResp, err4 := v.WechatCpnRepo.QueryProduct(ctx, product.MchId, product.BatchNo)
|
|
||||||
if err4 != nil {
|
|
||||||
return err4
|
|
||||||
}
|
|
||||||
|
|
||||||
reps = &v1.CmbQueryProductReply{
|
|
||||||
RespCode: vo.CmbResponseStatusSuccess.GetValue(),
|
|
||||||
RespMsg: "成功",
|
|
||||||
ActivityName: product.Name,
|
|
||||||
ActivityId: product.ProductNo,
|
|
||||||
Amount: "",
|
|
||||||
MinAmount: "",
|
|
||||||
AvailableType: "",
|
|
||||||
AvailableDays: "", // 动态有效期天数
|
|
||||||
StartTime: "",
|
|
||||||
EndTime: "",
|
|
||||||
AvailableStock: "",
|
|
||||||
Detail: *wechatResp.Description,
|
|
||||||
}
|
|
||||||
|
|
||||||
inputFormat := time.RFC3339
|
|
||||||
|
|
||||||
if wechatResp.AvailableBeginTime != nil {
|
|
||||||
|
|
||||||
availableBeginTime, _ := time.Parse(inputFormat, *wechatResp.AvailableBeginTime)
|
|
||||||
reps.StartTime = availableBeginTime.Format("2006-01-02 15:04:05.000")
|
|
||||||
reps.SaleStartTime = reps.StartTime
|
|
||||||
}
|
|
||||||
|
|
||||||
if wechatResp.AvailableEndTime != nil {
|
|
||||||
availableEndTime, _ := time.Parse(inputFormat, *wechatResp.AvailableEndTime)
|
|
||||||
reps.EndTime = availableEndTime.Format("2006-01-02 15:04:05.000")
|
|
||||||
reps.SaleEndTime = reps.EndTime
|
|
||||||
}
|
|
||||||
|
|
||||||
reps.Amount = fmt.Sprintf("%d", *wechatResp.StockUseRule.FixedNormalCoupon.CouponAmount)
|
|
||||||
reps.MinAmount = fmt.Sprintf("%d", *wechatResp.StockUseRule.FixedNormalCoupon.TransactionMinimum)
|
|
||||||
|
|
||||||
availableStock := *wechatResp.StockUseRule.MaxCoupons - *wechatResp.DistributedCoupons
|
|
||||||
reps.AvailableStock = fmt.Sprintf("%d", availableStock)
|
|
||||||
|
|
||||||
availableType, err3 := product.AvailableType.GetCmbAvailableType()
|
|
||||||
if err3 != nil {
|
|
||||||
return err3
|
|
||||||
}
|
|
||||||
|
|
||||||
reps.AvailableType = availableType.GetValue()
|
|
||||||
reps.AvailableDays = fmt.Sprintf("%d", product.AvailableDays)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package kx
|
||||||
|
|
||||||
|
// BBToWechatRequest 蓝色兄弟请求微信发券接口数据同步Api
|
||||||
|
type BBToWechatRequest struct {
|
||||||
|
// 微信为每个批次分配的唯一id
|
||||||
|
StockId string `protobuf:"bytes,9,opt,name=stockId,proto3" json:"stockId,omitempty"`
|
||||||
|
// 商户此次发放凭据号(格式:商户id+日期+流水号)
|
||||||
|
OutRequestNo string `protobuf:"bytes,10,opt,name=outRequestNo,proto3" json:"outRequestNo,omitempty"`
|
||||||
|
// 微信为发券方商户分配的公众账号ID
|
||||||
|
AppId string `protobuf:"bytes,11,opt,name=appId,proto3" json:"appId,omitempty"`
|
||||||
|
// 批次创建方商户号
|
||||||
|
StockCreatorMhId string `protobuf:"bytes,12,opt,name=stockCreatorMhId,json=stockCreatorMchid,proto3" json:"stockCreatorMhId,omitempty"`
|
||||||
|
// 券面额,单位:分
|
||||||
|
CouponValue int32 `protobuf:"bytes,13,opt,name=couponValue,proto3" json:"couponValue,omitempty"`
|
||||||
|
// 面额发券批次门槛,单位:分
|
||||||
|
CouponMinimum int32 `protobuf:"bytes,14,opt,name=couponMinimum,proto3" json:"couponMinimum,omitempty"`
|
||||||
|
// 微信为代金券唯一分配的id, 在微信请求失败时可能为空
|
||||||
|
CouponId string `protobuf:"bytes,15,opt,name=couponId,proto3" json:"couponId,omitempty"`
|
||||||
|
// 微信返回结果
|
||||||
|
WxRes string `protobuf:"bytes,16,opt,name=wxRes,proto3" json:"wxRes,omitempty"`
|
||||||
|
// 招行返回结果
|
||||||
|
CmbRes string `protobuf:"bytes,17,opt,name=cmbRes,proto3" json:"cmbRes,omitempty"`
|
||||||
|
// 招行此次请求的数据的唯一流水号
|
||||||
|
TransactionId string `protobuf:"bytes,18,opt,name=transactionId,proto3" json:"transactionId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *BBToWechatRequest) GetSynNotice() *SynNotice {
|
||||||
|
return &SynNotice{
|
||||||
|
OutBizBo: this.TransactionId,
|
||||||
|
Type: SynNoticeTypeBBToWechat,
|
||||||
|
BizContent: this,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package kx
|
||||||
|
|
||||||
|
// CmbToBBRequest 招行请求蓝色兄弟发券接口数据同步Api
|
||||||
|
type CmbToBBRequest struct {
|
||||||
|
// 唯一流水号
|
||||||
|
TransactionId string `protobuf:"bytes,9,opt,name=transactionId,proto3" json:"transactionId,omitempty"`
|
||||||
|
// 外部合作方权益批次号
|
||||||
|
ActivityId string `protobuf:"bytes,10,opt,name=activityId,proto3" json:"activityId,omitempty"`
|
||||||
|
// 招商银行用户号 用户标识,比如手机号、支付宝openId
|
||||||
|
CmbUid string `protobuf:"bytes,11,opt,name=cmbUid,proto3" json:"cmbUid,omitempty"`
|
||||||
|
// 用户标识类型,0-手机号,1-支付宝openId
|
||||||
|
CmbUidType string `protobuf:"bytes,12,opt,name=cmbUidType,proto3" json:"cmbUidType,omitempty"`
|
||||||
|
// 时间戳,长度为13位,精度为毫秒
|
||||||
|
Timestamp string `protobuf:"bytes,13,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||||
|
// appId
|
||||||
|
AppId string `protobuf:"bytes,14,opt,name=appId,proto3" json:"appId,omitempty"`
|
||||||
|
// 补丁
|
||||||
|
Attach string `protobuf:"bytes,15,opt,name=attach,proto3" json:"attach,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *CmbToBBRequest) GetSynNotice() *SynNotice {
|
||||||
|
return &SynNotice{
|
||||||
|
OutBizBo: this.TransactionId,
|
||||||
|
Type: SynNoticeTypeCmbToBB,
|
||||||
|
BizContent: this,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package kx
|
||||||
|
|
||||||
|
type SynNoticeType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
SynNoticeTypeCmbToBB SynNoticeType = iota + 1
|
||||||
|
SynNoticeTypeBBToWechat
|
||||||
|
SynNoticeTypeWechatToBB
|
||||||
|
)
|
||||||
|
|
||||||
|
var SynNoticeTypeMap = map[SynNoticeType]string{
|
||||||
|
SynNoticeTypeCmbToBB: "招行请求蓝色兄弟",
|
||||||
|
SynNoticeTypeBBToWechat: "蓝色兄弟请求微信",
|
||||||
|
SynNoticeTypeWechatToBB: "微信请求蓝色兄弟",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SynNoticeType) GetText() string {
|
||||||
|
if t, ok := SynNoticeTypeMap[s]; ok {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return "未知类型"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SynNoticeType) GetValue() uint8 {
|
||||||
|
return uint8(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SynNoticeType) IsCmbToBB() bool {
|
||||||
|
return s == SynNoticeTypeCmbToBB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SynNoticeType) IsBBToWechat() bool {
|
||||||
|
return s == SynNoticeTypeBBToWechat
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SynNoticeType) IsWechatToBB() bool {
|
||||||
|
return s == SynNoticeTypeWechatToBB
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package kx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ SynApiInterface = (*CmbToBBRequest)(nil)
|
||||||
|
var _ SynApiInterface = (*BBToWechatRequest)(nil)
|
||||||
|
var _ SynApiInterface = (*WechatToBBRequest)(nil)
|
||||||
|
|
||||||
|
type SynApiInterface interface {
|
||||||
|
GetSynNotice() *SynNotice
|
||||||
|
}
|
||||||
|
|
||||||
|
type SynNotice struct {
|
||||||
|
OutBizBo string
|
||||||
|
Type SynNoticeType
|
||||||
|
BizContent SynApiInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SynNotice) Marshal() ([]byte, error) {
|
||||||
|
return json.Marshal(this)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package kx
|
||||||
|
|
||||||
|
// WechatToBBRequest 微信回调蓝色兄弟接口数据同步Api
|
||||||
|
type WechatToBBRequest struct {
|
||||||
|
// 活动ID
|
||||||
|
ActivityId string `protobuf:"bytes,9,opt,name=activityId,proto3" json:"activityId,omitempty"`
|
||||||
|
// 活动名称
|
||||||
|
ActivityName string `protobuf:"bytes,10,opt,name=activityName,proto3" json:"activityName,omitempty"`
|
||||||
|
// 优惠券ID
|
||||||
|
VoucherId string `protobuf:"bytes,11,opt,name=voucherId,proto3" json:"voucherId,omitempty"`
|
||||||
|
// 领取用户ID
|
||||||
|
UserId string `protobuf:"bytes,12,opt,name=userId,proto3" json:"userId,omitempty"`
|
||||||
|
// 核销时间(Unix时间戳,毫秒)
|
||||||
|
UseTime string `protobuf:"bytes,13,opt,name=useTime,proto3" json:"useTime,omitempty"`
|
||||||
|
// 核销金额(分)
|
||||||
|
UseAmount string `protobuf:"bytes,14,opt,name=useAmount,proto3" json:"useAmount,omitempty"`
|
||||||
|
// 券消息类型,例如券核销(V_USE,V_REFUND)
|
||||||
|
BizType string `protobuf:"bytes,15,opt,name=bizType,proto3" json:"bizType,omitempty"`
|
||||||
|
// 退款时间(Unix时间戳,毫秒)
|
||||||
|
RefundTime string `protobuf:"bytes,16,opt,name=refundTime,proto3" json:"refundTime,omitempty"`
|
||||||
|
// 退款金额(分)
|
||||||
|
RefundAmount string `protobuf:"bytes,17,opt,name=refundAmount,proto3" json:"refundAmount,omitempty"`
|
||||||
|
// 券状态,可用(ENABLED)/不可用(DISABLED)
|
||||||
|
VoucherStatus string `protobuf:"bytes,18,opt,name=voucherStatus,proto3" json:"voucherStatus,omitempty"`
|
||||||
|
// 幂等ID
|
||||||
|
OrderId string `protobuf:"bytes,19,opt,name=orderId,proto3" json:"orderId,omitempty"`
|
||||||
|
// 支付宝交易号
|
||||||
|
TradeNo string `protobuf:"bytes,20,opt,name=tradeNo,proto3" json:"tradeNo,omitempty"`
|
||||||
|
// 券领取时间(Unix时间戳,毫秒)
|
||||||
|
GmtVoucherCreate string `protobuf:"bytes,21,opt,name=gmtVoucherCreate,proto3" json:"gmtVoucherCreate,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *WechatToBBRequest) GetSynNotice() *SynNotice {
|
||||||
|
return &SynNotice{
|
||||||
|
OutBizBo: this.OrderId,
|
||||||
|
Type: SynNoticeTypeBBToWechat,
|
||||||
|
BizContent: this,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package mixrepos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"voucher/internal/biz/kx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KxMixRepo interface {
|
||||||
|
Request(ctx context.Context, req *kx.SynNotice) error
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
package biz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
err2 "voucher/api/err"
|
||||||
|
"voucher/internal/biz/bo"
|
||||||
|
"voucher/internal/biz/vo"
|
||||||
|
"voucher/internal/pkg/lock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *VoucherBiz) registerNotifyTag(ctx context.Context, stockCreatorMchID, stockID string) error {
|
||||||
|
|
||||||
|
c := vo.WechatNotifyRegisterTagCacheKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID})
|
||||||
|
|
||||||
|
_, err := v.rdb.Rdb.Get(ctx, c.Key).Result()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// 缓存存在,直接返回
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != redis.Nil {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("获取redis缓存%s异常:%v", c.Key, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
cl := vo.WechatNotifyRegisterTagCacheLockKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID})
|
||||||
|
|
||||||
|
return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {
|
||||||
|
// 二次获取,判定处理,以免获取锁后又执行了一次
|
||||||
|
|
||||||
|
cacheValue, err3 := v.rdb.Rdb.Get(ctx, c.Key).Result()
|
||||||
|
|
||||||
|
if err3 != nil && err3 != redis.Nil {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("二次获取redis缓存%s异常:%v", c.Key, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cacheValue != "" {
|
||||||
|
return nil // 有直接返回
|
||||||
|
}
|
||||||
|
|
||||||
|
wechatNotifyTag, err3 := v.WechatNotifyRegisterTagRepo.GetByStockIdAndMchId(ctx, stockCreatorMchID, stockID)
|
||||||
|
if err3 != nil && !err2.IsDbNotFound(err3) {
|
||||||
|
return err3
|
||||||
|
}
|
||||||
|
|
||||||
|
if wechatNotifyTag != nil {
|
||||||
|
if wechatNotifyTag.Tag != v.bc.WechatNotifyMQ.Tag {
|
||||||
|
return fmt.Errorf("tag不一致,请检查tag配置:%s", wechatNotifyTag.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wechatNotifyTag.Status.IsSuccess() {
|
||||||
|
return v.setCache(ctx, c, wechatNotifyTag)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wechatNotifyTag, err3 = v.createWechatNotifyRegisterTag(ctx, stockCreatorMchID, stockID)
|
||||||
|
if err3 != nil {
|
||||||
|
return err3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = v.WechatCpnRepo.RegisterNotifyTag(ctx, stockID); err != nil {
|
||||||
|
|
||||||
|
return v.WechatNotifyRegisterTagRepo.Fail(ctx, wechatNotifyTag.ID, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = v.WechatNotifyRegisterTagRepo.Success(ctx, wechatNotifyTag.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.setCache(ctx, c, wechatNotifyTag)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VoucherBiz) createWechatNotifyRegisterTag(ctx context.Context, stockCreatorMchID, stockID string) (*bo.WechatNotifyRegisterTagBo, error) {
|
||||||
|
return v.WechatNotifyRegisterTagRepo.Create(ctx, &bo.WechatNotifyRegisterTagBo{
|
||||||
|
StockID: stockID,
|
||||||
|
StockCreatorMchID: stockCreatorMchID,
|
||||||
|
Tag: v.bc.WechatNotifyMQ.Tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VoucherBiz) setCache(ctx context.Context, c *vo.Cache, wechatNotifyTag *bo.WechatNotifyRegisterTagBo) error {
|
||||||
|
|
||||||
|
if err := v.rdb.Rdb.Set(ctx, c.Key, wechatNotifyTag.Tag, c.TTL).Err(); err != nil {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -2,22 +2,97 @@ package biz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/go-kratos/kratos/v2/errors"
|
||||||
"github.com/go-kratos/kratos/v2/log"
|
"github.com/go-kratos/kratos/v2/log"
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
err2 "voucher/api/err"
|
err2 "voucher/api/err"
|
||||||
|
v1 "voucher/api/v1"
|
||||||
"voucher/internal/biz/bo"
|
"voucher/internal/biz/bo"
|
||||||
"voucher/internal/biz/vo"
|
"voucher/internal/biz/vo"
|
||||||
"voucher/internal/pkg/lock"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (v *VoucherBiz) order(ctx context.Context, req *bo.OrderCreateReqBo, product *bo.ProductBo) (*bo.OrderBo, error) {
|
func (c *VoucherBiz) CmbOrder(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) {
|
||||||
|
|
||||||
|
order, err := c.cmbOrder(ctx, request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.OrderFail(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.OrderSuccess(ctx, order.OrderNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VoucherBiz) cmbOrder(ctx context.Context, request *v1.CmbRequest) (*bo.OrderBo, error) {
|
||||||
|
|
||||||
|
bizContent, err := c.CmbMixRepo.OrderVerify(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx2 := context.Background()
|
||||||
|
|
||||||
|
boReq := &bo.OrderCreateReqBo{
|
||||||
|
OutBizNo: bizContent.TransactionId,
|
||||||
|
ProductNo: bizContent.ActivityId,
|
||||||
|
Account: bizContent.CmbUid,
|
||||||
|
AppID: bizContent.AppId,
|
||||||
|
Attach: bizContent.Attach,
|
||||||
|
AccountType: vo.OrderAccountTypeOpenId,
|
||||||
|
Type: vo.OrderTypeCmb,
|
||||||
|
NotifyUrl: c.bc.Cmb.NotifyUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err := c.Order(ctx2, boReq, bizContent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return order, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VoucherBiz) Order(ctx context.Context, req *bo.OrderCreateReqBo, cmbReq *v1.CmbOrderRequest) (order *bo.OrderBo, err error) {
|
||||||
|
|
||||||
|
order, err = v.OrderRepo.GetByOutBizNo(ctx, vo.OrderTypeCmb, req.OutBizNo)
|
||||||
|
|
||||||
|
if err != nil && !err2.IsDbNotFound(err) {
|
||||||
|
return order, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if order != nil {
|
||||||
|
|
||||||
|
if order.Status.IsFail() {
|
||||||
|
|
||||||
|
if err4 := v.orderRetry(ctx, order); err4 != nil {
|
||||||
|
return order, err4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return order, err
|
||||||
|
}
|
||||||
|
|
||||||
|
product, err3 := v.ProductRepo.GetByProductNo(ctx, req.ProductNo)
|
||||||
|
if err3 != nil {
|
||||||
|
return order, err3
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err = v.order(ctx, req, product, cmbReq)
|
||||||
|
if err != nil {
|
||||||
|
return order, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return order, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VoucherBiz) order(ctx context.Context, req *bo.OrderCreateReqBo, product *bo.ProductBo, cmbReq *v1.CmbOrderRequest) (*bo.OrderBo, error) {
|
||||||
|
|
||||||
order, err := v.create(ctx, req, product)
|
order, err := v.create(ctx, req, product)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通知kx
|
||||||
|
|
||||||
// 注册通知标签 order.MerchantNo 批次创建商户, order.BatchNo 商品批次号
|
// 注册通知标签 order.MerchantNo 批次创建商户, order.BatchNo 商品批次号
|
||||||
if err = v.registerNotifyTag(ctx, order.MerchantNo, order.BatchNo); err != nil {
|
if err = v.registerNotifyTag(ctx, order.MerchantNo, order.BatchNo); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -70,91 +145,12 @@ func (v *VoucherBiz) create(ctx context.Context, req *bo.OrderCreateReqBo, produ
|
||||||
return v.OrderRepo.Create(ctx, o)
|
return v.OrderRepo.Create(ctx, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VoucherBiz) registerNotifyTag(ctx context.Context, stockCreatorMchID, stockID string) error {
|
|
||||||
|
|
||||||
c := vo.WechatNotifyRegisterTagCacheKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID})
|
|
||||||
|
|
||||||
_, err := v.rdb.Rdb.Get(ctx, c.Key).Result()
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
// 缓存存在,直接返回
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != redis.Nil {
|
|
||||||
return fmt.Errorf(fmt.Sprintf("获取redis缓存%s异常:%v", c.Key, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
cl := vo.WechatNotifyRegisterTagCacheLockKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID})
|
|
||||||
|
|
||||||
return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {
|
|
||||||
// 二次获取,判定处理,以免获取锁后又执行了一次
|
|
||||||
|
|
||||||
cacheValue, err3 := v.rdb.Rdb.Get(ctx, c.Key).Result()
|
|
||||||
|
|
||||||
if err3 != nil && err3 != redis.Nil {
|
|
||||||
return fmt.Errorf(fmt.Sprintf("二次获取redis缓存%s异常:%v", c.Key, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if cacheValue != "" {
|
|
||||||
return nil // 有直接返回
|
|
||||||
}
|
|
||||||
|
|
||||||
wechatNotifyTag, err3 := v.WechatNotifyRegisterTagRepo.GetByStockIdAndMchId(ctx, stockCreatorMchID, stockID)
|
|
||||||
if err3 != nil && !err2.IsDbNotFound(err3) {
|
|
||||||
return err3
|
|
||||||
}
|
|
||||||
|
|
||||||
if wechatNotifyTag != nil {
|
|
||||||
if wechatNotifyTag.Tag != v.bc.WechatNotifyMQ.Tag {
|
|
||||||
return fmt.Errorf("tag不一致,请检查tag配置:%s", wechatNotifyTag.Tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
if wechatNotifyTag.Status.IsSuccess() {
|
|
||||||
return v.setCache(ctx, c, wechatNotifyTag)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wechatNotifyTag, err3 = v.createWechatNotifyRegisterTag(ctx, stockCreatorMchID, stockID)
|
|
||||||
if err3 != nil {
|
|
||||||
return err3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = v.WechatCpnRepo.RegisterNotifyTag(ctx, stockID); err != nil {
|
|
||||||
|
|
||||||
return v.WechatNotifyRegisterTagRepo.Fail(ctx, wechatNotifyTag.ID, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = v.WechatNotifyRegisterTagRepo.Success(ctx, wechatNotifyTag.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return v.setCache(ctx, c, wechatNotifyTag)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VoucherBiz) createWechatNotifyRegisterTag(ctx context.Context, stockCreatorMchID, stockID string) (*bo.WechatNotifyRegisterTagBo, error) {
|
|
||||||
return v.WechatNotifyRegisterTagRepo.Create(ctx, &bo.WechatNotifyRegisterTagBo{
|
|
||||||
StockID: stockID,
|
|
||||||
StockCreatorMchID: stockCreatorMchID,
|
|
||||||
Tag: v.bc.WechatNotifyMQ.Tag,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VoucherBiz) setCache(ctx context.Context, c *vo.Cache, wechatNotifyTag *bo.WechatNotifyRegisterTagBo) error {
|
|
||||||
|
|
||||||
if err := v.rdb.Rdb.Set(ctx, c.Key, wechatNotifyTag.Tag, c.TTL).Err(); err != nil {
|
|
||||||
return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VoucherBiz) ing(ctx context.Context, id uint64) error {
|
func (v *VoucherBiz) ing(ctx context.Context, id uint64) error {
|
||||||
return v.OrderRepo.Ing(ctx, id)
|
return v.OrderRepo.Ing(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VoucherBiz) success(ctx context.Context, order *bo.OrderBo, voucherNo string) error {
|
func (v *VoucherBiz) success(ctx context.Context, order *bo.OrderBo, voucherNo string) error {
|
||||||
|
order.VoucherNo = voucherNo
|
||||||
return v.OrderRepo.Success(ctx, order.ID, voucherNo)
|
return v.OrderRepo.Success(ctx, order.ID, voucherNo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,115 +167,37 @@ func (v *VoucherBiz) fail(ctx context.Context, order *bo.OrderBo, errReq error)
|
||||||
return v.alarm(ctx, order, errReq.Error())
|
return v.alarm(ctx, order, errReq.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VoucherBiz) alarm(ctx context.Context, order *bo.OrderBo, errMsg string) error {
|
func (c *VoucherBiz) OrderSuccess(ctx context.Context, orderNo string) (*v1.CmbReply, error) {
|
||||||
|
|
||||||
// 1小时 内 指定的批次号 发放 发生错误 预警
|
bizReply := &v1.CmbOrderReply{
|
||||||
c := vo.OrderConsumeFailAlarmKey.BuildCache([]string{order.ProductNo})
|
RespCode: vo.CmbResponseStatusSuccess.GetValue(),
|
||||||
|
RespMsg: "成功",
|
||||||
_, err := v.rdb.Rdb.Get(ctx, c.Key).Result()
|
CodeNo: orderNo,
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
// 缓存存在,直接返回
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != redis.Nil {
|
replyBizContent, _ := json.Marshal(bizReply)
|
||||||
return fmt.Errorf(fmt.Sprintf("alarm 获取redis缓存%s异常:%v", c.Key, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
cl := vo.OrderConsumeFailAlarmLockKey.BuildCache([]string{order.ProductNo})
|
return c.GetResponse(ctx, replyBizContent)
|
||||||
|
|
||||||
return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {
|
|
||||||
// 二次获取,判定处理,以免获取锁后又执行了一次
|
|
||||||
|
|
||||||
cacheValue, err3 := v.rdb.Rdb.Get(ctx, c.Key).Result()
|
|
||||||
|
|
||||||
if err3 != nil && err3 != redis.Nil {
|
|
||||||
return fmt.Errorf(fmt.Sprintf("alarm 二次获取redis缓存%s异常:%v", c.Key, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cacheValue) > 0 {
|
|
||||||
return nil // 有直接返回
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通知
|
|
||||||
if err = v.DingMixRepo.SendMarkdownMessage(ctx, "异常通知", v.alarmText(ctx, order, errMsg)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = v.rdb.Rdb.Set(ctx, c.Key, order.ProductNo, c.TTL).Err(); err != nil {
|
|
||||||
return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VoucherBiz) alarmText(_ context.Context, order *bo.OrderBo, errMsg string) string {
|
func (c *VoucherBiz) OrderFail(ctx context.Context, err error) (*v1.CmbReply, error) {
|
||||||
|
|
||||||
remarks := fmt.Sprintf("订单号:%s,商品编号:%s,原因:%s", order.OrderNo, order.ProductNo, errMsg)
|
se := errors.FromError(err)
|
||||||
|
|
||||||
msg := "# <font color='green'>" +
|
if len(se.Reason) == 0 {
|
||||||
"<h1>立减金发放平台报警通知</h1>" +
|
se.Reason = err2.CmbErr_CMB_UNKNOWN.String()
|
||||||
"</font> \n" +
|
}
|
||||||
"<font color='black'>" +
|
|
||||||
"不好了,订单发放发生异常了" +
|
|
||||||
"[<font color='red'>%s</font>]请尽快处理@相关人员。" +
|
|
||||||
"</font>"
|
|
||||||
|
|
||||||
return fmt.Sprintf(msg, remarks)
|
log.Errorf("order fail: %v", se)
|
||||||
}
|
|
||||||
|
bizReply := &v1.CmbOrderReply{
|
||||||
func (v *VoucherBiz) Query(ctx context.Context, order *bo.OrderBo) error {
|
RespCode: vo.CmbResponseStatusFail.GetValue(),
|
||||||
|
RespMsg: se.Message,
|
||||||
status, err := v.WechatCpnRepo.Query(ctx, order)
|
CodeNo: "",
|
||||||
if err != nil {
|
ThirdErrCode: se.Reason,
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
replyBizContent, _ := json.Marshal(bizReply)
|
||||||
if order.Status == status {
|
|
||||||
log.Warnf("券状态未改变:%s,忽略不处理,orderNo:%s", order.Status.GetText(), order.OrderNo)
|
return c.GetResponse(ctx, replyBizContent)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = v.UpdateOrderStatus(ctx, order.ID, status); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
order.Status = status
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VoucherBiz) UpdateOrderStatus(ctx context.Context, orderId uint64, status vo.OrderStatus) error {
|
|
||||||
|
|
||||||
if status.IsSuccess() {
|
|
||||||
|
|
||||||
return v.OrderRepo.Available(ctx, orderId)
|
|
||||||
|
|
||||||
} else if status.IsUse() {
|
|
||||||
|
|
||||||
return v.OrderRepo.Used(ctx, orderId)
|
|
||||||
|
|
||||||
} else if status.IsExpired() {
|
|
||||||
|
|
||||||
return v.OrderRepo.Expired(ctx, orderId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("notice 未知券状态,orderId:%d,statuText:%s", orderId, status.GetText())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VoucherBiz) QueryOrder(ctx context.Context, orderNo string) (string, error) {
|
|
||||||
|
|
||||||
order, err3 := v.OrderRepo.GetByOrderNo(ctx, orderNo)
|
|
||||||
if err3 != nil {
|
|
||||||
return "", err3
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := v.WechatCpnRepo.Query(ctx, order)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("orderNo:%s,订单状态:%s,微信查询返回状态:%s", orderNo, order.Status.GetText(), status.GetText()), nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package biz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
v1 "voucher/api/v1"
|
||||||
|
"voucher/internal/biz/vo"
|
||||||
|
"voucher/internal/pkg/lock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *VoucherBiz) CmbProductQuery(ctx context.Context, productNo string) (reps *v1.CmbQueryProductReply, err error) {
|
||||||
|
|
||||||
|
c := vo.CmbProductQueryLockKey.BuildCache([]string{productNo})
|
||||||
|
|
||||||
|
err = lock.NewMutex(v.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
|
||||||
|
|
||||||
|
product, err3 := v.ProductRepo.GetByProductNo(ctx, productNo)
|
||||||
|
if err3 != nil {
|
||||||
|
return err3
|
||||||
|
}
|
||||||
|
|
||||||
|
if !product.Channel.IsWeChat() {
|
||||||
|
return fmt.Errorf("只支持微信")
|
||||||
|
}
|
||||||
|
|
||||||
|
wechatResp, err4 := v.WechatCpnRepo.QueryProduct(ctx, product.MchId, product.BatchNo)
|
||||||
|
if err4 != nil {
|
||||||
|
return err4
|
||||||
|
}
|
||||||
|
|
||||||
|
reps = &v1.CmbQueryProductReply{
|
||||||
|
RespCode: vo.CmbResponseStatusSuccess.GetValue(),
|
||||||
|
RespMsg: "成功",
|
||||||
|
ActivityName: product.Name,
|
||||||
|
ActivityId: product.ProductNo,
|
||||||
|
Amount: "",
|
||||||
|
MinAmount: "",
|
||||||
|
AvailableType: "",
|
||||||
|
AvailableDays: "", // 动态有效期天数
|
||||||
|
StartTime: "",
|
||||||
|
EndTime: "",
|
||||||
|
AvailableStock: "",
|
||||||
|
Detail: *wechatResp.Description,
|
||||||
|
}
|
||||||
|
|
||||||
|
inputFormat := time.RFC3339
|
||||||
|
|
||||||
|
if wechatResp.AvailableBeginTime != nil {
|
||||||
|
|
||||||
|
availableBeginTime, _ := time.Parse(inputFormat, *wechatResp.AvailableBeginTime)
|
||||||
|
reps.StartTime = availableBeginTime.Format("2006-01-02 15:04:05.000")
|
||||||
|
reps.SaleStartTime = reps.StartTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if wechatResp.AvailableEndTime != nil {
|
||||||
|
availableEndTime, _ := time.Parse(inputFormat, *wechatResp.AvailableEndTime)
|
||||||
|
reps.EndTime = availableEndTime.Format("2006-01-02 15:04:05.000")
|
||||||
|
reps.SaleEndTime = reps.EndTime
|
||||||
|
}
|
||||||
|
|
||||||
|
reps.Amount = fmt.Sprintf("%d", *wechatResp.StockUseRule.FixedNormalCoupon.CouponAmount)
|
||||||
|
reps.MinAmount = fmt.Sprintf("%d", *wechatResp.StockUseRule.FixedNormalCoupon.TransactionMinimum)
|
||||||
|
|
||||||
|
availableStock := *wechatResp.StockUseRule.MaxCoupons - *wechatResp.DistributedCoupons
|
||||||
|
reps.AvailableStock = fmt.Sprintf("%d", availableStock)
|
||||||
|
|
||||||
|
availableType, err3 := product.AvailableType.GetCmbAvailableType()
|
||||||
|
if err3 != nil {
|
||||||
|
return err3
|
||||||
|
}
|
||||||
|
|
||||||
|
reps.AvailableType = availableType.GetValue()
|
||||||
|
reps.AvailableDays = fmt.Sprintf("%d", product.AvailableDays)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package biz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-kratos/kratos/v2/log"
|
||||||
|
"time"
|
||||||
|
v1 "voucher/api/v1"
|
||||||
|
"voucher/internal/biz/bo"
|
||||||
|
"voucher/internal/biz/vo"
|
||||||
|
"voucher/internal/pkg/lock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *VoucherBiz) CmbQuery(ctx context.Context, orderNo string) (resp *v1.CmbQueryReply, err error) {
|
||||||
|
|
||||||
|
c := vo.CmbQueryLockKey.BuildCache([]string{orderNo})
|
||||||
|
|
||||||
|
err = lock.NewMutex(v.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
|
||||||
|
|
||||||
|
order, err3 := v.OrderRepo.GetByOrderNo(ctx, orderNo)
|
||||||
|
if err3 != nil {
|
||||||
|
return err3
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = v.Query(ctx, order); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err3 := order.Status.GetCmbStatusText()
|
||||||
|
if err3 != nil {
|
||||||
|
return err3
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = &v1.CmbQueryReply{
|
||||||
|
Ticket: order.OrderNo,
|
||||||
|
Status: status.GetValue(),
|
||||||
|
TransDate: time.Now().Format("20060102150405"),
|
||||||
|
OrgNo: v.bc.Cmb.OrgNo,
|
||||||
|
Ext: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VoucherBiz) Query(ctx context.Context, order *bo.OrderBo) error {
|
||||||
|
|
||||||
|
status, err := v.WechatCpnRepo.Query(ctx, order)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.Status == status {
|
||||||
|
log.Warnf("券状态未改变:%s,忽略不处理,orderNo:%s", order.Status.GetText(), order.OrderNo)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = v.UpdateOrderStatus(ctx, order.ID, status); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
order.Status = status
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VoucherBiz) UpdateOrderStatus(ctx context.Context, orderId uint64, status vo.OrderStatus) error {
|
||||||
|
|
||||||
|
if status.IsSuccess() {
|
||||||
|
|
||||||
|
return v.OrderRepo.Available(ctx, orderId)
|
||||||
|
|
||||||
|
} else if status.IsUse() {
|
||||||
|
|
||||||
|
return v.OrderRepo.Used(ctx, orderId)
|
||||||
|
|
||||||
|
} else if status.IsExpired() {
|
||||||
|
|
||||||
|
return v.OrderRepo.Expired(ctx, orderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("notice 未知券状态,orderId:%d,statuText:%s", orderId, status.GetText())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VoucherBiz) QueryOrder(ctx context.Context, orderNo string) (string, error) {
|
||||||
|
|
||||||
|
order, err3 := v.OrderRepo.GetByOrderNo(ctx, orderNo)
|
||||||
|
if err3 != nil {
|
||||||
|
return "", err3
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := v.WechatCpnRepo.Query(ctx, order)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("orderNo:%s,订单状态:%s,微信查询返回状态:%s", orderNo, order.Status.GetText(), status.GetText()), nil
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
package biz
|
package biz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/go-kratos/kratos/v2/log"
|
||||||
|
v1 "voucher/api/v1"
|
||||||
|
"voucher/internal/biz/bo"
|
||||||
"voucher/internal/biz/cmb"
|
"voucher/internal/biz/cmb"
|
||||||
"voucher/internal/biz/mixrepos"
|
"voucher/internal/biz/mixrepos"
|
||||||
"voucher/internal/biz/repo"
|
"voucher/internal/biz/repo"
|
||||||
|
"voucher/internal/biz/vo"
|
||||||
"voucher/internal/biz/wechatrepo"
|
"voucher/internal/biz/wechatrepo"
|
||||||
"voucher/internal/conf"
|
"voucher/internal/conf"
|
||||||
"voucher/internal/data"
|
"voucher/internal/data"
|
||||||
|
|
@ -22,6 +27,7 @@ type VoucherBiz struct {
|
||||||
WechatCpnRepo wechatrepo.WechatCpnRepo
|
WechatCpnRepo wechatrepo.WechatCpnRepo
|
||||||
DingMixRepo mixrepos.DingMixRepo
|
DingMixRepo mixrepos.DingMixRepo
|
||||||
CmbMixRepo mixrepos.CmbMixRepo
|
CmbMixRepo mixrepos.CmbMixRepo
|
||||||
|
KxMixRepo mixrepos.KxMixRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVoucherBiz(
|
func NewVoucherBiz(
|
||||||
|
|
@ -37,6 +43,7 @@ func NewVoucherBiz(
|
||||||
WechatCpnRepo wechatrepo.WechatCpnRepo,
|
WechatCpnRepo wechatrepo.WechatCpnRepo,
|
||||||
DingMixRepo mixrepos.DingMixRepo,
|
DingMixRepo mixrepos.DingMixRepo,
|
||||||
CmbMixRepo mixrepos.CmbMixRepo,
|
CmbMixRepo mixrepos.CmbMixRepo,
|
||||||
|
KxMixRepo mixrepos.KxMixRepo,
|
||||||
) *VoucherBiz {
|
) *VoucherBiz {
|
||||||
return &VoucherBiz{
|
return &VoucherBiz{
|
||||||
bc: bc,
|
bc: bc,
|
||||||
|
|
@ -51,5 +58,23 @@ func NewVoucherBiz(
|
||||||
WechatCpnRepo: WechatCpnRepo,
|
WechatCpnRepo: WechatCpnRepo,
|
||||||
DingMixRepo: DingMixRepo,
|
DingMixRepo: DingMixRepo,
|
||||||
CmbMixRepo: CmbMixRepo,
|
CmbMixRepo: CmbMixRepo,
|
||||||
|
KxMixRepo: KxMixRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *VoucherBiz) GetResponse(ctx context.Context, replyBizContent []byte) (*v1.CmbReply, error) {
|
||||||
|
|
||||||
|
req := &bo.CmbResponseBo{
|
||||||
|
RespCode: vo.CmbResponseStatusSuccess.GetValue(),
|
||||||
|
RespMsg: "成功",
|
||||||
|
BizContent: string(replyBizContent),
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err := c.CmbMixRepo.GetResponse(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("build cmb response fail: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package mixrepoimpl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-kratos/kratos/v2/log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
v1 "voucher/api/v1"
|
||||||
|
"voucher/internal/biz/kx"
|
||||||
|
"voucher/internal/biz/mixrepos"
|
||||||
|
"voucher/internal/biz/vo"
|
||||||
|
"voucher/internal/conf"
|
||||||
|
"voucher/internal/pkg/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KxMixRepoImpl struct {
|
||||||
|
bc *conf.Bootstrap
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKxMixRepoImpl(bc *conf.Bootstrap) mixrepos.KxMixRepo {
|
||||||
|
return &KxMixRepoImpl{bc: bc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *KxMixRepoImpl) Request(ctx context.Context, req *kx.SynNotice) error {
|
||||||
|
|
||||||
|
body, err := req.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("请求掌上生活Marshal报错:%s", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h := http.Header{
|
||||||
|
"Content-Type": []string{"application/x-www-form-urlencoded"},
|
||||||
|
}
|
||||||
|
|
||||||
|
url := ""
|
||||||
|
|
||||||
|
_, bodyBytes, err := request.Post(ctx, url, body, request.WithHeaders(h), request.WithTimeout(time.Second*20))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("请求kx报错,url:%s,err:%v", url, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var response *v1.CmbReply
|
||||||
|
if err = json.Unmarshal(bodyBytes, &response); err != nil {
|
||||||
|
log.Errorf("请求kx返回数据解析报错:%s,url:%s,bodyBytes:%s", err.Error(), url, string(bodyBytes))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.RespCode != vo.CmbResponseStatusSuccess.GetValue() {
|
||||||
|
log.Errorf("请求kx返回报错:msg:%s,url:%s,bodyBytes:%s", response.RespMsg, url, string(bodyBytes))
|
||||||
|
return fmt.Errorf(response.RespMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -10,4 +10,5 @@ var ProviderMixRepoImplSet = wire.NewSet(
|
||||||
NewMQSendMixRepoImpl,
|
NewMQSendMixRepoImpl,
|
||||||
NewCmbMixRepoImpl,
|
NewCmbMixRepoImpl,
|
||||||
NewDingMixRepoImpl,
|
NewDingMixRepoImpl,
|
||||||
|
NewKxMixRepoImpl,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,85 +2,10 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"github.com/go-kratos/kratos/v2/errors"
|
|
||||||
"github.com/go-kratos/kratos/v2/log"
|
|
||||||
err2 "voucher/api/err"
|
|
||||||
v1 "voucher/api/v1"
|
v1 "voucher/api/v1"
|
||||||
"voucher/internal/biz/bo"
|
|
||||||
"voucher/internal/biz/vo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *CmbService) OrderSuccess(ctx context.Context, orderNo string) (*v1.CmbReply, error) {
|
|
||||||
|
|
||||||
bizReply := &v1.CmbOrderReply{
|
|
||||||
RespCode: vo.CmbResponseStatusSuccess.GetValue(),
|
|
||||||
RespMsg: "成功",
|
|
||||||
CodeNo: orderNo,
|
|
||||||
}
|
|
||||||
|
|
||||||
replyBizContent, _ := json.Marshal(bizReply)
|
|
||||||
|
|
||||||
return c.GetResponse(ctx, replyBizContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CmbService) OrderFail(ctx context.Context, err error) (*v1.CmbReply, error) {
|
|
||||||
|
|
||||||
se := errors.FromError(err)
|
|
||||||
|
|
||||||
if len(se.Reason) == 0 {
|
|
||||||
se.Reason = err2.CmbErr_CMB_UNKNOWN.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Errorf("order fail: %v", se)
|
|
||||||
|
|
||||||
bizReply := &v1.CmbOrderReply{
|
|
||||||
RespCode: vo.CmbResponseStatusFail.GetValue(),
|
|
||||||
RespMsg: se.Message,
|
|
||||||
CodeNo: "",
|
|
||||||
ThirdErrCode: se.Reason,
|
|
||||||
}
|
|
||||||
|
|
||||||
replyBizContent, _ := json.Marshal(bizReply)
|
|
||||||
|
|
||||||
return c.GetResponse(ctx, replyBizContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CmbService) Order(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) {
|
func (c *CmbService) Order(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) {
|
||||||
|
|
||||||
orderNo, err := c.order(ctx, request)
|
return c.VoucherBiz.CmbOrder(ctx, request)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return c.OrderFail(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.OrderSuccess(ctx, orderNo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CmbService) order(ctx context.Context, request *v1.CmbRequest) (string, error) {
|
|
||||||
|
|
||||||
bizContent, err := c.CmbMixRepo.OrderVerify(ctx, request)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx2 := context.Background()
|
|
||||||
|
|
||||||
boReq := &bo.OrderCreateReqBo{
|
|
||||||
OutBizNo: bizContent.TransactionId,
|
|
||||||
ProductNo: bizContent.ActivityId,
|
|
||||||
Account: bizContent.CmbUid,
|
|
||||||
AppID: bizContent.AppId,
|
|
||||||
Attach: bizContent.Attach,
|
|
||||||
AccountType: vo.OrderAccountTypeOpenId,
|
|
||||||
Type: vo.OrderTypeCmb,
|
|
||||||
NotifyUrl: c.bc.Cmb.NotifyUrl,
|
|
||||||
}
|
|
||||||
|
|
||||||
orderNo, err := c.VoucherBiz.CmbOrder(ctx2, boReq)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return orderNo, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue