You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

310 lines
9.8 KiB

package pay
import (
"base/app/common"
"base/app/config"
"base/app/constant"
"base/app/model"
"context"
"errors"
"git.oa00.com/go/logger"
"git.oa00.com/go/mysql"
"git.oa00.com/go/pay"
"github.com/shopspring/decimal"
"github.com/smartwalle/alipay/v3"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/app"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/h5"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
"go.uber.org/zap"
"gorm.io/gorm"
"time"
)
var PayLogic = &payLogic{}
var MapNotify = map[uint]Notify{}
type payLogic struct {
}
// Pay @Title 支付
func (p *payLogic) Pay(orderId, orderType, payType uint, amount decimal.Decimal, subject string, openId string) (result interface{}, err error) {
payNo, err := p.name(orderId, orderType, payType, amount)
if err != nil {
return result, err
}
if !config.IsProd() {
// 测试金额
amount = decimal.NewFromFloat(0.01)
}
switch payType {
case model.PayTypeAli: // 支付宝
param := alipay.TradeAppPay{}
param.OutTradeNo = payNo
param.TotalAmount = amount.String()
param.Subject = subject
param.TimeExpire = time.Now().Add(time.Minute * time.Duration(config.Config.Alipay.TimeExpire)).Format("2006-01-02 15:04:05")
param.NotifyURL = config.Config.Alipay.NotifyURL
result, err = pay.Alipay.TradeAppPay(param)
if err != nil {
return result, err
}
case model.PayTypeWx: // 微信
apiService := app.AppApiService{Client: pay.Wxpay.Client}
result, _, err = apiService.PrepayWithRequestPayment(context.Background(), app.PrepayRequest{
Appid: &config.Config.Wxpay.BrokerAppId,
Mchid: &config.Config.Wxpay.MchID,
Description: &subject,
OutTradeNo: &payNo,
TimeExpire: core.Time(time.Now().Add(time.Minute * time.Duration(config.Config.Alipay.TimeExpire))),
NotifyUrl: &config.Config.Wxpay.NotifyURL,
Amount: &app.Amount{
Total: core.Int64(amount.Mul(decimal.NewFromInt(100)).IntPart()),
Currency: core.String("CNY"),
},
})
if err != nil {
return result, err
}
case model.PayTypeWxapp:
apiService := jsapi.JsapiApiService{Client: pay.Wxpay.Client}
result, _, err = apiService.PrepayWithRequestPayment(context.Background(), jsapi.PrepayRequest{
Appid: &config.Config.Wxpay.WxappAppId,
Mchid: &config.Config.Wxpay.MchID,
Description: &subject,
OutTradeNo: &payNo,
TimeExpire: core.Time(time.Now().Add(time.Minute * time.Duration(config.Config.Wxpay.TimeExpire))),
NotifyUrl: &config.Config.Wxpay.NotifyURL,
Amount: &jsapi.Amount{
Total: core.Int64(amount.Mul(decimal.NewFromInt(100)).IntPart()),
Currency: core.String("CNY"),
},
Payer: &jsapi.Payer{
Openid: &openId,
},
})
case model.PayTypeH5Ali:
param := alipay.TradeAppPay{}
param.OutTradeNo = payNo
param.TotalAmount = amount.String()
param.Subject = subject
param.TimeExpire = time.Now().Add(time.Minute * time.Duration(config.Config.Alipay.TimeExpire)).Format("2006-01-02 15:04:05")
param.NotifyURL = config.Config.Alipay.NotifyURL
result, err = pay.Alipay.TradeAppPay(param)
if err != nil {
return result, err
}
case model.PayTypeH5Wx:
apiService := h5.H5ApiService{Client: pay.Wxpay.Client}
result, _, err = apiService.Prepay(context.Background(), h5.PrepayRequest{
Appid: &config.Config.Wxpay.BrokerAppId,
Mchid: &config.Config.Wxpay.MchID,
Description: &subject,
OutTradeNo: &payNo,
TimeExpire: core.Time(time.Now().Add(time.Minute * time.Duration(config.Config.Alipay.TimeExpire))),
NotifyUrl: &config.Config.Wxpay.NotifyURL,
Amount: &h5.Amount{
Total: core.Int64(amount.Mul(decimal.NewFromInt(100)).IntPart()),
Currency: core.String("CNY"),
},
SceneInfo: &h5.SceneInfo{
PayerClientIp: core.String("127.0.0.1"),
H5Info: &h5.H5Info{
Type: core.String("Wap"),
},
},
})
if err != nil {
return result, err
}
default:
return result, errors.New("支付方式错误")
}
return
}
// Refund @Title 退款
func (p *payLogic) Refund(tx *gorm.DB, payId uint) error {
payModel := model.Pay{Id: payId}
if tx.First(&payModel).Error != nil {
return errors.New("退款失败")
}
if payModel.Status == model.PayStatusRefund {
return errors.New("已退款,请勿重复操作")
}
if payModel.Status != model.PayStatusPay {
return errors.New("未支付")
}
if tx.Model(&model.Pay{}).Where(&payModel).Updates(map[string]interface{}{
"status": model.PayStatusRefund,
"refund_time": time.Now(),
}).RowsAffected != 1 {
return errors.New("退款失败")
}
if !config.IsProd() {
// 测试金额
payModel.Amount = decimal.NewFromFloat(0.01)
}
switch payModel.PayType {
case model.PayTypeAli: // 支付宝
param := alipay.TradeFastPayRefundQuery{
OutTradeNo: payModel.PaySn,
}
_, err := pay.Alipay.TradeFastPayRefundQuery(param)
if err != nil {
logger.Logger.Error("支付宝退款失败", zap.Any("pay", payModel), zap.Error(err))
return errors.New("退款失败")
}
case model.PayTypeWx: // 微信
amount := payModel.Amount.Mul(decimal.NewFromInt(100)).IntPart()
apiService := refunddomestic.RefundsApiService{Client: pay.Wxpay.Client}
result, _, err := apiService.Create(context.Background(), refunddomestic.CreateRequest{
OutTradeNo: &payModel.PaySn,
OutRefundNo: &payModel.PaySn,
NotifyUrl: &config.Config.Wxpay.NotifyURL, // 同步处理退款结果
Amount: &refunddomestic.AmountReq{
Currency: core.String("CNY"),
Refund: &amount,
Total: &amount,
},
})
if err != nil {
logger.Logger.Error("微信退款失败", zap.Any("pay", payModel), zap.Error(err))
return errors.New("退款失败")
}
if *result.Status != refunddomestic.STATUS_SUCCESS { // 退款失败
logger.Logger.Error("微信退款失败", zap.Any("pay", payModel), zap.Error(err))
return errors.New("退款失败")
}
case model.PayTypeWxapp:
amount := payModel.Amount.Mul(decimal.NewFromInt(100)).IntPart()
apiService := refunddomestic.RefundsApiService{Client: pay.Wxpay.Client}
result, _, err := apiService.Create(context.Background(), refunddomestic.CreateRequest{
OutTradeNo: &payModel.PaySn,
OutRefundNo: &payModel.PaySn,
NotifyUrl: &config.Config.Wxpay.NotifyURL, // 同步处理退款结果
Amount: &refunddomestic.AmountReq{
Currency: core.String("CNY"),
Refund: &amount,
Total: &amount,
},
})
if err != nil {
logger.Logger.Error("微信小程序退款失败", zap.Any("pay", payModel), zap.Error(err))
return errors.New("退款失败")
}
logger.Logger.Info("退款回参", zap.Any("result", result))
if *result.Status != refunddomestic.STATUS_SUCCESS && *result.Status != refunddomestic.STATUS_PROCESSING { // 退款失败
logger.Logger.Error("微信小程序退款失败", zap.Any("pay", payModel), zap.Error(err))
return errors.New("退款失败")
}
default:
return errors.New("退款失败")
}
return nil
}
type Notify interface {
Success(db *gorm.DB, orderId uint, payModel model.Pay) error
Fail(db *gorm.DB, orderId uint, payModel model.Pay) error
RefundSuccess(db *gorm.DB, orderId uint, payModel model.Pay) error
}
// Success @Title 付款成功
func (p *payLogic) Success(paySn, payTime string) error {
payModle := model.Pay{PaySn: paySn}
if mysql.Db.Where(&payModle).First(&payModle).Error != nil {
return errors.New("付款单号错误")
}
if payModle.Status == model.PayStatusPay {
return nil
}
return mysql.Db.Transaction(func(tx *gorm.DB) error {
if tx.Model(&payModle).Where(&payModle).UpdateColumns(map[string]interface{}{
"status": model.PayStatusPay,
"pay_time": payTime,
}).Error != nil {
return errors.New("付款失败")
}
for orderType, notify := range MapNotify {
if orderType == payModle.OrderType {
if err := notify.Success(tx, payModle.OrderId, payModle); err != nil {
return errors.New("付款失败")
}
}
}
return nil
})
}
// Fail @Title 付款失败
func (p *payLogic) Fail(paySn string) error {
payModle := model.Pay{PaySn: paySn}
if mysql.Db.Where(&payModle).First(&payModle).Error != nil {
return errors.New("付款单号错误")
}
return mysql.Db.Transaction(func(tx *gorm.DB) error {
if tx.Where(&payModle).UpdateColumns(map[string]interface{}{
"status": model.PayStatusTimeout,
}).Error != nil {
return errors.New("处理失败")
}
for orderType, notify := range MapNotify {
if orderType == payModle.OrderType {
if err := notify.Fail(tx, payModle.OrderId, payModle); err != nil {
return errors.New("付款失败")
}
}
}
return nil
})
}
// @Title 创建付款记录
func (p *payLogic) name(orderId, orderType, payType uint, amount decimal.Decimal) (payNo string, err error) {
payNo, err = common.GenNo(constant.NoPrefixPaySn)
if err != nil {
return
}
payModel := model.Pay{
PaySn: payNo,
OrderType: orderType,
OrderId: orderId,
Amount: amount,
Status: model.PayStatusUnPay,
PayType: payType,
}
if mysql.Db.Create(&payModel).Error != nil {
return payNo, errors.New("下单失败")
}
return
}
// RefundSuccess @Title 退款成功
func (p *payLogic) RefundSuccess(paySn, payTime string) error {
payModel := model.Pay{PaySn: paySn}
if mysql.Db.Where(&payModel).First(&payModel).Error != nil {
return errors.New("付款单号错误")
}
if payModel.Status == model.PayStatusRefund {
return nil
}
return mysql.Db.Transaction(func(tx *gorm.DB) error {
if tx.Model(&model.Pay{}).Where(&payModel).Updates(map[string]interface{}{
"status": model.PayStatusRefund,
"refund_time": payTime,
}).Error != nil {
return errors.New("付款失败")
}
for orderType, notify := range MapNotify {
if orderType == payModel.OrderType {
if err := notify.RefundSuccess(tx, payModel.OrderId, payModel); err != nil {
return errors.New("付款失败")
}
}
}
return nil
})
}