118 lines
3.4 KiB
Go
118 lines
3.4 KiB
Go
package pay
|
||
|
||
import (
|
||
"crypto"
|
||
crand "crypto/rand"
|
||
"crypto/rsa"
|
||
"crypto/sha256"
|
||
"crypto/x509"
|
||
"encoding/base64"
|
||
"encoding/pem"
|
||
"errors"
|
||
"fmt"
|
||
mrand "math/rand"
|
||
"os"
|
||
"sync"
|
||
"time"
|
||
|
||
"bindbox-game/configs"
|
||
)
|
||
|
||
// 私钥缓存 - 避免每次请求都从磁盘读取
|
||
var (
|
||
cachedRSAKey *rsa.PrivateKey
|
||
rsaKeyOnce sync.Once
|
||
rsaKeyLoadErr error
|
||
rsaKeyConfigPath string // 记录加载时的路径,用于检测配置变更
|
||
)
|
||
|
||
// loadRSAPrivateKey 从磁盘加载私钥(内部函数,仅在首次调用时执行)
|
||
func loadRSAPrivateKey(keyPath string) (*rsa.PrivateKey, error) {
|
||
b, err := os.ReadFile(keyPath)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
block, _ := pem.Decode(b)
|
||
if block == nil {
|
||
return nil, errors.New("invalid merchant private key pem")
|
||
}
|
||
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||
if err != nil {
|
||
// 兼容PKCS1
|
||
k1, e1 := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||
if e1 != nil {
|
||
return nil, err
|
||
}
|
||
key = k1
|
||
}
|
||
rsaKey, ok := key.(*rsa.PrivateKey)
|
||
if !ok {
|
||
return nil, errors.New("merchant private key is not RSA")
|
||
}
|
||
return rsaKey, nil
|
||
}
|
||
|
||
// getCachedRSAKey 获取缓存的RSA私钥
|
||
func getCachedRSAKey(keyPath string) (*rsa.PrivateKey, error) {
|
||
rsaKeyOnce.Do(func() {
|
||
rsaKeyConfigPath = keyPath
|
||
cachedRSAKey, rsaKeyLoadErr = loadRSAPrivateKey(keyPath)
|
||
})
|
||
// 如果配置路径变更(理论上不应该发生),返回错误以提示重启
|
||
if rsaKeyConfigPath != keyPath {
|
||
return nil, errors.New("private key path changed, please restart the server")
|
||
}
|
||
return cachedRSAKey, rsaKeyLoadErr
|
||
}
|
||
|
||
// BuildJSAPIParams 为小程序支付构造客户端参数
|
||
// 入参:appid(微信小程序AppID)、prepayID(统一下单返回的prepay_id)
|
||
// 返回:timeStamp、nonceStr、package(格式为"prepay_id=***" )、signType(固定"RSA")、paySign(RSA-SHA256签名)
|
||
// 错误:当私钥读取或签名失败时返回错误
|
||
func BuildJSAPIParams(appid string, prepayID string) (timeStamp string, nonceStr string, pkg string, signType string, paySign string, err error) {
|
||
cfg := configs.Get()
|
||
if cfg.WechatPay.PrivateKeyPath == "" {
|
||
return "", "", "", "", "", errors.New("wechat pay private key path not configured")
|
||
}
|
||
|
||
// 使用缓存的私钥,避免每次都从磁盘读取
|
||
rsaKey, err := getCachedRSAKey(cfg.WechatPay.PrivateKeyPath)
|
||
if err != nil {
|
||
return "", "", "", "", "", err
|
||
}
|
||
|
||
ts := fmt.Sprintf("%d", time.Now().Unix())
|
||
// 随机串32位
|
||
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||
rb := make([]byte, 32)
|
||
for i := range rb {
|
||
rb[i] = letters[mrand.Intn(len(letters))]
|
||
}
|
||
nonce := string(rb)
|
||
pkg = "prepay_id=" + prepayID
|
||
signType = "RSA"
|
||
|
||
message := fmt.Sprintf("%s\n%s\n%s\n%s\n", appid, ts, nonce, pkg)
|
||
h := sha256.Sum256([]byte(message))
|
||
sig, err := rsa.SignPKCS1v15(crand.Reader, rsaKey, crypto.SHA256, h[:])
|
||
if err != nil {
|
||
return "", "", "", "", "", err
|
||
}
|
||
paySign = base64.StdEncoding.EncodeToString(sig)
|
||
return ts, nonce, pkg, signType, paySign, nil
|
||
}
|
||
|
||
// ValidateConfig 校验微信支付必要配置
|
||
// 入参:无
|
||
// 返回:true表示配置齐全;false表示缺失,并附带错误信息
|
||
func ValidateConfig() (bool, error) {
|
||
c := configs.Get()
|
||
if c.Wechat.AppID == "" {
|
||
return false, errors.New("wechat app_id missing")
|
||
}
|
||
if c.WechatPay.MchID == "" || c.WechatPay.SerialNo == "" || c.WechatPay.PrivateKeyPath == "" || c.WechatPay.ApiV3Key == "" {
|
||
return false, errors.New("wechat pay config incomplete")
|
||
}
|
||
return true, nil
|
||
}
|