diff --git a/internal/biz/alarm.go b/internal/biz/alarm.go
new file mode 100644
index 0000000..09945e2
--- /dev/null
+++ b/internal/biz/alarm.go
@@ -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 := "# " +
+ "立减金发放平台报警通知
" +
+ " \n" +
+ "" +
+ "不好了,订单发放发生异常了" +
+ "[%s]请尽快处理@相关人员。" +
+ ""
+
+ return fmt.Sprintf(msg, remarks)
+}
diff --git a/internal/biz/cmb.go b/internal/biz/cmb.go
index c394d85..26ded5e 100644
--- a/internal/biz/cmb.go
+++ b/internal/biz/cmb.go
@@ -1,162 +1 @@
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
-}
diff --git a/internal/biz/kx/bb_to_wechat.go b/internal/biz/kx/bb_to_wechat.go
new file mode 100644
index 0000000..49fb478
--- /dev/null
+++ b/internal/biz/kx/bb_to_wechat.go
@@ -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,
+ }
+}
diff --git a/internal/biz/kx/cmb_to_bb.go b/internal/biz/kx/cmb_to_bb.go
new file mode 100644
index 0000000..061db53
--- /dev/null
+++ b/internal/biz/kx/cmb_to_bb.go
@@ -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,
+ }
+}
diff --git a/internal/biz/kx/kx.go b/internal/biz/kx/kx.go
new file mode 100644
index 0000000..6f73a5c
--- /dev/null
+++ b/internal/biz/kx/kx.go
@@ -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
+}
diff --git a/internal/biz/kx/kx_notice.go b/internal/biz/kx/kx_notice.go
new file mode 100644
index 0000000..f64c644
--- /dev/null
+++ b/internal/biz/kx/kx_notice.go
@@ -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)
+}
diff --git a/internal/biz/kx/wx_to_bb.go b/internal/biz/kx/wx_to_bb.go
new file mode 100644
index 0000000..38473bd
--- /dev/null
+++ b/internal/biz/kx/wx_to_bb.go
@@ -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,
+ }
+}
diff --git a/internal/biz/mixrepos/kx.go b/internal/biz/mixrepos/kx.go
new file mode 100644
index 0000000..d3d17dc
--- /dev/null
+++ b/internal/biz/mixrepos/kx.go
@@ -0,0 +1,10 @@
+package mixrepos
+
+import (
+ "context"
+ "voucher/internal/biz/kx"
+)
+
+type KxMixRepo interface {
+ Request(ctx context.Context, req *kx.SynNotice) error
+}
diff --git a/internal/biz/notify_tag.go b/internal/biz/notify_tag.go
new file mode 100644
index 0000000..efe69c9
--- /dev/null
+++ b/internal/biz/notify_tag.go
@@ -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
+}
diff --git a/internal/biz/order.go b/internal/biz/order.go
index 8cccf2c..d3e3989 100644
--- a/internal/biz/order.go
+++ b/internal/biz/order.go
@@ -2,22 +2,97 @@ package biz
import (
"context"
+ "encoding/json"
"fmt"
+ "github.com/go-kratos/kratos/v2/errors"
"github.com/go-kratos/kratos/v2/log"
- "github.com/redis/go-redis/v9"
err2 "voucher/api/err"
+ v1 "voucher/api/v1"
"voucher/internal/biz/bo"
"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)
if err != nil {
return nil, err
}
+ // 通知kx
+
// 注册通知标签 order.MerchantNo 批次创建商户, order.BatchNo 商品批次号
if err = v.registerNotifyTag(ctx, order.MerchantNo, order.BatchNo); err != nil {
return nil, err
@@ -70,91 +145,12 @@ func (v *VoucherBiz) create(ctx context.Context, req *bo.OrderCreateReqBo, produ
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 {
return v.OrderRepo.Ing(ctx, id)
}
func (v *VoucherBiz) success(ctx context.Context, order *bo.OrderBo, voucherNo string) error {
+ order.VoucherNo = 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())
}
-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小时 内 指定的批次号 发放 发生错误 预警
- c := vo.OrderConsumeFailAlarmKey.BuildCache([]string{order.ProductNo})
-
- _, err := v.rdb.Rdb.Get(ctx, c.Key).Result()
-
- if err == nil {
- // 缓存存在,直接返回
- return nil
+ bizReply := &v1.CmbOrderReply{
+ RespCode: vo.CmbResponseStatusSuccess.GetValue(),
+ RespMsg: "成功",
+ CodeNo: orderNo,
}
- if err != redis.Nil {
- return fmt.Errorf(fmt.Sprintf("alarm 获取redis缓存%s异常:%v", c.Key, err))
- }
+ replyBizContent, _ := json.Marshal(bizReply)
- 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
- })
+ return c.GetResponse(ctx, replyBizContent)
}
-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 := "# " +
- "立减金发放平台报警通知
" +
- " \n" +
- "" +
- "不好了,订单发放发生异常了" +
- "[%s]请尽快处理@相关人员。" +
- ""
+ if len(se.Reason) == 0 {
+ se.Reason = err2.CmbErr_CMB_UNKNOWN.String()
+ }
- return fmt.Sprintf(msg, remarks)
-}
-
-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
+ 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)
}
diff --git a/internal/biz/product.go b/internal/biz/product.go
new file mode 100644
index 0000000..9d62b66
--- /dev/null
+++ b/internal/biz/product.go
@@ -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
+}
diff --git a/internal/biz/query.go b/internal/biz/query.go
new file mode 100644
index 0000000..dd0463f
--- /dev/null
+++ b/internal/biz/query.go
@@ -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
+}
diff --git a/internal/biz/voucher.go b/internal/biz/voucher.go
index de5de99..be8443a 100644
--- a/internal/biz/voucher.go
+++ b/internal/biz/voucher.go
@@ -1,9 +1,14 @@
package biz
import (
+ "context"
+ "github.com/go-kratos/kratos/v2/log"
+ v1 "voucher/api/v1"
+ "voucher/internal/biz/bo"
"voucher/internal/biz/cmb"
"voucher/internal/biz/mixrepos"
"voucher/internal/biz/repo"
+ "voucher/internal/biz/vo"
"voucher/internal/biz/wechatrepo"
"voucher/internal/conf"
"voucher/internal/data"
@@ -22,6 +27,7 @@ type VoucherBiz struct {
WechatCpnRepo wechatrepo.WechatCpnRepo
DingMixRepo mixrepos.DingMixRepo
CmbMixRepo mixrepos.CmbMixRepo
+ KxMixRepo mixrepos.KxMixRepo
}
func NewVoucherBiz(
@@ -37,6 +43,7 @@ func NewVoucherBiz(
WechatCpnRepo wechatrepo.WechatCpnRepo,
DingMixRepo mixrepos.DingMixRepo,
CmbMixRepo mixrepos.CmbMixRepo,
+ KxMixRepo mixrepos.KxMixRepo,
) *VoucherBiz {
return &VoucherBiz{
bc: bc,
@@ -51,5 +58,23 @@ func NewVoucherBiz(
WechatCpnRepo: WechatCpnRepo,
DingMixRepo: DingMixRepo,
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
+}
diff --git a/internal/data/mixrepoimpl/kx.go b/internal/data/mixrepoimpl/kx.go
new file mode 100644
index 0000000..141e58d
--- /dev/null
+++ b/internal/data/mixrepoimpl/kx.go
@@ -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
+}
diff --git a/internal/data/mixrepoimpl/provider_set.go b/internal/data/mixrepoimpl/provider_set.go
index 9a3ba66..a74a74c 100644
--- a/internal/data/mixrepoimpl/provider_set.go
+++ b/internal/data/mixrepoimpl/provider_set.go
@@ -10,4 +10,5 @@ var ProviderMixRepoImplSet = wire.NewSet(
NewMQSendMixRepoImpl,
NewCmbMixRepoImpl,
NewDingMixRepoImpl,
+ NewKxMixRepoImpl,
)
diff --git a/internal/service/cmb_order.go b/internal/service/cmb_order.go
index 3351c1f..33bc3a0 100644
--- a/internal/service/cmb_order.go
+++ b/internal/service/cmb_order.go
@@ -2,85 +2,10 @@ package service
import (
"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"
- "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) {
- orderNo, err := c.order(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
+ return c.VoucherBiz.CmbOrder(ctx, request)
}