plugin 云闪付,回调签名,相关处理,提单查国密加密数据处理
This commit is contained in:
		
							parent
							
								
									fc0680609e
								
							
						
					
					
						commit
						a0b5a08071
					
				|  | @ -1,8 +1,43 @@ | |||
| package po | ||||
| 
 | ||||
| type Notify struct { | ||||
| 	MerchantId      int    `json:"merchantId" validate:"required"` | ||||
| 	OutTradeNo      string `json:"outTradeNo" validate:"required"` | ||||
| 	RechargeAccount string `json:"rechargeAccount" validate:"required"` | ||||
| 	Sign            string `json:"sign" validate:"required"` | ||||
| import "plugins/union_pay/internal/vo" | ||||
| 
 | ||||
| type Headers struct { | ||||
| 	SignMethod  string `json:"signmethod" validate:"required"` | ||||
| 	Version     string `json:"version" validate:"required"` | ||||
| 	ContentType string `json:"content-type" validate:"required"` | ||||
| 	AppId       string `json:"appid" validate:"required"` | ||||
| 	BizMethod   string `json:"bizmethod" validate:"required"` | ||||
| 	ReqId       string `json:"reqid" validate:"required"` | ||||
| 	Reqts       string `json:"reqts" validate:"required"` | ||||
| 	Sign        string `json:"sign" validate:"required"` | ||||
| } | ||||
| 
 | ||||
| type Body struct { | ||||
| 	CouponNum     interface{}    `json:"couponNum"` | ||||
| 	TraceID       string         `json:"traceId"` | ||||
| 	TransTp       string         `json:"transTp"` | ||||
| 	ChnlID        string         `json:"chnlId"` | ||||
| 	TransChnl     string         `json:"transChnl"` | ||||
| 	CouponNm      string         `json:"couponNm"` | ||||
| 	OperTp        vo.OperaTp     `json:"operTp"` | ||||
| 	CouponCd      string         `json:"couponCd"` | ||||
| 	CouponID      string         `json:"couponId"` | ||||
| 	OrderAt       interface{}    `json:"orderAt"` | ||||
| 	TransSeq      string         `json:"transSeq"` | ||||
| 	PosTmn        string         `json:"posTmn"` | ||||
| 	DiscountAt    interface{}    `json:"discountAt"` | ||||
| 	CouponCdInfos []CouponCdInfo `json:"couponCdInfos"` | ||||
| 	MchntCd       string         `json:"mchntCd"` | ||||
| 	TransDtTm     string         `json:"transDtTm"` | ||||
| } | ||||
| 
 | ||||
| type CouponCdInfo struct { | ||||
| 	SubAcctOperAt interface{} `json:"subAcctOperAt"` | ||||
| 	CouponCd      string      `json:"couponCd"` | ||||
| } | ||||
| 
 | ||||
| type Notify struct { | ||||
| 	Headers *Headers `json:"headers"` | ||||
| 	Body    *Body    `json:"body"` | ||||
| } | ||||
|  |  | |||
|  | @ -65,10 +65,12 @@ func (req *Notify) Validate() error { | |||
| } | ||||
| 
 | ||||
| func (req *Notify) GetReId() string { | ||||
| 	return "" | ||||
| 	return req.Headers.ReqId | ||||
| } | ||||
| 
 | ||||
| func (req *Notify) ToJson() []byte { | ||||
| 	b, _ := json.Marshal(req) | ||||
| 	//kvRows := utils.SortStruct(req.Body)
 | ||||
| 	//b, _ := json.Marshal(kvRows)
 | ||||
| 	b, _ := json.Marshal(req.Body) | ||||
| 	return b | ||||
| } | ||||
|  |  | |||
|  | @ -4,6 +4,8 @@ import ( | |||
| 	"codeup.aliyun.com/6552e56cc3b2728a4557fc18/plugin/proto" | ||||
| 	"encoding/json" | ||||
| 	"plugins/union_pay/internal/po" | ||||
| 	"plugins/union_pay/internal/utils" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| type Config struct { | ||||
|  | @ -96,3 +98,32 @@ func queryResp(request *proto.QueryRequest, resp po.QueryResp) *proto.QueryRespo | |||
| 	} | ||||
| 	return &proto.QueryResponse{Result: result} | ||||
| } | ||||
| 
 | ||||
| func notifyReq(in *proto.NotifyRequest) *po.Notify { | ||||
| 	var h po.Headers | ||||
| 	_ = json.Unmarshal(in.Headers, &h) | ||||
| 	var body po.Body | ||||
| 	_ = json.NewDecoder(strings.NewReader(string(in.Body))).Decode(&body) | ||||
| 	body.CouponNum = utils.ConvertToInt(body.CouponNum) | ||||
| 	body.OrderAt = utils.ConvertToInt(body.OrderAt) | ||||
| 	body.DiscountAt = utils.ConvertToInt(body.DiscountAt) | ||||
| 	for i, cdInfo := range body.CouponCdInfos { | ||||
| 		cdInfo.SubAcctOperAt = utils.ConvertToInt(cdInfo.SubAcctOperAt) | ||||
| 		body.CouponCdInfos[i] = cdInfo | ||||
| 	} | ||||
| 	return &po.Notify{ | ||||
| 		Headers: &h, | ||||
| 		Body:    &body, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func notifyResp(request *proto.NotifyRequest, n *po.Notify) *proto.NotifyResponse { | ||||
| 	result := &proto.Result{ | ||||
| 		OrderNo: n.Body.TransSeq, | ||||
| 		TradeNo: n.Body.CouponCd, | ||||
| 		Status:  n.Body.OperTp.GetOrderStatus(), | ||||
| 		Message: n.Body.OperTp.GetText(), | ||||
| 		Data:    request.Body, | ||||
| 	} | ||||
| 	return &proto.NotifyResponse{Result: result} | ||||
| } | ||||
|  |  | |||
|  | @ -67,5 +67,19 @@ func (p *UnionPayService) Query(ctx context.Context, request *proto.QueryRequest | |||
| } | ||||
| 
 | ||||
| func (p *UnionPayService) Notify(_ context.Context, request *proto.NotifyRequest) (*proto.NotifyResponse, error) { | ||||
| 	return nil, nil | ||||
| 	uv := notifyReq(request) | ||||
| 	if err := uv.Validate(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	conf, err := transConfig(request.Config) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if err = verify(conf, uv, request); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return notifyResp(request, uv), nil | ||||
| } | ||||
|  |  | |||
|  | @ -72,16 +72,18 @@ func TestQuery(t *testing.T) { | |||
| 
 | ||||
| func TestNotify(t *testing.T) { | ||||
| 	in := &proto.NotifyRequest{ | ||||
| 		Config:  config, | ||||
| 		Queries: nil, | ||||
| 		Headers: nil, | ||||
| 		Body:    []byte(`{"merchantId":10, "outTradeNo":"123", "rechargeAccount":"1866666666", "status":"01", "sign":"sign"}`), | ||||
| 		Headers: []byte(`{"bizmethod":"mkt.CpnStateUpdtNotify","apptype":"00","appid":"up_49pau3fu6latj_03h","sign":"F1WQAg3CRdXsBE+1dmAy+ulNkxFWDRkI6Q\/5xcpC\/bOCbcEFeun536hfhU0In+e+5GHPbfpIqyZjjglTm60QvSwvutqOiVlEBy\/G4GpIJPuLy6hdzy9Z+f56qSa0wzIjSxKqG1VuaFd2uS4WzSrx8E6hdPotXkuKzdcRwCCq\/wlmFPVxRRIVNTf6KgqkX4aNsC\/MGdLN9E5DOfoFdC8+oPqV5N71i6SjLvrzo4yaGJm+utSqEORsWZLgfZLUw+pZGjS83rQpFEKC5UyL6KwbIefNJ7a\/5cykiNIGzTRR9Tzz+UxuoiVglJ4AeUb2vs0rL8SoUjr8aLAAqPcXzPE5wQ==","reqts":"1690252684000","version":"1.0.0","signmethod":"RSA2","reqid":"8091_lsxd6688-10","content-type":"application/json"}`), | ||||
| 		Body:    []byte(`{"couponNum":"1","traceId":"lsxd6688-10","OperTp":"","chnlId":"9001","transChnl":"","couponNm":"众邦银行1元立减-测试","operTp":"04","couponCd":"INNER_23072510380403358473638771025186","couponId":"3102023071900090","orderAt":"0","transSeq":"lsxd6688-10","posTmn":"","discountAt":"0","couponCdInfos":[{"subAcctOperAt":"1","couponCd":"INNER_23072510380403358473638771025186"}],"mchntCd":"","transDtTm":"20230725103804"}`), | ||||
| 	} | ||||
| 	t.Run("TestNotify", func(t *testing.T) { | ||||
| 		got, err := server.Notify(context.Background(), in) | ||||
| 		if assert.Nil(t, err) { | ||||
| 			assert.Equal(t, true, got.Result) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("Notify() error = %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 		fmt.Printf("%+v \n", got) | ||||
| 		fmt.Printf("TestNotify : %+v \n", got) | ||||
| 		assert.Equal(t, int(proto.Status_SUCCESS), int(got.Result.Status)) | ||||
| 	}) | ||||
| } | ||||
|  |  | |||
|  | @ -1,20 +1,22 @@ | |||
| package internal | ||||
| 
 | ||||
| import ( | ||||
| 	"codeup.aliyun.com/6552e56cc3b2728a4557fc18/plugin/proto" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"plugins/union_pay/internal/po" | ||||
| 	"plugins/union_pay/internal/utils" | ||||
| 	"plugins/union_pay/internal/vo" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| func headers(config *Config, req po.Req, bizMethod string) map[string][]string { | ||||
| 	version := "1.0.0" | ||||
| 	h := make(http.Header) | ||||
| 	h.Add("Content-type", "application/json") | ||||
| 	h.Add("version", version) | ||||
| 	h.Add("appType", "01") | ||||
| 	h.Add("signMethod", "RSA2") | ||||
| 	h.Add("Content-type", vo.ContentType) | ||||
| 	h.Add("version", vo.Version) | ||||
| 	h.Add("appType", vo.AppType) | ||||
| 	h.Add("signMethod", vo.SignMethod) | ||||
| 
 | ||||
| 	h.Add("appId", config.Config.AppId) | ||||
| 	h.Add("bizMethod", bizMethod) | ||||
|  | @ -25,7 +27,7 @@ func headers(config *Config, req po.Req, bizMethod string) map[string][]string { | |||
| 	milliseconds := now.Unix()*1000 + int64(now.Nanosecond())/1e6 | ||||
| 	h.Add("reqTs", fmt.Sprintf("%d", milliseconds)) | ||||
| 
 | ||||
| 	encodedHash := utils.Sha(version, config.Config.AppId, bizMethod, req.GetReId(), string(req.ToJson())) | ||||
| 	encodedHash := utils.Sha(vo.Version, config.Config.AppId, bizMethod, req.GetReId(), string(req.ToJson())) | ||||
| 	signValue, err := utils.Sign(encodedHash, utils.FormatPEMPrivateKey(config.Extra.RsaPrk)) | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
|  | @ -33,3 +35,15 @@ func headers(config *Config, req po.Req, bizMethod string) map[string][]string { | |||
| 	h.Add("sign", signValue) | ||||
| 	return h | ||||
| } | ||||
| 
 | ||||
| func verify(config *Config, req *po.Notify, request *proto.NotifyRequest) error { | ||||
| 	if req.Headers.SignMethod != vo.SignMethod { | ||||
| 		return fmt.Errorf("签名方式不匹配") | ||||
| 	} | ||||
| 	encodedHash := utils.Sha(req.Headers.Version, config.Config.AppId, req.Headers.BizMethod, req.GetReId(), string(req.ToJson())) | ||||
| 	lowerStr := strings.ToLower(encodedHash) | ||||
| 	if utils.Verify(lowerStr, req.Headers.Sign, utils.FormatPEMPrivateKey(config.Extra.RsaNpk)) { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return fmt.Errorf("验签失败") | ||||
| } | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ package utils | |||
| import ( | ||||
| 	"crypto/sha256" | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| ) | ||||
| 
 | ||||
| func Sha(version, appId, bizMethod, reId, body string) string { | ||||
|  | @ -27,3 +28,15 @@ func getFormattedText(bytes []byte) string { | |||
| 
 | ||||
| 	return string(buf) | ||||
| } | ||||
| 
 | ||||
| func ConvertToInt(value interface{}) int { | ||||
| 	switch v := value.(type) { | ||||
| 	case string: | ||||
| 		if intValue, err := strconv.Atoi(v); err == nil { | ||||
| 			return intValue | ||||
| 		} | ||||
| 	case int: | ||||
| 		return v | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,8 @@ | |||
| package vo | ||||
| 
 | ||||
| const ( | ||||
| 	ContentType = "application/json" | ||||
| 	Version     = "1.0.0" | ||||
| 	AppType     = "01" | ||||
| 	SignMethod  = "RSA2" | ||||
| ) | ||||
|  | @ -0,0 +1,50 @@ | |||
| package vo | ||||
| 
 | ||||
| import "codeup.aliyun.com/6552e56cc3b2728a4557fc18/plugin/proto" | ||||
| 
 | ||||
| type OperaTp string | ||||
| 
 | ||||
| const ( | ||||
| 	OperaTpAccept  OperaTp = "01" | ||||
| 	OperaTpReturn  OperaTp = "02" | ||||
| 	OperaTpNoOpera OperaTp = "03" | ||||
| 	OperaTpGet     OperaTp = "04" | ||||
| 	OperaTpDelete  OperaTp = "05" | ||||
| 	OperaTpExpire  OperaTp = "06" | ||||
| ) | ||||
| 
 | ||||
| var notifyOrderTextMap = map[OperaTp]string{ | ||||
| 	OperaTpAccept:  "优惠券承兑 (用户消费)", | ||||
| 	OperaTpReturn:  "优惠券返还 (撤销或者退货 T+1)", | ||||
| 	OperaTpNoOpera: "优惠券无操作 (撤销或者退货不返还券的情况)", | ||||
| 	OperaTpGet:     "优惠券获取 (赠券成功时)", | ||||
| 	OperaTpDelete:  "优惠券删除 (删除券)", | ||||
| 	OperaTpExpire:  "优惠券过期 (过期实时通知)", | ||||
| } | ||||
| 
 | ||||
| var notifyOrderStatusMap = map[OperaTp]proto.Status{ | ||||
| 	OperaTpAccept:  proto.Status_WRITE_OFF, | ||||
| 	OperaTpReturn:  proto.Status_REFUND, | ||||
| 	OperaTpNoOpera: proto.Status_REVOKE, | ||||
| 	OperaTpGet:     proto.Status_SUCCESS, | ||||
| 	OperaTpDelete:  proto.Status_DELETE, | ||||
| 	OperaTpExpire:  proto.Status_OVERDUE, | ||||
| } | ||||
| 
 | ||||
| func (o OperaTp) GetText() string { | ||||
| 	msg, ok := notifyOrderTextMap[o] | ||||
| 	if !ok { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return msg | ||||
| } | ||||
| 
 | ||||
| func (o OperaTp) GetOrderStatus() proto.Status { | ||||
| 	if o == "" { | ||||
| 		return proto.Status_INVALID | ||||
| 	} | ||||
| 	if resultStatus, ok := notifyOrderStatusMap[o]; ok { | ||||
| 		return resultStatus | ||||
| 	} | ||||
| 	return proto.Status_FAIL | ||||
| } | ||||
		Loading…
	
		Reference in New Issue