From 84f53cc4fca9d262f00e495dfec5cacdb929e1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=AD=90=E9=93=AD?= Date: Tue, 4 Mar 2025 14:19:55 +0800 Subject: [PATCH] cmb --- api/v1/cmb_cpn.proto | 143 +++++++++++++++++++++++++++++++ api/v1/order.proto | 79 ----------------- configs/config.yaml | 4 + internal/biz/bo/order_bo.go | 5 +- internal/biz/cmb.go | 28 +++++- internal/biz/cmb/cmb.go | 1 + internal/biz/repo/order.go | 2 +- internal/biz/vo/order_type.go | 26 ++++++ internal/biz/voucher.go | 3 + internal/data/model/order.gen.go | 9 +- internal/data/repoimpl/order.go | 2 +- internal/service/cmb.go | 9 +- 12 files changed, 219 insertions(+), 92 deletions(-) create mode 100644 api/v1/cmb_cpn.proto delete mode 100644 api/v1/order.proto create mode 100644 internal/biz/cmb/cmb.go create mode 100644 internal/biz/vo/order_type.go diff --git a/api/v1/cmb_cpn.proto b/api/v1/cmb_cpn.proto new file mode 100644 index 0000000..6ef6bfe --- /dev/null +++ b/api/v1/cmb_cpn.proto @@ -0,0 +1,143 @@ +syntax = "proto3"; + +package api.v1; +option go_package = "voucher/api/v1;v1"; + +import "validate/validate.proto"; + + +message CmbOrderRequest { + // 公共参数 + // 合作方唯一ID,32位定长 + string mid = 1 [json_name = "mid"]; + // 应用唯一ID,32位定长 + string aid = 2 [json_name = "aid"]; + // 时间戳 yyyyMMddHHmmss + string date = 3 [json_name = "date"]; + // 随机字符串,保证签名不可预测,不长于32位 + string random = 4 [json_name = "random"]; + // 合作方密钥对别名 + string keyAlias = 5 [json_name = "keyAlias"]; + // 掌上生活密钥对别名 + string cmbKeyAlias = 6 [json_name = "cmbKeyAlias"]; + // 加密报文,是否需要加密,请查看各API的说明文档 + string encryptBody = 7 [json_name = "encryptBody"]; + // 签名,具体详见签名规范 + string sign = 8 [json_name = "sign"]; + + // 业务参数 + // 唯一流水号,需支持14天内幂等 + string transactionId = 9 [json_name = "transactionId", (validate.rules).string = {min_len: 1,max_len: 50}]; + // 外部合作方权益批次号 + string activityId = 10 [json_name = "activityId", (validate.rules).string = {min_len: 1,max_len: 32}]; + // 招商银行用户号 用户标识,比如手机号、支付宝openId + string cmbUid = 11 [json_name = "cmbUid", (validate.rules).string = {min_len: 1,max_len: 100}]; + // 用户标识类型,0-手机号,1-支付宝openId + string cmbUidType = 12 [json_name = "cmbUidType", (validate.rules).string = {min_len: 1,max_len: 10}]; + // 时间戳,长度为13位,精度为毫秒 + string timestamp = 13 [json_name = "timestamp", (validate.rules).string = {min_len: 1,max_len: 20}]; +} +message CmbOrderReply { + // 公共参数 + // 接口调用返回码,1000 成功,1001 失败 + string respCode = 1 [json_name = "respCode"]; + // 返回话术,失败信息落此字段 + string respMsg = 2 [json_name = "respMsg"]; + // 时间戳 yyyyMMddHHmmss + string date = 3 [json_name = "date"]; + // 合作方密钥对别名 + string keyAlias = 5 [json_name = "keyAlias"]; + // 掌上生活密钥对别名 + string cmbKeyAlias = 6 [json_name = "cmbKeyAlias"]; + // 加密报文,是否需要加密,请查看各API的说明文档 + string encryptBody = 7 [json_name = "encryptBody"]; + // 签名,具体详见签名规范 + string sign = 8 [json_name = "sign"]; + + // 业务参数 + // 权益标识,优惠券券码,最大长度为50位 + string codeNo = 9 [json_name = "codeNo"]; +} + + +message CmbQueryProductRequest { + // 公共参数 + // 合作方唯一ID,32位定长 + string mid = 1 [json_name = "mid"]; + // 应用唯一ID,32位定长 + string aid = 2 [json_name = "aid"]; + // 时间戳 yyyyMMddHHmmss + string date = 3 [json_name = "date"]; + // 随机字符串,保证签名不可预测,不长于32位 + string random = 4 [json_name = "random"]; + // 合作方密钥对别名 + string keyAlias = 5 [json_name = "keyAlias"]; + // 掌上生活密钥对别名 + string cmbKeyAlias = 6 [json_name = "cmbKeyAlias"]; + // 加密报文,是否需要加密,请查看各API的说明文档 + string encryptBody = 7 [json_name = "encryptBody"]; + // 签名,具体详见签名规范 + string sign = 8 [json_name = "sign"]; + + // 业务参数 + // 外部合作方权益批次号 + string activityId = 9 [json_name = "activityId", (validate.rules).string = {min_len: 1,max_len: 32}]; +} +message CmbQueryProductReply { + // 公共参数 + // 接口调用返回码,1000 成功,1001 失败 + string respCode = 1 [json_name = "respCode"]; + // 返回话术,失败信息落此字段 + string respMsg = 2 [json_name = "respMsg"]; + // 时间戳 yyyyMMddHHmmss + string date = 3 [json_name = "date"]; + // 合作方密钥对别名 + string keyAlias = 5 [json_name = "keyAlias"]; + // 掌上生活密钥对别名 + string cmbKeyAlias = 6 [json_name = "cmbKeyAlias"]; + // 加密报文,是否需要加密,请查看各API的说明文档 + string encryptBody = 7 [json_name = "encryptBody"]; + // 签名,具体详见签名规范 + string sign = 8 [json_name = "sign"]; + + // 业务参数 + // 批次名称 + string activityName = 9 [json_name = "activityName"]; + // 外部合作方权益批次号 + string activityId = 10 [json_name = "activityId"]; + // 批次额度 单位为分 + string amount = 11 [json_name = "amount"]; + // 门槛,单位为分 + string minAmount = 12 [json_name = "minAmount"]; + // 有效期形式,0:固定有效期,1:动态有效期 + string availableType = 13 [json_name = "availableType"]; + // 动态有效期天数-非必填 格式yyyy-mm-dd hh:mm:ss.sss + string availableDays = 14 [json_name = "availableDays"]; + // 有效期开始时间-非必填 + string startTime = 15 [json_name = "startTime"]; + // 有效期结束时间-非必填 + string endTime = 16 [json_name = "endTime"]; + // 当前可用库存 + string availableStock = 17 [json_name = "availableStock"]; + // 细则描述 + string detail = 18 [json_name = "detail"]; +} + +message CmbNotifyRequest { + // 优惠券券码,codeNo + string ticket = 1 [json_name = "ticket"]; + // 更新后串码状态,0:可使用,1:已使用 + string status = 2 [json_name = "status"]; + // 验码日期,格式yyyy-mm-dd hh:mm:ss.sss + string transDate = 3 [json_name = "transDate"]; + // 发码机构号,固定值,掌上生活优惠券系统提供 + string orgNo = 4 [json_name = "orgNo"]; + // 扩展字段 + string ext = 5 [json_name = "ext"]; +} +message CmbNotifyReply { + // 接口调用返回码,1000 成功,1001 失败 + string respCode = 1 [json_name = "respCode"]; + // 返回信息,失败信息落此字段 + string respMsg = 2 [json_name = "respMsg"]; +} \ No newline at end of file diff --git a/api/v1/order.proto b/api/v1/order.proto deleted file mode 100644 index 50a4b7d..0000000 --- a/api/v1/order.proto +++ /dev/null @@ -1,79 +0,0 @@ -syntax = "proto3"; - -package api.v1; -option go_package = "voucher/api/v1;v1"; - -import "validate/validate.proto"; - - -message CmbOrderRequest { - // 唯一流水号,需支持14天内幂等 - string transactionId = 1 [json_name = "transactionId", (validate.rules).string = {min_len: 1,max_len: 50}]; - // 外部合作方权益批次号 - string activityId = 2 [json_name = "activityId", (validate.rules).string = {min_len: 1,max_len: 32}]; - // 招商银行用户号 用户标识,比如手机号、支付宝openId - string cmbUid = 3 [json_name = "cmbUid", (validate.rules).string = {min_len: 1,max_len: 100}]; - // 用户标识类型,0-手机号,1-支付宝openId - string cmbUidType = 4 [json_name = "cmbUidType", (validate.rules).string = {min_len: 1,max_len: 10}]; - // 时间戳,长度为13位,精度为毫秒 - string timestamp = 5 [json_name = "timestamp", (validate.rules).string = {min_len: 1,max_len: 20}]; -} -message CmbOrderReply { - // 接口调用返回码,1000 成功,1001 失败 - string respCode = 1 [json_name = "respCode"]; - // 返回信息,失败信息落此字段 - string respMsg = 2 [json_name = "respMsg"]; - // 权益标识,优惠券券码,最大长度为50位 - string codeNo = 3 [json_name = "codeNo"]; -} - - -message CmbQueryProductRequest { - // 外部合作方权益批次号 - string activityId = 2 [json_name = "activityId", (validate.rules).string = {min_len: 1,max_len: 32}]; -} -message CmbQueryProductReply { - // 接口调用返回码,1000 成功,1001 失败 - string respCode = 1 [json_name = "respCode"]; - // 返回信息,失败信息落此字段 - string respMsg = 2 [json_name = "respMsg"]; - // 批次名称 - string activityName = 3 [json_name = "activityName"]; - // 外部合作方权益批次号 - string activityId = 4 [json_name = "activityId"]; - // 批次额度 单位为分 - string amount = 5 [json_name = "amount"]; - // 门槛,单位为分 - string minAmount = 6 [json_name = "minAmount"]; - // 有效期形式,0:固定有效期,1:动态有效期 - string availableType = 7 [json_name = "availableType"]; - // 动态有效期天数-非必填 格式yyyy-mm-dd hh:mm:ss.sss - string availableDays = 8 [json_name = "availableDays"]; - // 有效期开始时间-非必填 - string startTime = 9 [json_name = "startTime"]; - // 有效期结束时间-非必填 - string endTime = 10 [json_name = "endTime"]; - // 当前可用库存 - string availableStock = 11 [json_name = "availableStock"]; - // 细则描述 - string detail = 12 [json_name = "detail"]; -} - -message CmbNotifyRequest { - // 优惠券券码,codeNo - string ticket = 1 [json_name = "ticket"]; - // 更新后串码状态,0:可使用,1:已使用 - string status = 2 [json_name = "status"]; - // 验码日期,格式yyyy-mm-dd hh:mm:ss.sss - string transDate = 3 [json_name = "transDate"]; - // 发码机构号,固定值,掌上生活优惠券系统提供 - string orgNo = 4 [json_name = "orgNo"]; - // 扩展字段 - string ext = 5 [json_name = "ext"]; -} -message CmbNotifyReply { - // 接口调用返回码,1000 成功,1001 失败 - string respCode = 1 [json_name = "respCode"]; - // 返回信息,失败信息落此字段 - string respMsg = 2 [json_name = "respMsg"]; -} \ No newline at end of file diff --git a/configs/config.yaml b/configs/config.yaml index cea7bf0..6812577 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -52,6 +52,10 @@ wechat: mchID: "1605446142" # 证书所属商户 mchCertificateSerialNumber: "4D081089DEB385316CBDCB55C070287E4920AC76" +cmb: + mid: "d6fdd78b6fd13a808818286b9cad9687" + aid: "5efaa21263b94f669a1c90ed0279df20" + #配置日志 logs: business: business.log #业务日志路径:如果不写日志,则不配置或配置为空 diff --git a/internal/biz/bo/order_bo.go b/internal/biz/bo/order_bo.go index 00f60a3..2a87d0b 100644 --- a/internal/biz/bo/order_bo.go +++ b/internal/biz/bo/order_bo.go @@ -12,6 +12,7 @@ type OrderBo struct { OutBizNo string ProductNo string Account string + Type vo.OrderType AccountType vo.OrderAccountType Status vo.OrderStatus AppID string @@ -22,14 +23,10 @@ type OrderBo struct { } type OrderCreateReqBo struct { - OrderNo string OutBizNo string ProductNo string Account string AccountType vo.OrderAccountType - Channel vo.Channel - AppID string - MerchantNo string } type OrderCreateRepBo struct { diff --git a/internal/biz/cmb.go b/internal/biz/cmb.go index fc526f7..43d4963 100644 --- a/internal/biz/cmb.go +++ b/internal/biz/cmb.go @@ -7,6 +7,7 @@ import ( "gorm.io/gorm" "time" "voucher/internal/biz/bo" + "voucher/internal/biz/vo" "voucher/internal/pkg/lock" "voucher/internal/pkg/uid" ) @@ -25,13 +26,36 @@ func (v *VoucherBiz) CmbOrder(ctx context.Context, req *bo.OrderCreateReqBo) (re return nil } + product, err := v.ProductRepo.GetByPNO(ctx, req.ProductNo) + if err != nil { + return err + } + orderNo, err := v.GenerateMixRepo.GeneratorString(ctx, uid.Order) if err != nil { return err } - req.OrderNo = orderNo - v.OrderRepo.Create(ctx, req) + o := &bo.OrderBo{ + OrderNo: orderNo, + + OutBizNo: req.OutBizNo, + ProductNo: req.ProductNo, + Account: req.Account, + + AppID: product.AppID, + MerchantNo: product.MerchantNo, + Channel: product.Channel, + + AccountType: vo.OrderAccountTypeOpenId, + Type: vo.OrderTypeCmb, + Status: vo.OrderStatusWait, + } + + order, err = v.OrderRepo.Create(ctx, o) + if err != nil { + return err + } return nil }) diff --git a/internal/biz/cmb/cmb.go b/internal/biz/cmb/cmb.go new file mode 100644 index 0000000..08cceb5 --- /dev/null +++ b/internal/biz/cmb/cmb.go @@ -0,0 +1 @@ +package cmb diff --git a/internal/biz/repo/order.go b/internal/biz/repo/order.go index ebe7c67..f608b65 100644 --- a/internal/biz/repo/order.go +++ b/internal/biz/repo/order.go @@ -7,7 +7,7 @@ import ( type OrderRepo interface { GetByOutBizNo(ctx context.Context, outBizNo string) (*bo.OrderBo, error) - Create(ctx context.Context, req *bo.OrderCreateReqBo) (*bo.OrderBo, error) + Create(ctx context.Context, req *bo.OrderBo) (*bo.OrderBo, error) GetByID(ctx context.Context, id uint64) (*bo.OrderBo, error) Ing(ctx context.Context, id uint64) error Success(ctx context.Context, id uint64) error diff --git a/internal/biz/vo/order_type.go b/internal/biz/vo/order_type.go new file mode 100644 index 0000000..a0664f6 --- /dev/null +++ b/internal/biz/vo/order_type.go @@ -0,0 +1,26 @@ +package vo + +type OrderType uint8 + +const ( + OrderTypeCmb OrderType = iota + 1 +) + +var OrderTypeMap = map[OrderType]string{ + OrderTypeCmb: "招行", +} + +func (s OrderType) GetText() string { + if t, ok := OrderTypeMap[s]; ok { + return t + } + return "" +} + +func (s OrderType) GetValue() uint8 { + return uint8(s) +} + +func (s OrderType) IsCmb() bool { + return s == OrderTypeCmb +} diff --git a/internal/biz/voucher.go b/internal/biz/voucher.go index dc12cc0..eea847f 100644 --- a/internal/biz/voucher.go +++ b/internal/biz/voucher.go @@ -11,6 +11,7 @@ import ( type VoucherBiz struct { rdb *data.Rdb OrderRepo repo.OrderRepo + ProductRepo repo.ProductRepo ThirdMQSend thirdrepo.ThirdMQSend WechatCpnRepo wechatrepo.WechatCpnRepo GenerateMixRepo mixrepos.GenerateMixRepo @@ -19,6 +20,7 @@ type VoucherBiz struct { func NewVoucherBiz( rdb *data.Rdb, orderRepo repo.OrderRepo, + ProductRepo repo.ProductRepo, thirdMQSend thirdrepo.ThirdMQSend, WechatCpnRepo wechatrepo.WechatCpnRepo, GenerateMixRepo mixrepos.GenerateMixRepo, @@ -26,6 +28,7 @@ func NewVoucherBiz( return &VoucherBiz{ rdb: rdb, OrderRepo: orderRepo, + ProductRepo: ProductRepo, ThirdMQSend: thirdMQSend, WechatCpnRepo: WechatCpnRepo, GenerateMixRepo: GenerateMixRepo, diff --git a/internal/data/model/order.gen.go b/internal/data/model/order.gen.go index aabd36b..58c7dde 100644 --- a/internal/data/model/order.gen.go +++ b/internal/data/model/order.gen.go @@ -18,10 +18,11 @@ type Order struct { ProductNo string `gorm:"column:product_no;not null;comment:商品编号" json:"product_no"` // 商品编号 Account string `gorm:"column:account;not null;comment:充值账号" json:"account"` // 充值账号 AccountType uint8 `gorm:"column:account_type;not null;comment:1:oepnid/userid 2:手机号" json:"account_type"` // 1:oepnid/userid 2:手机号 - Status uint8 `gorm:"column:status;not null;comment:1:待发放 2:发放中 3:发放成功 4:发放失败" json:"status"` // 1:待发放 2:发放中 3:发放成功 4:发放失败 - AppID string `gorm:"column:app_id;not null;comment:批次所属应用" json:"app_id"` // 批次所属应用 - MerchantNo string `gorm:"column:merchant_no;not null;comment:创建批次号的商户号" json:"merchant_no"` // 创建批次号的商户号 - Channel uint8 `gorm:"column:channel;not null;comment:1:微信 2:支付宝" json:"channel"` // 1:微信 2:支付宝 + Type uint8 `gorm:"column:type;not null;comment:1:招行" json:"type"` + Status uint8 `gorm:"column:status;not null;comment:1:待发放 2:发放中 3:发放成功 4:发放失败" json:"status"` // 1:待发放 2:发放中 3:发放成功 4:发放失败 + AppID string `gorm:"column:app_id;not null;comment:批次所属应用" json:"app_id"` // 批次所属应用 + MerchantNo string `gorm:"column:merchant_no;not null;comment:创建批次号的商户号" json:"merchant_no"` // 创建批次号的商户号 + Channel uint8 `gorm:"column:channel;not null;comment:1:微信 2:支付宝" json:"channel"` // 1:微信 2:支付宝 CreateTime *time.Time `gorm:"column:create_time" json:"create_time"` UpdateTime *time.Time `gorm:"column:update_time" json:"update_time"` } diff --git a/internal/data/repoimpl/order.go b/internal/data/repoimpl/order.go index 47e4579..f886846 100644 --- a/internal/data/repoimpl/order.go +++ b/internal/data/repoimpl/order.go @@ -26,7 +26,7 @@ func (p *OrderRepoImpl) DB(ctx context.Context) *gorm.DB { return p.db.DB(ctx).Model(model.Order{}) } -func (p *OrderRepoImpl) Create(ctx context.Context, req *bo.OrderCreateReqBo) (*bo.OrderBo, error) { +func (p *OrderRepoImpl) Create(ctx context.Context, req *bo.OrderBo) (*bo.OrderBo, error) { now := time.Now() info := &model.Order{ diff --git a/internal/service/cmb.go b/internal/service/cmb.go index 1b41669..6b4ac9b 100644 --- a/internal/service/cmb.go +++ b/internal/service/cmb.go @@ -14,6 +14,10 @@ func (s *VoucherService) CmbOrder(ctx http.Context) error { return err } + if err := req.Validate(); err != nil { + return err + } + // todo 签名验证 boReq := &bo.OrderCreateReqBo{ @@ -21,7 +25,6 @@ func (s *VoucherService) CmbOrder(ctx http.Context) error { ProductNo: req.ActivityId, Account: req.CmbUid, AccountType: vo.OrderAccountTypeOpenId, - Channel: vo.OrderChannelWechat, } boRep, err := s.VoucherBiz.CmbOrder(ctx, boReq) @@ -45,6 +48,10 @@ func (s *VoucherService) CmbProductQuery(ctx http.Context) error { return err } + if err := req.Validate(); err != nil { + return err + } + rep := &v1.CmbQueryProductReply{} return ctx.JSON(200, rep)