temp
This commit is contained in:
@@ -1,196 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
"tyc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
)
|
||||
|
||||
type AliPayService struct {
|
||||
config config.AlipayConfig
|
||||
AlipayClient *alipay.Client
|
||||
}
|
||||
|
||||
// NewAliPayService 是一个构造函数,用于初始化 AliPayService
|
||||
func NewAliPayService(c config.Config) *AliPayService {
|
||||
client, err := alipay.New(c.Alipay.AppID, c.Alipay.PrivateKey, c.Alipay.IsProduction)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("创建支付宝客户端失败: %v", err))
|
||||
}
|
||||
|
||||
// 加载支付宝公钥
|
||||
err = client.LoadAliPayPublicKey(c.Alipay.AlipayPublicKey)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("加载支付宝公钥失败: %v", err))
|
||||
}
|
||||
return &AliPayService{
|
||||
config: c.Alipay,
|
||||
AlipayClient: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, outTradeNo string) (string, error) {
|
||||
client := a.AlipayClient
|
||||
totalAmount := lzUtils.ToAlipayAmount(amount)
|
||||
// 构造移动支付请求
|
||||
p := alipay.TradeAppPay{
|
||||
Trade: alipay.Trade{
|
||||
Subject: subject,
|
||||
OutTradeNo: outTradeNo,
|
||||
TotalAmount: totalAmount,
|
||||
ProductCode: "QUICK_MSECURITY_PAY", // 移动端支付专用代码
|
||||
NotifyURL: a.config.NotifyUrl, // 异步回调通知地址
|
||||
},
|
||||
}
|
||||
|
||||
// 获取APP支付字符串,这里会签名
|
||||
payStr, err := client.TradeAppPay(p)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建支付宝订单失败: %v", err)
|
||||
}
|
||||
|
||||
return payStr, nil
|
||||
}
|
||||
|
||||
// CreateAlipayH5Order 创建支付宝H5支付订单
|
||||
func (a *AliPayService) CreateAlipayH5Order(amount float64, subject string, outTradeNo string, brand string) (string, error) {
|
||||
var returnURL string
|
||||
var notifyURL string
|
||||
if brand == "tyc" {
|
||||
returnURL = "https://www.tianyuancha.cn/report"
|
||||
notifyURL = "https://www.tianyuancha.cn/api/v1/pay/alipay/callback"
|
||||
} else {
|
||||
returnURL = a.config.ReturnURL
|
||||
notifyURL = a.config.NotifyUrl
|
||||
}
|
||||
client := a.AlipayClient
|
||||
totalAmount := lzUtils.ToAlipayAmount(amount)
|
||||
// 构造H5支付请求
|
||||
p := alipay.TradeWapPay{
|
||||
Trade: alipay.Trade{
|
||||
Subject: subject,
|
||||
OutTradeNo: outTradeNo,
|
||||
TotalAmount: totalAmount,
|
||||
ProductCode: "QUICK_WAP_PAY", // H5支付专用产品码
|
||||
NotifyURL: notifyURL, // 异步回调通知地址
|
||||
ReturnURL: returnURL,
|
||||
},
|
||||
}
|
||||
// 获取H5支付请求字符串,这里会签名
|
||||
payUrl, err := client.TradeWapPay(p)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建支付宝H5订单失败: %v", err)
|
||||
}
|
||||
|
||||
return payUrl.String(), nil
|
||||
}
|
||||
|
||||
// CreateAlipayOrder 根据平台类型创建支付宝支付订单
|
||||
func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, subject string, outTradeNo string, brand string) (string, error) {
|
||||
// 根据 ctx 中的 platform 判断平台
|
||||
platform, platformOk := ctx.Value("platform").(string)
|
||||
if !platformOk {
|
||||
return "", fmt.Errorf("无的支付平台: %s", platform)
|
||||
}
|
||||
switch platform {
|
||||
case "app":
|
||||
// 调用App支付的创建方法
|
||||
return a.CreateAlipayAppOrder(amount, subject, outTradeNo)
|
||||
case "h5":
|
||||
// 调用H5支付的创建方法,并传入 returnUrl
|
||||
return a.CreateAlipayH5Order(amount, subject, outTradeNo, brand)
|
||||
default:
|
||||
return "", fmt.Errorf("不支持的支付平台: %s", platform)
|
||||
}
|
||||
}
|
||||
|
||||
// AliRefund 发起支付宝退款
|
||||
func (a *AliPayService) AliRefund(ctx context.Context, outTradeNo string, refundAmount float64) (*alipay.TradeRefundRsp, error) {
|
||||
refund := alipay.TradeRefund{
|
||||
OutTradeNo: outTradeNo,
|
||||
RefundAmount: lzUtils.ToAlipayAmount(refundAmount),
|
||||
OutRequestNo: fmt.Sprintf("%s-refund", outTradeNo),
|
||||
}
|
||||
|
||||
// 发起退款请求
|
||||
refundResp, err := a.AlipayClient.TradeRefund(ctx, refund)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("支付宝退款请求错误:%v", err)
|
||||
}
|
||||
return refundResp, nil
|
||||
}
|
||||
|
||||
// HandleAliPaymentNotification 支付宝支付回调
|
||||
func (a *AliPayService) HandleAliPaymentNotification(r *http.Request) (*alipay.Notification, error) {
|
||||
// 解析表单
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析请求表单失败:%v", err)
|
||||
}
|
||||
// 解析并验证通知,DecodeNotification 会自动验证签名
|
||||
notification, err := a.AlipayClient.DecodeNotification(r.Form)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("验证签名失败: %v", err)
|
||||
}
|
||||
return notification, nil
|
||||
}
|
||||
func (a *AliPayService) QueryOrderStatus(ctx context.Context, outTradeNo string) (*alipay.TradeQueryRsp, error) {
|
||||
queryRequest := alipay.TradeQuery{
|
||||
OutTradeNo: outTradeNo,
|
||||
}
|
||||
|
||||
// 发起查询请求
|
||||
resp, err := a.AlipayClient.TradeQuery(ctx, queryRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询支付宝订单失败: %v", err)
|
||||
}
|
||||
|
||||
// 返回交易状态
|
||||
if resp.IsSuccess() {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("查询支付宝订单失败: %v", resp.SubMsg)
|
||||
}
|
||||
|
||||
// 添加全局原子计数器
|
||||
var alipayOrderCounter uint32 = 0
|
||||
|
||||
// GenerateOutTradeNo 生成唯一订单号的函数 - 优化版本
|
||||
func (a *AliPayService) GenerateOutTradeNo() string {
|
||||
|
||||
// 获取当前时间戳(毫秒级)
|
||||
timestamp := time.Now().UnixMilli()
|
||||
timeStr := strconv.FormatInt(timestamp, 10)
|
||||
|
||||
// 原子递增计数器
|
||||
counter := atomic.AddUint32(&alipayOrderCounter, 1)
|
||||
|
||||
// 生成4字节真随机数
|
||||
randomBytes := make([]byte, 4)
|
||||
_, err := rand.Read(randomBytes)
|
||||
if err != nil {
|
||||
// 如果随机数生成失败,回退到使用时间纳秒数据
|
||||
randomBytes = []byte(strconv.FormatInt(time.Now().UnixNano()%1000000, 16))
|
||||
}
|
||||
randomHex := hex.EncodeToString(randomBytes)
|
||||
|
||||
// 组合所有部分: 前缀 + 时间戳 + 计数器 + 随机数
|
||||
orderNo := fmt.Sprintf("%s%06x%s", timeStr[:10], counter%0xFFFFFF, randomHex[:6])
|
||||
|
||||
// 确保长度不超过32字符(大多数支付平台的限制)
|
||||
if len(orderNo) > 32 {
|
||||
orderNo = orderNo[:32]
|
||||
}
|
||||
|
||||
return orderNo
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
// ApplePayService 是 Apple IAP 支付服务的结构体
|
||||
type ApplePayService struct {
|
||||
config config.ApplepayConfig // 配置项
|
||||
}
|
||||
|
||||
// NewApplePayService 是一个构造函数,用于初始化 ApplePayService
|
||||
func NewApplePayService(c config.Config) *ApplePayService {
|
||||
return &ApplePayService{
|
||||
config: c.Applepay,
|
||||
}
|
||||
}
|
||||
func (a *ApplePayService) GetIappayAppID(productName string) string {
|
||||
return fmt.Sprintf("%s.%s", a.config.BundleID, productName)
|
||||
}
|
||||
|
||||
// VerifyReceipt 验证苹果支付凭证
|
||||
func (a *ApplePayService) VerifyReceipt(ctx context.Context, receipt string) (*AppleVerifyResponse, error) {
|
||||
var reqUrl string
|
||||
if a.config.Sandbox {
|
||||
reqUrl = a.config.SandboxVerifyURL
|
||||
} else {
|
||||
reqUrl = a.config.ProductionVerifyURL
|
||||
}
|
||||
|
||||
// 读取私钥
|
||||
privateKey, err := loadPrivateKey(a.config.LoadPrivateKeyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("加载私钥失败:%v", err)
|
||||
}
|
||||
|
||||
// 生成 JWT
|
||||
token, err := generateJWT(privateKey, a.config.KeyID, a.config.IssuerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成JWT失败:%v", err)
|
||||
}
|
||||
|
||||
// 构造查询参数
|
||||
queryParams := fmt.Sprintf("?receipt-data=%s", receipt)
|
||||
fullUrl := reqUrl + queryParams
|
||||
|
||||
// 构建 HTTP GET 请求
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullUrl, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建 HTTP 请求失败:%v", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求苹果验证接口失败:%v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 解析响应
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应体失败:%v", err)
|
||||
}
|
||||
|
||||
var verifyResponse AppleVerifyResponse
|
||||
err = json.Unmarshal(body, &verifyResponse)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析响应体失败:%v", err)
|
||||
}
|
||||
|
||||
// 根据实际响应处理逻辑
|
||||
if verifyResponse.Status != 0 {
|
||||
return nil, fmt.Errorf("验证失败,状态码:%d", verifyResponse.Status)
|
||||
}
|
||||
|
||||
return &verifyResponse, nil
|
||||
}
|
||||
|
||||
func loadPrivateKey(path string) (*ecdsa.PrivateKey, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil || block.Type != "PRIVATE KEY" {
|
||||
return nil, fmt.Errorf("无效的私钥数据")
|
||||
}
|
||||
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ecdsaKey, ok := key.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("私钥类型错误")
|
||||
}
|
||||
return ecdsaKey, nil
|
||||
}
|
||||
|
||||
func generateJWT(privateKey *ecdsa.PrivateKey, keyID, issuerID string) (string, error) {
|
||||
now := time.Now()
|
||||
claims := jwt.RegisteredClaims{
|
||||
Issuer: issuerID,
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
ExpiresAt: jwt.NewNumericDate(now.Add(1 * time.Hour)),
|
||||
Audience: jwt.ClaimStrings{"appstoreconnect-v1"},
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
|
||||
token.Header["kid"] = keyID
|
||||
tokenString, err := token.SignedString(privateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
// GenerateOutTradeNo 生成唯一订单号
|
||||
func (a *ApplePayService) GenerateOutTradeNo() string {
|
||||
length := 16
|
||||
timestamp := time.Now().UnixNano()
|
||||
timeStr := strconv.FormatInt(timestamp, 10)
|
||||
randomPart := strconv.Itoa(int(timestamp % 1e6))
|
||||
combined := timeStr + randomPart
|
||||
|
||||
if len(combined) >= length {
|
||||
return combined[:length]
|
||||
}
|
||||
|
||||
for len(combined) < length {
|
||||
combined += strconv.Itoa(int(timestamp % 10))
|
||||
}
|
||||
|
||||
return combined
|
||||
}
|
||||
|
||||
// AppleVerifyResponse 定义苹果验证接口的响应结构
|
||||
type AppleVerifyResponse struct {
|
||||
Status int `json:"status"` // 验证状态码:0 表示收据有效
|
||||
Receipt *Receipt `json:"receipt"` // 收据信息
|
||||
}
|
||||
|
||||
// Receipt 定义收据的精简结构
|
||||
type Receipt struct {
|
||||
BundleID string `json:"bundle_id"` // 应用的 Bundle ID
|
||||
InApp []InAppItem `json:"in_app"` // 应用内购买记录
|
||||
}
|
||||
|
||||
// InAppItem 定义单条交易记录
|
||||
type InAppItem struct {
|
||||
ProductID string `json:"product_id"` // 商品 ID
|
||||
TransactionID string `json:"transaction_id"` // 交易 ID
|
||||
PurchaseDate string `json:"purchase_date"` // 购买日期 (ISO 8601)
|
||||
OriginalTransID string `json:"original_transaction_id"` // 原始交易 ID
|
||||
}
|
||||
495
app/main/api/internal/service/payService.go
Normal file
495
app/main/api/internal/service/payService.go
Normal file
@@ -0,0 +1,495 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
"tyc-server/app/main/model"
|
||||
"tyc-server/common/ctxdata"
|
||||
"tyc-server/pkg/core/payment"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
// PayService 支付服务
|
||||
type PayService struct {
|
||||
config config.Config
|
||||
orderModel model.OrderModel
|
||||
orderRefundModel model.OrderRefundModel
|
||||
productModel model.ProductModel
|
||||
userAuthModel model.UserAuthModel
|
||||
userModel model.UserModel
|
||||
alipayService *payment.AliPayService
|
||||
wechatService *payment.WechatPayService
|
||||
appleService *payment.ApplePayService
|
||||
}
|
||||
|
||||
// NewPayService 创建支付服务
|
||||
func NewPayService(c config.Config, orderModel model.OrderModel, orderRefundModel model.OrderRefundModel, productModel model.ProductModel, userAuthModel model.UserAuthModel) *PayService {
|
||||
ps := &PayService{
|
||||
config: c,
|
||||
orderModel: orderModel,
|
||||
orderRefundModel: orderRefundModel,
|
||||
productModel: productModel,
|
||||
userAuthModel: userAuthModel,
|
||||
}
|
||||
|
||||
// 根据配置选择性初始化支付服务
|
||||
if c.Alipay.Enabled {
|
||||
// 初始化支付宝服务
|
||||
alipayConfig := payment.AlipayConfig{
|
||||
AppID: c.Alipay.AppID,
|
||||
PrivateKey: c.Alipay.PrivateKey,
|
||||
AlipayPublicKey: c.Alipay.AlipayPublicKey,
|
||||
IsProduction: c.Alipay.IsProduction,
|
||||
NotifyURL: c.Alipay.NotifyUrl,
|
||||
ReturnURL: c.Alipay.ReturnURL,
|
||||
}
|
||||
alipayService, err := payment.NewAliPayService(alipayConfig)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("初始化支付宝服务失败: %v", err))
|
||||
}
|
||||
ps.alipayService = alipayService
|
||||
}
|
||||
|
||||
if c.Wxpay.Enabled {
|
||||
// 初始化微信支付服务
|
||||
wechatConfig := &payment.WechatPayConfig{
|
||||
AppID: c.Wxpay.AppID,
|
||||
MchID: c.Wxpay.MchID,
|
||||
SerialNo: c.Wxpay.MchCertificateSerialNumber,
|
||||
MchAPIv3Key: c.Wxpay.MchApiv3Key,
|
||||
PrivateKey: c.Wxpay.MchPrivateKeyPath,
|
||||
NotifyURL: c.Wxpay.NotifyUrl,
|
||||
RefundURL: c.Wxpay.RefundNotifyUrl,
|
||||
}
|
||||
wechatService, err := payment.NewWechatPayService(wechatConfig)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("初始化微信支付服务失败: %v", err))
|
||||
}
|
||||
ps.wechatService = wechatService
|
||||
}
|
||||
|
||||
if c.Applepay.Enabled {
|
||||
// 初始化苹果支付服务
|
||||
appleConfig := payment.ApplePayConfig{
|
||||
BundleID: c.Applepay.BundleID,
|
||||
KeyID: c.Applepay.KeyID,
|
||||
TeamID: c.Applepay.IssuerID,
|
||||
PrivateKey: c.Applepay.LoadPrivateKeyPath,
|
||||
IsProduction: !c.Applepay.Sandbox,
|
||||
NotifyURL: c.Applepay.ProductionVerifyURL,
|
||||
RefundNotifyURL: c.Applepay.SandboxVerifyURL,
|
||||
}
|
||||
appleService, err := payment.NewApplePayService(appleConfig)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("初始化苹果支付服务失败: %v", err))
|
||||
}
|
||||
ps.appleService = appleService
|
||||
}
|
||||
|
||||
return ps
|
||||
}
|
||||
|
||||
// CreateOrder 创建支付订单
|
||||
func (p *PayService) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*CreateOrderResponse, error) {
|
||||
platform, ok := ctx.Value("platform").(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("获取平台失败")
|
||||
}
|
||||
userID, err := ctxdata.GetUidFromCtx(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取用户信息失败: %v", err)
|
||||
}
|
||||
product, err := p.productModel.FindOneByProductEn(ctx, req.ProductEn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取商品失败: %v", err)
|
||||
}
|
||||
user, err := p.userModel.FindOne(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取用户失败: %v", err)
|
||||
}
|
||||
var outTradeNo string
|
||||
var orderID int64
|
||||
amount := product.SellPrice
|
||||
if user.Inside == 1 {
|
||||
amount = 0.01
|
||||
}
|
||||
// 使用事务创建订单
|
||||
var payData interface{}
|
||||
err = p.orderModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
// 根据支付方式创建支付订单
|
||||
switch req.PayMethod {
|
||||
case "alipay":
|
||||
orderReq := &payment.AlipayCreateOrderRequest{
|
||||
Amount: amount,
|
||||
Subject: product.ProductName,
|
||||
Platform: payment.PlatformApp,
|
||||
}
|
||||
resp, err := p.alipayService.CreateOrder(ctx, orderReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建支付宝订单失败: %v", err)
|
||||
}
|
||||
payData = resp.PayParams
|
||||
case "wechat":
|
||||
orderReq := &payment.Order{
|
||||
OutTradeNo: outTradeNo,
|
||||
Subject: product.ProductName,
|
||||
Amount: amount,
|
||||
Platform: payment.PlatformApp,
|
||||
NotifyURL: p.config.Wxpay.NotifyUrl,
|
||||
}
|
||||
|
||||
// 如果是小程序支付,需要获取用户的 OpenID
|
||||
if platform == "mp-weixin" {
|
||||
userAuth, err := p.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWechatMiniOpenID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取用户认证信息失败: %v", err)
|
||||
}
|
||||
|
||||
orderReq.OpenID = userAuth.AuthKey
|
||||
}
|
||||
|
||||
resp, err := p.wechatService.CreateOrder(orderReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建微信支付订单失败: %v", err)
|
||||
}
|
||||
payData = resp.PayParams
|
||||
outTradeNo = resp.OrderNo
|
||||
// case "apple":
|
||||
// orderReq := &payment.AppleCreateOrderRequest{
|
||||
// Amount: amount,
|
||||
// Subject: product.ProductName,
|
||||
// OutTradeNo: outTradeNo,
|
||||
// Platform: payment.PlatformApp,
|
||||
// NotifyURL: p.config.Applepay.ProductionVerifyURL,
|
||||
// }
|
||||
// resp, err := p.appleService.CreateOrder(ctx, orderReq)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("创建苹果支付订单失败: %v", err)
|
||||
// }
|
||||
// payData = resp.PayParams
|
||||
default:
|
||||
return fmt.Errorf("不支持的支付方式: %s", req.PayMethod)
|
||||
}
|
||||
// 创建订单记录
|
||||
order := &model.Order{
|
||||
OrderNo: outTradeNo,
|
||||
UserId: userID,
|
||||
ProductId: product.Id,
|
||||
PaymentPlatform: string(req.PayMethod),
|
||||
PaymentScene: platform,
|
||||
Amount: amount,
|
||||
Status: model.OrderStatusPending,
|
||||
}
|
||||
result, err := p.orderModel.Insert(ctx, session, order)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建订单记录失败: %v", err)
|
||||
}
|
||||
orderID, err = result.LastInsertId()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取订单ID失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &CreateOrderResponse{
|
||||
OrderNo: outTradeNo,
|
||||
PayData: payData,
|
||||
PayMethod: req.PayMethod,
|
||||
OrderID: orderID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HandlePaymentCallback 处理支付回调
|
||||
func (p *PayService) HandlePaymentCallback(ctx context.Context, payType string, req *http.Request) error {
|
||||
return p.orderModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
var payOrder *payment.QueryOrderResponse
|
||||
var err error
|
||||
switch payType {
|
||||
case "alipay":
|
||||
payOrder, err = p.handleAlipayCallback(ctx, req, session)
|
||||
case "wechat":
|
||||
payOrder, err = p.handleWechatCallback(ctx, req, session)
|
||||
case "apple":
|
||||
payOrder, err = p.handleAppleCallback(ctx, req, session)
|
||||
default:
|
||||
return fmt.Errorf("不支持的支付类型: %s", payType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if payOrder == nil {
|
||||
return fmt.Errorf("支付订单信息为空")
|
||||
}
|
||||
|
||||
// 查询订单
|
||||
order, err := p.orderModel.FindOneByOrderNo(ctx, payOrder.OrderNo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询订单失败: %v", err)
|
||||
}
|
||||
|
||||
// 验证订单金额
|
||||
if payOrder.Amount != order.Amount {
|
||||
return fmt.Errorf("订单金额不匹配")
|
||||
}
|
||||
|
||||
// 根据支付状态更新订单状态
|
||||
switch payOrder.Status {
|
||||
case payment.StatusSuccess:
|
||||
order.Status = model.OrderStatusPaid
|
||||
case payment.StatusFailed:
|
||||
order.Status = model.OrderStatusFailed
|
||||
case payment.StatusClosed:
|
||||
order.Status = model.OrderStatusClosed
|
||||
case payment.StatusRefunded:
|
||||
order.Status = model.OrderStatusRefunding
|
||||
default:
|
||||
return fmt.Errorf("不支持的支付状态: %s", payOrder.Status)
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
order.PlatformOrderId = sql.NullString{String: payOrder.TransactionID, Valid: true}
|
||||
_, err = p.orderModel.Update(ctx, session, order)
|
||||
if err != nil {
|
||||
return fmt.Errorf("更新订单状态失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// handleAlipayCallback 处理支付宝回调
|
||||
func (p *PayService) handleAlipayCallback(ctx context.Context, req *http.Request, session sqlx.Session) (*payment.QueryOrderResponse, error) {
|
||||
return p.alipayService.HandlePaymentNotification(req)
|
||||
}
|
||||
|
||||
// handleWechatCallback 处理微信支付回调
|
||||
func (p *PayService) handleWechatCallback(ctx context.Context, req *http.Request, session sqlx.Session) (*payment.QueryOrderResponse, error) {
|
||||
notification, err := p.wechatService.HandlePaymentNotification(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("处理微信支付回调失败: %v", err)
|
||||
}
|
||||
|
||||
// 将微信支付通知转换为统一的查询订单响应格式
|
||||
return &payment.QueryOrderResponse{
|
||||
OrderNo: notification.OrderNo,
|
||||
TransactionID: notification.TransactionID,
|
||||
Status: notification.Status,
|
||||
Amount: notification.Amount,
|
||||
PayTime: notification.PayTime,
|
||||
PayMethod: payment.MethodWechat,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handleAppleCallback 处理苹果支付回调
|
||||
func (p *PayService) handleAppleCallback(ctx context.Context, req *http.Request, session sqlx.Session) (*payment.QueryOrderResponse, error) {
|
||||
receipt := req.FormValue("receipt-data")
|
||||
if receipt == "" {
|
||||
return nil, fmt.Errorf("无效的苹果支付收据")
|
||||
}
|
||||
|
||||
verifyResp, err := p.appleService.VerifyReceipt(ctx, receipt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("验证苹果支付收据失败: %v", err)
|
||||
}
|
||||
|
||||
if verifyResp.Status != payment.StatusSuccess {
|
||||
return nil, fmt.Errorf("苹果支付验证失败,状态码: %s", verifyResp.Status)
|
||||
}
|
||||
|
||||
// 将苹果支付验证响应转换为统一的查询订单响应格式
|
||||
return &payment.QueryOrderResponse{
|
||||
OrderNo: verifyResp.OrderNo,
|
||||
TransactionID: verifyResp.TransactionID,
|
||||
Status: verifyResp.Status,
|
||||
Amount: verifyResp.Amount,
|
||||
PayTime: time.Now(),
|
||||
PayMethod: payment.MethodApple,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Refund 申请退款
|
||||
func (p *PayService) Refund(ctx context.Context, req *RefundRequest) (*RefundResponse, error) {
|
||||
|
||||
// 查询订单信息
|
||||
order, err := p.orderModel.FindOne(ctx, req.OrderId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询订单失败: %v", err)
|
||||
}
|
||||
|
||||
// 验证订单状态
|
||||
if order.Status != model.OrderStatusPaid {
|
||||
return nil, fmt.Errorf("订单状态为 %s,无法退款", order.Status)
|
||||
}
|
||||
if req.RefundAmount > order.Amount {
|
||||
return nil, fmt.Errorf("退款金额 %.2f 不能大于订单金额 %.2f", req.RefundAmount, order.Amount)
|
||||
}
|
||||
|
||||
var result refundResult
|
||||
result.status = model.OrderRefundStatusPending
|
||||
order.Status = model.OrderStatusRefunding
|
||||
// 使用事务处理退款流程
|
||||
err = p.orderModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
// 根据支付方式申请退款
|
||||
switch order.PaymentPlatform {
|
||||
case string(PayMethodAlipay):
|
||||
refundReq := &payment.AlipayRefundRequest{
|
||||
OrderNo: order.OrderNo,
|
||||
RefundAmount: req.RefundAmount,
|
||||
}
|
||||
resp, err := p.alipayService.Refund(ctx, refundReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("支付宝退款申请失败: %v", err)
|
||||
}
|
||||
result = refundResult{
|
||||
refundNo: resp.RefundNo,
|
||||
status: model.OrderRefundStatusSuccess,
|
||||
refundTime: resp.RefundTime,
|
||||
refundAmount: resp.RefundAmount,
|
||||
platformRefundId: sql.NullString{String: resp.RefundID, Valid: true},
|
||||
}
|
||||
order.Status = model.OrderRefundStatusSuccess
|
||||
default:
|
||||
return fmt.Errorf("不支持的支付平台退款: %s", order.PaymentPlatform)
|
||||
}
|
||||
|
||||
// 创建退款记录
|
||||
refund := &model.OrderRefund{
|
||||
RefundNo: result.refundNo,
|
||||
PlatformRefundId: result.platformRefundId,
|
||||
OrderId: order.Id,
|
||||
UserId: order.UserId,
|
||||
ProductId: order.ProductId,
|
||||
RefundAmount: result.refundAmount,
|
||||
RefundReason: sql.NullString{String: req.Reason, Valid: true},
|
||||
Status: result.status,
|
||||
RefundTime: sql.NullTime{Time: result.refundTime, Valid: !result.refundTime.IsZero()},
|
||||
}
|
||||
|
||||
if _, err := p.orderRefundModel.Insert(ctx, session, refund); err != nil {
|
||||
return fmt.Errorf("创建退款记录失败: %v", err)
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
order.Status = model.OrderStatusRefunding
|
||||
order.RefundTime = sql.NullTime{Time: result.refundTime, Valid: !result.refundTime.IsZero()}
|
||||
if _, err := p.orderModel.Update(ctx, session, order); err != nil {
|
||||
return fmt.Errorf("更新订单状态失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &RefundResponse{
|
||||
RefundNo: result.refundNo,
|
||||
Amount: result.refundAmount,
|
||||
Status: result.status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// QueryOrderStatus 查询订单状态
|
||||
func (p *PayService) QueryOrderStatus(ctx context.Context, orderNo string) (*OrderStatusResponse, error) {
|
||||
// 查询订单信息
|
||||
order, err := p.orderModel.FindOneByOrderNo(ctx, orderNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询订单失败: %v", err)
|
||||
}
|
||||
|
||||
// 根据支付方式查询支付状态
|
||||
var payStatus payment.PaymentStatus
|
||||
switch order.PaymentPlatform {
|
||||
case "alipay":
|
||||
queryReq := &payment.QueryOrderRequest{
|
||||
OrderNo: orderNo,
|
||||
}
|
||||
resp, err := p.alipayService.QueryOrder(ctx, queryReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询支付宝订单状态失败: %v", err)
|
||||
}
|
||||
payStatus = resp.Status
|
||||
// case "wechat":
|
||||
// queryReq := &payment.OrderQuery{
|
||||
// OrderNo: orderNo,
|
||||
// }
|
||||
// resp, err := p.wechatService.QueryOrder(queryReq)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("查询微信支付订单状态失败: %v", err)
|
||||
// }
|
||||
// payStatus = resp.Status
|
||||
// case "apple":
|
||||
// return nil, fmt.Errorf("苹果支付暂不支持主动查询订单状态")
|
||||
default:
|
||||
return nil, fmt.Errorf("不支持的支付方式: %s", order.PaymentPlatform)
|
||||
}
|
||||
|
||||
return &OrderStatusResponse{
|
||||
OrderNo: orderNo,
|
||||
PayMethod: PayMethod(order.PaymentPlatform),
|
||||
PayStatus: string(payStatus),
|
||||
OrderStatus: order.Status,
|
||||
Amount: order.Amount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type PayMethod string
|
||||
|
||||
const (
|
||||
PayMethodAlipay PayMethod = "alipay"
|
||||
PayMethodWechat PayMethod = "wechat"
|
||||
PayMethodApple PayMethod = "apple"
|
||||
)
|
||||
|
||||
// 请求和响应结构体定义
|
||||
type CreateOrderRequest struct {
|
||||
PayMethod PayMethod `json:"pay_method"` // 支付方式:alipay, wechat, apple
|
||||
ProductEn string `json:"product_en"` // 商品英文名
|
||||
}
|
||||
|
||||
type CreateOrderResponse struct {
|
||||
OrderNo string `json:"order_no"` // 订单号
|
||||
PayData interface{} `json:"pay_data"` // 支付数据
|
||||
PayMethod PayMethod `json:"pay_type"` // 支付方式
|
||||
OrderID int64 `json:"order_id"` // 订单ID
|
||||
}
|
||||
|
||||
type RefundRequest struct {
|
||||
OrderId int64 `json:"order_id"` // 订单ID
|
||||
RefundAmount float64 `json:"refund_amount"` // 退款金额
|
||||
Reason string `json:"reason"` // 退款原因
|
||||
}
|
||||
|
||||
// 定义退款结果结构
|
||||
type refundResult struct {
|
||||
refundNo string
|
||||
status string
|
||||
refundTime time.Time
|
||||
refundAmount float64
|
||||
platformRefundId sql.NullString
|
||||
}
|
||||
|
||||
type RefundResponse struct {
|
||||
RefundNo string `json:"refund_no"` // 退款单号
|
||||
Amount float64 `json:"amount"` // 退款金额
|
||||
Status string `json:"status"` // 退款状态
|
||||
}
|
||||
|
||||
type OrderStatusResponse struct {
|
||||
OrderNo string `json:"order_no"` // 订单号
|
||||
PayMethod PayMethod `json:"pay_type"` // 支付方式
|
||||
PayStatus string `json:"pay_status"` // 支付状态
|
||||
OrderStatus string `json:"order_status"` // 订单状态
|
||||
Amount float64 `json:"amount"` // 订单金额
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
"tyc-server/app/main/model"
|
||||
"tyc-server/common/ctxdata"
|
||||
"tyc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/app"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/utils"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
const (
|
||||
TradeStateSuccess = "SUCCESS" // 支付成功
|
||||
TradeStateRefund = "REFUND" // 转入退款
|
||||
TradeStateNotPay = "NOTPAY" // 未支付
|
||||
TradeStateClosed = "CLOSED" // 已关闭
|
||||
TradeStateRevoked = "REVOKED" // 已撤销(付款码支付)
|
||||
TradeStateUserPaying = "USERPAYING" // 用户支付中(付款码支付)
|
||||
TradeStatePayError = "PAYERROR" // 支付失败(其他原因,如银行返回失败)
|
||||
)
|
||||
|
||||
type WechatPayService struct {
|
||||
config config.WxpayConfig
|
||||
wechatClient *core.Client
|
||||
notifyHandler *notify.Handler
|
||||
userAuthModel model.UserAuthModel
|
||||
}
|
||||
|
||||
// NewWechatPayService 初始化微信支付服务
|
||||
func NewWechatPayService(c config.Config, userAuthModel model.UserAuthModel) *WechatPayService {
|
||||
// 从配置中加载商户信息
|
||||
mchID := c.Wxpay.MchID
|
||||
mchCertificateSerialNumber := c.Wxpay.MchCertificateSerialNumber
|
||||
mchAPIv3Key := c.Wxpay.MchApiv3Key
|
||||
|
||||
// 从文件中加载商户私钥
|
||||
mchPrivateKey, err := utils.LoadPrivateKeyWithPath(c.Wxpay.MchPrivateKeyPath)
|
||||
if err != nil {
|
||||
logx.Errorf("加载商户私钥失败: %v", err)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序
|
||||
}
|
||||
|
||||
// 使用商户私钥和其他参数初始化微信支付客户端
|
||||
opts := []core.ClientOption{
|
||||
option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key),
|
||||
}
|
||||
client, err := core.NewClient(context.Background(), opts...)
|
||||
if err != nil {
|
||||
logx.Errorf("创建微信支付客户端失败: %v", err)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序
|
||||
}
|
||||
// 在初始化时获取证书访问器并创建 notifyHandler
|
||||
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
|
||||
notifyHandler, err := notify.NewRSANotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor))
|
||||
if err != nil {
|
||||
logx.Errorf("获取证书访问器失败: %v", err)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序
|
||||
}
|
||||
return &WechatPayService{
|
||||
config: c.Wxpay,
|
||||
wechatClient: client,
|
||||
notifyHandler: notifyHandler,
|
||||
userAuthModel: userAuthModel,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWechatAppOrder 创建微信APP支付订单
|
||||
func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount float64, description string, outTradeNo string) (string, error) {
|
||||
totalAmount := lzUtils.ToWechatAmount(amount)
|
||||
|
||||
// 构建支付请求参数
|
||||
payRequest := app.PrepayRequest{
|
||||
Appid: core.String(w.config.AppID),
|
||||
Mchid: core.String(w.config.MchID),
|
||||
Description: core.String(description),
|
||||
OutTradeNo: core.String(outTradeNo),
|
||||
NotifyUrl: core.String(w.config.NotifyUrl),
|
||||
Amount: &app.Amount{
|
||||
Total: core.Int64(totalAmount),
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化 AppApiService
|
||||
svc := app.AppApiService{Client: w.wechatClient}
|
||||
|
||||
// 发起预支付请求
|
||||
resp, result, err := svc.Prepay(ctx, payRequest)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode)
|
||||
}
|
||||
|
||||
// 返回预支付交易会话标识
|
||||
return *resp.PrepayId, nil
|
||||
}
|
||||
|
||||
// CreateWechatMiniProgramOrder 创建微信小程序支付订单
|
||||
func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) {
|
||||
totalAmount := lzUtils.ToWechatAmount(amount)
|
||||
|
||||
// 构建支付请求参数
|
||||
payRequest := jsapi.PrepayRequest{
|
||||
Appid: core.String(w.config.AppID),
|
||||
Mchid: core.String(w.config.MchID),
|
||||
Description: core.String(description),
|
||||
OutTradeNo: core.String(outTradeNo),
|
||||
NotifyUrl: core.String(w.config.NotifyUrl),
|
||||
Amount: &jsapi.Amount{
|
||||
Total: core.Int64(totalAmount),
|
||||
},
|
||||
Payer: &jsapi.Payer{
|
||||
Openid: core.String(openid), // 用户的 OpenID,通过前端传入
|
||||
}}
|
||||
|
||||
// 初始化 AppApiService
|
||||
svc := jsapi.JsapiApiService{Client: w.wechatClient}
|
||||
|
||||
// 发起预支付请求
|
||||
resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode)
|
||||
}
|
||||
|
||||
// 返回预支付交易会话标识
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// CreateWechatOrder 创建微信支付订单(集成 APP、H5、小程序)
|
||||
func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64, description string, outTradeNo string) (interface{}, error) {
|
||||
// 根据 ctx 中的 platform 判断平台
|
||||
platform := ctx.Value("platform").(string)
|
||||
|
||||
var prepayData interface{}
|
||||
var err error
|
||||
|
||||
switch platform {
|
||||
case "mp-weixin":
|
||||
userID, getUidErr := ctxdata.GetUidFromCtx(ctx)
|
||||
if getUidErr != nil {
|
||||
return "", fmt.Errorf("获取用户信息失败: %s", getUidErr)
|
||||
}
|
||||
userAuthModel, findUserAuthErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, "wx_mini")
|
||||
if findUserAuthErr != nil {
|
||||
return "", fmt.Errorf("获取用户认证信息失败: %s", findUserAuthErr)
|
||||
}
|
||||
// 如果是小程序平台,调用小程序支付订单创建
|
||||
prepayData, err = w.CreateWechatMiniProgramOrder(ctx, amount, description, outTradeNo, userAuthModel.AuthKey)
|
||||
case "app":
|
||||
// 如果是 APP 平台,调用 APP 支付订单创建
|
||||
prepayData, err = w.CreateWechatAppOrder(ctx, amount, description, outTradeNo)
|
||||
default:
|
||||
return "", fmt.Errorf("不支持的支付平台: %s", platform)
|
||||
}
|
||||
|
||||
// 如果创建支付订单失败,返回错误
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("支付订单创建失败: %v", err)
|
||||
}
|
||||
|
||||
// 返回预支付ID
|
||||
return prepayData, nil
|
||||
}
|
||||
|
||||
// HandleWechatPayNotification 处理微信支付回调
|
||||
func (w *WechatPayService) HandleWechatPayNotification(ctx context.Context, req *http.Request) (*payments.Transaction, error) {
|
||||
transaction := new(payments.Transaction)
|
||||
_, err := w.notifyHandler.ParseNotifyRequest(ctx, req, transaction)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("微信支付通知处理失败: %v", err)
|
||||
}
|
||||
// 返回交易信息
|
||||
return transaction, nil
|
||||
}
|
||||
|
||||
// HandleRefundNotification 处理微信退款回调
|
||||
func (w *WechatPayService) HandleRefundNotification(ctx context.Context, req *http.Request) (*refunddomestic.Refund, error) {
|
||||
refund := new(refunddomestic.Refund)
|
||||
_, err := w.notifyHandler.ParseNotifyRequest(ctx, req, refund)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("微信退款回调通知处理失败: %v", err)
|
||||
}
|
||||
return refund, nil
|
||||
}
|
||||
|
||||
// QueryOrderStatus 主动查询订单状态
|
||||
func (w *WechatPayService) QueryOrderStatus(ctx context.Context, transactionID string) (*payments.Transaction, error) {
|
||||
svc := jsapi.JsapiApiService{Client: w.wechatClient}
|
||||
|
||||
// 调用 QueryOrderById 方法查询订单状态
|
||||
resp, result, err := svc.QueryOrderById(ctx, jsapi.QueryOrderByIdRequest{
|
||||
TransactionId: core.String(transactionID),
|
||||
Mchid: core.String(w.config.MchID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, result.Response.StatusCode)
|
||||
}
|
||||
return resp, nil
|
||||
|
||||
}
|
||||
|
||||
// WeChatRefund 申请微信退款
|
||||
func (w *WechatPayService) WeChatRefund(ctx context.Context, outTradeNo string, refundAmount float64, totalAmount float64) error {
|
||||
|
||||
// 生成唯一的退款单号
|
||||
outRefundNo := fmt.Sprintf("%s-refund", outTradeNo)
|
||||
|
||||
// 初始化退款服务
|
||||
svc := refunddomestic.RefundsApiService{Client: w.wechatClient}
|
||||
|
||||
// 创建退款请求
|
||||
resp, result, err := svc.Create(ctx, refunddomestic.CreateRequest{
|
||||
OutTradeNo: core.String(outTradeNo),
|
||||
OutRefundNo: core.String(outRefundNo),
|
||||
NotifyUrl: core.String(w.config.RefundNotifyUrl),
|
||||
Amount: &refunddomestic.AmountReq{
|
||||
Currency: core.String("CNY"),
|
||||
Refund: core.Int64(lzUtils.ToWechatAmount(refundAmount)),
|
||||
Total: core.Int64(lzUtils.ToWechatAmount(totalAmount)),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("微信订单申请退款错误: %v", err)
|
||||
}
|
||||
// 打印退款结果
|
||||
logx.Infof("退款申请成功,状态码=%d,退款单号=%s,微信退款单号=%s", result.Response.StatusCode, *resp.OutRefundNo, *resp.RefundId)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateOutTradeNo 生成唯一订单号
|
||||
func (w *WechatPayService) GenerateOutTradeNo() string {
|
||||
length := 16
|
||||
timestamp := time.Now().UnixNano()
|
||||
timeStr := strconv.FormatInt(timestamp, 10)
|
||||
randomPart := strconv.Itoa(int(timestamp % 1e6))
|
||||
combined := timeStr + randomPart
|
||||
|
||||
if len(combined) >= length {
|
||||
return combined[:length]
|
||||
}
|
||||
|
||||
for len(combined) < length {
|
||||
combined += strconv.Itoa(int(timestamp % 10))
|
||||
}
|
||||
|
||||
return combined
|
||||
}
|
||||
Reference in New Issue
Block a user