-
Notifications
You must be signed in to change notification settings - Fork 781
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
20 changed files
with
1,534 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package alipay | ||
|
||
import ( | ||
"crypto/md5" | ||
"crypto/x509" | ||
"encoding/hex" | ||
"encoding/pem" | ||
"errors" | ||
"os" | ||
"strings" | ||
|
||
"github.com/go-pay/gopay" | ||
) | ||
|
||
// 允许进行 sn 提取的证书签名算法 | ||
var allowSignatureAlgorithm = map[string]bool{ | ||
"MD2-RSA": true, | ||
"MD5-RSA": true, | ||
"SHA1-RSA": true, | ||
"SHA256-RSA": true, | ||
"SHA384-RSA": true, | ||
"SHA512-RSA": true, | ||
"SHA256-RSAPSS": true, | ||
"SHA384-RSAPSS": true, | ||
"SHA512-RSAPSS": true, | ||
} | ||
|
||
// GetCertSN 获取证书序列号SN | ||
// certPathOrData x509证书文件路径(appPublicCert.crt、alipayPublicCert.crt) 或证书 buffer | ||
// 返回 sn:证书序列号(app_cert_sn、alipay_cert_sn) | ||
// 返回 err:error 信息 | ||
func GetCertSN(certPathOrData any) (sn string, err error) { | ||
var certData []byte | ||
switch pathOrData := certPathOrData.(type) { | ||
case string: | ||
certData, err = os.ReadFile(pathOrData) | ||
if err != nil { | ||
return gopay.NULL, err | ||
} | ||
case []byte: | ||
certData = pathOrData | ||
default: | ||
return gopay.NULL, errors.New("certPathOrData 证书类型断言错误") | ||
} | ||
|
||
if block, _ := pem.Decode(certData); block != nil { | ||
cert, err := x509.ParseCertificate(block.Bytes) | ||
if err != nil { | ||
return gopay.NULL, err | ||
} | ||
name := cert.Issuer.String() | ||
serialNumber := cert.SerialNumber.String() | ||
h := md5.New() | ||
h.Write([]byte(name)) | ||
h.Write([]byte(serialNumber)) | ||
sn = hex.EncodeToString(h.Sum(nil)) | ||
} | ||
if sn == gopay.NULL { | ||
return gopay.NULL, errors.New("failed to get sn,please check your cert") | ||
} | ||
return sn, nil | ||
} | ||
|
||
// GetRootCertSN 获取root证书序列号SN | ||
// rootCertPathOrData x509证书文件路径(alipayRootCert.crt) 或文件 buffer | ||
// 返回 sn:证书序列号(alipay_root_cert_sn) | ||
// 返回 err:error 信息 | ||
func GetRootCertSN(rootCertPathOrData any) (sn string, err error) { | ||
var ( | ||
certData []byte | ||
certEnd = `-----END CERTIFICATE-----` | ||
) | ||
switch pathOrData := rootCertPathOrData.(type) { | ||
case string: | ||
certData, err = os.ReadFile(pathOrData) | ||
if err != nil { | ||
return gopay.NULL, err | ||
} | ||
case []byte: | ||
certData = pathOrData | ||
default: | ||
return gopay.NULL, errors.New("rootCertPathOrData 断言异常") | ||
} | ||
|
||
pems := strings.Split(string(certData), certEnd) | ||
for _, c := range pems { | ||
if block, _ := pem.Decode([]byte(c + certEnd)); block != nil { | ||
cert, err := x509.ParseCertificate(block.Bytes) | ||
if err != nil { | ||
continue | ||
} | ||
if !allowSignatureAlgorithm[cert.SignatureAlgorithm.String()] { | ||
continue | ||
} | ||
name := cert.Issuer.String() | ||
serialNumber := cert.SerialNumber.String() | ||
h := md5.New() | ||
h.Write([]byte(name)) | ||
h.Write([]byte(serialNumber)) | ||
if sn == gopay.NULL { | ||
sn += hex.EncodeToString(h.Sum(nil)) | ||
} else { | ||
sn += "_" + hex.EncodeToString(h.Sum(nil)) | ||
} | ||
} | ||
} | ||
if sn == gopay.NULL { | ||
return gopay.NULL, errors.New("failed to get sn,please check your cert") | ||
} | ||
return sn, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package alipay | ||
|
||
import ( | ||
"crypto/rsa" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/go-pay/crypto/xpem" | ||
"github.com/go-pay/crypto/xrsa" | ||
"github.com/go-pay/gopay" | ||
"github.com/go-pay/gopay/pkg/xhttp" | ||
"github.com/go-pay/xlog" | ||
) | ||
|
||
// ClientV3 支付宝 V3 | ||
type ClientV3 struct { | ||
AppId string | ||
AppCertSN string | ||
AliPayPublicCertSN string | ||
AliPayRootCertSN string | ||
AppAuthToken string | ||
IsProd bool | ||
aesKey string // biz_content 加密的 AES KEY | ||
ivKey []byte | ||
privateKey *rsa.PrivateKey | ||
aliPayPublicKey *rsa.PublicKey // 支付宝证书公钥内容 alipayPublicCert.crt | ||
DebugSwitch gopay.DebugSwitch | ||
logger xlog.XLogger | ||
requestIdFunc xhttp.RequestIdHandler | ||
location *time.Location | ||
hc *xhttp.Client | ||
} | ||
|
||
// NewClientV3 初始化支付宝客户端 V3 | ||
// appid:应用ID | ||
// privateKey:应用私钥,支持PKCS1和PKCS8 | ||
// isProd:是否是正式环境,沙箱环境请选择新版沙箱应用。 | ||
func NewClientV3(appid, privateKey string, isProd bool) (client *ClientV3, err error) { | ||
if appid == gopay.NULL || privateKey == gopay.NULL { | ||
return nil, gopay.MissAlipayInitParamErr | ||
} | ||
key := xrsa.FormatAlipayPrivateKey(privateKey) | ||
priKey, err := xpem.DecodePrivateKey([]byte(key)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
logger := xlog.NewLogger() | ||
logger.SetLevel(xlog.DebugLevel) | ||
client = &ClientV3{ | ||
AppId: appid, | ||
IsProd: isProd, | ||
privateKey: priKey, | ||
DebugSwitch: gopay.DebugOff, | ||
logger: logger, | ||
requestIdFunc: defaultRequestIdFunc, | ||
hc: xhttp.NewClient(), | ||
} | ||
return client, nil | ||
} | ||
|
||
// 设置自定义RequestId生成函数 | ||
func (a *ClientV3) SetRequestIdFunc(requestIdFunc xhttp.RequestIdHandler) { | ||
if requestIdFunc != nil { | ||
a.requestIdFunc = requestIdFunc | ||
} | ||
} | ||
|
||
// 应用公钥证书内容设置 app_cert_sn、alipay_root_cert_sn、alipay_cert_sn | ||
// appCertContent:应用公钥证书文件内容 | ||
// alipayRootCertContent:支付宝根证书文件内容 | ||
// alipayPublicCertContent:支付宝公钥证书文件内容 | ||
func (a *ClientV3) SetCert(appCertContent, alipayRootCertContent, alipayPublicCertContent []byte) (err error) { | ||
appCertSn, err := GetCertSN(appCertContent) | ||
if err != nil { | ||
return fmt.Errorf("get app_cert_sn return err, but alse return alipay client. err: %w", err) | ||
} | ||
rootCertSn, err := GetRootCertSN(alipayRootCertContent) | ||
if err != nil { | ||
return fmt.Errorf("get alipay_root_cert_sn return err, but alse return alipay client. err: %w", err) | ||
} | ||
publicCertSn, err := GetCertSN(alipayPublicCertContent) | ||
if err != nil { | ||
return fmt.Errorf("get alipay_cert_sn return err, but alse return alipay client. err: %w", err) | ||
} | ||
|
||
// alipay public key | ||
pubKey, err := xpem.DecodePublicKey(alipayPublicCertContent) | ||
if err != nil { | ||
return fmt.Errorf("decode alipayPublicCertContent err: %w", err) | ||
} | ||
|
||
a.AppCertSN = appCertSn | ||
a.AliPayRootCertSN = rootCertSn | ||
a.AliPayPublicCertSN = publicCertSn | ||
a.aliPayPublicKey = pubKey | ||
return nil | ||
} | ||
|
||
// SetAESKey 设置 biz_content 的AES加密key,设置此参数默认开启 biz_content 参数加密 | ||
// 注意:目前不可用,设置后会报错 | ||
func (a *ClientV3) SetAESKey(aesKey string) { | ||
a.aesKey = aesKey | ||
a.ivKey = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package alipay | ||
|
||
import ( | ||
"context" | ||
"os" | ||
"testing" | ||
|
||
"github.com/go-pay/gopay" | ||
"github.com/go-pay/gopay/alipay/cert" | ||
"github.com/go-pay/xlog" | ||
) | ||
|
||
var ( | ||
ctx = context.Background() | ||
client *ClientV3 | ||
err error | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
xlog.SetLevel(xlog.DebugLevel) | ||
// 初始化支付宝客户端 | ||
// appid:应用ID | ||
// privateKey:应用私钥,支持PKCS1和PKCS8 | ||
// isProd:是否是正式环境,沙箱环境请选择新版沙箱应用。 | ||
client, err = NewClientV3(cert.Appid, cert.PrivateKey, false) | ||
if err != nil { | ||
xlog.Error(err) | ||
return | ||
} | ||
// Debug开关,输出/关闭日志 | ||
client.DebugSwitch = gopay.DebugOn | ||
|
||
// 设置biz_content加密KEY,设置此参数默认开启加密(目前不可用,设置后会报错) | ||
//client.SetAESKey("KvKUTqSVZX2fUgmxnFyMaQ==") | ||
|
||
// 传入证书内容 | ||
err := client.SetCert(cert.AppPublicContent, cert.AlipayRootContent, cert.AlipayPublicContentRSA2) | ||
// 传入证书文件路径 | ||
//err := client.SetCertSnByPath("cert/appPublicCert.crt", "cert/alipayRootCert.crt", "cert/alipayPublicCert.crt") | ||
if err != nil { | ||
xlog.Debug("SetCert:", err) | ||
return | ||
} | ||
os.Exit(m.Run()) | ||
} |
Oops, something went wrong.