service/vendor/github.com/go-pay/gopay/alipay/common_api.go

236 lines
7.8 KiB
Go
Raw Normal View History

2023-12-21 22:17:40 +08:00
package alipay
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rsa"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/url"
"reflect"
"time"
"github.com/go-pay/gopay"
xaes "github.com/go-pay/gopay/pkg/aes"
"github.com/go-pay/gopay/pkg/util"
"github.com/go-pay/gopay/pkg/xhttp"
"github.com/go-pay/gopay/pkg/xpem"
"github.com/go-pay/gopay/pkg/xrsa"
)
// 格式化请求URL参数
func FormatURLParam(body gopay.BodyMap) (urlParam string) {
v := url.Values{}
for key, value := range body {
v.Add(key, value.(string))
}
return v.Encode()
}
// DecryptOpenDataToStruct 解密支付宝开放数据到 结构体
// encryptedData:包括敏感数据在内的完整用户信息的加密数据
// secretKey:AES密钥支付宝管理平台配置
// beanPtr:需要解析到的结构体指针
// 文档https://opendocs.alipay.com/mini/introduce/aes
// 文档https://opendocs.alipay.com/open/common/104567
func DecryptOpenDataToStruct(encryptedData, secretKey string, beanPtr interface{}) (err error) {
if encryptedData == util.NULL || secretKey == util.NULL {
return errors.New("encryptedData or secretKey is null")
}
beanValue := reflect.ValueOf(beanPtr)
if beanValue.Kind() != reflect.Ptr {
return errors.New("传入参数类型必须是以指针形式")
}
if beanValue.Elem().Kind() != reflect.Struct {
return errors.New("传入interface{}必须是结构体")
}
var (
block cipher.Block
blockMode cipher.BlockMode
originData []byte
)
aesKey, _ := base64.StdEncoding.DecodeString(secretKey)
ivKey := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
secretData, _ := base64.StdEncoding.DecodeString(encryptedData)
if block, err = aes.NewCipher(aesKey); err != nil {
return fmt.Errorf("aes.NewCipher%w", err)
}
if len(secretData)%len(aesKey) != 0 {
return errors.New("encryptedData is error")
}
blockMode = cipher.NewCBCDecrypter(block, ivKey)
originData = make([]byte, len(secretData))
blockMode.CryptBlocks(originData, secretData)
if len(originData) > 0 {
originData = xaes.PKCS5UnPadding(originData)
}
if err = json.Unmarshal(originData, beanPtr); err != nil {
return fmt.Errorf("json.Unmarshal(%s)%w", string(originData), err)
}
return nil
}
// DecryptOpenDataToBodyMap 解密支付宝开放数据到 BodyMap
// encryptedData:包括敏感数据在内的完整用户信息的加密数据
// secretKey:AES密钥支付宝管理平台配置
// 文档https://opendocs.alipay.com/mini/introduce/aes
// 文档https://opendocs.alipay.com/open/common/104567
func DecryptOpenDataToBodyMap(encryptedData, secretKey string) (bm gopay.BodyMap, err error) {
if encryptedData == util.NULL || secretKey == util.NULL {
return nil, errors.New("encryptedData or secretKey is null")
}
var (
aesKey, originData []byte
ivKey = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
block cipher.Block
blockMode cipher.BlockMode
)
aesKey, _ = base64.StdEncoding.DecodeString(secretKey)
secretData, _ := base64.StdEncoding.DecodeString(encryptedData)
if block, err = aes.NewCipher(aesKey); err != nil {
return nil, fmt.Errorf("aes.NewCipher%w", err)
}
if len(secretData)%len(aesKey) != 0 {
return nil, errors.New("encryptedData is error")
}
blockMode = cipher.NewCBCDecrypter(block, ivKey)
originData = make([]byte, len(secretData))
blockMode.CryptBlocks(originData, secretData)
if len(originData) > 0 {
originData = xaes.PKCS5UnPadding(originData)
}
bm = make(gopay.BodyMap)
if err = json.Unmarshal(originData, &bm); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s)%w", string(originData), err)
}
return
}
// SystemOauthToken 换取授权访问令牌默认使用utf-8RSA2
// appId应用ID
// privateKey应用私钥
// grantType值为 authorization_code 时代表用code换取值为 refresh_token 时代表用refresh_token换取传空默认code换取
// codeOrToken支付宝授权码或refresh_token
// signType签名方式 RSA 或 RSA2默认 RSA2
// appAuthToken可选参数三方授权令牌
// 文档https://opendocs.alipay.com/apis/api_9/alipay.system.oauth.token
func SystemOauthToken(ctx context.Context, appId string, privateKey, grantType, codeOrToken, signType string, appAuthToken ...string) (rsp *SystemOauthTokenResponse, err error) {
key := xrsa.FormatAlipayPrivateKey(privateKey)
aat := ""
if len(appAuthToken) > 0 {
aat = appAuthToken[0]
}
priKey, err := xpem.DecodePrivateKey([]byte(key))
if err != nil {
return nil, err
}
var bs []byte
bm := make(gopay.BodyMap)
switch grantType {
case "authorization_code":
bm.Set("grant_type", "authorization_code")
bm.Set("code", codeOrToken)
case "refresh_token":
bm.Set("grant_type", "refresh_token")
bm.Set("refresh_token", codeOrToken)
default:
bm.Set("grant_type", "authorization_code")
bm.Set("code", codeOrToken)
}
if bs, err = systemOauthToken(ctx, appId, priKey, bm, "alipay.system.oauth.token", true, signType, aat); err != nil {
return
}
rsp = new(SystemOauthTokenResponse)
if err = json.Unmarshal(bs, rsp); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s)%w", string(bs), err)
}
if (rsp.Response == nil) || (rsp.Response != nil && rsp.Response.AccessToken == "") {
return nil, errors.New("response is nil or access_token is NULL")
}
return
}
// systemOauthToken 向支付宝发送请求
func systemOauthToken(ctx context.Context, appId string, privateKey *rsa.PrivateKey, bm gopay.BodyMap, method string, isProd bool, signType, appAuthToken string) (bs []byte, err error) {
bm.Set("app_id", appId)
bm.Set("method", method)
bm.Set("format", "JSON")
bm.Set("charset", "utf-8")
if signType == util.NULL {
bm.Set("sign_type", RSA2)
} else {
bm.Set("sign_type", signType)
}
bm.Set("timestamp", time.Now().Format(util.TimeLayout))
bm.Set("version", "1.0")
if appAuthToken != util.NULL {
bm.Set("app_auth_token", appAuthToken)
}
var (
sign string
baseUrl = baseUrlUtf8
)
if sign, err = GetRsaSign(bm, bm.GetString("sign_type"), privateKey); err != nil {
return nil, err
}
bm.Set("sign", sign)
if !isProd {
baseUrl = sandboxBaseUrlUtf8
}
_, bs, err = xhttp.NewClient().Type(xhttp.TypeForm).Post(baseUrl).SendString(bm.EncodeURLParams()).EndBytes(ctx)
if err != nil {
return nil, err
}
return bs, nil
}
// monitor.heartbeat.syn(验签接口)
// appId应用ID
// privateKey应用私钥支持PKCS1和PKCS8
// signType签名方式 alipay.RSA 或 alipay.RSA2默认 RSA2
// bizContent验签时该参数不做任何处理{任意值},此参数具体看文档
// 文档https://opendocs.alipay.com/apis/api_9/monitor.heartbeat.syn
func MonitorHeartbeatSyn(ctx context.Context, appId string, privateKey, signType, bizContent string) (aliRsp *MonitorHeartbeatSynResponse, err error) {
key := xrsa.FormatAlipayPrivateKey(privateKey)
priKey, err := xpem.DecodePrivateKey([]byte(key))
if err != nil {
return nil, err
}
var bs []byte
bm := make(gopay.BodyMap)
bm.Set("biz_content", bizContent)
bm.Set("app_id", appId)
bm.Set("method", "monitor.heartbeat.syn")
bm.Set("format", "JSON")
bm.Set("charset", "utf-8")
if signType == util.NULL {
bm.Set("sign_type", RSA2)
} else {
bm.Set("sign_type", signType)
}
bm.Set("timestamp", time.Now().Format(util.TimeLayout))
bm.Set("version", "1.0")
sign, err := GetRsaSign(bm, bm.GetString("sign_type"), priKey)
if err != nil {
return nil, err
}
bm.Set("sign", sign)
_, bs, err = xhttp.NewClient().Type(xhttp.TypeForm).Post(baseUrlUtf8).SendString(bm.EncodeURLParams()).EndBytes(ctx)
if err != nil {
return nil, err
}
aliRsp = new(MonitorHeartbeatSynResponse)
if err = json.Unmarshal(bs, aliRsp); err != nil || aliRsp.Response == nil {
return nil, fmt.Errorf("[%w], bytes: %s", gopay.UnmarshalErr, string(bs))
}
if err = bizErrCheck(aliRsp.Response.ErrorResponse); err != nil {
return aliRsp, err
}
return aliRsp, nil
}