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

536 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package alipay
import (
"context"
"crypto/rsa"
"encoding/json"
"fmt"
"time"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
"github.com/go-pay/gopay/pkg/xhttp"
"github.com/go-pay/gopay/pkg/xlog"
"github.com/go-pay/gopay/pkg/xpem"
"github.com/go-pay/gopay/pkg/xrsa"
)
type Client struct {
AppId string
AppCertSN string
AliPayPublicCertSN string
AliPayRootCertSN string
ReturnUrl string
NotifyUrl string
Charset string
SignType string
AppAuthToken string
IsProd bool
bodySize int // http response body size(MB), default is 10MB
privateKey *rsa.PrivateKey
aliPayPublicKey *rsa.PublicKey // 支付宝证书公钥内容 alipayPublicCert.crt
autoSign bool
DebugSwitch gopay.DebugSwitch
location *time.Location
}
// 初始化支付宝客户端
// 注意:如果使用支付宝公钥证书验签,请设置 支付宝根证书SNclient.SetAlipayRootCertSN()、应用公钥证书SNclient.SetAppCertSN()
// appid应用ID
// privateKey应用私钥支持PKCS1和PKCS8
// isProd是否是正式环境沙箱环境请选择新版沙箱应用。
func NewClient(appid, privateKey string, isProd bool) (client *Client, err error) {
if appid == util.NULL || privateKey == util.NULL {
return nil, gopay.MissAlipayInitParamErr
}
key := xrsa.FormatAlipayPrivateKey(privateKey)
priKey, err := xpem.DecodePrivateKey([]byte(key))
if err != nil {
return nil, err
}
client = &Client{
AppId: appid,
Charset: UTF8,
SignType: RSA2,
IsProd: isProd,
privateKey: priKey,
DebugSwitch: gopay.DebugOff,
}
return client, nil
}
// 开启请求完自动验签功能(默认不开启,推荐开启,只支持证书模式)
// 注意:只支持证书模式
// alipayPublicKeyContent支付宝公钥证书文件内容[]byte
func (a *Client) AutoVerifySign(alipayPublicKeyContent []byte) {
pubKey, err := xpem.DecodePublicKey(alipayPublicKeyContent)
if err != nil {
xlog.Errorf("AutoVerifySign(%s),err:%+v", alipayPublicKeyContent, err)
}
if pubKey != nil {
a.aliPayPublicKey = pubKey
a.autoSign = true
}
}
// SetBodySize 设置http response body size(MB)
func (a *Client) SetBodySize(sizeMB int) {
if sizeMB > 0 {
a.bodySize = sizeMB
}
}
// Deprecated
// 推荐使用 PostAliPayAPISelfV2()
// 示例:请参考 client_test.go 的 TestClient_PostAliPayAPISelf() 方法
func (a *Client) PostAliPayAPISelf(ctx context.Context, bm gopay.BodyMap, method string, aliRsp interface{}) (err error) {
var bs []byte
if bs, err = a.doAliPay(ctx, bm, method); err != nil {
return err
}
if err = json.Unmarshal(bs, aliRsp); err != nil {
return err
}
return nil
}
// Deprecated
// 推荐使用 RequestParam()
func (a *Client) GetRequestSignParam(bm gopay.BodyMap, method string) (string, error) {
return a.RequestParam(bm, method)
}
// RequestParam 获取支付宝完整请求参数包含签名
// 注意biz_content 需要自行通过bm.SetBodyMap()设置,不设置则没有此参数
func (a *Client) RequestParam(bm gopay.BodyMap, method string) (string, error) {
var (
bodyBs []byte
err error
sign string
)
// check if there is biz_content
bz := bm.GetInterface("biz_content")
if bzBody, ok := bz.(gopay.BodyMap); ok {
if bodyBs, err = json.Marshal(bzBody); err != nil {
return "", fmt.Errorf("json.Marshal(%v)%w", bzBody, err)
}
bm.Set("biz_content", string(bodyBs))
}
bm.Set("method", method)
// check public parameter
a.checkPublicParam(bm)
// check sign
if bm.GetString("sign") == "" {
sign, err = a.getRsaSign(bm, bm.GetString("sign_type"), a.privateKey)
if err != nil {
return "", fmt.Errorf("GetRsaSign Error: %w", err)
}
bm.Set("sign", sign)
}
if a.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Alipay_Request: %s", bm.JsonBody())
}
return bm.EncodeURLParams(), nil
}
// PostAliPayAPISelfV2 支付宝接口自行实现方法
// 注意biz_content 需要自行通过bm.SetBodyMap()设置,不设置则没有此参数
// 示例:请参考 client_test.go 的 TestClient_PostAliPayAPISelfV2() 方法
func (a *Client) PostAliPayAPISelfV2(ctx context.Context, bm gopay.BodyMap, method string, aliRsp interface{}) (err error) {
var (
bs, bodyBs []byte
)
// check if there is biz_content
bz := bm.GetInterface("biz_content")
if bzBody, ok := bz.(gopay.BodyMap); ok {
if bodyBs, err = json.Marshal(bzBody); err != nil {
return fmt.Errorf("json.Marshal(%v)%w", bzBody, err)
}
bm.Set("biz_content", string(bodyBs))
}
if bs, err = a.doAliPaySelf(ctx, bm, method); err != nil {
return err
}
if err = json.Unmarshal(bs, aliRsp); err != nil {
return err
}
return nil
}
// 向支付宝发送自定义请求
func (a *Client) doAliPaySelf(ctx context.Context, bm gopay.BodyMap, method string) (bs []byte, err error) {
var (
url, sign string
)
bm.Set("method", method)
// check public parameter
a.checkPublicParam(bm)
// check sign
if bm.GetString("sign") == "" {
sign, err = a.getRsaSign(bm, bm.GetString("sign_type"), a.privateKey)
if err != nil {
return nil, fmt.Errorf("GetRsaSign Error: %w", err)
}
bm.Set("sign", sign)
}
if a.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Alipay_Request: %s", bm.JsonBody())
}
httpClient := xhttp.NewClient()
if a.bodySize > 0 {
httpClient.SetBodySize(a.bodySize)
}
if a.IsProd {
url = baseUrlUtf8
} else {
url = sandboxBaseUrlUtf8
}
res, bs, err := httpClient.Type(xhttp.TypeForm).Post(url).SendString(bm.EncodeURLParams()).EndBytes(ctx)
if err != nil {
return nil, err
}
if a.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs))
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode)
}
return bs, nil
}
// 向支付宝发送请求
func (a *Client) doAliPay(ctx context.Context, bm gopay.BodyMap, method string, authToken ...string) (bs []byte, err error) {
var (
bizContent, url string
bodyBs []byte
)
if bm != nil {
_, has := appAuthTokenInBizContent[method]
if has {
if bodyBs, err = json.Marshal(bm); err != nil {
return nil, fmt.Errorf("json.Marshal%w", err)
}
bizContent = string(bodyBs)
bm.Remove("app_auth_token")
} else {
aat := bm.GetString("app_auth_token")
bm.Remove("app_auth_token")
if bodyBs, err = json.Marshal(bm); err != nil {
return nil, fmt.Errorf("json.Marshal%w", err)
}
bizContent = string(bodyBs)
bm.Set("app_auth_token", aat)
}
}
// 处理公共参数
param, err := a.pubParamsHandle(bm, method, bizContent, authToken...)
if err != nil {
return nil, err
}
switch method {
case "alipay.trade.app.pay", "alipay.fund.auth.order.app.freeze":
return []byte(param), nil
case "alipay.trade.wap.pay", "alipay.trade.page.pay", "alipay.user.certify.open.certify":
if !a.IsProd {
return []byte(sandboxBaseUrl + "?" + param), nil
}
return []byte(baseUrl + "?" + param), nil
default:
httpClient := xhttp.NewClient()
if a.bodySize > 0 {
httpClient.SetBodySize(a.bodySize)
}
url = baseUrlUtf8
if !a.IsProd {
url = sandboxBaseUrlUtf8
}
res, bs, err := httpClient.Type(xhttp.TypeForm).Post(url).SendString(param).EndBytes(ctx)
if err != nil {
return nil, err
}
if a.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs))
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode)
}
return bs, nil
}
}
// 向支付宝发送请求
func (a *Client) DoAliPay(ctx context.Context, bm gopay.BodyMap, method string, authToken ...string) (bs []byte, err error) {
var (
bizContent, url string
bodyBs []byte
)
if bm != nil {
_, has := appAuthTokenInBizContent[method]
if has {
if bodyBs, err = json.Marshal(bm); err != nil {
return nil, fmt.Errorf("json.Marshal%w", err)
}
bizContent = string(bodyBs)
bm.Remove("app_auth_token")
} else {
aat := bm.GetString("app_auth_token")
bm.Remove("app_auth_token")
if bodyBs, err = json.Marshal(bm); err != nil {
return nil, fmt.Errorf("json.Marshal%w", err)
}
bizContent = string(bodyBs)
bm.Set("app_auth_token", aat)
}
}
// 处理公共参数
param, err := a.pubParamsHandle(bm, method, bizContent, authToken...)
if err != nil {
return nil, err
}
switch method {
case "alipay.trade.app.pay", "alipay.fund.auth.order.app.freeze":
return []byte(param), nil
case "alipay.trade.wap.pay", "alipay.trade.page.pay", "alipay.user.certify.open.certify":
if !a.IsProd {
return []byte(sandboxBaseUrl + "?" + param), nil
}
return []byte(baseUrl + "?" + param), nil
default:
httpClient := xhttp.NewClient()
if a.bodySize > 0 {
httpClient.SetBodySize(a.bodySize)
}
url = baseUrlUtf8
if !a.IsProd {
url = sandboxBaseUrlUtf8
}
res, bs, err := httpClient.Type(xhttp.TypeForm).Post(url).SendString(param).EndBytes(ctx)
if err != nil {
return nil, err
}
if a.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs))
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode)
}
return bs, nil
}
}
// 保持和官方 SDK 命名方式一致
func (a *Client) PageExecute(ctx context.Context, bm gopay.BodyMap, method string, authToken ...string) (url string, err error) {
var (
bizContent string
bodyBs []byte
)
if bm != nil {
_, has := appAuthTokenInBizContent[method]
if has {
if bodyBs, err = json.Marshal(bm); err != nil {
return "", fmt.Errorf("json.Marshal%w", err)
}
bizContent = string(bodyBs)
bm.Remove("app_auth_token")
} else {
aat := bm.GetString("app_auth_token")
bm.Remove("app_auth_token")
if bodyBs, err = json.Marshal(bm); err != nil {
return "", fmt.Errorf("json.Marshal%w", err)
}
bizContent = string(bodyBs)
bm.Set("app_auth_token", aat)
}
}
// 处理公共参数
param, err := a.pubParamsHandle(bm, method, bizContent, authToken...)
if err != nil {
return "", err
}
if !a.IsProd {
return sandboxBaseUrl + "?" + param, nil
}
return baseUrl + "?" + param, nil
}
// 公共参数处理
func (a *Client) pubParamsHandle(bm gopay.BodyMap, method, bizContent string, authToken ...string) (param string, err error) {
pubBody := make(gopay.BodyMap)
pubBody.Set("app_id", a.AppId).
Set("method", method).
Set("format", "JSON").
Set("charset", a.Charset).
Set("sign_type", a.SignType).
Set("version", "1.0").
Set("timestamp", time.Now().Format(util.TimeLayout))
// version
if version := bm.GetString("version"); version != util.NULL {
pubBody.Set("version", version)
}
if a.AppCertSN != util.NULL {
pubBody.Set("app_cert_sn", a.AppCertSN)
}
if a.AliPayRootCertSN != util.NULL {
pubBody.Set("alipay_root_cert_sn", a.AliPayRootCertSN)
}
// return_url
if a.ReturnUrl != util.NULL {
pubBody.Set("return_url", a.ReturnUrl)
}
if returnUrl := bm.GetString("return_url"); returnUrl != util.NULL {
pubBody.Set("return_url", returnUrl)
}
if a.location != nil {
pubBody.Set("timestamp", time.Now().In(a.location).Format(util.TimeLayout))
}
// notify_url
if a.NotifyUrl != util.NULL {
pubBody.Set("notify_url", a.NotifyUrl)
}
if notifyUrl := bm.GetString("notify_url"); notifyUrl != util.NULL {
pubBody.Set("notify_url", notifyUrl)
}
// default use app_auth_token
if a.AppAuthToken != util.NULL {
pubBody.Set("app_auth_token", a.AppAuthToken)
}
// if user set app_auth_token in body_map, use this
if aat := bm.GetString("app_auth_token"); aat != util.NULL {
pubBody.Set("app_auth_token", aat)
}
if len(authToken) > 0 {
pubBody.Set("auth_token", authToken[0])
}
if bizContent != util.NULL {
pubBody.Set("biz_content", bizContent)
}
// sign
sign, err := a.getRsaSign(pubBody, pubBody.GetString("sign_type"), a.privateKey)
if err != nil {
return "", fmt.Errorf("GetRsaSign Error: %w", err)
}
pubBody.Set("sign", sign)
if a.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Alipay_Request: %s", pubBody.JsonBody())
}
param = pubBody.EncodeURLParams()
return
}
// 公共参数检查
func (a *Client) checkPublicParam(bm gopay.BodyMap) {
bm.Set("format", "JSON").
Set("charset", a.Charset).
Set("sign_type", a.SignType).
Set("version", "1.0").
Set("timestamp", time.Now().Format(util.TimeLayout))
if bm.GetString("app_id") == "" && a.AppId != util.NULL {
bm.Set("app_id", a.AppId)
}
if bm.GetString("app_cert_sn") == "" && a.AppCertSN != util.NULL {
bm.Set("app_cert_sn", a.AppCertSN)
}
if bm.GetString("alipay_root_cert_sn") == "" && a.AliPayRootCertSN != util.NULL {
bm.Set("alipay_root_cert_sn", a.AliPayRootCertSN)
}
if bm.GetString("return_url") == "" && a.ReturnUrl != util.NULL {
bm.Set("return_url", a.ReturnUrl)
}
if a.location != nil {
bm.Set("timestamp", time.Now().In(a.location).Format(util.TimeLayout))
}
if bm.GetString("notify_url") == "" && a.NotifyUrl != util.NULL {
bm.Set("notify_url", a.NotifyUrl)
}
if bm.GetString("app_auth_token") == "" && a.AppAuthToken != util.NULL {
bm.Set("app_auth_token", a.AppAuthToken)
}
}
// 文件上传
func (a *Client) FileRequest(ctx context.Context, bm gopay.BodyMap, file *util.File, method string) (bs []byte, err error) {
var (
bodyStr string
bodyBs []byte
aat string
)
if bm != nil {
aat = bm.GetString("app_auth_token")
bm.Remove("app_auth_token")
if bodyBs, err = json.Marshal(bm); err != nil {
return nil, fmt.Errorf("json.Marshal%w", err)
}
bodyStr = string(bodyBs)
}
pubBody := make(gopay.BodyMap)
pubBody.Set("app_id", a.AppId).
Set("method", method).
Set("format", "JSON").
Set("charset", a.Charset).
Set("sign_type", a.SignType).
Set("version", "1.0").
Set("scene", "SYNC_ORDER").
Set("timestamp", time.Now().Format(util.TimeLayout))
if a.AppCertSN != util.NULL {
pubBody.Set("app_cert_sn", a.AppCertSN)
}
if a.AliPayRootCertSN != util.NULL {
pubBody.Set("alipay_root_cert_sn", a.AliPayRootCertSN)
}
if a.ReturnUrl != util.NULL {
pubBody.Set("return_url", a.ReturnUrl)
}
if a.location != nil {
pubBody.Set("timestamp", time.Now().In(a.location).Format(util.TimeLayout))
}
if a.NotifyUrl != util.NULL { //如果返回url为空传过来的返回url不为空
//fmt.Println("url不为空", a.NotifyUrl)
pubBody.Set("notify_url", a.NotifyUrl)
}
//fmt.Println("notify,", pubBody.JsonBody())
if a.AppAuthToken != util.NULL {
pubBody.Set("app_auth_token", a.AppAuthToken)
}
if aat != util.NULL {
pubBody.Set("app_auth_token", aat)
}
if bodyStr != util.NULL {
pubBody.Set("biz_content", bodyStr)
}
sign, err := a.getRsaSign(pubBody, pubBody.GetString("sign_type"), a.privateKey)
if err != nil {
return nil, fmt.Errorf("GetRsaSign Error: %w", err)
}
//pubBody.Set("file_content", file.Content)
pubBody.Set("sign", sign)
if a.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Alipay_Request: %s", pubBody.JsonBody())
}
param := pubBody.EncodeURLParams()
url := baseUrlUtf8 + "&" + param
bm.Reset()
bm.SetFormFile("file_content", file)
httpClient := xhttp.NewClient()
res, bs, err := httpClient.Type(xhttp.TypeMultipartFormData).Post(url).
SendMultipartBodyMap(bm).EndBytes(ctx)
if err != nil {
return nil, err
}
if a.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Alipay_Response: %s%d %s%s", xlog.Red, res.StatusCode, xlog.Reset, string(bs))
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("HTTP Request Error, StatusCode = %d", res.StatusCode)
}
return bs, nil
}