plugin 云闪付,回调签名,相关处理,提单查国密加密数据处理

This commit is contained in:
李子铭 2024-07-04 17:42:41 +08:00
parent fc0680609e
commit a0b5a08071
9 changed files with 188 additions and 19 deletions

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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}
}

View File

@ -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
}

View File

@ -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))
})
}

View File

@ -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("验签失败")
}

View File

@ -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
}

View File

@ -0,0 +1,8 @@
package vo
const (
ContentType = "application/json"
Version = "1.0.0"
AppType = "01"
SignMethod = "RSA2"
)

View File

@ -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
}