Skip to content

Commit

Permalink
Merge pull request #12 from alipay/fearure-250106
Browse files Browse the repository at this point in the history
Fearure 250106
  • Loading branch information
ScottWryyyyy authored Jan 17, 2025
2 parents 3b6d595 + 08cf9f0 commit c504ec0
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 67 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 1.2.9 - 2025-01-06
* [#11](https://github.com/alipay/global-open-sdk-go/pull/11) feature-250106
- 订阅支付新增“更新接口”
- 增加验签

## 1.2.8 - 2024-12-24
* [#10](https://github.com/alipay/global-open-sdk-go/pull/10) feature-241224
- CKP二期支持商户传入可选支付方式列表
Expand Down
82 changes: 18 additions & 64 deletions com/alipay/api/defaultAlipayClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
Expand All @@ -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 {
Expand All @@ -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
}
1 change: 1 addition & 0 deletions com/alipay/api/model/AntomPathConstants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 4 additions & 2 deletions com/alipay/api/model/Discount.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package model

type Discount struct {
DiscountName string `json:"discountName,omitempty"`
SavingsAmount Amount `json:"savingsAmount,omitempty"`
DiscountTag string `json:"discountTag,omitempty"`
DiscountName string `json:"discountName,omitempty"`
SavingsAmount Amount `json:"savingsAmount,omitempty"`
EstimateSavingsAmount Amount `json:"estimateSavingsAmount,omitempty"`
}
25 changes: 25 additions & 0 deletions com/alipay/api/request/auth/AlipayAuthCreateSessionRequest.go
Original file line number Diff line number Diff line change
@@ -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"`
PaymentRedirectUrl string `json:"paymentRedirectUrl,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
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ type AlipaySubscriptionPayNotify struct {
SubscriptionId string `json:"subscriptionId,omitempty"`
PeriodStartTime string `json:"periodStartTime,omitempty"`
PeriodEndTime string `json:"periodEndTime,omitempty"`
PhaseNo int `json:"phaseNo,omitempty"`
PhaseNo string `json:"phaseNo,omitempty"`
}
Original file line number Diff line number Diff line change
@@ -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{})
}
10 changes: 10 additions & 0 deletions com/alipay/api/response/auth/AlipayAuthCreateSessionResponse.go
Original file line number Diff line number Diff line change
@@ -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"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package responseSubscription

import "github.com/alipay/global-open-sdk-go/com/alipay/api/response"

type AlipaySubscriptionUpdateResponse struct {
response.AlipayResponse
}
114 changes: 114 additions & 0 deletions com/alipay/api/tools/SignatureTool.go
Original file line number Diff line number Diff line change
@@ -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-----"
}
29 changes: 29 additions & 0 deletions com/alipay/api/tools/WebhookTool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package tools

import (
"errors"
"strings"
)

func CheckSignature(requestUri, httpMethod, clientId, requestTime, responseBody, 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, responseBody, realSignature, alipayPublicKey)
if !isValid {
return false, errors.New("signature verification failed")
}

return true, nil
}

0 comments on commit c504ec0

Please sign in to comment.