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