From c55ac26e7791e73c37ef16ff5de3ff262402321c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9B=BD=E6=A0=8B?= Date: Mon, 6 Jan 2025 18:27:37 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E8=AE=A2=E9=98=85=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E2=80=9C=E6=9B=B4=E6=96=B0=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 +++ README.md | 2 +- com/alipay/api/model/AntomPathConstants.go | 1 + .../auth/AlipayAuthCreateSessionRequest.go | 25 +++++++++++++++++++ .../AlipaySubscriptionUpdateRequest.go | 21 ++++++++++++++++ .../auth/AlipayAuthCreateSessionResponse.go | 10 ++++++++ .../AlipaySubscriptionUpdateResponse.go | 7 ++++++ 7 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 com/alipay/api/request/auth/AlipayAuthCreateSessionRequest.go create mode 100644 com/alipay/api/request/subscription/AlipaySubscriptionUpdateRequest.go create mode 100644 com/alipay/api/response/auth/AlipayAuthCreateSessionResponse.go create mode 100644 com/alipay/api/response/subscription/AlipaySubscriptionUpdateResponse.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 9385d38..f6016d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.2.8 - 2025-01-06 +* [#10](https://github.com/alipay/global-open-sdk-go/pull/10) feature-250106 + - 订阅支付新增“更新接口” + ## 1.2.7 - 2024-12-16 * [#9](https://github.com/alipay/global-open-sdk-go/pull/9) feature-241216 - RDR拒付通知优化通用能力变更 diff --git a/README.md b/README.md index 47f895b..5628bed 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ``` Language:GO GO version:1.22.5+ -Tags:v1.2.7 +Tags:v1.2.8 Copyright:Ant financial services group ``` diff --git a/com/alipay/api/model/AntomPathConstants.go b/com/alipay/api/model/AntomPathConstants.go index 3f2ef0c..acfbe63 100644 --- a/com/alipay/api/model/AntomPathConstants.go +++ b/com/alipay/api/model/AntomPathConstants.go @@ -19,6 +19,7 @@ const ( SUBSCRIPTION_CREATE_PATH = "/ams/api/v1/subscriptions/create" SUBSCRIPTION_CHANGE_PATH = "/ams/api/v1/subscriptions/change" SUBSCRIPTION_CANCEL_PATH = "/ams/api/v1/subscriptions/cancel" + SUBSCRIPTION_UPDATE_PATH = "/ams/api/v1/subscriptions/update" ACCEPT_DISPUTE_PATH = "/ams/api/v1/payments/acceptDispute" SUPPLY_DEFENCE_DOC_PATH = "/ams/api/v1/payments/supplyDefenseDocument" DOWNLOAD_DISPUTE_EVIDENCE_PATH = "/ams/api/v1/payments/downloadDisputeEvidence" diff --git a/com/alipay/api/request/auth/AlipayAuthCreateSessionRequest.go b/com/alipay/api/request/auth/AlipayAuthCreateSessionRequest.go new file mode 100644 index 0000000..82ac9ba --- /dev/null +++ b/com/alipay/api/request/auth/AlipayAuthCreateSessionRequest.go @@ -0,0 +1,25 @@ +package auth + +import ( + "github.com/alipay/global-open-sdk-go/com/alipay/api/model" + "github.com/alipay/global-open-sdk-go/com/alipay/api/request" + responseAuth "github.com/alipay/global-open-sdk-go/com/alipay/api/response/auth" +) + +type AlipayAuthCreateSessionRequest struct { + ProductCode model.ProductCodeType `json:"productCode,omitempty"` + AgreementInfo *model.AgreementInfo `json:"agreementInfo,omitempty"` + Scopes []model.ScopeType `json:"scopes,omitempty"` + PaymentMethod *model.PaymentMethod `json:"paymentMethod,omitempty"` + PaymentNotifyUrl string `json:"paymentNotifyUrl,omitempty"` +} + +func (alipayAuthCreateSessionRequest *AlipayAuthCreateSessionRequest) NewRequest() *request.AlipayRequest { + return request.NewAlipayRequest(&alipayAuthCreateSessionRequest, model.CREATE_SESSION_PATH, &responseAuth.AlipayAuthCreateSessionResponse{}) +} + +func NewAlipayAuthCreateSessionRequest() (*request.AlipayRequest, *AlipayAuthCreateSessionRequest) { + alipayAuthCreateSessionRequest := &AlipayAuthCreateSessionRequest{} + alipayRequest := request.NewAlipayRequest(alipayAuthCreateSessionRequest, model.CREATE_SESSION_PATH, &responseAuth.AlipayAuthCreateSessionResponse{}) + return alipayRequest, alipayAuthCreateSessionRequest +} diff --git a/com/alipay/api/request/subscription/AlipaySubscriptionUpdateRequest.go b/com/alipay/api/request/subscription/AlipaySubscriptionUpdateRequest.go new file mode 100644 index 0000000..fe0cf97 --- /dev/null +++ b/com/alipay/api/request/subscription/AlipaySubscriptionUpdateRequest.go @@ -0,0 +1,21 @@ +package subscription + +import ( + "github.com/alipay/global-open-sdk-go/com/alipay/api/model" + "github.com/alipay/global-open-sdk-go/com/alipay/api/request" + responseSubscription "github.com/alipay/global-open-sdk-go/com/alipay/api/response/subscription" +) + +type AlipaySubscriptionUpdateRequest struct { + SubscriptionUpdateRequestId string `json:"subscriptionUpdateRequestId,omitempty"` + SubscriptionId string `json:"subscriptionId,omitempty"` + SubscriptionDescription string `json:"subscriptionDescription,omitempty"` + PeriodRule *model.PeriodRule `json:"periodRule,omitempty"` + PaymentAmount *model.Amount `json:"paymentAmount,omitempty"` + SubscriptionEndTime string `json:"subscriptionEndTime,omitempty"` + OrderInfo *model.OrderInfo `json:"orderInfo,omitempty"` +} + +func (alipaySubscriptionUpdateRequest *AlipaySubscriptionUpdateRequest) NewRequest() *request.AlipayRequest { + return request.NewAlipayRequest(&alipaySubscriptionUpdateRequest, model.SUBSCRIPTION_UPDATE_PATH, &responseSubscription.AlipaySubscriptionUpdateResponse{}) +} diff --git a/com/alipay/api/response/auth/AlipayAuthCreateSessionResponse.go b/com/alipay/api/response/auth/AlipayAuthCreateSessionResponse.go new file mode 100644 index 0000000..1e4b3cc --- /dev/null +++ b/com/alipay/api/response/auth/AlipayAuthCreateSessionResponse.go @@ -0,0 +1,10 @@ +package responseAuth + +import "github.com/alipay/global-open-sdk-go/com/alipay/api/response" + +type AlipayAuthCreateSessionResponse struct { + response.AlipayResponse + PaymentSessionId string `json:"paymentSessionId,omitempty"` + PaymentSessionData string `json:"paymentSessionData,omitempty"` + PaymentSessionExpiryTime string `json:"paymentSessionExpiryTime,omitempty"` +} diff --git a/com/alipay/api/response/subscription/AlipaySubscriptionUpdateResponse.go b/com/alipay/api/response/subscription/AlipaySubscriptionUpdateResponse.go new file mode 100644 index 0000000..81f404c --- /dev/null +++ b/com/alipay/api/response/subscription/AlipaySubscriptionUpdateResponse.go @@ -0,0 +1,7 @@ +package responseSubscription + +import "github.com/alipay/global-open-sdk-go/com/alipay/api/response" + +type AlipaySubscriptionUpdateResponse struct { + response.AlipayResponse +} From 471b9c3fb1304663236027632efb6b3cc32f6360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9B=BD=E6=A0=8B?= Date: Wed, 8 Jan 2025 18:21:12 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=AA=8C=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + com/alipay/api/defaultAlipayClient.go | 82 ++++-------------- com/alipay/api/tools/SignatureTool.go | 114 ++++++++++++++++++++++++++ com/alipay/api/tools/WebhookTool.go | 29 +++++++ 4 files changed, 162 insertions(+), 64 deletions(-) create mode 100644 com/alipay/api/tools/SignatureTool.go create mode 100644 com/alipay/api/tools/WebhookTool.go diff --git a/CHANGELOG.md b/CHANGELOG.md index f6016d8..bd384fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.2.8 - 2025-01-06 * [#10](https://github.com/alipay/global-open-sdk-go/pull/10) feature-250106 - 订阅支付新增“更新接口” + - 增加验签 ## 1.2.7 - 2024-12-16 * [#9](https://github.com/alipay/global-open-sdk-go/pull/9) feature-241216 diff --git a/com/alipay/api/defaultAlipayClient.go b/com/alipay/api/defaultAlipayClient.go index 4f2184d..50b1ec5 100644 --- a/com/alipay/api/defaultAlipayClient.go +++ b/com/alipay/api/defaultAlipayClient.go @@ -2,22 +2,14 @@ package defaultAlipayClient import ( "bytes" - "crypto" - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "crypto/x509" - "encoding/base64" "encoding/json" - "encoding/pem" "fmt" "github.com/alipay/global-open-sdk-go/com/alipay/api/exception" "github.com/alipay/global-open-sdk-go/com/alipay/api/request" + "github.com/alipay/global-open-sdk-go/com/alipay/api/tools" "io/ioutil" - "log" "net" "net/http" - "net/url" "strconv" "strings" "time" @@ -103,8 +95,15 @@ func (alipayClient *DefaultAlipayClient) httpDo(url, method string, params, head return nil, &exception.AlipayLibraryError{Message: "client.Do is fail " + err.Error()} } defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) + responseTime := resp.Header.Get("response-time") + sign, err := checkRspSign(resp.Request.Method, req.URL.Path, resp.Header.Get("Client-id"), responseTime, string(body), resp.Header.Get("Signature"), alipayClient.AlipayPublicKey) + if err != nil { + return nil, &exception.AlipayLibraryError{Message: "checkRspSign is fail " + err.Error()} + } + if !sign { + return nil, &exception.AlipayLibraryError{Message: "check signature fail"} + } //通过指针将request的response值赋值,这样虽然是any类型, //但是我们在上次必然已经定义了any的类型 err = json.Unmarshal(body, alipayResponse) @@ -123,7 +122,7 @@ func (alipayClient *DefaultAlipayClient) Execute(alipayRequest *request.AlipayRe path := alipayRequest.Path httpMethod := alipayRequest.HttpMethod reqTime := strconv.FormatInt(time.Now().UnixNano(), 10) - sign, err := genSign(fmt.Sprintf("%s", httpMethod), path, alipayClient.ClientId, reqTime, string(reqPayload), getPkcsKeu(alipayClient.MerchantPrivateKey)) + sign, err := tools.GenSign(fmt.Sprintf("%s", httpMethod), path, alipayClient.ClientId, reqTime, string(reqPayload), alipayClient.MerchantPrivateKey) if err != nil { return nil, err } @@ -134,49 +133,18 @@ func (alipayClient *DefaultAlipayClient) Execute(alipayRequest *request.AlipayRe } return alipayResponse, nil } -func getPkcsKeu(key string) string { - return "-----BEGIN PRIVATE KEY-----\n" + key + "\n-----END PRIVATE KEY-----" -} -func genSign(httpMethod string, path string, clientId string, reqTime string, reqBody string, merchantPrivateKey string) (string, error) { - block, _ := pem.Decode([]byte(merchantPrivateKey)) - if block == nil { - return "", &exception.AlipayLibraryError{Message: "Failed to decode private key"} - } - privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) - if err != nil { - return "", &exception.AlipayLibraryError{Message: "Failed to parse private key " + err.Error()} - } - payload := genSignContent(httpMethod, path, clientId, reqTime, reqBody) - signature, err := Sign(privateKey.(*rsa.PrivateKey), []byte(payload)) - if err != nil { - return "", &exception.AlipayLibraryError{Message: "Failed to sign data " + err.Error()} - } - - return signature, nil - -} +func checkRspSign(httpMethod string, path string, clientId string, responseTime string, rspBody string, rspSignValue string, alipayPublicKey string) (bool, error) { -func genSignContent(httpMethod string, path string, clientId string, reqTime string, reqBody string) string { - return httpMethod + " " + path + "\n" + clientId + "." + reqTime + "." + reqBody -} - -func Sign(privateKey *rsa.PrivateKey, data []byte) (string, error) { - // 计算数据的SHA256哈希 - hashed := sha256.Sum256(data) - - // 使用私钥签名哈希值 - signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.Hash.HashFunc(crypto.SHA256), hashed[:]) + signature, err := tools.CheckSignature(path, httpMethod, clientId, responseTime, rspBody, rspSignValue, alipayPublicKey) if err != nil { - return "", err + return false, &exception.AlipayLibraryError{Message: "Failed to check signature " + err.Error()} + } + if !signature { + return false, &exception.AlipayLibraryError{Message: "check signature fail"} + } else { + return true, nil } - //base64编码 如果signature直接传string会造成乱码 - base64Signature := base64.StdEncoding.EncodeToString(signature) - return Encode(base64Signature) -} - -func Encode(signature string) (string, error) { - return url.QueryEscape(signature), nil } func buildBaseHeader(reqTime string, clientId string, keyVersion string, signatureValue string) map[string]string { @@ -192,17 +160,3 @@ func buildBaseHeader(reqTime string, clientId string, keyVersion string, signatu "Signature": signatureValue, } } - -func getJsonValue(jsonValue []byte, jsonField string) any { - var jsonMap map[string]any - - if err := json.Unmarshal(jsonValue, &jsonMap); err != nil { - log.Fatalf("json.Unmarshal is fail: %v \n") - } - - value, ok := jsonMap[jsonField] - if !ok { - log.Fatalf("jsonMap is not contain jsonField: %v \n") - } - return value -} diff --git a/com/alipay/api/tools/SignatureTool.go b/com/alipay/api/tools/SignatureTool.go new file mode 100644 index 0000000..261717d --- /dev/null +++ b/com/alipay/api/tools/SignatureTool.go @@ -0,0 +1,114 @@ +package tools + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "github.com/alipay/global-open-sdk-go/com/alipay/api/exception" + "net/url" +) + +func GenSign(httpMethod string, path string, clientId string, reqTime string, reqBody string, merchantPrivateKey string) (string, error) { + block, _ := pem.Decode([]byte(getPkcsKey(merchantPrivateKey))) + if block == nil { + return "", &exception.AlipayLibraryError{Message: "Failed to decode private key"} + } + privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return "", &exception.AlipayLibraryError{Message: "Failed to parse private key " + err.Error()} + } + payload := genSignContent(httpMethod, path, clientId, reqTime, reqBody) + signature, err := Sign(privateKey.(*rsa.PrivateKey), []byte(payload)) + if err != nil { + return "", &exception.AlipayLibraryError{Message: "Failed to sign data " + err.Error()} + } + + return signature, nil + +} + +func Sign(privateKey *rsa.PrivateKey, data []byte) (string, error) { + // 计算数据的SHA256哈希 + hashed := sha256.Sum256(data) + + // 使用私钥签名哈希值 + signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.Hash.HashFunc(crypto.SHA256), hashed[:]) + if err != nil { + return "", err + } + //base64编码 如果signature直接传string会造成乱码 + base64Signature := base64.StdEncoding.EncodeToString(signature) + return Encode(base64Signature) +} + +func Verify(httpMethod string, path string, clientId string, rspTimeStr string, rspBody string, signature string, alipayPublicKey string) (bool, error) { + rspContent := genSignContent(httpMethod, path, clientId, rspTimeStr, rspBody) + return verifySignatureWithSHA256RSA(rspContent, Decode(signature), alipayPublicKey) +} + +func verifySignatureWithSHA256RSA(rspContent string, signature string, strPk string) (bool, error) { + publicKey, err := getPublicKeyFromBase64String(strPk) + if err != nil { + return false, err + } + + hash := sha256.New() + hash.Write([]byte(rspContent)) + digest := hash.Sum(nil) + + signatureBytes, err := base64.StdEncoding.DecodeString(signature) + if err != nil { + return false, err + } + + err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, digest, signatureBytes) + if err != nil { + return false, err + } + + return true, nil +} + +func getPublicKeyFromBase64String(publicKeyString string) (*rsa.PublicKey, error) { + keyBytes, err := base64.StdEncoding.DecodeString(publicKeyString) + if err != nil { + return nil, err + } + + pub, err := x509.ParsePKIXPublicKey(keyBytes) + if err != nil { + return nil, err + } + + switch pub := pub.(type) { + case *rsa.PublicKey: + return pub, nil + default: + return nil, errors.New("not an RSA public key") + } +} + +func genSignContent(httpMethod string, path string, clientId string, reqTime string, reqBody string) string { + return httpMethod + " " + path + "\n" + clientId + "." + reqTime + "." + reqBody +} + +func Encode(signature string) (string, error) { + return url.QueryEscape(signature), nil +} + +func Decode(originalStr string) string { + unescape, err := url.QueryUnescape(originalStr) + if err != nil { + return "" + } + return unescape +} + +func getPkcsKey(key string) string { + return "-----BEGIN PRIVATE KEY-----\n" + key + "\n-----END PRIVATE KEY-----" +} diff --git a/com/alipay/api/tools/WebhookTool.go b/com/alipay/api/tools/WebhookTool.go new file mode 100644 index 0000000..1b55c80 --- /dev/null +++ b/com/alipay/api/tools/WebhookTool.go @@ -0,0 +1,29 @@ +package tools + +import ( + "errors" + "strings" +) + +func CheckSignature(requestUri, httpMethod, clientId, requestTime, notifyBody, signature, alipayPublicKey string) (bool, error) { + realSignature := "" + + // Check if signature is nil or empty + if signature == "" { + return false, errors.New("empty notify signature") + } + + // Get valid part from raw signature + parts := strings.Split(signature, "signature=") + if len(parts) > 1 { + realSignature = parts[1] + } + + // Verify signature + isValid, _ := Verify(httpMethod, requestUri, clientId, requestTime, notifyBody, realSignature, alipayPublicKey) + if !isValid { + return false, errors.New("signature verification failed") + } + + return true, nil +} From 53e1390498c8d72d356f898f7190cb3aaca91850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9B=BD=E6=A0=8B?= Date: Thu, 9 Jan 2025 10:04:54 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=AA=8C=E7=AD=BE?= =?UTF-8?q?=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- com/alipay/api/tools/WebhookTool.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com/alipay/api/tools/WebhookTool.go b/com/alipay/api/tools/WebhookTool.go index 1b55c80..7e1c7ca 100644 --- a/com/alipay/api/tools/WebhookTool.go +++ b/com/alipay/api/tools/WebhookTool.go @@ -5,7 +5,7 @@ import ( "strings" ) -func CheckSignature(requestUri, httpMethod, clientId, requestTime, notifyBody, signature, alipayPublicKey string) (bool, error) { +func CheckSignature(requestUri, httpMethod, clientId, requestTime, responseBody, signature, alipayPublicKey string) (bool, error) { realSignature := "" // Check if signature is nil or empty @@ -20,7 +20,7 @@ func CheckSignature(requestUri, httpMethod, clientId, requestTime, notifyBody, s } // Verify signature - isValid, _ := Verify(httpMethod, requestUri, clientId, requestTime, notifyBody, realSignature, alipayPublicKey) + isValid, _ := Verify(httpMethod, requestUri, clientId, requestTime, responseBody, realSignature, alipayPublicKey) if !isValid { return false, errors.New("signature verification failed") }