diff --git a/controller/order.go b/controller/order.go index 4bd4e35..98fd604 100644 --- a/controller/order.go +++ b/controller/order.go @@ -497,7 +497,14 @@ func RentCardOrderCreate(c *gin.Context) { RespJson(c, status.InternalServerError, nil) return } - webPay, err := wxpay.WebPay(order.OrderSn, req.Price, user.WxOpenID, "N", wxpay.WxPayRentCard, configInfo.NotifyUrl) + //webPay, err := wxpay.WebPay(order.OrderSn, req.Price, user.WxOpenID, "N", wxpay.WxPayRentCard, configInfo.NotifyUrl) + //if err != nil { + // logger.Error(errors.New("WebPay err")) + // RespJson(c, status.InternalServerError, nil) + // return + //} + + webPay, err := wxpay.HmJsPayUnifiedOrder(order.OrderSn, req.Price, user.WxOpenID, "N", wxpay.WxPayRentCard, configInfo.NotifyUrl) if err != nil { logger.Error(errors.New("WebPay err")) RespJson(c, status.InternalServerError, nil) diff --git a/go.mod b/go.mod index b451043..57cfcd3 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/qiniu/x v7.0.8+incompatible // indirect github.com/rs/zerolog v1.23.0 github.com/satori/go.uuid v1.2.0 // indirect + github.com/shopspring/decimal v1.3.1 github.com/spf13/cobra v1.1.3 github.com/spf13/viper v1.7.1 github.com/wechatpay-apiv3/wechatpay-go v0.2.6 diff --git a/go.sum b/go.sum index f044fdb..05f9297 100644 --- a/go.sum +++ b/go.sum @@ -227,6 +227,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= diff --git a/lib/wxpay/wx_pay.go b/lib/wxpay/wx_pay.go index 6dd091c..190df15 100644 --- a/lib/wxpay/wx_pay.go +++ b/lib/wxpay/wx_pay.go @@ -3,9 +3,15 @@ package wxpay import ( "bytes" "context" + "crypto" "crypto/md5" + "crypto/rand" + "crypto/rsa" "crypto/tls" + "crypto/x509" + b64 "encoding/base64" "encoding/json" + "encoding/pem" "encoding/xml" "errors" "fmt" @@ -21,7 +27,7 @@ import ( mathrand "math/rand" "mh-server/config" "mh-server/lib/utils" - //"mh-server/model" + "net/http" "sort" "strconv" @@ -50,6 +56,11 @@ const ( //NotifyUrl = "https://dev.switch.deovo.com:8004/api/v1/wxpay/notice" // 测试 wxPayOrderRefundsUrl = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds" + + HmPayMerchantId = "664403000030115" + HmWxSubMerchantId = "546017470" + //HmPayUnifiedOrderUrl = "https://hmpay.sandpay.com.cn/gateway/api" + HmPayUnifiedOrderUrl = "https://hmpay.sandpay.com.cn/gateway/api" ) //web 微信支付 @@ -300,6 +311,11 @@ type T struct { CouponId3 string `json:"coupon_id_3"` } +const ( + DateTimeFormat = "2006-01-02" + TimeFormat = "2006-01-02 15:04:05" +) + //type T struct { // Xml struct { // Appid string `json:"appid"` @@ -419,9 +435,9 @@ func struct2Map(r interface{}) (s map[string]string, err error) { case string: result[k] = v2 case uint, int8, uint8, int, int16, uint16, int32, uint32, int64, uint64: - result[k] = fmt.Sprintf("%d", v2) + result[k] = fmt.Sprintf("%d", v) case float32, float64: - result[k] = fmt.Sprintf("%.0f", v2) + result[k] = fmt.Sprintf("%.02f", v) } } @@ -940,3 +956,392 @@ func newHttpClient(certFile, keyFile string) (*http.Client, error) { return client, nil } + +type HmPayPublicPara struct { + AppId string `json:"app_id"` + //SubAppId string `json:"sub_app_id"` + Method string `json:"method"` + Charset string `json:"charset"` + //SignType string `json:"sign_type"` + Sign string `json:"sign"` + Timestamp string `json:"timestamp"` + Nonce string `json:"nonce"` + //Version string `json:"version"` + //Format string `json:"format"` + BizContent string `json:"biz_content"` +} + +type HmJsPayUnifiedOrderReq struct { + CreateIp string `json:"create_ip"` + CreateTime string `json:"create_time"` + //ExpireTime string `json:"expire_time"` + //BankWay string `json:"bank_way"` + PayWay string `json:"pay_way"` + PayType string `json:"pay_type"` + MerAppId string `json:"mer_app_id"` + MerBuyerId string `json:"mer_buyer_id"` + //BuyerId string `json:"buyer_id"` + TotalAmount float64 `json:"total_amount"` + OutOrderNo string `json:"out_order_no"` + //scene_info + Body string `json:"body"` + StoreId string `json:"store_id"` + //TerminalId string `json:"terminal_id"` + //OperatorId string `json:"operator_id"` + NotifyUrl string `json:"notify_url"` + //RedirectUrl string `json:"redirect_url"` + //LimitPay string `json:"limit_pay"` + //ReqReserved string `json:"req_reserved"` + + //extend_params + //discount_info + //goods_details + //device_info + //ext_user_info + + HmPayPublicPara +} + +//hm 微信支付 +func HmJsPayUnifiedOrder(orderId string, totalFee uint32, openId, profitSharing, attach, notifyUrl string) (*Sextuple, error) { + now := time.Now().Local() + strTime := fmt.Sprintf("%04d%02d%02d%02d%02d%02d", now.Year(), now.Month(), now.Day(), now.Hour(), + now.Minute(), now.Second()) + nonce := utils.GenRandStr(NonceStringLength) + + if notifyUrl == "" { + logger.Error("NotifyUrl is null") + return nil, errors.New("NotifyUrl is null") + } + + logger.Info("MchId:", config.AppConfig.WxMchID) + logger.Info("AppId:", config.AppConfig.WxAppId) + logger.Info("MchSecret:", config.AppConfig.WxMchSecret) + + //unifiedOrderReq := UnifiedOrderReq{ + // DeviceInfo: "WEB", + // NonceStr: nonce, + // Sign: "", + // SignType: "MD5", + // Body: "创建订单", + // OutTradeNo: orderId, + // FeeType: "CNY", + // TotalFee: strconv.Itoa(int(totalFee)), + // //SpbillCreateIp: config.AppConfig.IP, + // SpbillCreateIp: clientIp, + // //NotifyUrl: "https://" + config.AppConfig.Domain + config.AppConfig.WxPayNotifyUrl, + // //NotifyUrl: "https://" + domain + wxPayNotifyUrl, + // //NotifyUrl: configInfo.NotifyUrl, + // NotifyUrl: notifyUrl, + // + // TradeType: "JSAPI", + // MchId: config.AppConfig.WxMchID, + // AppId: config.AppConfig.WxAppId, + // OpenId: openId, + // TimeStart: strTime, + // ProfitSharing: profitSharing, + // Attach: attach, + //} + + unifiedOrderReq := HmJsPayUnifiedOrderReq{ + CreateIp: clientIp, + CreateTime: strTime, + //ExpireTime: "", + //BankWay: "", + PayWay: "WECHAT", + PayType: "JSAPI", + MerAppId: config.AppConfig.WxAppId, + MerBuyerId: openId, + //BuyerId: "", + TotalAmount: float64(totalFee) / 100, + OutOrderNo: orderId, + Body: "会员", + StoreId: "100001", + //TerminalId: "", + //OperatorId: "", + NotifyUrl: notifyUrl, + //RedirectUrl: "", + //LimitPay: "", + //ReqReserved: "", + } + publicPara := HmPayPublicPara{ + AppId: HmPayMerchantId, + //SubAppId: HmWxSubMerchantId, + Method: "trade.create", + Charset: "UTF-8", + //SignType: "", + Sign: "", + Timestamp: now.Format(TimeFormat), + Nonce: nonce, + //Version: "", + //Format: "", + BizContent: fmt.Sprintf(`{"attach":"%s"}`, attach), + } + unifiedOrderReq.HmPayPublicPara = publicPara + + //fmt.Println("OutTradeNo:", unifiedOrderReq.OutTradeNo) + fmt.Println("OutOrderNo:", unifiedOrderReq.OutOrderNo) + + m, err := struct2Map(unifiedOrderReq) + if err != nil { + logger.Error(err) + return nil, err + } + + //mJson, _ := json.MarshalIndent(&m, "", " ") + //fmt.Println("mJson:", string(mJson)) + + sign, err := GenHmPaySign(m) + if err != nil { + logger.Error(err) + return nil, err + } + //unifiedOrderReq.Sign = strings.ToUpper(sign) + unifiedOrderReq.Sign = sign + + //unifiedOrderReqJson, _ := json.Marshal(&unifiedOrderReq) + //fmt.Println("unifiedOrderReqJson:", string(unifiedOrderReqJson)) + + unifiedOrderResp, err := HmPayUnifiedOrder(unifiedOrderReq) + if err != nil { + logger.Errorf("WxUnifiedOrder unified order error %#v", err) + return nil, err + } + + var sextuple Sextuple + sextuple.NonceStr = unifiedOrderResp.NonceStr + sextuple.AppId = unifiedOrderResp.AppId + sextuple.Timestamp = fmt.Sprintf("%d", now.Unix()) + sextuple.Package = "prepay_id=" + unifiedOrderResp.PrepayId + sextuple.SignType = "MD5" + //logger.Debugf("unified order sextuple: %#v", sextuple) + + m, err = struct2Map(sextuple) + if err != nil { + logger.Errorf("struct to map error %#v", err) + return nil, err + } + + sextuple.PaySign, err = GenWxPaySign(m, config.AppConfig.WxMchSecret) + if err != nil { + logger.Errorf("GenWxPaySign gen response sign error: %#v", err) + return nil, err + } + + return &sextuple, nil +} + +//func RsaSign(signContent string, privateKey string, hash crypto.Hash) string { +// shaNew := hash.New() +// shaNew.Write([]byte(signContent)) +// hashed := shaNew.Sum(nil) +// priKey, err := ParsePrivateKey(privateKey) +// if err != nil { +// panic(err) +// } +// +// signature, err := rsa.SignPKCS1v15(rand.Reader, priKey, hash, hashed) +// if err != nil { +// panic(err) +// } +// return base64.StdEncoding.EncodeToString(signature) + +//digest := sha256.Sum256(data) +// +//signature, signErr := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, digest[:]) +// +//if signErr != nil { +//t.Errorf("Could not sign message:%s", signErr.Error()) +//} +// +//// just to check that we can survive to and from b64 +//b64sig := base64.StdEncoding.EncodeToString(signature) +// +//decodedSignature, _ := base64.StdEncoding.DecodeString(b64sig) +//} + +func GenHmPaySign(m map[string]string) (string, error) { + delete(m, "sign") + var signData []string + for k, v := range m { + if k == "openid" { + fmt.Println(k, ":", v) + } + //fmt.Println("k:", k) + //fmt.Println("v:", v) + if v != "" && v != "0" { + signData = append(signData, fmt.Sprintf("%s=%s", k, v)) + } + } + signDataJson, _ := json.MarshalIndent(&signData, "", " ") + fmt.Println("signDataJson1", string(signDataJson)) + + sort.Strings(signData) + + signDataJson2, _ := json.MarshalIndent(&signData, "", " ") + fmt.Println("signDataJson2", string(signDataJson2)) + + signStr := strings.Join(signData, "&") + //signStr = signStr + "&key=" + payKey + logger.Info("签字符串1 :", signStr) + //c := md5.New() + //_, err := c.Write([]byte(signStr)) + //if err != nil { + // logger.Error(err) + // return "", err + //} + // + //signByte := c.Sum(nil) + // + //return fmt.Sprintf("%x", signByte), nil + signature, err := Sha256withRsa(signStr) + if err != nil { + logger.Error("signature err:", err) + return "", err + } + return signature, nil +} + +func Sha256withRsa(signContent string) (string, error) { + hash := crypto.SHA256 + shaNew := hash.New() + shaNew.Write([]byte(signContent)) + hashed := shaNew.Sum(nil) + priKey, err := ParsePrivateKey() + if err != nil { + logger.Error("parse err:", err) + return "", err + } + + signature, err := rsa.SignPKCS1v15(rand.Reader, priKey, hash, hashed) + if err != nil { + logger.Error("sign err:", err) + return "", err + } + return b64.StdEncoding.EncodeToString(signature), nil +} + +func ParsePrivateKey() (*rsa.PrivateKey, error) { + fp := "/Users/li/mh/mh_server/pack/configs/hm_pay/private_key.pem" + privateKey, err := ioutil.ReadFile(fp) + if err != nil { + logger.Error("read file err:", err) + return nil, err + } + + block, _ := pem.Decode([]byte(privateKey)) + if block == nil { + return nil, errors.New("私钥信息错误!") + } + //priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + priKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + if priKey == nil { + return nil, errors.New("pri key is nil") + } + return priKey.(*rsa.PrivateKey), nil +} + +const ( + PEM_BEGIN = "-----BEGIN RSA PRIVATE KEY-----\n" + PEM_END = "\n-----END RSA PRIVATE KEY-----" +) + +func FormatPrivateKey(privateKey string) string { + //privateKey = strings.Trim(privateKey, "\n") + if !strings.HasPrefix(privateKey, PEM_BEGIN) { + privateKey = PEM_BEGIN + privateKey + } + if !strings.HasSuffix(privateKey, PEM_END) { + privateKey = privateKey + PEM_END + } + return privateKey +} + +func VerifySha256Rsa(signContent, signBase string) error { + fp := "/Users/li/mh/mh_server/pack/configs/hm_pay/public_key.pme" + publicKeyString, err := ioutil.ReadFile(fp) + if err != nil { + fmt.Println("read file err:", err) + return err + } + + block, _ := pem.Decode([]byte(publicKeyString)) + publicKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + fmt.Println("public key err:", err) + return err + } + + if publicKey == nil { + fmt.Println("public key nil:") + return err + } + hash := crypto.SHA256 + shaNew := hash.New() + shaNew.Write([]byte(signContent)) + hashed := shaNew.Sum(nil) + + sign, err := b64.StdEncoding.DecodeString(signBase) + if err != nil { + fmt.Println("sign decode err:", err) + return err + } + + err = rsa.VerifyPKCS1v15(publicKey.(*rsa.PublicKey), crypto.SHA256, hashed, sign) + if err != nil { + fmt.Println("verify err:", err) + return err + } + //logger.Error("验签成功") + fmt.Println("验签成功") + return nil +} + +func HmPayUnifiedOrder(r HmJsPayUnifiedOrderReq) (UnifiedOrderResp, error) { + var payResp UnifiedOrderResp + + data, err := json.Marshal(r) + if err != nil { + logger.Error(err) + return payResp, err + } + fmt.Println("data json:", string(data)) + client := http.Client{} + req, err := http.NewRequest("POST", HmPayUnifiedOrderUrl, bytes.NewBuffer(data)) + if err != nil { + logger.Error(err) + return payResp, err + } + //req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + logger.Error(err) + return payResp, err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + logger.Error(err) + return payResp, err + } + fmt.Println("body:", string(body)) + + defer resp.Body.Close() + + //err = xml.Unmarshal(body, &payResp) + //if err != nil { + // logger.Error(err) + // return payResp, err + //} + // + //if payResp.ReturnCode != "SUCCESS" { + // return payResp, errors.New(payResp.ReturnMsg) + //} + + return payResp, nil +} diff --git a/lib/wxpay/wx_pay_test.go b/lib/wxpay/wx_pay_test.go index 7f1eb05..5fb82a7 100644 --- a/lib/wxpay/wx_pay_test.go +++ b/lib/wxpay/wx_pay_test.go @@ -6,6 +6,8 @@ import ( "fmt" "github.com/codinl/go-logger" "mh-server/model" + + //"mh-server/model" "strings" "testing" ) @@ -48,3 +50,12 @@ func TestWxPayTransactionOrderClose(t *testing.T) { WxPayTransactionOrderClose("100000", "1609877389") } + +func TestHmJsPayUnifiedOrder(t *testing.T) { + order, err := HmJsPayUnifiedOrder("84FDC15BCC", 2, "ohuHh4riVVPxwKHrYHsWwZRpxVMk", "N", WxPayRentCard, "https://dev.switch.deovo.com:8004/api/v1/wxpay/notice") + if err != nil { + fmt.Println("err:", err) + } + + fmt.Println("order:", order) +} diff --git a/model/model_test.go b/model/model_test.go index 21bf81c..d81e293 100644 --- a/model/model_test.go +++ b/model/model_test.go @@ -2,10 +2,19 @@ package model import ( "bytes" + "crypto" + cryrand "crypto/rand" + "crypto/rsa" + "crypto/sha1" "crypto/sha512" + "crypto/x509" + "encoding/base64" "encoding/hex" "encoding/json" + "encoding/pem" "fmt" + "io/ioutil" + "math/rand" "mh-server/lib/auth" "mh-server/lib/utils" "mh-server/lib/wxpay" @@ -19,7 +28,7 @@ import ( "github.com/codinl/go-logger" "github.com/jinzhu/gorm" "github.com/xuri/excelize/v2" - "math/rand" + //"math/rand" "strings" "testing" "time" @@ -2060,3 +2069,55 @@ func TestNewUser(t *testing.T) { } } } + +func TestPasKey(t *testing.T) { + d := `{"hm":10}` + + p := "/Users/li/mh/mh_server/pack/configs/hm_pay/private_key.pem" + readFile, err := ioutil.ReadFile(p) + if err != nil { + logger.Error("read file err:", err) + } + block, _ := pem.Decode(readFile) + priKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil || priKey == nil { + logger.Error("parse private err:", err) + } + //x509.SHA256WithRSA + h := sha1.New() + h.Write([]byte(d)) + sum := h.Sum(nil) + signPKCS1v15, err := rsa.SignPKCS1v15(cryrand.Reader, priKey.(*rsa.PrivateKey), crypto.SHA1, sum[:]) + if err != nil { + logger.Error("sign err:", err) + } + encodeToString := base64.StdEncoding.EncodeToString(signPKCS1v15) + + fmt.Println("key:", encodeToString) +} + +func TestSha256withRsa(t *testing.T) { + d := `{"hm":10}` + + withRsa, err := wxpay.Sha256withRsa(d) + if err != nil { + logger.Error("err:", err) + } + + fmt.Println("sign:", withRsa) + + err = wxpay.VerifySha256Rsa(d, withRsa) + if err != nil { + logger.Error("verify rsa err:", err) + } + +} + +func TestHmJsPayUnifiedOrder(t *testing.T) { + order, err := wxpay.HmJsPayUnifiedOrder("84FDC15BCC", 2, "ohuHh4riVVPxwKHrYHsWwZRpxVMk", "N", wxpay.WxPayRentCard, "https://dev.switch.deovo.com:8004/api/v1/wxpay/notice") + if err != nil { + fmt.Println("err:", err) + } + + fmt.Println("order:", order) +}