多笔立减金,错误映射
This commit is contained in:
parent
9bd8646e57
commit
bbfc91da57
|
|
@ -209,6 +209,8 @@ message CmbNotifyRequest {
|
|||
// 扩展字段
|
||||
string ext = 13 [json_name = "ext"];
|
||||
string attach = 14 [json_name = "attach"];
|
||||
// 招行唯一流水号
|
||||
string transactionId = 15 [json_name = "transactionId"];
|
||||
}
|
||||
|
||||
message CmbNotifyReply {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
package businesserr
|
||||
|
||||
// ThirdErrCode 表示发券接口失败时返回的第三方业务错误码
|
||||
// 格式为 tecxxx,用于统一抽象不同支付平台的错误
|
||||
type ThirdErrCode string
|
||||
|
||||
// 定义具体的第三方错误码常量(微信/支付宝的都有)
|
||||
const (
|
||||
ThirdErrCodeDefault ThirdErrCode = "tec999" // 默认错误码
|
||||
|
||||
ThirdErrCodeStockNotEnough ThirdErrCode = "tec001" // 库存不足
|
||||
ThirdErrCodeAdvanceFundingNotEnough ThirdErrCode = "tec002" // 垫资不足
|
||||
ThirdErrCodeBatchNotStarted ThirdErrCode = "tec003" // 批次未开始
|
||||
ThirdErrCodeBatchEnded ThirdErrCode = "tec004" // 批次已结束
|
||||
ThirdErrCodeBatchOffline ThirdErrCode = "tec005" // 批次已下线
|
||||
ThirdErrCodePaymentPlatformError ThirdErrCode = "tec006" // 微信/支付宝异常
|
||||
|
||||
ThirdErrCodeUserNotRealNameVerified ThirdErrCode = "tec007" // 用户没有实名认证
|
||||
ThirdErrCodeUserNotFound ThirdErrCode = "tec008" // 用户账号不存在
|
||||
ThirdErrCodeUserAccountFrozen ThirdErrCode = "tec009" // 用户账户被冻结
|
||||
ThirdErrCodeUserHighRiskOrCheater ThirdErrCode = "tec010" // 用户为作弊用户或高风险用户
|
||||
ThirdErrCodeUserNoMobileBound ThirdErrCode = "tec011" // 用户没有绑定手机号
|
||||
ThirdErrCodeUserAccountAbnormal ThirdErrCode = "tec012" // 用户账号异常(没有明确的原因)
|
||||
ThirdErrCodeUserParticipationExceeded ThirdErrCode = "tec013" // 用户参与次数超限
|
||||
ThirdErrCodeAppIDOpenIDMismatch ThirdErrCode = "tec014" // appid与openid不匹配
|
||||
|
||||
ThirdErrCodeDailyLimit ThirdErrCode = "tec015" // 超批次当天限额
|
||||
ThirdErrCodeCallHigh ThirdErrCode = "tec016" // 调用频率过高
|
||||
)
|
||||
|
||||
// CmbAPIError 定义 API 错误结构体
|
||||
type CmbAPIError struct {
|
||||
StatusCode int `json:"status_code"`
|
||||
ErrorCode ErrCode `json:"error_code"`
|
||||
Description string `json:"description"`
|
||||
Hint string `json:"hint"` // 解决方案
|
||||
ThirdErrCode ThirdErrCode `json:"third_err_code"`
|
||||
}
|
||||
|
||||
var CmbAPIPublicErrorMap = map[ErrCode][]CmbAPIError{
|
||||
|
||||
BATCH_NOT_STARTED: {
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: BATCH_NOT_STARTED,
|
||||
Description: "批次未开始",
|
||||
Hint: "批次未开始",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
BATCH_ENDED: {
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: BATCH_ENDED,
|
||||
Description: "批次已结束",
|
||||
Hint: "批次已结束",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -1,19 +1,18 @@
|
|||
package wechatrepoimpl
|
||||
package businesserr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
"strings"
|
||||
err2 "voucher/api/err"
|
||||
)
|
||||
|
||||
// ErrCode 定义错误码类型
|
||||
// https://pay.weixin.qq.com/doc/v3/merchant/4012463767
|
||||
type ErrCode string
|
||||
|
||||
// 定义错误码常量
|
||||
// https://pay.weixin.qq.com/doc/v3/merchant/4012463767
|
||||
const (
|
||||
SYSTEM_ERROR ErrCode = "SYSTEM_ERROR"
|
||||
SIGN_ERROR ErrCode = "SIGN_ERROR"
|
||||
|
||||
APPID_MCHID_NOT_MATCH ErrCode = "APPID_MCHID_NOT_MATCH"
|
||||
INVALID_REQUEST ErrCode = "INVALID_REQUEST"
|
||||
PARAM_ERROR ErrCode = "PARAM_ERROR"
|
||||
|
|
@ -26,196 +25,255 @@ const (
|
|||
FREQUENCY_LIMITED ErrCode = "FREQUENCY_LIMITED"
|
||||
)
|
||||
|
||||
// APIError 定义 API 错误结构体
|
||||
type APIError struct {
|
||||
StatusCode int `json:"status_code"`
|
||||
ErrorCode ErrCode `json:"error_code"`
|
||||
Description string `json:"description"`
|
||||
Hint string `json:"hint"`
|
||||
}
|
||||
const (
|
||||
BATCH_NOT_STARTED ErrCode = "BATCH_NOT_STARTED"
|
||||
BATCH_ENDED ErrCode = "BATCH_ENDED"
|
||||
)
|
||||
|
||||
// CmbAPIErrorMap 定义错误映射,方便根据错误码获取错误信息
|
||||
var CmbAPIErrorMap = map[ErrCode][]CmbAPIError{
|
||||
|
||||
SYSTEM_ERROR: {
|
||||
{
|
||||
StatusCode: 500,
|
||||
ErrorCode: SYSTEM_ERROR,
|
||||
Description: "系统异常,请稍后重试",
|
||||
Hint: "请稍后重试",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
SIGN_ERROR: {
|
||||
{
|
||||
StatusCode: 401,
|
||||
ErrorCode: SIGN_ERROR,
|
||||
Description: "验证不通过",
|
||||
Hint: "请参阅 签名常见问题",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
|
||||
// 定义错误映射,方便根据错误码获取错误信息
|
||||
var _ = map[ErrCode][]APIError{
|
||||
APPID_MCHID_NOT_MATCH: {
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: APPID_MCHID_NOT_MATCH,
|
||||
Description: "商户号与AppID不匹配",
|
||||
Hint: "调用接口的商户号需与接口传入的AppID有绑定关系,请参考常见问题Q4",
|
||||
StatusCode: 400,
|
||||
ErrorCode: APPID_MCHID_NOT_MATCH,
|
||||
Description: "商户号与AppID不匹配",
|
||||
Hint: "调用接口的商户号需与接口传入的AppID有绑定关系,请参考常见问题Q4",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
INVALID_REQUEST: {
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: INVALID_REQUEST,
|
||||
Description: "OpenID与AppID不匹配",
|
||||
Hint: "OpenID与AppID需有对应关系",
|
||||
StatusCode: 400,
|
||||
ErrorCode: INVALID_REQUEST,
|
||||
Description: "HTTP 请求不符合微信支付 APIv3 接口规则",
|
||||
Hint: "请参阅 接口规则",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: INVALID_REQUEST,
|
||||
Description: "非法的商户号",
|
||||
Hint: "请检查商户号准确性",
|
||||
StatusCode: 400,
|
||||
ErrorCode: INVALID_REQUEST,
|
||||
Description: "OpenID与AppID不匹配",
|
||||
Hint: "OpenID与AppID需有对应关系",
|
||||
ThirdErrCode: ThirdErrCodeAppIDOpenIDMismatch,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: INVALID_REQUEST,
|
||||
Description: "调用频率过高",
|
||||
Hint: "请降低API调用频率",
|
||||
StatusCode: 400,
|
||||
ErrorCode: INVALID_REQUEST,
|
||||
Description: "非法的商户号",
|
||||
Hint: "请检查商户号准确性",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: INVALID_REQUEST,
|
||||
Description: "活动已结束或未激活",
|
||||
Hint: "请检查批次状态",
|
||||
StatusCode: 400,
|
||||
ErrorCode: INVALID_REQUEST,
|
||||
Description: "调用频率过高",
|
||||
Hint: "请降低API调用频率",
|
||||
ThirdErrCode: ThirdErrCodeCallHigh,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: INVALID_REQUEST,
|
||||
Description: "批次信息获取失败,请确认参数是否有误",
|
||||
Hint: "请检查创建商户号与批次号的对应关系",
|
||||
StatusCode: 400,
|
||||
ErrorCode: INVALID_REQUEST,
|
||||
Description: "活动已结束或未激活",
|
||||
Hint: "请检查批次状态",
|
||||
ThirdErrCode: ThirdErrCodeBatchEnded, //前置判断时间处理一下
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: INVALID_REQUEST,
|
||||
Description: "批次信息获取失败,请确认参数是否有误",
|
||||
Hint: "请检查创建商户号与批次号的对应关系",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
PARAM_ERROR: {
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: PARAM_ERROR,
|
||||
Description: "AppID必填",
|
||||
Hint: "请输入AppID",
|
||||
StatusCode: 400,
|
||||
ErrorCode: PARAM_ERROR,
|
||||
Description: "参数错误",
|
||||
Hint: "请根据错误提示正确传入参数",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: PARAM_ERROR,
|
||||
Description: "AppID必填",
|
||||
Hint: "请输入AppID",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: PARAM_ERROR,
|
||||
Description: "OpenID必填",
|
||||
Hint: "请输入OpenID",
|
||||
StatusCode: 400,
|
||||
ErrorCode: PARAM_ERROR,
|
||||
Description: "OpenID必填",
|
||||
Hint: "请输入OpenID",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: PARAM_ERROR,
|
||||
Description: "批次号必填",
|
||||
Hint: "请输入批次号",
|
||||
StatusCode: 400,
|
||||
ErrorCode: PARAM_ERROR,
|
||||
Description: "批次号必填",
|
||||
Hint: "请输入批次号",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: PARAM_ERROR,
|
||||
Description: "商户号必填",
|
||||
Hint: "请输入商户号",
|
||||
StatusCode: 400,
|
||||
ErrorCode: PARAM_ERROR,
|
||||
Description: "商户号必填",
|
||||
Hint: "请输入商户号",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: PARAM_ERROR,
|
||||
Description: "非法的批次状态",
|
||||
Hint: "请检查批次状态,仅支持发放状态为“运营中”的代金券批次",
|
||||
StatusCode: 400,
|
||||
ErrorCode: PARAM_ERROR,
|
||||
Description: "非法的批次状态",
|
||||
Hint: "请检查批次状态,仅支持发放状态为“运营中”的代金券批次",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
MCH_NOT_EXISTS: {
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: MCH_NOT_EXISTS,
|
||||
Description: "商户号不合法",
|
||||
Hint: "请检查商户号准确性",
|
||||
StatusCode: 403,
|
||||
ErrorCode: MCH_NOT_EXISTS,
|
||||
Description: "商户号不合法",
|
||||
Hint: "请检查商户号准确性",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
NOT_ENOUGH: {
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: NOT_ENOUGH,
|
||||
Description: "批次预算不足",
|
||||
Hint: "批次预算已发放完,请补充批次预算",
|
||||
StatusCode: 403,
|
||||
ErrorCode: NOT_ENOUGH,
|
||||
Description: "批次预算不足",
|
||||
Hint: "批次预算已发放完,请补充批次预算",
|
||||
ThirdErrCode: ThirdErrCodeAdvanceFundingNotEnough,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: NOT_ENOUGH,
|
||||
Description: "发券超过单天限额",
|
||||
Hint: "已超过该批次设置的单天发放限制额度,无法发放",
|
||||
StatusCode: 403,
|
||||
ErrorCode: NOT_ENOUGH,
|
||||
Description: "发券超过单天限额",
|
||||
Hint: "已超过该批次设置的单天发放限制额度,无法发放",
|
||||
ThirdErrCode: ThirdErrCodeDailyLimit,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: NOT_ENOUGH,
|
||||
Description: "账户余额不足,请充值",
|
||||
Hint: "商户号余额不足,无法继续发券,请充值",
|
||||
StatusCode: 403,
|
||||
ErrorCode: NOT_ENOUGH,
|
||||
Description: "账户余额不足,请充值",
|
||||
Hint: "商户号余额不足,无法继续发券,请充值",
|
||||
ThirdErrCode: ThirdErrCodeAdvanceFundingNotEnough,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: NOT_ENOUGH,
|
||||
Description: "批次预算耗尽",
|
||||
Hint: "该批次的预算已经耗尽",
|
||||
StatusCode: 403,
|
||||
ErrorCode: NOT_ENOUGH,
|
||||
Description: "批次预算耗尽",
|
||||
Hint: "该批次的预算已经耗尽",
|
||||
ThirdErrCode: ThirdErrCodeAdvanceFundingNotEnough,
|
||||
},
|
||||
},
|
||||
REQUEST_BLOCKED: {
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: REQUEST_BLOCKED,
|
||||
Description: "商户无权发券",
|
||||
Hint: "该批次不支持其他商户发放,请参考常见问题Q1",
|
||||
StatusCode: 403,
|
||||
ErrorCode: REQUEST_BLOCKED,
|
||||
Description: "商户无权发券",
|
||||
Hint: "该批次不支持其他商户发放,请参考常见问题Q1",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: REQUEST_BLOCKED,
|
||||
Description: "批次不支持跨商户发券",
|
||||
Hint: "该批次不支持其他商户发放,请参考常见问题Q1",
|
||||
StatusCode: 403,
|
||||
ErrorCode: REQUEST_BLOCKED,
|
||||
Description: "批次不支持跨商户发券",
|
||||
Hint: "该批次不支持其他商户发放,请参考常见问题Q1",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: REQUEST_BLOCKED,
|
||||
Description: "用户被限领拦截",
|
||||
Hint: "该用户已达到该批次的领取上限,请参考常见问题Q6",
|
||||
StatusCode: 403,
|
||||
ErrorCode: REQUEST_BLOCKED,
|
||||
Description: "用户被限领拦截",
|
||||
Hint: "该用户已达到该批次的领取上限,请参考常见问题Q6",
|
||||
ThirdErrCode: ThirdErrCodeUserParticipationExceeded,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: REQUEST_BLOCKED,
|
||||
Description: "不能在API渠道发放",
|
||||
Hint: "请检查批次信息,仅支持发放微信支付代金券,不支持发放立减与折扣",
|
||||
StatusCode: 403,
|
||||
ErrorCode: REQUEST_BLOCKED,
|
||||
Description: "不能在API渠道发放",
|
||||
Hint: "请检查批次信息,仅支持发放微信支付代金券,不支持发放立减与折扣",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: REQUEST_BLOCKED,
|
||||
Description: "不支持指定面额发券",
|
||||
Hint: "仅在发券时指定面额及门槛的场景才生效,常规发券场景请勿传入该信息",
|
||||
StatusCode: 403,
|
||||
ErrorCode: REQUEST_BLOCKED,
|
||||
Description: "不支持指定面额发券",
|
||||
Hint: "仅在发券时指定面额及门槛的场景才生效,常规发券场景请勿传入该信息",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: REQUEST_BLOCKED,
|
||||
Description: "仅在广告场景下发放批次",
|
||||
Hint: "该批次已在朋友圈广告发放,不支持在其他渠道发放",
|
||||
StatusCode: 403,
|
||||
ErrorCode: REQUEST_BLOCKED,
|
||||
Description: "仅在广告场景下发放批次",
|
||||
Hint: "该批次已在朋友圈广告发放,不支持在其他渠道发放",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
RULE_LIMIT: {
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: RULE_LIMIT,
|
||||
Description: "用户已达最大领券次数",
|
||||
Hint: "该用户已达到该批次的领取上限,请参考常见问题Q6",
|
||||
StatusCode: 403,
|
||||
ErrorCode: RULE_LIMIT,
|
||||
Description: "用户已达最大领券次数",
|
||||
Hint: "该用户已达到该批次的领取上限,请参考常见问题Q6",
|
||||
ThirdErrCode: ThirdErrCodeUserParticipationExceeded,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: RULE_LIMIT,
|
||||
Description: "被自然人规则拦截",
|
||||
Hint: "该自然人已达到该批次的领取上限,请参考常见问题Q6",
|
||||
StatusCode: 403,
|
||||
ErrorCode: RULE_LIMIT,
|
||||
Description: "被自然人规则拦截",
|
||||
Hint: "该自然人已达到该批次的领取上限,请参考常见问题Q6",
|
||||
ThirdErrCode: ThirdErrCodeUserAccountAbnormal,
|
||||
},
|
||||
},
|
||||
USER_ACCOUNT_ABNORMAL: {
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: USER_ACCOUNT_ABNORMAL,
|
||||
Description: "用户非法",
|
||||
Hint: "用户命中微信支付风控模型,请参考常见问题Q5",
|
||||
StatusCode: 403,
|
||||
ErrorCode: USER_ACCOUNT_ABNORMAL,
|
||||
Description: "用户非法",
|
||||
Hint: "用户命中微信支付风控模型,请参考常见问题Q5",
|
||||
ThirdErrCode: ThirdErrCodeUserAccountFrozen,
|
||||
},
|
||||
},
|
||||
RESOURCE_NOT_EXISTS: {
|
||||
{
|
||||
StatusCode: 404,
|
||||
ErrorCode: RESOURCE_NOT_EXISTS,
|
||||
Description: "批次不存在",
|
||||
Hint: "请检查批次及制券商户号信息",
|
||||
StatusCode: 404,
|
||||
ErrorCode: RESOURCE_NOT_EXISTS,
|
||||
Description: "批次不存在",
|
||||
Hint: "请检查批次及制券商户号信息",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
FREQUENCY_LIMITED: {
|
||||
{
|
||||
StatusCode: 429,
|
||||
ErrorCode: FREQUENCY_LIMITED,
|
||||
Description: "当前请求人数过多,请稍后重试",
|
||||
Hint: "请降低API调用频率",
|
||||
StatusCode: 429,
|
||||
ErrorCode: FREQUENCY_LIMITED,
|
||||
Description: "当前请求人数过多,请稍后重试",
|
||||
Hint: "请降低API调用频率",
|
||||
ThirdErrCode: ThirdErrCodeCallHigh,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -284,13 +342,13 @@ var WechatError = map[string]*errors.Error{
|
|||
ErrorWechatAccountFail: err2.ErrorWechatAccountFail(ErrorWechatAccountFail),
|
||||
}
|
||||
|
||||
type ErrBody struct {
|
||||
type WechatErrBody struct {
|
||||
Code ErrCode `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
}
|
||||
|
||||
// GetWechatError 根据错误描述获取具体的错误处理
|
||||
func (s ErrBody) GetWechatError() *errors.Error {
|
||||
func (s WechatErrBody) GetWechatError() *errors.Error {
|
||||
lowerDesc := strings.ToLower(s.Message)
|
||||
for desc, err := range WechatError {
|
||||
if strings.ToLower(desc) == lowerDesc {
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
package businesserr
|
||||
|
||||
// 公共 & 业务错误码常量(统一定义,前缀 MULTI_)
|
||||
// https://pay.weixin.qq.com/doc/v3/merchant/4012463767
|
||||
const (
|
||||
MULTI_PARAM_ERROR ErrCode = "PARAM_ERROR" // 参数错误(如缺少必填字段)
|
||||
MULTI_INVALID_REQUEST ErrCode = "INVALID_REQUEST" // 非法请求(如 OpenID 与 AppID 不匹配、批次状态异常等)
|
||||
MULTI_SIGN_ERROR ErrCode = "SIGN_ERROR" // 签名验证失败
|
||||
MULTI_SYSTEM_ERROR ErrCode = "SYSTEM_ERROR" // 系统异常
|
||||
MULTI_APPID_MCHID_NOT_MATCH ErrCode = "APPID_MCHID_NOT_MATCH" // 商户号与 AppID 不匹配
|
||||
MULTI_MCH_NOT_EXISTS ErrCode = "MCH_NOT_EXISTS" // 商户号不合法
|
||||
MULTI_NOT_ENOUGH ErrCode = "NOT_ENOUGH" // 资源不足(预算/余额/限额耗尽)
|
||||
MULTI_REQUEST_BLOCKED ErrCode = "REQUEST_BLOCKED" // 请求被拦截(跨商户、渠道限制等)
|
||||
MULTI_RULE_LIMIT ErrCode = "RULE_LIMIT" // 用户或自然人达到领取上限
|
||||
MULTI_USER_ACCOUNT_ABNORMAL ErrCode = "USER_ACCOUNT_ABNORMAL" // 用户账号异常(风控、未实名等)
|
||||
MULTI_RESOURCE_NOT_EXISTS ErrCode = "RESOURCE_NOT_EXISTS" // 批次不存在
|
||||
)
|
||||
|
||||
// MULTIAPIErrorMap 定义错误映射,方便根据错误码获取所有可能的错误场景
|
||||
var CmbMULTIAPIErrorMap = map[ErrCode][]CmbAPIError{
|
||||
MULTI_SYSTEM_ERROR: {
|
||||
{
|
||||
StatusCode: 500,
|
||||
ErrorCode: MULTI_SYSTEM_ERROR,
|
||||
Description: "系统异常,请稍后重试",
|
||||
Hint: "请稍后重试",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
MULTI_SIGN_ERROR: {
|
||||
{
|
||||
StatusCode: 401,
|
||||
ErrorCode: MULTI_SIGN_ERROR,
|
||||
Description: "验证不通过",
|
||||
Hint: "请参阅 签名常见问题",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
MULTI_APPID_MCHID_NOT_MATCH: {
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: MULTI_APPID_MCHID_NOT_MATCH,
|
||||
Description: "商户号与AppID不匹配",
|
||||
Hint: "调用接口的商户号需与接口传入的AppID有绑定关系,请参考常见问题Q4",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
MULTI_INVALID_REQUEST: {
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: MULTI_INVALID_REQUEST,
|
||||
Description: "HTTP 请求不符合微信支付 APIv3 接口规则",
|
||||
Hint: "请参阅 接口规则",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: MULTI_INVALID_REQUEST,
|
||||
Description: "非法的商户号",
|
||||
Hint: "请检查商户号准确性",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: MULTI_INVALID_REQUEST,
|
||||
Description: "OpenID与AppID不匹配",
|
||||
Hint: "OpenID与AppID需有对应关系",
|
||||
ThirdErrCode: ThirdErrCodeAppIDOpenIDMismatch,
|
||||
},
|
||||
},
|
||||
MULTI_PARAM_ERROR: {
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: MULTI_PARAM_ERROR,
|
||||
Description: "参数错误",
|
||||
Hint: "请根据错误提示正确传入参数",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: MULTI_PARAM_ERROR,
|
||||
Description: "AppID必填",
|
||||
Hint: "请输入AppID",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: MULTI_PARAM_ERROR,
|
||||
Description: "OpenID必填",
|
||||
Hint: "请输入OpenID",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: MULTI_PARAM_ERROR,
|
||||
Description: "批次号必填",
|
||||
Hint: "请输入批次号",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 400,
|
||||
ErrorCode: MULTI_PARAM_ERROR,
|
||||
Description: "商户号必填",
|
||||
Hint: "请输入商户号",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
MULTI_MCH_NOT_EXISTS: {
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: MULTI_MCH_NOT_EXISTS,
|
||||
Description: "商户号不合法",
|
||||
Hint: "请检查商户号准确性",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
MULTI_NOT_ENOUGH: {
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: MULTI_NOT_ENOUGH,
|
||||
Description: "批次预算耗尽",
|
||||
Hint: "该批次的预算已经耗尽",
|
||||
ThirdErrCode: ThirdErrCodeAdvanceFundingNotEnough,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: MULTI_NOT_ENOUGH,
|
||||
Description: "账户余额不足,请充值",
|
||||
Hint: "商户号余额不足,无法继续发券,请充值",
|
||||
ThirdErrCode: ThirdErrCodeAdvanceFundingNotEnough,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: MULTI_NOT_ENOUGH,
|
||||
Description: "发券超过单天限额",
|
||||
Hint: "已超过该批次设置的单天发放限制额度,无法发放",
|
||||
ThirdErrCode: ThirdErrCodeDailyLimit,
|
||||
},
|
||||
},
|
||||
MULTI_REQUEST_BLOCKED: {
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: MULTI_REQUEST_BLOCKED,
|
||||
Description: "参数错误,请检查批次参数",
|
||||
Hint: "活动未开始或已结束",
|
||||
ThirdErrCode: ThirdErrCodeBatchNotStarted, // 时间前置判断一下
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: MULTI_REQUEST_BLOCKED,
|
||||
Description: "仅在广告场景下发放批次",
|
||||
Hint: "该批次已在朋友圈广告发放,不支持在其他渠道发放",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: MULTI_REQUEST_BLOCKED,
|
||||
Description: "商户号收款功能受限,无法发券",
|
||||
Hint: "商户号收款功能受限",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: MULTI_REQUEST_BLOCKED,
|
||||
Description: "批次不支持跨商户发券",
|
||||
Hint: "该批次不支持其他商户发放",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
MULTI_RULE_LIMIT: {
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: MULTI_RULE_LIMIT,
|
||||
Description: "用户已达最大领券次数",
|
||||
Hint: "该用户已达到该批次的领取上限",
|
||||
ThirdErrCode: ThirdErrCodeUserParticipationExceeded,
|
||||
},
|
||||
},
|
||||
MULTI_USER_ACCOUNT_ABNORMAL: {
|
||||
{
|
||||
StatusCode: 403,
|
||||
ErrorCode: MULTI_USER_ACCOUNT_ABNORMAL,
|
||||
Description: "用户未实名",
|
||||
Hint: "该用户无实名信息,无法领券。商家可联系微信支付或让用户联系微信支付客服处理。",
|
||||
ThirdErrCode: ThirdErrCodeUserNotRealNameVerified,
|
||||
},
|
||||
},
|
||||
MULTI_RESOURCE_NOT_EXISTS: {
|
||||
{
|
||||
StatusCode: 404,
|
||||
ErrorCode: MULTI_RESOURCE_NOT_EXISTS,
|
||||
Description: "批次不存在",
|
||||
Hint: "请检查批次及制券商户号信息",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package businesserr
|
||||
|
||||
// ErrCode 定义错误码类型
|
||||
type ErrCode string
|
||||
|
||||
type BusinessErr struct {
|
||||
Code ErrCode `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (e *BusinessErr) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
var (
|
||||
BatchNotStartedError = &BusinessErr{Code: ErrCode("400"), Message: "批次未开始"}
|
||||
BatchEndedError = &BusinessErr{Code: ErrCode("400"), Message: "批次已结束"}
|
||||
)
|
||||
|
||||
func (err *BusinessErr) HandleThirdErrCode() CmbAPIError {
|
||||
|
||||
errCode := err.Code
|
||||
|
||||
emp, ok := CmbAPIPublicErrorMap[errCode]
|
||||
if ok {
|
||||
for _, e := range emp {
|
||||
if e.Description == err.Message {
|
||||
return e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
em, ok := CmbAPIErrorMap[errCode]
|
||||
if ok {
|
||||
for _, e := range em {
|
||||
if e.Description == err.Message {
|
||||
return e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CmbAPIError{
|
||||
StatusCode: 499,
|
||||
ErrorCode: errCode,
|
||||
Description: err.Message,
|
||||
Hint: "",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
}
|
||||
}
|
||||
|
||||
func (err *BusinessErr) HandleMULTIThirdErrCode() CmbAPIError {
|
||||
|
||||
errCode := err.Code
|
||||
|
||||
emp, ok := CmbAPIPublicErrorMap[errCode]
|
||||
if ok {
|
||||
for _, e := range emp {
|
||||
if e.Description == err.Message {
|
||||
return e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
em2, ok := CmbMULTIAPIErrorMap[errCode]
|
||||
if ok {
|
||||
for _, e := range em2 {
|
||||
if e.Description == err.Message {
|
||||
return e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CmbAPIError{
|
||||
StatusCode: 499,
|
||||
ErrorCode: errCode,
|
||||
Description: err.Message,
|
||||
Hint: "",
|
||||
ThirdErrCode: ThirdErrCodeDefault,
|
||||
}
|
||||
}
|
||||
|
|
@ -63,12 +63,13 @@ func (v *Cmb) bizContent(_ context.Context, order *bo.OrderBo, orderNotify *bo.O
|
|||
}
|
||||
|
||||
req := &v1.CmbNotifyRequest{
|
||||
Ticket: orderNotify.OrderNo,
|
||||
Status: cmbStatus.GetValue(),
|
||||
TransDate: "", // 格式yyyy-mm-dd hh:mm:ss.sss
|
||||
OrgNo: v.bc.Cmb.OrgNo,
|
||||
Attach: order.Attach,
|
||||
Ext: "",
|
||||
Ticket: orderNotify.OrderNo,
|
||||
Status: cmbStatus.GetValue(),
|
||||
TransDate: "", // 格式yyyy-mm-dd hh:mm:ss.sss
|
||||
OrgNo: v.bc.Cmb.OrgNo,
|
||||
Attach: order.Attach,
|
||||
Ext: "",
|
||||
TransactionId: order.OutBizNo, // 招行订单号
|
||||
}
|
||||
|
||||
if cmbStatus == vo.CmbStatusUse {
|
||||
|
|
|
|||
|
|
@ -3,16 +3,18 @@ package biz
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
err2 "voucher/api/err"
|
||||
"voucher/internal/biz/bo"
|
||||
"voucher/internal/biz/businesserr"
|
||||
"voucher/internal/biz/vo"
|
||||
)
|
||||
|
||||
func (this *VoucherBiz) CmbOrder(ctx context.Context, req *bo.OrderCreateReqBo) (orderNo string, err error) {
|
||||
func (this *VoucherBiz) CmbOrder(ctx context.Context, req *bo.OrderCreateReqBo) (*bo.OrderBo, error) {
|
||||
|
||||
order, err3 := this.GetByOutBizNo(ctx, req)
|
||||
if err3 != nil {
|
||||
return "", err3
|
||||
return nil, err3
|
||||
}
|
||||
|
||||
if order != nil {
|
||||
|
|
@ -20,24 +22,32 @@ func (this *VoucherBiz) CmbOrder(ctx context.Context, req *bo.OrderCreateReqBo)
|
|||
if order.Status.IsFail() || order.Status.IsIng() {
|
||||
|
||||
if err4 := this.orderRetry(ctx, order); err4 != nil {
|
||||
return "", err4
|
||||
return nil, err4
|
||||
}
|
||||
}
|
||||
|
||||
return order.OrderNo, err
|
||||
return order, nil
|
||||
}
|
||||
|
||||
product, err3 := this.ProductRepo.GetByProductNo(ctx, req.ProductNo)
|
||||
if err3 != nil {
|
||||
return "", err3
|
||||
return nil, err3
|
||||
}
|
||||
|
||||
nowTime := time.Now()
|
||||
if nowTime.Before(*product.StartTime) {
|
||||
return nil, businesserr.BatchNotStartedError
|
||||
}
|
||||
if nowTime.After(*product.EndTime) {
|
||||
return nil, businesserr.BatchEndedError
|
||||
}
|
||||
|
||||
order, err3 = this.order(ctx, req, product)
|
||||
if err3 != nil {
|
||||
return "", err3
|
||||
return nil, err3
|
||||
}
|
||||
|
||||
return order.OrderNo, nil
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (this *VoucherBiz) order(ctx context.Context, req *bo.OrderCreateReqBo, product *bo.ProductBo) (*bo.OrderBo, error) {
|
||||
|
|
@ -130,8 +140,9 @@ func (this *VoucherBiz) fail(ctx context.Context, order *bo.OrderBo, errReq erro
|
|||
return err
|
||||
}
|
||||
|
||||
if errMsg == `Post "https://api.mch.weixin.qq.com/v3/marketing/favor/users/oW97fjrv_ntYBPjMsaaEMRSj6TPA/coupons": EOF` {
|
||||
// 微信:不同微信号,领取了很多次,自然人限领,发放频率限制,微信研发排期,后续这个错误信息应该会更新,近期没那么快同步上去
|
||||
// 微信:不同微信号,领取了很多次,自然人限领,发放频率限制,微信研发排期,后续这个错误信息应该会更新,近期没那么快同步上去
|
||||
eqMsg := fmt.Sprintf(`Post "https://api.mch.weixin.qq.com/v3/marketing/favor/users/%s/coupons": EOF`, order.Account)
|
||||
if errMsg == eqMsg {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ type OrderRepo interface {
|
|||
Success(ctx context.Context, id uint64, voucherNo string) error
|
||||
Fail(ctx context.Context, id uint64, remark string) error
|
||||
Used(ctx context.Context, id uint64) error
|
||||
NotifyUsed(ctx context.Context, id uint64, transactionId string) error
|
||||
NotifyUsed(ctx context.Context, id uint64, transactionId string, lastUseTime time.Time) error
|
||||
MultiLastUsed(ctx context.Context, id uint64, lastUseTime time.Time) error
|
||||
MultiOverUsed(ctx context.Context, id uint64, lastUseTime time.Time, remark string) error
|
||||
Available(ctx context.Context, id uint64) error
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ func (this *VoucherBiz) notifyUsed(ctx context.Context, order *bo.OrderBo, req *
|
|||
return nil
|
||||
}
|
||||
|
||||
if err := this.OrderRepo.NotifyUsed(ctx, order.ID, req.PlainText.ConsumeInformation.TransactionID); err != nil {
|
||||
if err := this.OrderRepo.NotifyUsed(ctx, order.ID, req.PlainText.ConsumeInformation.TransactionID, req.PlainText.ConsumeInformation.ConsumeTime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -549,7 +549,7 @@ func (p *OrderRepoImpl) MultiOverUsed(ctx context.Context, id uint64, lastUseTim
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *OrderRepoImpl) NotifyUsed(ctx context.Context, id uint64, transactionId string) error {
|
||||
func (p *OrderRepoImpl) NotifyUsed(ctx context.Context, id uint64, transactionId string, lastUseTime time.Time) error {
|
||||
now := time.Now()
|
||||
|
||||
tx := p.DB(ctx).
|
||||
|
|
@ -560,7 +560,7 @@ func (p *OrderRepoImpl) NotifyUsed(ctx context.Context, id uint64, transactionId
|
|||
Status: vo.OrderStatusUse.GetValue(),
|
||||
TransactionId: transactionId,
|
||||
Remark: "微信回调核销",
|
||||
LastUseTime: &now,
|
||||
LastUseTime: &lastUseTime,
|
||||
UpdateTime: &now,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package wechatrepoimpl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core"
|
||||
err2 "voucher/api/err"
|
||||
"voucher/internal/biz/bo"
|
||||
"voucher/internal/biz/businesserr"
|
||||
"voucher/internal/biz/wechatrepo"
|
||||
"voucher/internal/conf"
|
||||
"voucher/internal/data"
|
||||
|
|
@ -43,11 +46,20 @@ func (w *BankMultiActivityImpl) Order(order *bo.OrderBo) (couponId string, err e
|
|||
|
||||
var e *utils.ApiException
|
||||
if errors.As(err, &e) {
|
||||
apiErr, err3 := marketing.BuildErr(e.Body())
|
||||
if err3 != nil {
|
||||
return "", fmt.Errorf("ApiException analysis err: %+v", err3)
|
||||
// 格式:{"code":"INVALID_REQUEST","message":"对应单号已超出重试期;请查单确认后决定是否换单请求"}
|
||||
var beer *businesserr.BusinessErr
|
||||
if err = json.Unmarshal(e.Body(), &beer); err != nil {
|
||||
log.Errorf("微信错误返回body解析报错,body:%s,err:%s", string(e.Body()), err.Error())
|
||||
return "", err2.ErrorWechatFAIL(fmt.Sprintf("微信错误返回内容解析错误:%s", err.Error()))
|
||||
}
|
||||
return "", err2.ErrorWechatFAIL("%s-%s", apiErr.Code, apiErr.Message)
|
||||
|
||||
//apiErr, err3 := marketing.BuildErr(e.Body())
|
||||
//if err3 != nil {
|
||||
// return "", fmt.Errorf("ApiException analysis err: %+v", err3)
|
||||
//}
|
||||
//return "", err2.ErrorWechatFAIL("%s-%s", apiErr.Code, apiErr.Message)
|
||||
|
||||
return "", beer
|
||||
}
|
||||
|
||||
return "", err
|
||||
|
|
|
|||
|
|
@ -9,14 +9,18 @@ import (
|
|||
"github.com/wechatpay-apiv3/wechatpay-go/services/cashcoupons"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
err2 "voucher/api/err"
|
||||
"voucher/internal/biz/bo"
|
||||
"voucher/internal/biz/businesserr"
|
||||
"voucher/internal/biz/vo"
|
||||
"voucher/internal/biz/wechatrepo"
|
||||
"voucher/internal/conf"
|
||||
"voucher/internal/data"
|
||||
"voucher/internal/pkg/helper"
|
||||
"voucher/internal/pkg/request"
|
||||
"voucher/internal/pkg/supplier/qixing"
|
||||
)
|
||||
|
||||
// CpnRepoImpl .
|
||||
|
|
@ -52,18 +56,19 @@ func (c *CpnRepoImpl) GetClient(ctx context.Context) (*core.Client, error) {
|
|||
|
||||
func (c *CpnRepoImpl) bodyErr(_ context.Context, result *core.APIResult) error {
|
||||
|
||||
// // 格式:{"code":"INVALID_REQUEST","message":"对应单号已超出重试期;请查单确认后决定是否换单请求"}
|
||||
bodyBytes, err := io.ReadAll(result.Response.Body)
|
||||
if err != nil {
|
||||
return err2.ErrorWechatFAIL(fmt.Sprintf("读取微信错误返回body报错:%s", err.Error()))
|
||||
}
|
||||
|
||||
var errBody ErrBody
|
||||
if err = json.Unmarshal(bodyBytes, &errBody); err != nil {
|
||||
var beer *businesserr.BusinessErr
|
||||
if err = json.Unmarshal(bodyBytes, &beer); err != nil {
|
||||
log.Errorf("微信错误返回body解析报错,body:%s,err:%s", string(bodyBytes), err.Error())
|
||||
return err2.ErrorWechatFAIL(fmt.Sprintf("微信错误返回内容解析错误:%s", err.Error()))
|
||||
}
|
||||
|
||||
return errBody.GetWechatError()
|
||||
return beer
|
||||
}
|
||||
|
||||
func (c *CpnRepoImpl) Order(ctx context.Context, order *bo.OrderBo) (string, error) {
|
||||
|
|
@ -97,12 +102,85 @@ func (c *CpnRepoImpl) Order(ctx context.Context, order *bo.OrderBo) (string, err
|
|||
return *resp.CouponId, nil
|
||||
}
|
||||
|
||||
func (c *CpnRepoImpl) Query(ctx context.Context, orderWechat *bo.OrderBo) (vo.OrderStatus, error) {
|
||||
func (c *CpnRepoImpl) Query(ctx context.Context, order *bo.OrderBo) (vo.OrderStatus, error) {
|
||||
|
||||
// todo 确认下,多笔立减金用普通立减金的接口查询也能查,结果是准确的吗
|
||||
|
||||
// 福州启蒙 - 启星
|
||||
if order.MerchantNo == "1715349578" {
|
||||
//return c.QxQuery(ctx, order)
|
||||
}
|
||||
|
||||
return c.LsxdQuery(ctx, order)
|
||||
}
|
||||
|
||||
func (c *CpnRepoImpl) QxQuery(ctx context.Context, order *bo.OrderBo) (vo.OrderStatus, error) {
|
||||
|
||||
if order.ActivityId == "" {
|
||||
return 0, fmt.Errorf("商户号 %s 只支持多笔立减金查询", order.MerchantNo)
|
||||
}
|
||||
|
||||
b := qixing.QxQueryReq{
|
||||
OrderNo: order.OrderNo,
|
||||
CouponId: order.VoucherNo,
|
||||
OpenId: order.Account,
|
||||
}
|
||||
|
||||
var strToBeSigned strings.Builder
|
||||
|
||||
kvRows := helper.SortStructJsonTag(b)
|
||||
for _, kv := range kvRows {
|
||||
if kv.Key == "sign" || kv.Value == "" {
|
||||
continue
|
||||
}
|
||||
strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value))
|
||||
}
|
||||
s := strToBeSigned.String() + "config.AppKey"
|
||||
|
||||
b.Sign = helper.Md5(s)
|
||||
|
||||
body, err := json.Marshal(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
isSuccess := func(code int) bool {
|
||||
return code == http.StatusOK
|
||||
}
|
||||
|
||||
h := http.Header{
|
||||
"Content-Type": []string{"application/json"},
|
||||
}
|
||||
|
||||
_, body, err = request.Post(
|
||||
ctx,
|
||||
c.bc.WechatNotifyMQ.RegisterTagUrl,
|
||||
body,
|
||||
request.WithHeaders(h),
|
||||
request.WithStatusCodeFunc(isSuccess),
|
||||
request.WithTimeout(time.Second*10),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var resp qixing.QxQueryResp
|
||||
if err := json.Unmarshal(body, &resp); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 统一返回状态类型
|
||||
return resp.Data.CouponState.GetStatus()
|
||||
}
|
||||
|
||||
func (c *CpnRepoImpl) LsxdQuery(ctx context.Context, order *bo.OrderBo) (vo.OrderStatus, error) {
|
||||
|
||||
// 需要判断是多笔立减金查询还是普通立减金查询,此处需要更正查询方式
|
||||
|
||||
req := cashcoupons.QueryCouponRequest{
|
||||
CouponId: core.String(orderWechat.VoucherNo),
|
||||
Appid: core.String(orderWechat.AppID),
|
||||
Openid: core.String(orderWechat.Account),
|
||||
CouponId: core.String(order.VoucherNo),
|
||||
Appid: core.String(order.AppID),
|
||||
Openid: core.String(order.Account),
|
||||
}
|
||||
|
||||
client, err := c.GetClient(ctx)
|
||||
|
|
|
|||
|
|
@ -1,39 +1 @@
|
|||
package wechatrepoimpl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
errors2 "github.com/go-kratos/kratos/v2/errors"
|
||||
"gorm.io/gorm"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetErrorByDescription(t *testing.T) {
|
||||
e := &ErrBody{
|
||||
Code: "INVALID_REQUEST",
|
||||
Message: "活动已结束或未激活",
|
||||
}
|
||||
t.Log(e.GetWechatError())
|
||||
|
||||
err := gorm.ErrRecordNotFound
|
||||
|
||||
t.Log(errors.Is(err, gorm.ErrRecordNotFound))
|
||||
}
|
||||
|
||||
func TestErr(t *testing.T) {
|
||||
e := &ErrBody{
|
||||
Code: "INVALID_REQUEST",
|
||||
Message: "活动已结束或未激活",
|
||||
}
|
||||
|
||||
se := errors2.FromError(e.GetWechatError())
|
||||
|
||||
t.Log(se.Reason)
|
||||
t.Log(se.Message)
|
||||
|
||||
e2 := errors.New("活动已结束或未激活")
|
||||
se2 := errors2.FromError(e2)
|
||||
|
||||
t.Log(se2.Reason)
|
||||
t.Log(len(se2.Reason))
|
||||
t.Log(se2.Message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type KeyValue struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func SortStruct(data any) []KeyValue {
|
||||
v := reflect.ValueOf(data).Elem()
|
||||
t := v.Type()
|
||||
var kv []KeyValue
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
key := LowercaseFirstLetter(t.Field(i).Name)
|
||||
value := fmt.Sprintf("%v", field.Interface())
|
||||
kv = append(kv, KeyValue{Key: key, Value: value})
|
||||
}
|
||||
sort.SliceStable(kv, func(i, j int) bool {
|
||||
return kv[i].Key < kv[j].Key
|
||||
})
|
||||
return kv
|
||||
}
|
||||
|
||||
func SortStructJsonTag(data any) []KeyValue {
|
||||
// 获取 data 结构体的类型和值
|
||||
dataType := reflect.TypeOf(data).Elem()
|
||||
dataValue := reflect.ValueOf(data).Elem()
|
||||
|
||||
var kv []KeyValue
|
||||
for i := 0; i < dataType.NumField(); i++ {
|
||||
field := dataType.Field(i)
|
||||
// 获取字段的值
|
||||
fieldValue := dataValue.FieldByName(field.Name).Interface()
|
||||
// 获取 JSON 字段名
|
||||
jsonTagName := field.Tag.Get("json")
|
||||
if jsonTagName != "" {
|
||||
kv = append(kv, KeyValue{Key: jsonTagName, Value: fmt.Sprintf("%v", fieldValue)})
|
||||
}
|
||||
}
|
||||
|
||||
// 排序
|
||||
sort.SliceStable(kv, func(i, j int) bool {
|
||||
return kv[i].Key < kv[j].Key
|
||||
})
|
||||
|
||||
return kv
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package bo
|
||||
package qixing
|
||||
|
||||
import "github.com/go-playground/validator/v10"
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type QiXingRequestBo struct {
|
||||
Content string `json:"content" validate:"required"`
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package qixing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"voucher/internal/biz/vo"
|
||||
)
|
||||
|
||||
type CouponState string
|
||||
|
||||
const (
|
||||
CouponStateUnknown CouponState = "COUPON_STATE_UNKNOW" // 未知状态
|
||||
CouponStateSend CouponState = "COUPON_STATE_SEND" // 可用
|
||||
CouponStateUsed CouponState = "COUPON_STATE_USED" // 已实扣
|
||||
CouponStateExpired CouponState = "COUPON_STATE_EXPIRED" // 已过期
|
||||
)
|
||||
|
||||
var CpnStatusMap = map[CouponState]vo.OrderStatus{
|
||||
CouponStateSend: vo.OrderStatusSuccess,
|
||||
CouponStateUsed: vo.OrderStatusUse,
|
||||
CouponStateExpired: vo.OrderStatusExpired,
|
||||
}
|
||||
|
||||
func (o CouponState) GetStatus() (vo.OrderStatus, error) {
|
||||
if resultStatus, ok := CpnStatusMap[o]; ok {
|
||||
return resultStatus, nil
|
||||
}
|
||||
return 0, fmt.Errorf("CpnStatus[%s]未定义", o)
|
||||
}
|
||||
|
||||
type QxQueryReq struct {
|
||||
OrderNo string `json:"order_no"`
|
||||
CouponId string `json:"coupon_id"`
|
||||
OpenId string `json:"open_id"`
|
||||
Sign string `json:"sign"`
|
||||
}
|
||||
|
||||
type QxQueryResp struct {
|
||||
Code any `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data *struct {
|
||||
StockCreatorMchID string `json:"stock_creator_mchid"`
|
||||
StockID string `json:"stock_id"`
|
||||
CouponID string `json:"coupon_id"`
|
||||
CouponName string `json:"coupon_name"`
|
||||
CouponState CouponState `json:"coupon_state"`
|
||||
ReceiveTime time.Time `json:"receive_time"`
|
||||
AvailableBeginTime time.Time `json:"available_begin_time"`
|
||||
AvailableEndTime time.Time `json:"available_end_time"`
|
||||
ActivityID string `json:"activity_id"`
|
||||
MaxUseNumber int `json:"max_use_number"`
|
||||
AvailableNumber int `json:"available_number"`
|
||||
UsedNumber int `json:"used_number"`
|
||||
UseAmountList struct {
|
||||
UsedAmounts []string `json:"used_amounts"`
|
||||
} `json:"use_amount_list"`
|
||||
OpenID string `json:"openid"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
|
@ -3,29 +3,28 @@ package service
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
err2 "voucher/api/err"
|
||||
v1 "voucher/api/v1"
|
||||
"voucher/internal/biz/bo"
|
||||
"voucher/internal/biz/businesserr"
|
||||
"voucher/internal/biz/vo"
|
||||
)
|
||||
|
||||
func (c *CmbService) Order(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) {
|
||||
|
||||
orderNo, err := c.order(ctx, request)
|
||||
order, err := c.order(ctx, request)
|
||||
|
||||
if err != nil {
|
||||
return c.OrderFail(ctx, err)
|
||||
return c.OrderFail(ctx, order, err)
|
||||
}
|
||||
|
||||
return c.OrderSuccess(ctx, orderNo)
|
||||
return c.OrderSuccess(ctx, order.OrderNo)
|
||||
}
|
||||
|
||||
func (c *CmbService) order(ctx context.Context, request *v1.CmbRequest) (string, error) {
|
||||
func (c *CmbService) order(ctx context.Context, request *v1.CmbRequest) (*bo.OrderBo, error) {
|
||||
|
||||
bizContent, err := c.CmbMixRepo.OrderVerify(ctx, request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
boReq := &bo.OrderCreateReqBo{
|
||||
|
|
@ -39,12 +38,12 @@ func (c *CmbService) order(ctx context.Context, request *v1.CmbRequest) (string,
|
|||
NotifyUrl: c.bc.Cmb.NotifyUrl,
|
||||
}
|
||||
|
||||
orderNo, err := c.VoucherBiz.CmbOrder(ctx, boReq)
|
||||
order, err := c.VoucherBiz.CmbOrder(ctx, boReq)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return orderNo, nil
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (c *CmbService) OrderSuccess(ctx context.Context, orderNo string) (*v1.CmbReply, error) {
|
||||
|
|
@ -60,19 +59,37 @@ func (c *CmbService) OrderSuccess(ctx context.Context, orderNo string) (*v1.CmbR
|
|||
return c.GetResponse(ctx, replyBizContent)
|
||||
}
|
||||
|
||||
func (c *CmbService) OrderFail(ctx context.Context, err error) (*v1.CmbReply, error) {
|
||||
func (c *CmbService) OrderFail(ctx context.Context, order *bo.OrderBo, err error) (*v1.CmbReply, error) {
|
||||
|
||||
se := errors.FromError(err)
|
||||
bizReply := &v1.CmbOrderReply{}
|
||||
|
||||
if len(se.Reason) == 0 {
|
||||
se.Reason = err2.CmbErr_CMB_UNKNOWN.String()
|
||||
}
|
||||
if e, ok := err.(*businesserr.BusinessErr); ok {
|
||||
|
||||
bizReply := &v1.CmbOrderReply{
|
||||
RespCode: vo.CmbResponseStatusFail.GetValue(),
|
||||
RespMsg: se.Message,
|
||||
CodeNo: "",
|
||||
ThirdErrCode: se.Reason,
|
||||
if order.ActivityId != "" {
|
||||
cmbAPIError := e.HandleMULTIThirdErrCode()
|
||||
bizReply = &v1.CmbOrderReply{
|
||||
RespCode: vo.CmbResponseStatusFail.GetValue(),
|
||||
RespMsg: cmbAPIError.Description,
|
||||
CodeNo: "",
|
||||
ThirdErrCode: string(cmbAPIError.ThirdErrCode),
|
||||
}
|
||||
} else {
|
||||
cmbAPIError := e.HandleThirdErrCode()
|
||||
bizReply = &v1.CmbOrderReply{
|
||||
RespCode: vo.CmbResponseStatusFail.GetValue(),
|
||||
RespMsg: cmbAPIError.Description,
|
||||
CodeNo: "",
|
||||
ThirdErrCode: string(cmbAPIError.ThirdErrCode),
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
bizReply = &v1.CmbOrderReply{
|
||||
RespCode: vo.CmbResponseStatusFail.GetValue(),
|
||||
RespMsg: err.Error(),
|
||||
CodeNo: "",
|
||||
ThirdErrCode: string(businesserr.ThirdErrCodeDefault),
|
||||
}
|
||||
}
|
||||
|
||||
replyBizContent, _ := json.Marshal(bizReply)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"voucher/internal/biz/bo"
|
||||
"voucher/internal/conf"
|
||||
"voucher/internal/pkg/helper"
|
||||
"voucher/internal/pkg/supplier/qixing"
|
||||
)
|
||||
|
||||
type TripartiteService struct {
|
||||
|
|
@ -40,7 +41,7 @@ func (srv *TripartiteService) QiXingNotify(ctx http.Context) error {
|
|||
return srv.ResponseErr(ctx, fmt.Sprintf("read body error: %v", err))
|
||||
}
|
||||
|
||||
var req *bo.QiXingRequestBo
|
||||
var req *qixing.QiXingRequestBo
|
||||
if err = json.Unmarshal(bodyBytes, &req); err != nil {
|
||||
log.Errorf("qixing notify bodyBytes err ip:%s,error:%v,body:%s", ip, err, string(bodyBytes))
|
||||
return srv.ResponseErr(ctx, fmt.Sprintf("json unmarshal bodyBytes error: %v", err))
|
||||
|
|
@ -89,13 +90,13 @@ func (srv *TripartiteService) QiXingNotify(ctx http.Context) error {
|
|||
}
|
||||
|
||||
func (this *TripartiteService) ResponseOK(ctx http.Context) error {
|
||||
return ctx.JSON(http2.StatusOK, &bo.QiXingResponse{
|
||||
return ctx.JSON(http2.StatusOK, &qixing.QiXingResponse{
|
||||
Msg: "SUCCESS",
|
||||
})
|
||||
}
|
||||
|
||||
func (this *TripartiteService) ResponseErr(ctx http.Context, msg string) error {
|
||||
return ctx.JSON(http2.StatusOK, &bo.QiXingResponse{
|
||||
return ctx.JSON(http2.StatusOK, &qixing.QiXingResponse{
|
||||
Msg: msg,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@ package test
|
|||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
"voucher/internal/biz/bo"
|
||||
"voucher/internal/biz/businesserr"
|
||||
"voucher/internal/pkg/helper"
|
||||
"voucher/internal/pkg/supplier/qixing"
|
||||
"voucher/internal/pkg/wechat/utils"
|
||||
)
|
||||
|
||||
func Test_MarketingSend(t *testing.T) {
|
||||
|
|
@ -25,22 +28,39 @@ func Test_MarketingSend(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_MarketingQuery(t *testing.T) {
|
||||
|
||||
appId := "wx619991cc795028f5"
|
||||
openId := "oSNb4fixFZ4uRvcP6F25_FySgUE0"
|
||||
couponId := "147079366189"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
appId, openId, couponId string
|
||||
}{
|
||||
{
|
||||
name: "查询绑定多笔立减活动的代金券详情", // 查询的商户非创建方商户 查询商户要为创建方商户
|
||||
appId: "wx619991cc795028f5",
|
||||
openId: "oSNb4ftgnWC22Z0cWTjsQebdr2Yk",
|
||||
couponId: "139923450432",
|
||||
appId: appId,
|
||||
openId: openId,
|
||||
couponId: couponId,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resp, err := MarketingQuery(tt.appId, tt.openId, tt.couponId)
|
||||
if err != nil {
|
||||
t.Errorf("MarketingQuery() error = %v", err)
|
||||
|
||||
var e *utils.ApiException
|
||||
if errors.As(err, &e) {
|
||||
// 格式:{"code":"INVALID_REQUEST","message":"对应单号已超出重试期;请查单确认后决定是否换单请求"}
|
||||
var beer *businesserr.BusinessErr
|
||||
if err = json.Unmarshal(e.Body(), &beer); err != nil {
|
||||
t.Errorf("微信错误返回body解析报错,body:%s,err:%s", string(e.Body()), err.Error())
|
||||
}
|
||||
t.Logf("MarketingQuery error,body:%s,err:%s", string(e.Body()), e.ErrCode)
|
||||
} else {
|
||||
t.Errorf("MarketingQuery() error = %v", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
t.Logf("MarketingQuery() = %v", resp)
|
||||
|
|
@ -78,7 +98,7 @@ func Test_QixingNotifyData(t *testing.T) {
|
|||
|
||||
ciphertext := helper.Md5(content + "DrY1zLkOH01q0sN66vrmkdpbWsyb41ho")
|
||||
|
||||
req := bo.QiXingRequestBo{
|
||||
req := qixing.QiXingRequestBo{
|
||||
Content: content,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
Ciphertext: ciphertext,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
v1 "voucher/api/v1"
|
||||
"voucher/internal/biz/vo"
|
||||
"voucher/internal/pkg/cmb"
|
||||
"voucher/internal/pkg/helper"
|
||||
"voucher/internal/pkg/request"
|
||||
)
|
||||
|
||||
func Test_CMBRequest(t *testing.T) {
|
||||
|
||||
bizParma := &v1.CmbNotifyRequest{
|
||||
Ticket: "190662195271022592015", // 优惠券券码,codeNo
|
||||
Status: "0", // 更新后串码状态,0:可使用,1:已使用
|
||||
TransDate: "", // 验码日期,格式yyyy-mm-dd hh:mm:ss.sss
|
||||
OrgNo: "LANSEXIONGDI", // 固定值
|
||||
Ext: "", // 扩展字段
|
||||
Attach: "bFUWzuzvJfBshobjcQPFTvpqcH1AvGqHJtiV44mdsWEQKCPXgydk8ft/b227S3TM", // 扩展字段
|
||||
TransactionId: "2000000000001422730", // 招行唯一流水号
|
||||
}
|
||||
|
||||
bizParmaJsonBytes, err := json.Marshal(bizParma)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
encryptBody, err := cmb.EncryptBody(&cmb.Encrypts{SoaPubKey: "0416445bc16cbf42e47002ad9fe7c7af67d902b48be1eb69b98f6a006b0918630e1127f5f2fff83b2ecb30fc7fd72c34c33f37c7c355dffde3589f66800f0036ca", JsonParam: string(bizParmaJsonBytes)})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
req := &v1.CmbRequest{
|
||||
Mid: "d6fdd78b6fd13a808818286b9cad9687",
|
||||
Aid: "5efaa21263b94f669a1c90ed0279df20",
|
||||
Date: time.Now().Format("20060102150405"),
|
||||
Random: string(cmb.RandomBytes(16)),
|
||||
KeyAlias: "CO_PUB_KEY_SM2",
|
||||
CmbKeyAlias: "SM2_CMBLIFE",
|
||||
EncryptBody: encryptBody,
|
||||
Sign: "",
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("%s?%s", vo.CmbNotifyFuncName, cmb.SortStructStr(req))
|
||||
|
||||
sign, err := cmb.SignBody(str, "8d39ff3d2559258c163f4510f082727f51531e1953ab203d5ab1ea4a6d94fd73")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Sign = sign
|
||||
|
||||
kvRows := helper.SortStructFieldsByKey(req)
|
||||
|
||||
uv := url.Values{}
|
||||
|
||||
for _, kv := range kvRows {
|
||||
uv.Set(kv.Key, fmt.Sprintf("%v", kv.Value))
|
||||
}
|
||||
|
||||
h := http.Header{
|
||||
"Content-Type": []string{"application/x-www-form-urlencoded"},
|
||||
}
|
||||
|
||||
uri := "https://sandbox.cdcc.cmbchina.com/AccessGateway/transIn/updateCodeStatus.json"
|
||||
r := uri + "?" + uv.Encode()
|
||||
|
||||
respHeader, bodyBytes, err := request.Post(context.Background(), r, nil, request.WithHeaders(h), request.WithTimeout(time.Second*10))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("请求地址:%s", uri)
|
||||
t.Logf("业务参数:%s", string(bizParmaJsonBytes))
|
||||
t.Logf("请求响应体:%s", string(bodyBytes))
|
||||
t.Logf("请求响应头:%+v", respHeader)
|
||||
}
|
||||
|
||||
func Test_CmMultiRequest(t *testing.T) {
|
||||
|
||||
bizParma := &v1.CmbMultiNotifyRequest{
|
||||
TransactionId: "2000000000001636670", // 招行唯一流水号
|
||||
ActivityId: "11941580000000008",
|
||||
CouponId: "118770338167",
|
||||
AcquiredDate: "2025-08-25 14:56:54.123",
|
||||
Status: "1", // 更新后串码状态,0:可使用,1:已使用
|
||||
CouponStatus: "1", // String|M 整张券总状态0:可使用,1:已使用
|
||||
TransDate: "2025-08-28 12:09:41.123", // 验码日期,格式yyyy-mm-dd hh:mm:ss.sss
|
||||
TransAmount: "10",
|
||||
OrderId: "4200002772202508280813272296",
|
||||
Ticket: "195987259358664294429", // 优惠券券码,codeNo
|
||||
OrgNo: "LANSEXIONGDI", // 固定值
|
||||
Attach: "bFUWzuzvJfBshobjcQPFTvpqcH1AvGqHJtiV44mdsWEQKCPXgydk8ft/b227S3TM", // 扩展字段
|
||||
Ext: "", // 扩展字段
|
||||
}
|
||||
|
||||
bizParmaJsonBytes, err := json.Marshal(bizParma)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
encryptBody, err := cmb.EncryptBody(&cmb.Encrypts{SoaPubKey: "0416445bc16cbf42e47002ad9fe7c7af67d902b48be1eb69b98f6a006b0918630e1127f5f2fff83b2ecb30fc7fd72c34c33f37c7c355dffde3589f66800f0036ca", JsonParam: string(bizParmaJsonBytes)})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
req := &v1.CmbRequest{
|
||||
Mid: "d6fdd78b6fd13a808818286b9cad9687",
|
||||
Aid: "5efaa21263b94f669a1c90ed0279df20",
|
||||
Date: time.Now().Format("20060102150405"),
|
||||
Random: string(cmb.RandomBytes(16)),
|
||||
KeyAlias: "CO_PUB_KEY_SM2",
|
||||
CmbKeyAlias: "SM2_CMBLIFE",
|
||||
EncryptBody: encryptBody,
|
||||
Sign: "",
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("%s?%s", vo.CmbNotifyFuncNameUpdateCodeStatusForMulti, cmb.SortStructStr(req))
|
||||
|
||||
sign, err := cmb.SignBody(str, "8d39ff3d2559258c163f4510f082727f51531e1953ab203d5ab1ea4a6d94fd73")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Sign = sign
|
||||
|
||||
kvRows := helper.SortStructFieldsByKey(req)
|
||||
|
||||
uv := url.Values{}
|
||||
|
||||
for _, kv := range kvRows {
|
||||
uv.Set(kv.Key, fmt.Sprintf("%v", kv.Value))
|
||||
}
|
||||
|
||||
h := http.Header{
|
||||
"Content-Type": []string{"application/x-www-form-urlencoded"},
|
||||
}
|
||||
|
||||
uri := "https://sandbox.cdcc.cmbchina.com/AccessGateway/transIn/updateCodeStatusForMulti.json"
|
||||
r := uri + "?" + uv.Encode()
|
||||
|
||||
respHeader, bodyBytes, err := request.Post(context.Background(), r, nil, request.WithHeaders(h), request.WithTimeout(time.Second*10))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("请求地址:%s", uri)
|
||||
t.Logf("业务参数:%s", string(bizParmaJsonBytes))
|
||||
t.Logf("请求响应体:%s", string(bodyBytes))
|
||||
t.Logf("请求响应头:%+v", respHeader)
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
"voucher/internal/biz/businesserr"
|
||||
"voucher/internal/biz/do"
|
||||
"voucher/internal/conf"
|
||||
"voucher/internal/data"
|
||||
|
|
@ -33,14 +34,13 @@ var bc2 = &conf.Bootstrap{
|
|||
},
|
||||
}
|
||||
|
||||
func SendCoupon() {
|
||||
func SendCoupon() error {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Printf("os.Getwd() error = %v", err)
|
||||
return
|
||||
return fmt.Errorf("os.Getwd() error = %v", err)
|
||||
}
|
||||
parentDir := filepath.Dir(dir)
|
||||
|
||||
|
|
@ -52,8 +52,7 @@ func SendCoupon() {
|
|||
}
|
||||
client, err := data.GetClient(ctx, server)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
return fmt.Errorf("data.GetClient(ctx, server) error = %v", err)
|
||||
}
|
||||
|
||||
req := cashcoupons.SendCouponRequest{
|
||||
|
|
@ -62,7 +61,7 @@ func SendCoupon() {
|
|||
Appid: core.String("wxd27e255810842ba8"),
|
||||
Openid: core.String("o3dEt5b_1lFtKc-aAT3tiYjJIGwk"),
|
||||
StockId: core.String("21502886"),
|
||||
StockCreatorMchid: core.String("1652465541"),
|
||||
StockCreatorMchid: core.String("16524655411111"),
|
||||
}
|
||||
fmt.Printf("\nreq:%+v", req)
|
||||
|
||||
|
|
@ -71,15 +70,26 @@ func SendCoupon() {
|
|||
resp, result, err := svc.SendCoupon(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
|
||||
bodyBytes, err := io.ReadAll(result.Response.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取微信错误返回body报错:%s", err.Error())
|
||||
}
|
||||
fmt.Printf("\nbodyBytes:%s\n", string(bodyBytes))
|
||||
|
||||
var beer *businesserr.BusinessErr
|
||||
if err = json.Unmarshal(bodyBytes, &beer); err != nil {
|
||||
return fmt.Errorf("微信错误返回body解析报错,body:%s,err:%s", string(bodyBytes), err.Error())
|
||||
}
|
||||
|
||||
return beer
|
||||
}
|
||||
|
||||
fmt.Printf("\nresp:%+v\n result:%+v", resp, result)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func QueryCoupon() {
|
||||
func QueryCoupon(appId, openId, couponId string) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
@ -102,10 +112,6 @@ func QueryCoupon() {
|
|||
return
|
||||
}
|
||||
|
||||
appId := "wx619991cc795028f5"
|
||||
openId := "oSNb4fnWoktz7YVNIXE5bvoB3-1w"
|
||||
couponId := "106048490308"
|
||||
|
||||
req := cashcoupons.QueryCouponRequest{
|
||||
CouponId: core.String(couponId),
|
||||
Appid: core.String(appId),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package test
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"voucher/internal/biz/businesserr"
|
||||
)
|
||||
|
||||
func Test_SendCoupon(t *testing.T) {
|
||||
|
|
@ -14,22 +15,38 @@ func Test_SendCoupon(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
SendCoupon()
|
||||
if err := SendCoupon(); err != nil {
|
||||
if ee, ok := err.(*businesserr.BusinessErr); ok {
|
||||
t.Errorf("errorcode:%s,errmsg:%s", ee.Code, ee.Message)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_QueryCoupon(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
name string
|
||||
appId string
|
||||
openId string
|
||||
couponId string
|
||||
}{
|
||||
{
|
||||
name: "查询券订单信息",
|
||||
name: "查询券订单信息",
|
||||
appId: "wx619991cc795028f5",
|
||||
openId: "oSNb4fnRWr7HdgbDOO8TD66LXofE",
|
||||
couponId: "147089832782",
|
||||
},
|
||||
{
|
||||
name: "查询券订单信息",
|
||||
appId: "wx619991cc795028f5",
|
||||
openId: "oSNb4foo87dBNx1D9KTH-bB-G6YA",
|
||||
couponId: "147094824239",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
QueryCoupon()
|
||||
QueryCoupon(tt.appId, tt.openId, tt.couponId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -64,3 +81,34 @@ func Test_QueryCallback(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetErrorByDescription(t *testing.T) {
|
||||
//e := &businesserr.ErrBody{
|
||||
// Code: "INVALID_REQUEST",
|
||||
// Message: "活动已结束或未激活",
|
||||
//}
|
||||
//t.Log(e.GetWechatError())
|
||||
//
|
||||
//err := gorm.ErrRecordNotFound
|
||||
//
|
||||
//t.Log(errors.Is(err, gorm.ErrRecordNotFound))
|
||||
}
|
||||
|
||||
func TestErr(t *testing.T) {
|
||||
//e := &businesserr.ErrBody{
|
||||
// Code: "INVALID_REQUEST",
|
||||
// Message: "活动已结束或未激活",
|
||||
//}
|
||||
//
|
||||
//se := errors2.FromError(e.GetWechatError())
|
||||
//
|
||||
//t.Log(se.Reason)
|
||||
//t.Log(se.Message)
|
||||
//
|
||||
//e2 := errors.New("活动已结束或未激活")
|
||||
//se2 := errors2.FromError(e2)
|
||||
//
|
||||
//t.Log(se2.Reason)
|
||||
//t.Log(len(se2.Reason))
|
||||
//t.Log(se2.Message)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue