优化支付退款钱包链路
This commit is contained in:
@@ -72,6 +72,56 @@ type QueryPaymentResponse struct {
|
||||
Raw map[string]string
|
||||
}
|
||||
|
||||
type CreateRefundRequest struct {
|
||||
ThirdOrderID string // 原支付商户订单号
|
||||
LeshuaOrderID string // 原支付乐刷订单号(优先使用)
|
||||
MerchantRefundID string // 商户退款单号(唯一)
|
||||
RefundAmountCent int64 // 退款金额(分)
|
||||
NotifyURL string
|
||||
Attach string
|
||||
}
|
||||
|
||||
type CreateRefundResponse struct {
|
||||
RespCode string
|
||||
ResultCode string
|
||||
ErrorCode string
|
||||
ErrorMessage string
|
||||
MerchantID string
|
||||
ThirdOrderID string
|
||||
LeshuaOrderID string
|
||||
MerchantRefundID string
|
||||
LeshuaRefundID string
|
||||
RefundAmount string
|
||||
TotalAmount string
|
||||
OrderBalance string
|
||||
Status string
|
||||
Raw map[string]string
|
||||
}
|
||||
|
||||
type QueryRefundRequest struct {
|
||||
ThirdOrderID string
|
||||
LeshuaOrderID string
|
||||
MerchantRefundID string
|
||||
LeshuaRefundID string
|
||||
}
|
||||
|
||||
type QueryRefundResponse struct {
|
||||
RespCode string
|
||||
ResultCode string
|
||||
ErrorCode string
|
||||
ErrorMessage string
|
||||
MerchantID string
|
||||
ThirdOrderID string
|
||||
LeshuaOrderID string
|
||||
MerchantRefundID string
|
||||
LeshuaRefundID string
|
||||
Status string
|
||||
RefundAmount string
|
||||
TotalAmount string
|
||||
RefundTime string
|
||||
Raw map[string]string
|
||||
}
|
||||
|
||||
type VerifyNotifyResult struct {
|
||||
OK bool
|
||||
MatchedKey string
|
||||
@@ -179,6 +229,100 @@ func (c *Client) QueryPayment(ctx context.Context, thirdOrderID, providerOrderID
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateRefund(ctx context.Context, req CreateRefundRequest) (*CreateRefundResponse, map[string]string, error) {
|
||||
if err := c.validate(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
params := map[string]string{
|
||||
"service": "unified_refund",
|
||||
"merchant_id": c.cfg.MerchantID,
|
||||
"merchant_refund_id": req.MerchantRefundID,
|
||||
"refund_amount": fmt.Sprintf("%d", req.RefundAmountCent),
|
||||
"nonce_str": Nonce(32),
|
||||
}
|
||||
if req.LeshuaOrderID != "" {
|
||||
params["leshua_order_id"] = req.LeshuaOrderID
|
||||
} else if req.ThirdOrderID != "" {
|
||||
params["third_order_id"] = req.ThirdOrderID
|
||||
}
|
||||
if req.Attach != "" {
|
||||
params["attach"] = sanitizeText(req.Attach, 64)
|
||||
}
|
||||
if c.cfg.SignType != "" && !strings.EqualFold(c.cfg.SignType, "MD5") {
|
||||
params["sign_type"] = c.cfg.SignType
|
||||
}
|
||||
if req.NotifyURL != "" {
|
||||
params["notify_url"] = req.NotifyURL
|
||||
}
|
||||
params["sign"] = Sign(params, c.cfg.SignKey, SignOptions{})
|
||||
raw, err := c.post(ctx, params)
|
||||
if err != nil {
|
||||
return nil, params, err
|
||||
}
|
||||
resp := &CreateRefundResponse{
|
||||
RespCode: raw["resp_code"],
|
||||
ResultCode: raw["result_code"],
|
||||
ErrorCode: raw["error_code"],
|
||||
ErrorMessage: firstNonEmpty(raw["error_msg"], raw["resp_msg"]),
|
||||
MerchantID: raw["merchant_id"],
|
||||
ThirdOrderID: raw["third_order_id"],
|
||||
LeshuaOrderID: raw["leshua_order_id"],
|
||||
MerchantRefundID: raw["merchant_refund_id"],
|
||||
LeshuaRefundID: raw["leshua_refund_id"],
|
||||
RefundAmount: raw["refund_amount"],
|
||||
TotalAmount: raw["total_amount"],
|
||||
OrderBalance: raw["order_balance"],
|
||||
Status: raw["status"],
|
||||
Raw: raw,
|
||||
}
|
||||
return resp, params, nil
|
||||
}
|
||||
|
||||
func (c *Client) QueryRefund(ctx context.Context, req QueryRefundRequest) (*QueryRefundResponse, error) {
|
||||
if err := c.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params := map[string]string{
|
||||
"service": "unified_query_refund",
|
||||
"merchant_id": c.cfg.MerchantID,
|
||||
"nonce_str": Nonce(32),
|
||||
}
|
||||
if req.LeshuaOrderID != "" {
|
||||
params["leshua_order_id"] = req.LeshuaOrderID
|
||||
} else if req.ThirdOrderID != "" {
|
||||
params["third_order_id"] = req.ThirdOrderID
|
||||
}
|
||||
if req.LeshuaRefundID != "" {
|
||||
params["leshua_refund_id"] = req.LeshuaRefundID
|
||||
} else if req.MerchantRefundID != "" {
|
||||
params["merchant_refund_id"] = req.MerchantRefundID
|
||||
}
|
||||
if c.cfg.SignType != "" && !strings.EqualFold(c.cfg.SignType, "MD5") {
|
||||
params["sign_type"] = c.cfg.SignType
|
||||
}
|
||||
params["sign"] = Sign(params, c.cfg.SignKey, SignOptions{})
|
||||
raw, err := c.post(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &QueryRefundResponse{
|
||||
RespCode: raw["resp_code"],
|
||||
ResultCode: raw["result_code"],
|
||||
ErrorCode: raw["error_code"],
|
||||
ErrorMessage: firstNonEmpty(raw["error_msg"], raw["resp_msg"]),
|
||||
MerchantID: raw["merchant_id"],
|
||||
ThirdOrderID: raw["third_order_id"],
|
||||
LeshuaOrderID: raw["leshua_order_id"],
|
||||
MerchantRefundID: raw["merchant_refund_id"],
|
||||
LeshuaRefundID: raw["leshua_refund_id"],
|
||||
Status: raw["status"],
|
||||
RefundAmount: raw["refund_amount"],
|
||||
TotalAmount: raw["total_amount"],
|
||||
RefundTime: raw["refund_time"],
|
||||
Raw: raw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) VerifyNotify(params map[string]string) bool {
|
||||
return c.VerifyNotifyDetail(params).OK
|
||||
}
|
||||
|
||||
@@ -159,3 +159,60 @@ func TestParsePayloadSupportsFormAndXML(t *testing.T) {
|
||||
t.Fatalf("ParsePayload(xml).coupon = %q, exists=%v; want empty value", value, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignRefundUsesSameAlgorithm(t *testing.T) {
|
||||
params := map[string]string{
|
||||
"service": "unified_refund",
|
||||
"merchant_id": "1234567890",
|
||||
"merchant_refund_id": "REF001",
|
||||
"refund_amount": "100",
|
||||
"leshua_order_id": "LS1",
|
||||
"nonce_str": "abc",
|
||||
"sign": "ignored",
|
||||
}
|
||||
got := Sign(params, "secret", SignOptions{})
|
||||
baseString := SignBaseString(params, SignOptions{})
|
||||
wantBaseString := "leshua_order_id=LS1&merchant_id=1234567890&merchant_refund_id=REF001&nonce_str=abc&refund_amount=100&service=unified_refund"
|
||||
if baseString != wantBaseString {
|
||||
t.Fatalf("SignBaseString() = %s, want %s", baseString, wantBaseString)
|
||||
}
|
||||
want := Sign(map[string]string{
|
||||
"service": "unified_refund",
|
||||
"merchant_id": "1234567890",
|
||||
"merchant_refund_id": "REF001",
|
||||
"refund_amount": "100",
|
||||
"leshua_order_id": "LS1",
|
||||
"nonce_str": "abc",
|
||||
}, "secret", SignOptions{})
|
||||
if got != want {
|
||||
t.Fatalf("Sign() = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyRefundNotify(t *testing.T) {
|
||||
client := NewClient(config.LeshuaPaymentConfig{NotifyKey: "notify-secret"})
|
||||
params := map[string]string{
|
||||
"merchant_id": "1234567890",
|
||||
"third_order_id": "NO1",
|
||||
"leshua_order_id": "LS1",
|
||||
"merchant_refund_id": "REF001",
|
||||
"leshua_refund_id": "LREF001",
|
||||
"refund_amount": "100",
|
||||
"total_amount": "200",
|
||||
"status": "11",
|
||||
"attach": "",
|
||||
}
|
||||
params["sign"] = Sign(params, "notify-secret", SignOptions{
|
||||
IncludeEmpty: true,
|
||||
ExcludeKeys: []string{"error_code", "leshua", "sign"},
|
||||
})
|
||||
|
||||
if !client.VerifyNotify(params) {
|
||||
t.Fatal("VerifyNotify() = false, want true for refund notify")
|
||||
}
|
||||
|
||||
params["status"] = "12"
|
||||
if client.VerifyNotify(params) {
|
||||
t.Fatal("VerifyNotify() = true after status changed without re-sign, want false")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user