From a0b5a0807184d211fe2fb4686e906be2e1c7bbe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=AD=90=E9=93=AD?= Date: Thu, 4 Jul 2024 17:42:41 +0800 Subject: [PATCH] =?UTF-8?q?plugin=20=E4=BA=91=E9=97=AA=E4=BB=98=EF=BC=8C?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E7=AD=BE=E5=90=8D=EF=BC=8C=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=A4=84=E7=90=86,=E6=8F=90=E5=8D=95=E6=9F=A5=E5=9B=BD?= =?UTF-8?q?=E5=AF=86=E5=8A=A0=E5=AF=86=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/union_pay/internal/po/notify.go | 45 ++++++++++++++++-- plugins/union_pay/internal/po/po.go | 6 ++- plugins/union_pay/internal/transform.go | 31 ++++++++++++ plugins/union_pay/internal/union_pay.go | 16 ++++++- plugins/union_pay/internal/union_pay_test.go | 12 +++-- plugins/union_pay/internal/util.go | 26 +++++++--- plugins/union_pay/internal/utils/sha.go | 13 +++++ plugins/union_pay/internal/vo/headers.go | 8 ++++ plugins/union_pay/internal/vo/opera_tp.go | 50 ++++++++++++++++++++ 9 files changed, 188 insertions(+), 19 deletions(-) create mode 100644 plugins/union_pay/internal/vo/headers.go create mode 100644 plugins/union_pay/internal/vo/opera_tp.go diff --git a/plugins/union_pay/internal/po/notify.go b/plugins/union_pay/internal/po/notify.go index 63d7ee0..fcffd63 100644 --- a/plugins/union_pay/internal/po/notify.go +++ b/plugins/union_pay/internal/po/notify.go @@ -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"` } diff --git a/plugins/union_pay/internal/po/po.go b/plugins/union_pay/internal/po/po.go index 98b9e5f..306dcf0 100644 --- a/plugins/union_pay/internal/po/po.go +++ b/plugins/union_pay/internal/po/po.go @@ -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 } diff --git a/plugins/union_pay/internal/transform.go b/plugins/union_pay/internal/transform.go index 8ed00d0..86af24a 100644 --- a/plugins/union_pay/internal/transform.go +++ b/plugins/union_pay/internal/transform.go @@ -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} +} diff --git a/plugins/union_pay/internal/union_pay.go b/plugins/union_pay/internal/union_pay.go index 49fbdce..f1d3311 100644 --- a/plugins/union_pay/internal/union_pay.go +++ b/plugins/union_pay/internal/union_pay.go @@ -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 } diff --git a/plugins/union_pay/internal/union_pay_test.go b/plugins/union_pay/internal/union_pay_test.go index ce377ca..f63ae9f 100644 --- a/plugins/union_pay/internal/union_pay_test.go +++ b/plugins/union_pay/internal/union_pay_test.go @@ -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)) }) } diff --git a/plugins/union_pay/internal/util.go b/plugins/union_pay/internal/util.go index d77e566..7913093 100644 --- a/plugins/union_pay/internal/util.go +++ b/plugins/union_pay/internal/util.go @@ -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("验签失败") +} diff --git a/plugins/union_pay/internal/utils/sha.go b/plugins/union_pay/internal/utils/sha.go index 4ad7425..57e6e69 100644 --- a/plugins/union_pay/internal/utils/sha.go +++ b/plugins/union_pay/internal/utils/sha.go @@ -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 +} diff --git a/plugins/union_pay/internal/vo/headers.go b/plugins/union_pay/internal/vo/headers.go new file mode 100644 index 0000000..2dcab78 --- /dev/null +++ b/plugins/union_pay/internal/vo/headers.go @@ -0,0 +1,8 @@ +package vo + +const ( + ContentType = "application/json" + Version = "1.0.0" + AppType = "01" + SignMethod = "RSA2" +) diff --git a/plugins/union_pay/internal/vo/opera_tp.go b/plugins/union_pay/internal/vo/opera_tp.go new file mode 100644 index 0000000..4e8f3de --- /dev/null +++ b/plugins/union_pay/internal/vo/opera_tp.go @@ -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 +}