package pay import ( "context" "errors" "sync" "bindbox-game/configs" "github.com/wechatpay-apiv3/wechatpay-go/core" "github.com/wechatpay-apiv3/wechatpay-go/core/option" "github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi" refundsvc "github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic" "github.com/wechatpay-apiv3/wechatpay-go/utils" ) type WechatPayClient struct { client *core.Client } // 单例模式:避免每次请求都重新初始化客户端 var ( clientInstance *WechatPayClient clientOnce sync.Once clientErr error ) // NewWechatPayClient 获取微信支付客户端(单例模式) // 首次调用会初始化客户端,后续调用直接返回缓存的实例 func NewWechatPayClient(ctx context.Context) (*WechatPayClient, error) { clientOnce.Do(func() { clientInstance, clientErr = initWechatPayClient(ctx) }) if clientErr != nil { return nil, clientErr } return clientInstance, nil } // initWechatPayClient 初始化微信支付客户端(内部实现) func initWechatPayClient(ctx context.Context) (*WechatPayClient, error) { cfg := configs.Get() if cfg.WechatPay.ApiV3Key == "" { return nil, errors.New("wechat pay config incomplete") } var opts []core.ClientOption if cfg.WechatPay.PublicKeyID != "" && cfg.WechatPay.PublicKeyPath != "" { if cfg.WechatPay.MchID == "" || cfg.WechatPay.SerialNo == "" || cfg.WechatPay.PrivateKeyPath == "" { return nil, errors.New("wechat pay config incomplete") } mchPrivateKey, err := utils.LoadPrivateKeyWithPath(cfg.WechatPay.PrivateKeyPath) if err != nil { return nil, err } pubKey, err := utils.LoadPublicKeyWithPath(cfg.WechatPay.PublicKeyPath) if err != nil { return nil, err } opts = []core.ClientOption{option.WithWechatPayPublicKeyAuthCipher(cfg.WechatPay.MchID, cfg.WechatPay.SerialNo, mchPrivateKey, cfg.WechatPay.PublicKeyID, pubKey)} } else { if cfg.WechatPay.MchID == "" || cfg.WechatPay.SerialNo == "" || cfg.WechatPay.PrivateKeyPath == "" { return nil, errors.New("wechat pay config incomplete") } mchPrivateKey, err := utils.LoadPrivateKeyWithPath(cfg.WechatPay.PrivateKeyPath) if err != nil { return nil, err } opts = []core.ClientOption{option.WithWechatPayAutoAuthCipher(cfg.WechatPay.MchID, cfg.WechatPay.SerialNo, mchPrivateKey, cfg.WechatPay.ApiV3Key)} } client, err := core.NewClient(ctx, opts...) if err != nil { return nil, err } return &WechatPayClient{client: client}, nil } // JSAPIPrepay 直连商户JSAPI预下单,返回prepay_id // 入参:appid、mchid、描述、商户订单号、总金额(分)、openid、回调URL // 返回:prepay_id 或错误 func (c *WechatPayClient) JSAPIPrepay(ctx context.Context, appid, mchid, description, outTradeNo string, total int64, openid, notifyURL string) (string, error) { svc := jsapi.JsapiApiService{Client: c.client} resp, _, err := svc.Prepay(ctx, jsapi.PrepayRequest{ Appid: core.String(appid), Mchid: core.String(mchid), Description: core.String(description), OutTradeNo: core.String(outTradeNo), NotifyUrl: core.String(notifyURL), Amount: &jsapi.Amount{ Total: core.Int64(total), }, Payer: &jsapi.Payer{ Openid: core.String(openid), }, }) if err != nil { return "", err } if resp == nil || resp.PrepayId == nil { return "", errors.New("missing prepay_id in response") } return *resp.PrepayId, nil } // RefundOrder 直连商户退款 // 入参:outTradeNo(商户订单号)、refundNo(商户退款单号)、amountRefund(退款金额分)、total(原订单金额分)、reason(退款原因,可空) // 返回:微信退款单ID与状态,或错误 func (c *WechatPayClient) RefundOrder(ctx context.Context, outTradeNo, refundNo string, amountRefund, total int64, reason string) (string, string, error) { svc := refundsvc.RefundsApiService{Client: c.client} req := refundsvc.CreateRequest{ OutTradeNo: core.String(outTradeNo), OutRefundNo: core.String(refundNo), Amount: &refundsvc.AmountReq{ Refund: core.Int64(amountRefund), Total: core.Int64(total), Currency: core.String("CNY"), }, } if reason != "" { req.Reason = core.String(reason) } resp, _, err := svc.Create(ctx, req) if err != nil { return "", "", err } var refundID, status string if resp != nil { if resp.RefundId != nil { refundID = *resp.RefundId } if resp.Status != nil { status = string(*resp.Status) } } if refundID == "" { return "", status, errors.New("missing refund_id in response") } return refundID, status, nil }