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 }