更新小程序API路径,统一为/api/miniprogram前缀,确保与后端一致性。同时,调整微信支付相关配置,增强系统的灵活性和可维护性。
This commit is contained in:
203
soul-api/internal/wechat/transfer.go
Normal file
203
soul-api/internal/wechat/transfer.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"soul-api/internal/config"
|
||||
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/transferbatch"
|
||||
)
|
||||
|
||||
var (
|
||||
transferClient *core.Client
|
||||
transferCfg *config.Config
|
||||
)
|
||||
|
||||
// InitTransfer 初始化转账客户端
|
||||
func InitTransfer(c *config.Config) error {
|
||||
transferCfg = c
|
||||
|
||||
// 加载商户私钥
|
||||
privateKey, err := loadPrivateKey(c.WechatKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("加载商户私钥失败: %w", err)
|
||||
}
|
||||
|
||||
// 初始化客户端
|
||||
opts := []core.ClientOption{
|
||||
option.WithWechatPayAutoAuthCipher(c.WechatMchID, c.WechatSerialNo, privateKey, c.WechatAPIv3Key),
|
||||
}
|
||||
|
||||
client, err := core.NewClient(context.Background(), opts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("初始化微信支付客户端失败: %w", err)
|
||||
}
|
||||
|
||||
transferClient = client
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadPrivateKey 加载商户私钥
|
||||
func loadPrivateKey(path string) (*rsa.PrivateKey, error) {
|
||||
privateKeyBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取私钥文件失败: %w", err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(privateKeyBytes)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("解析私钥失败:无效的 PEM 格式")
|
||||
}
|
||||
|
||||
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
// 尝试 PKCS8 格式
|
||||
key, err2 := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err2 != nil {
|
||||
return nil, fmt.Errorf("解析私钥失败: %w", err)
|
||||
}
|
||||
var ok bool
|
||||
privateKey, ok = key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("私钥不是 RSA 格式")
|
||||
}
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
// TransferParams 转账参数
|
||||
type TransferParams struct {
|
||||
OutBatchNo string // 商家批次单号(唯一)
|
||||
OutDetailNo string // 商家明细单号(唯一)
|
||||
OpenID string // 收款用户 openid
|
||||
Amount int // 转账金额(分)
|
||||
UserName string // 收款用户姓名(可选,用于实名校验)
|
||||
Remark string // 转账备注
|
||||
BatchName string // 批次名称(如"提现")
|
||||
BatchRemark string // 批次备注
|
||||
}
|
||||
|
||||
// TransferResult 转账结果
|
||||
type TransferResult struct {
|
||||
BatchID string // 微信批次单号
|
||||
OutBatchNo string // 商家批次单号
|
||||
CreateTime time.Time // 批次创建时间
|
||||
BatchStatus string // 批次状态:ACCEPTED-已受理, PROCESSING-处理中, FINISHED-已完成, CLOSED-已关闭
|
||||
}
|
||||
|
||||
// InitiateTransfer 发起转账
|
||||
func InitiateTransfer(params TransferParams) (*TransferResult, error) {
|
||||
if transferClient == nil {
|
||||
return nil, fmt.Errorf("转账客户端未初始化")
|
||||
}
|
||||
|
||||
svc := transferbatch.TransferBatchApiService{Client: transferClient}
|
||||
|
||||
// 构建转账明细
|
||||
details := []transferbatch.TransferDetailInput{
|
||||
{
|
||||
OutDetailNo: core.String(params.OutDetailNo),
|
||||
TransferAmount: core.Int64(int64(params.Amount)),
|
||||
TransferRemark: core.String(params.Remark),
|
||||
Openid: core.String(params.OpenID),
|
||||
},
|
||||
}
|
||||
|
||||
// 如果提供了姓名,添加实名校验
|
||||
if params.UserName != "" {
|
||||
details[0].UserName = core.String(params.UserName)
|
||||
}
|
||||
|
||||
// 发起转账请求
|
||||
req := transferbatch.InitiateBatchTransferRequest{
|
||||
Appid: core.String(transferCfg.WechatAppID),
|
||||
OutBatchNo: core.String(params.OutBatchNo),
|
||||
BatchName: core.String(params.BatchName),
|
||||
BatchRemark: core.String(params.BatchRemark),
|
||||
TotalAmount: core.Int64(int64(params.Amount)),
|
||||
TotalNum: core.Int64(1),
|
||||
TransferDetailList: details,
|
||||
}
|
||||
|
||||
resp, result, err := svc.InitiateBatchTransfer(context.Background(), req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("发起转账失败: %w", err)
|
||||
}
|
||||
|
||||
if result.Response.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("转账请求失败,状态码: %d", result.Response.StatusCode)
|
||||
}
|
||||
|
||||
return &TransferResult{
|
||||
BatchID: *resp.BatchId,
|
||||
OutBatchNo: *resp.OutBatchNo,
|
||||
CreateTime: *resp.CreateTime,
|
||||
BatchStatus: "ACCEPTED",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// QueryTransfer 查询转账结果(暂不实现,转账状态通过回调获取)
|
||||
func QueryTransfer(outBatchNo, outDetailNo string) (map[string]interface{}, error) {
|
||||
// TODO: 实现查询转账结果
|
||||
// 微信转账采用异步模式,通过回调通知最终结果
|
||||
return map[string]interface{}{
|
||||
"out_batch_no": outBatchNo,
|
||||
"out_detail_no": outDetailNo,
|
||||
"status": "processing",
|
||||
"message": "转账处理中,请等待回调通知",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateTransferBatchNo 生成转账批次单号
|
||||
func GenerateTransferBatchNo() string {
|
||||
now := time.Now()
|
||||
timestamp := now.Format("20060102150405")
|
||||
random := now.UnixNano() % 1000000
|
||||
return fmt.Sprintf("WD%s%06d", timestamp, random)
|
||||
}
|
||||
|
||||
// GenerateTransferDetailNo 生成转账明细单号
|
||||
func GenerateTransferDetailNo() string {
|
||||
now := time.Now()
|
||||
timestamp := now.Format("20060102150405")
|
||||
random := now.UnixNano() % 1000000
|
||||
return fmt.Sprintf("WDD%s%06d", timestamp, random)
|
||||
}
|
||||
|
||||
// TransferNotifyResult 转账回调结果
|
||||
type TransferNotifyResult struct {
|
||||
MchID *string `json:"mchid"`
|
||||
OutBatchNo *string `json:"out_batch_no"`
|
||||
BatchID *string `json:"batch_id"`
|
||||
AppID *string `json:"appid"`
|
||||
OutDetailNo *string `json:"out_detail_no"`
|
||||
DetailID *string `json:"detail_id"`
|
||||
DetailStatus *string `json:"detail_status"`
|
||||
TransferAmount *int64 `json:"transfer_amount"`
|
||||
OpenID *string `json:"openid"`
|
||||
UserName *string `json:"user_name"`
|
||||
InitiateTime *string `json:"initiate_time"`
|
||||
UpdateTime *string `json:"update_time"`
|
||||
FailReason *string `json:"fail_reason"`
|
||||
}
|
||||
|
||||
// VerifyTransferNotify 验证转账回调签名(使用 notify handler)
|
||||
func VerifyTransferNotify(ctx context.Context, request interface{}) (*TransferNotifyResult, error) {
|
||||
// 微信官方 SDK 的回调处理
|
||||
// 实际使用时,微信会 POST JSON 数据,包含加密信息
|
||||
// 这里暂时返回简化版本,实际项目中需要完整实现签名验证
|
||||
|
||||
// TODO: 完整实现回调验证
|
||||
// 需要解析请求体中的 resource.ciphertext,使用 APIv3 密钥解密
|
||||
|
||||
return &TransferNotifyResult{}, fmt.Errorf("转账回调需要完整实现")
|
||||
}
|
||||
Reference in New Issue
Block a user