This commit is contained in:
李子铭 2025-03-14 20:18:10 +08:00
parent bd377b5b81
commit 0ed61a0f2c
8 changed files with 359 additions and 25 deletions

View File

@ -6,6 +6,29 @@ option go_package = "voucher/api/err";
enum WechatErr{ enum WechatErr{
option (errors.default_code) = 1; option (errors.default_code) = 1;
// WechatFAIL = 0 [(errors.code) = 500];
WECHAT_FAIL = 0 [(errors.code) = 500];
WechatUserIllegal = 1 [(errors.code) = 500];
WechatMismatch = 2 [(errors.code) = 500];
WechatInvalidMerchantID = 4 [(errors.code) = 500];
WechatHighFrequency = 5 [(errors.code) = 500];
WechatActivityInactive = 6 [(errors.code) = 500];
WechatBatchInfoError = 7 [(errors.code) = 500];
WechatParamRequired = 8 [(errors.code) = 500];
WechatInvalidBatchStatus = 10 [(errors.code) = 500];
WechatMchNotExists = 11 [(errors.code) = 500];
WechatBatchBudgetInsufficient = 9 [(errors.code) = 500];
WechatDailyLimitExceeded = 12 [(errors.code) = 500];
WechatAccountBalanceInsufficient = 13 [(errors.code) = 500];
WechatBatchBudgetDepleted = 14 [(errors.code) = 500];
WechatMerchantNoPermission = 15 [(errors.code) = 500];
WechatCrossMerchantNotSupported = 16 [(errors.code) = 500];
WechatUserReceiveLimit = 17 [(errors.code) = 500];
WechatAPIChannelNotSupported = 18 [(errors.code) = 500];
WechatSpecifiedDenominationNotSupported = 19 [(errors.code) = 500];
WechatOnlyAdvertisingBatch = 20 [(errors.code) = 500];
WechatUserMaxCoupons = 21 [(errors.code) = 500];
WechatNaturalPersonRuleBlocked = 22 [(errors.code) = 500];
WechatResourceNotExists = 23 [(errors.code) = 500];
WechatFrequencyLimited = 24 [(errors.code) = 500];
} }

View File

@ -61,11 +61,11 @@ message CmbOrderRequest {
string attach = 15 [json_name = "attach"]; string attach = 15 [json_name = "attach"];
} }
message CmbOrderReply { message CmbOrderReply {
//
// 1000 1001 // 1000 1001
string respCode = 1 [json_name = "respCode"]; string respCode = 1 [json_name = "respCode"];
// //
string respMsg = 2 [json_name = "respMsg"]; string respMsg = 2 [json_name = "respMsg"];
//
// 50 // 50
string codeNo = 9 [json_name = "codeNo"]; string codeNo = 9 [json_name = "codeNo"];
// //

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"gorm.io/gorm" "gorm.io/gorm"
"time" "time"
err2 "voucher/api/err"
v1 "voucher/api/v1" v1 "voucher/api/v1"
"voucher/internal/biz/bo" "voucher/internal/biz/bo"
"voucher/internal/biz/vo" "voucher/internal/biz/vo"
@ -42,7 +43,7 @@ func (v *VoucherBiz) CmbOrder(ctx context.Context, req *bo.OrderCreateReqBo) (vo
} }
if !product.Channel.IsWeChat() { if !product.Channel.IsWeChat() {
return fmt.Errorf("只支持微信") return err2.ErrorCmbProductNotSupported("只支持微信")
} }
order, err = v.order(ctx, req, product) order, err = v.order(ctx, req, product)

View File

@ -32,7 +32,7 @@ func (c *Server) Validate() error {
return nil return nil
} }
func newClient(ctx context.Context, c Server) (*core.Client, error) { func NewClient(ctx context.Context, c Server) (*core.Client, error) {
dir, err := os.Getwd() dir, err := os.Getwd()
if err != nil { if err != nil {
@ -101,7 +101,7 @@ func GetClient(ctx context.Context, c Server) (*core.Client, error) {
instance.once.Do(func() { instance.once.Do(func() {
i, err2 := newClient(ctx, c) i, err2 := NewClient(ctx, c)
if err2 != nil { if err2 != nil {
err = err2 err = err2
return return

View File

@ -64,11 +64,12 @@ func (c *CpnRepoImpl) Order(ctx context.Context, order *bo.OrderBo) (string, err
log.Errorf("请求微信返回错误:%s", string(bodyBytes)) log.Errorf("请求微信返回错误:%s", string(bodyBytes))
if err = json.Unmarshal(bodyBytes, &ErrBody); err != nil { var errBody ErrBody
if err = json.Unmarshal(bodyBytes, &errBody); err != nil {
return "", err return "", err
} }
return "", fmt.Errorf(ErrBody.Message) return "", errBody.GetWechatError()
} }
if result.Response.StatusCode != CodeSuccess { if result.Response.StatusCode != CodeSuccess {
@ -103,16 +104,17 @@ func (c *CpnRepoImpl) Query(ctx context.Context, orderWechat *bo.OrderBo) (vo.Or
log.Errorf("请求微信返回错误:%s", string(bodyBytes)) log.Errorf("请求微信返回错误:%s", string(bodyBytes))
if err = json.Unmarshal(bodyBytes, &ErrBody); err != nil { var errBody ErrBody
if err = json.Unmarshal(bodyBytes, &errBody); err != nil {
return 0, err return 0, err
} }
return 0, fmt.Errorf("微信返回错误:%s", ErrBody.Message) return 0, errBody.GetWechatError()
} }
if result.Response.StatusCode != CodeSuccess { if result.Response.StatusCode != CodeSuccess {
return 0, fmt.Errorf("微信返回错误 Response StatusCode[%s]", result.Response.StatusCode) return 0, fmt.Errorf("微信返回错误 Response StatusCode[%d]", result.Response.StatusCode)
} }
return CpnStatus(*resp.Status).GetStatus() return CpnStatus(*resp.Status).GetStatus()
@ -138,15 +140,16 @@ func (c *CpnRepoImpl) QueryProduct(ctx context.Context, stockCreatorMchId, stock
return nil, err return nil, err
} }
if err = json.Unmarshal(bodyBytes, &ErrBody); err != nil { var errBody ErrBody
if err = json.Unmarshal(bodyBytes, &errBody); err != nil {
return nil, err return nil, err
} }
return nil, fmt.Errorf("微信返回错误:%s", ErrBody.Message) return nil, errBody.GetWechatError()
} }
if result.Response.StatusCode != CodeSuccess { if result.Response.StatusCode != CodeSuccess {
return nil, fmt.Errorf("查询活动微信返回错误 Response StatusCode[%s]", result.Response.StatusCode) return nil, fmt.Errorf("查询活动微信返回错误 Response StatusCode[%d]", result.Response.StatusCode)
} }
return response, nil return response, nil
@ -183,11 +186,12 @@ func (c *CpnRepoImpl) QueryCallback(ctx context.Context, mchId, mchCertNo string
return nil, err return nil, err
} }
if err = json.Unmarshal(bodyBytes, &ErrBody); err != nil { var errBody ErrBody
if err = json.Unmarshal(bodyBytes, &errBody); err != nil {
return nil, err return nil, err
} }
return nil, fmt.Errorf("微信返回错误:%s", ErrBody.Message) return nil, errBody.GetWechatError()
} }
if result.Response.StatusCode != CodeSuccess { if result.Response.StatusCode != CodeSuccess {
@ -230,11 +234,12 @@ func (c *CpnRepoImpl) SetCallback(ctx context.Context, mchId, mchCertNo, url str
return nil, err return nil, err
} }
if err = json.Unmarshal(bodyBytes, &ErrBody); err != nil { var errBody ErrBody
if err = json.Unmarshal(bodyBytes, &errBody); err != nil {
return nil, err return nil, err
} }
return nil, fmt.Errorf("微信返回错误:%s", ErrBody.Message) return nil, fmt.Errorf("微信返回错误:%s", errBody.Message)
} }
if result.Response.StatusCode != CodeSuccess { if result.Response.StatusCode != CodeSuccess {

View File

@ -0,0 +1,299 @@
package wechatrepoimpl
import (
"fmt"
"strings"
"github.com/go-kratos/kratos/v2/errors"
err2 "voucher/api/err"
)
// ErrCode 定义错误码类型
// https://pay.weixin.qq.com/doc/v3/merchant/4012463767
type ErrCode string
// 定义错误码常量
const (
APPID_MCHID_NOT_MATCH ErrCode = "APPID_MCHID_NOT_MATCH"
INVALID_REQUEST ErrCode = "INVALID_REQUEST"
PARAM_ERROR ErrCode = "PARAM_ERROR"
MCH_NOT_EXISTS ErrCode = "MCH_NOT_EXISTS"
NOT_ENOUGH ErrCode = "NOT_ENOUGH"
REQUEST_BLOCKED ErrCode = "REQUEST_BLOCKED"
RULE_LIMIT ErrCode = "RULE_LIMIT"
USER_ACCOUNT_ABNORMAL ErrCode = "USER_ACCOUNT_ABNORMAL"
RESOURCE_NOT_EXISTS ErrCode = "RESOURCE_NOT_EXISTS"
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"`
}
// 定义错误映射,方便根据错误码获取错误信息
var _ = map[ErrCode][]APIError{
APPID_MCHID_NOT_MATCH: {
{
StatusCode: 400,
ErrorCode: APPID_MCHID_NOT_MATCH,
Description: "商户号与AppID不匹配",
Hint: "调用接口的商户号需与接口传入的AppID有绑定关系请参考常见问题Q4",
},
},
INVALID_REQUEST: {
{
StatusCode: 400,
ErrorCode: INVALID_REQUEST,
Description: "OpenID与AppID不匹配",
Hint: "OpenID与AppID需有对应关系",
},
{
StatusCode: 400,
ErrorCode: INVALID_REQUEST,
Description: "非法的商户号",
Hint: "请检查商户号准确性",
},
{
StatusCode: 400,
ErrorCode: INVALID_REQUEST,
Description: "调用频率过高",
Hint: "请降低API调用频率",
},
{
StatusCode: 400,
ErrorCode: INVALID_REQUEST,
Description: "活动已结束或未激活",
Hint: "请检查批次状态",
},
{
StatusCode: 400,
ErrorCode: INVALID_REQUEST,
Description: "批次信息获取失败,请确认参数是否有误",
Hint: "请检查创建商户号与批次号的对应关系",
},
},
PARAM_ERROR: {
{
StatusCode: 400,
ErrorCode: PARAM_ERROR,
Description: "AppID必填",
Hint: "请输入AppID",
},
{
StatusCode: 400,
ErrorCode: PARAM_ERROR,
Description: "OpenID必填",
Hint: "请输入OpenID",
},
{
StatusCode: 400,
ErrorCode: PARAM_ERROR,
Description: "批次号必填",
Hint: "请输入批次号",
},
{
StatusCode: 400,
ErrorCode: PARAM_ERROR,
Description: "商户号必填",
Hint: "请输入商户号",
},
{
StatusCode: 400,
ErrorCode: PARAM_ERROR,
Description: "非法的批次状态",
Hint: "请检查批次状态,仅支持发放状态为“运营中”的代金券批次",
},
},
MCH_NOT_EXISTS: {
{
StatusCode: 403,
ErrorCode: MCH_NOT_EXISTS,
Description: "商户号不合法",
Hint: "请检查商户号准确性",
},
},
NOT_ENOUGH: {
{
StatusCode: 403,
ErrorCode: NOT_ENOUGH,
Description: "批次预算不足",
Hint: "批次预算已发放完,请补充批次预算",
},
{
StatusCode: 403,
ErrorCode: NOT_ENOUGH,
Description: "发券超过单天限额",
Hint: "已超过该批次设置的单天发放限制额度,无法发放",
},
{
StatusCode: 403,
ErrorCode: NOT_ENOUGH,
Description: "账户余额不足,请充值",
Hint: "商户号余额不足,无法继续发券,请充值",
},
{
StatusCode: 403,
ErrorCode: NOT_ENOUGH,
Description: "批次预算耗尽",
Hint: "该批次的预算已经耗尽",
},
},
REQUEST_BLOCKED: {
{
StatusCode: 403,
ErrorCode: REQUEST_BLOCKED,
Description: "商户无权发券",
Hint: "该批次不支持其他商户发放请参考常见问题Q1",
},
{
StatusCode: 403,
ErrorCode: REQUEST_BLOCKED,
Description: "批次不支持跨商户发券",
Hint: "该批次不支持其他商户发放请参考常见问题Q1",
},
{
StatusCode: 403,
ErrorCode: REQUEST_BLOCKED,
Description: "用户被限领拦截",
Hint: "该用户已达到该批次的领取上限请参考常见问题Q6",
},
{
StatusCode: 403,
ErrorCode: REQUEST_BLOCKED,
Description: "不能在API渠道发放",
Hint: "请检查批次信息,仅支持发放微信支付代金券,不支持发放立减与折扣",
},
{
StatusCode: 403,
ErrorCode: REQUEST_BLOCKED,
Description: "不支持指定面额发券",
Hint: "仅在发券时指定面额及门槛的场景才生效,常规发券场景请勿传入该信息",
},
{
StatusCode: 403,
ErrorCode: REQUEST_BLOCKED,
Description: "仅在广告场景下发放批次",
Hint: "该批次已在朋友圈广告发放,不支持在其他渠道发放",
},
},
RULE_LIMIT: {
{
StatusCode: 403,
ErrorCode: RULE_LIMIT,
Description: "用户已达最大领券次数",
Hint: "该用户已达到该批次的领取上限请参考常见问题Q6",
},
{
StatusCode: 403,
ErrorCode: RULE_LIMIT,
Description: "被自然人规则拦截",
Hint: "该自然人已达到该批次的领取上限请参考常见问题Q6",
},
},
USER_ACCOUNT_ABNORMAL: {
{
StatusCode: 403,
ErrorCode: USER_ACCOUNT_ABNORMAL,
Description: "用户非法",
Hint: "用户命中微信支付风控模型请参考常见问题Q5",
},
},
RESOURCE_NOT_EXISTS: {
{
StatusCode: 404,
ErrorCode: RESOURCE_NOT_EXISTS,
Description: "批次不存在",
Hint: "请检查批次及制券商户号信息",
},
},
FREQUENCY_LIMITED: {
{
StatusCode: 429,
ErrorCode: FREQUENCY_LIMITED,
Description: "当前请求人数过多,请稍后重试",
Hint: "请降低API调用频率",
},
},
}
// 使用常量定义错误描述,减少硬编码
const (
ErrorUserIllegal = "用户非法"
ErrorAppIDMchIDMismatch = "商户号与AppID不匹配"
ErrorOpenIDAppIDMismatch = "OpenID与AppID不匹配"
ErrorInvalidMerchantID = "非法的商户号"
ErrorHighFrequency = "调用频率过高"
ErrorActivityInactive = "活动已结束或未激活"
ErrorBatchInfoError = "批次信息获取失败,请确认参数是否有误"
ErrorAppIDRequired = "AppID必填"
ErrorOpenIDRequired = "OpenID必填"
ErrorBatchIDRequired = "批次号必填"
ErrorMerchantIDRequired = "商户号必填"
ErrorInvalidBatchStatus = "非法的批次状态"
ErrorMchNotExists = "商户号不合法"
ErrorBatchBudgetInsufficient = "批次预算不足"
ErrorDailyLimitExceeded = "发券超过单天限额"
ErrorAccountBalanceInsufficient = "账户余额不足,请充值"
ErrorBatchBudgetDepleted = "批次预算耗尽"
ErrorMerchantNoPermission = "商户无权发券"
ErrorCrossMerchantNotSupported = "批次不支持跨商户发券"
ErrorUserReceiveLimit = "用户被限领拦截"
ErrorAPIChannelNotSupported = "不能在API渠道发放"
ErrorSpecifiedDenominationNotSupported = "不支持指定面额发券"
ErrorOnlyAdvertisingBatch = "仅在广告场景下发放批次"
ErrorUserMaxCoupons = "用户已达最大领券次数"
ErrorNaturalPersonRuleBlocked = "被自然人规则拦截"
ErrorResourceNotExists = "批次不存在"
ErrorFrequencyLimited = "当前请求人数过多,请稍后重试"
)
// WechatError 映射错误描述到具体的错误处理
var WechatError = map[string]*errors.Error{
ErrorUserIllegal: err2.ErrorWechatUserIllegal(ErrorUserIllegal),
ErrorAppIDMchIDMismatch: err2.ErrorWechatMismatch(ErrorAppIDMchIDMismatch),
ErrorOpenIDAppIDMismatch: err2.ErrorWechatMismatch(ErrorOpenIDAppIDMismatch),
ErrorInvalidMerchantID: err2.ErrorWechatInvalidMerchantID(ErrorInvalidMerchantID),
ErrorHighFrequency: err2.ErrorWechatHighFrequency(ErrorHighFrequency),
ErrorActivityInactive: err2.ErrorWechatActivityInactive(ErrorActivityInactive),
ErrorBatchInfoError: err2.ErrorWechatBatchInfoError(ErrorBatchInfoError),
ErrorAppIDRequired: err2.ErrorWechatParamRequired(ErrorAppIDRequired),
ErrorOpenIDRequired: err2.ErrorWechatParamRequired(ErrorOpenIDRequired),
ErrorBatchIDRequired: err2.ErrorWechatParamRequired(ErrorBatchIDRequired),
ErrorMerchantIDRequired: err2.ErrorWechatParamRequired(ErrorMerchantIDRequired),
ErrorInvalidBatchStatus: err2.ErrorWechatInvalidBatchStatus(ErrorInvalidBatchStatus),
ErrorMchNotExists: err2.ErrorWechatMchNotExists(ErrorMchNotExists),
ErrorBatchBudgetInsufficient: err2.ErrorWechatBatchBudgetInsufficient(ErrorBatchBudgetInsufficient),
ErrorDailyLimitExceeded: err2.ErrorWechatDailyLimitExceeded(ErrorDailyLimitExceeded),
ErrorAccountBalanceInsufficient: err2.ErrorWechatAccountBalanceInsufficient(ErrorAccountBalanceInsufficient),
ErrorBatchBudgetDepleted: err2.ErrorWechatBatchBudgetDepleted(ErrorBatchBudgetDepleted),
ErrorMerchantNoPermission: err2.ErrorWechatMerchantNoPermission(ErrorMerchantNoPermission),
ErrorCrossMerchantNotSupported: err2.ErrorWechatCrossMerchantNotSupported(ErrorCrossMerchantNotSupported),
ErrorUserReceiveLimit: err2.ErrorWechatUserReceiveLimit(ErrorUserReceiveLimit),
ErrorAPIChannelNotSupported: err2.ErrorWechatAPIChannelNotSupported(ErrorAPIChannelNotSupported),
ErrorSpecifiedDenominationNotSupported: err2.ErrorWechatSpecifiedDenominationNotSupported(ErrorSpecifiedDenominationNotSupported),
ErrorOnlyAdvertisingBatch: err2.ErrorWechatOnlyAdvertisingBatch(ErrorOnlyAdvertisingBatch),
ErrorUserMaxCoupons: err2.ErrorWechatUserMaxCoupons(ErrorUserMaxCoupons),
ErrorNaturalPersonRuleBlocked: err2.ErrorWechatNaturalPersonRuleBlocked(ErrorNaturalPersonRuleBlocked),
ErrorResourceNotExists: err2.ErrorWechatResourceNotExists(ErrorResourceNotExists),
ErrorFrequencyLimited: err2.ErrorWechatFrequencyLimited(ErrorFrequencyLimited),
}
type ErrBody struct {
Code ErrCode `json:"Code"`
Message string `json:"Message"`
}
// GetWechatError 根据错误描述获取具体的错误处理
func (s ErrBody) GetWechatError() *errors.Error {
lowerDesc := strings.ToLower(s.Message)
for desc, err := range WechatError {
if strings.ToLower(desc) == lowerDesc {
return err
}
}
return err2.ErrorWechatFAIL(fmt.Sprintf("微信返回错误:%s", s.Message))
}

View File

@ -0,0 +1,13 @@
package wechatrepoimpl
import (
"testing"
)
func TestGetErrorByDescription(t *testing.T) {
e := &ErrBody{
Code: "INVALID_REQUEST",
Message: "活动已结束或未激活",
}
t.Log(e.GetWechatError())
}

View File

@ -1,7 +0,0 @@
package wechatrepoimpl
var ErrBody struct {
StatusCode string `json:"StatusCode"`
Code string `json:"Code"`
Message string `json:"Message"`
}