diff --git a/package/pay/vivo/client.go b/package/pay/vivo/client.go new file mode 100644 index 0000000..b2cf55c --- /dev/null +++ b/package/pay/vivo/client.go @@ -0,0 +1,15 @@ +package vivo + +type Pay struct { + AppId string + AppKey string + AppSecret string +} + +func New(appId, appKey, appSecret string) (client *Pay) { + return &Pay{ + AppId: appId, + AppKey: appKey, + AppSecret: appSecret, + } +} diff --git a/package/pay/vivo/const.go b/package/pay/vivo/const.go new file mode 100644 index 0000000..6b73193 --- /dev/null +++ b/package/pay/vivo/const.go @@ -0,0 +1,9 @@ +package vivo + +const ( + AuthTokenUrl = "https://joint-account.vivo.com.cn/cp/user/auth" + LocationShanghai = "Asia/Shanghai" + + RSA = "RSA" + RSA2 = "RSA2" +) diff --git a/package/pay/vivo/model.go b/package/pay/vivo/model.go new file mode 100644 index 0000000..f6efef1 --- /dev/null +++ b/package/pay/vivo/model.go @@ -0,0 +1,11 @@ +package vivo + +type TokenAuthResponse struct { + ReturnCode int `json:"retcode"` + Data *TokenAuthResponseData `json:"data,omitempty"` +} + +type TokenAuthResponseData struct { + Success bool `json:"success,omitempty"` + OpenId string `json:"openid,omitempty"` +} diff --git a/package/pay/vivo/payment_api.go b/package/pay/vivo/payment_api.go new file mode 100644 index 0000000..e8ee7e9 --- /dev/null +++ b/package/pay/vivo/payment_api.go @@ -0,0 +1,3 @@ +package vivo + + diff --git a/package/pay/vivo/sign.go b/package/pay/vivo/sign.go new file mode 100644 index 0000000..e75db16 --- /dev/null +++ b/package/pay/vivo/sign.go @@ -0,0 +1,47 @@ +package vivo + +import ( + "errors" + "github.com/gogf/gf/v2/crypto/gmd5" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" + + "sort" + "strings" +) + +func (p *Pay) VerifySign(bm g.Map, key string) bool { + signature := bm["signature"] + delete(bm, "signature") + delete(bm, "signMethod") + return signature == p.sign(bm, key) +} + +func (p *Pay) sign(bm g.Map, key string) string { + s, _ := p.buildSignStr(bm) + s += "&" + gmd5.MustEncrypt(key) + return gmd5.MustEncrypt(s) +} + +func (p *Pay) buildSignStr(bm g.Map) (string, error) { + var ( + buf strings.Builder + keyList []string + ) + for k := range bm { + keyList = append(keyList, k) + } + sort.Strings(keyList) + for _, k := range keyList { + if v := bm[k]; v != "" { + buf.WriteString(k) + buf.WriteByte('=') + buf.WriteString(gconv.String(v)) + buf.WriteByte('&') + } + } + if buf.Len() <= 0 { + return "", errors.New("length is error") + } + return buf.String()[:buf.Len()-1], nil +} diff --git a/package/pay/vivo/sign_test.go b/package/pay/vivo/sign_test.go new file mode 100644 index 0000000..9c6539c --- /dev/null +++ b/package/pay/vivo/sign_test.go @@ -0,0 +1,49 @@ +package vivo + +import ( + "github.com/gogf/gf/v2/frame/g" + "testing" +) + +func TestVerifySign(t *testing.T) { + type args struct { + bm g.Map + key string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "testCase-01", + args: args{ + bm: map[string]interface{}{ + "appId": "111", + "cpId": "11", + "cpOrderNumber": "111", + "extInfo": "扩展参数", + "orderAmount": "1", + "orderNumber": "11", + "payTime": "20210610213219", + "respCode": "200", + "respMsg": "交易成功", + "signMethod": "MD5", + "signature": "111", + "tradeStatus": "0000", + "tradeType": "01", + "uid": "111", + }, + key: "1111", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := New("123", "123", "123").VerifySign(tt.args.bm, tt.args.key); got != tt.want { + t.Errorf("VerifySign() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/package/pay/vivo/user_api.go b/package/pay/vivo/user_api.go new file mode 100644 index 0000000..7b4591d --- /dev/null +++ b/package/pay/vivo/user_api.go @@ -0,0 +1,52 @@ +package vivo + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "net/url" +) + +func (p *Pay) AuthToken(bm g.Map) (rsp *TokenAuthResponse, err error) { + if _, ok := bm["opentoken"]; !ok { + return + } + //err = bm.CheckEmptyError("opentoken") + if err != nil { + return nil, err + } + bs, err := p.doAuthToken(bm) + if err != nil { + return nil, err + } + rsp = new(TokenAuthResponse) + if err = json.Unmarshal(bs, rsp); err != nil { + return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) + } + return rsp, nil +} + +func (p *Pay) doAuthToken(bm g.Map) (bs []byte, err error) { + param := p.FormatURLParam(bm) + //httpClient := xhttp.NewClient() + //res, bs, errs := httpClient.Type(xhttp.TypeFormData).Post(AuthTokenUrl).SendString(param).EndBytes() + res, err := g.Client().Post(gctx.New(), AuthTokenUrl, param) + + if err != nil { + return nil, err + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode) + } + return res.ReadAll(), nil +} + +// 格式化请求URL参数 +func (p *Pay) FormatURLParam(body g.Map) (urlParam string) { + v := url.Values{} + for key, value := range body { + v.Add(key, value.(string)) + } + return v.Encode() +} diff --git a/package/pay/vivo/user_api_test.go b/package/pay/vivo/user_api_test.go new file mode 100644 index 0000000..db106b3 --- /dev/null +++ b/package/pay/vivo/user_api_test.go @@ -0,0 +1,70 @@ +package vivo + +import ( + "encoding/json" + "github.com/gogf/gf/v2/frame/g" + "reflect" + "testing" +) + +func TestAuthToken(t *testing.T) { + type args struct { + bm g.Map + } + tests := []struct { + name string + args args + wantRsp *TokenAuthResponse + wantErr bool + }{ + { + name: "testCase-01", + args: args{ + bm: map[string]interface{}{ + "opentoken": "_STV1_797e3324f7e3f1a3_797e3324f7e3f1a3_8db97942_Awykia3hpb90kcu3l", + }, + }, + wantRsp: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotRsp, err := New("", "", "").AuthToken(tt.args.bm) + if (err != nil) != tt.wantErr { + t.Errorf("AuthToken() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotRsp, tt.wantRsp) { + if gotRsp != nil { + marshal, _ := json.Marshal(gotRsp) + println(string(marshal)) + } + t.Errorf("AuthToken() gotRsp = %v, want %v", gotRsp, tt.wantRsp) + } + }) + } +} + +func TestFormatURLParam(t *testing.T) { + type args struct { + body g.Map + } + tests := []struct { + name string + args args + wantUrlParam string + }{ + {name: "testCase-01", args: args{body: map[string]interface{}{ + "opentoken": "_STV1_797e3324f7e3f1a3_797e3324f7e3f1a3_8db97942_Abbccayhpb90kvd3m", + "123": "123", + }}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotUrlParam := New("", "", "").FormatURLParam(tt.args.body); gotUrlParam != tt.wantUrlParam { + t.Errorf("FormatURLParam() = %v, want %v", gotUrlParam, tt.wantUrlParam) + } + }) + } +}