266 lines
9.7 KiB
Go
266 lines
9.7 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"github.com/smartwalle/alipay/v3"
|
||
wxcore "github.com/wechatpay-apiv3/wechatpay-go/core"
|
||
"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"
|
||
"log"
|
||
"net/http"
|
||
"qnc-server/config"
|
||
"qnc-server/db"
|
||
"qnc-server/global"
|
||
"qnc-server/model/model"
|
||
"qnc-server/utils"
|
||
)
|
||
|
||
type OrderService struct {
|
||
}
|
||
|
||
func (a *OrderService) QueryConsumedOrder(userid uint, productEn string) (payOrder model.PayOrder, err error) {
|
||
err = db.DB.Joins("JOIN product ON product.id = pay_order.product_id").Where("pay_order.userid = ? AND pay_order.pay_status = ? AND pay_order.has_consumed = ? AND product.product_en = ?", userid, model.PayStatusSuccess, model.NotConsumed, productEn).First(&payOrder).Error
|
||
return
|
||
}
|
||
|
||
// 付款消费
|
||
func (a *OrderService) OrderConsumed(payOrder model.PayOrder) {
|
||
payOrder.HasConsumed = 1
|
||
err := db.DB.Save(&payOrder).Error
|
||
if err != nil {
|
||
log.Printf("消费订单失败:%v", err)
|
||
}
|
||
}
|
||
|
||
// 失效并退款
|
||
func (a *OrderService) OrderFailure(payOrder model.PayOrder) {
|
||
payOrder.HasConsumed = 2
|
||
err := db.DB.Save(&payOrder).Error
|
||
if err != nil {
|
||
log.Printf("失效订单失败:%v", err)
|
||
}
|
||
}
|
||
func (a *OrderService) GetList(pageSize int, pageNum int, userId uint) (list []model.PayOrder, err error) {
|
||
offset := (pageNum - 1) * pageSize
|
||
result := db.DB.Preload("Product").Where("userid = ? AND pay_status IN ?", userId, []string{model.PayStatusSuccess, model.PayStatusRefund, model.PayStatusUnderRefund}).Order("id desc").Limit(pageSize).Offset(offset).Find(&list)
|
||
if result.Error != nil {
|
||
return list, result.Error
|
||
}
|
||
return list, nil
|
||
}
|
||
func (a *OrderService) GetListByPromotion(pageSize int, pageNum int, promotion string) (list []model.PayOrder, err error) {
|
||
offset := (pageNum - 1) * pageSize
|
||
result := db.DB.Preload("Product").Where("promotion = ? AND pay_status IN ?", promotion, []string{model.PayStatusSuccess}).Order("id desc").Limit(pageSize).Offset(offset).Find(&list)
|
||
if result.Error != nil {
|
||
return list, result.Error
|
||
}
|
||
return list, nil
|
||
}
|
||
func (a *OrderService) GetSummaryByPromotion(promotion string) (totalCount int64, totalAmount float64, err error) {
|
||
var count int64
|
||
var amount float64
|
||
|
||
// 查询总单数
|
||
if err := db.DB.Model(&model.PayOrder{}).
|
||
Where("promotion = ? AND pay_status IN ?", promotion, []string{model.PayStatusSuccess}).
|
||
Count(&count).Error; err != nil {
|
||
return 0, 0, err
|
||
}
|
||
|
||
// 查询总金额,使用 COALESCE 防止 NULL
|
||
if err := db.DB.Model(&model.PayOrder{}).
|
||
Where("promotion = ? AND pay_status IN ?", promotion, []string{model.PayStatusSuccess}).
|
||
Select("COALESCE(SUM(amount), 0)").Scan(&amount).Error; err != nil {
|
||
return 0, 0, err
|
||
}
|
||
|
||
return count, amount, nil
|
||
}
|
||
|
||
func (a *OrderService) GetOrderByid(id uint) (order *model.PayOrder, err error) {
|
||
order = &model.PayOrder{}
|
||
err = db.DB.Preload("Product").Where("id = ?", id).First(order).Error
|
||
if err != nil {
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
// 微信小程序或公众号
|
||
func (a *OrderService) WechatJSAPIPrepay(appid string, mchID string, product model.Product, outTradeNo string, amount int64, openid string, platform string, client *wxcore.Client) (resp *jsapi.PrepayWithRequestPaymentResponse, err error) {
|
||
prepayReq := jsapi.PrepayRequest{
|
||
Appid: &appid,
|
||
Mchid: &mchID,
|
||
Description: wxcore.String(product.ProductName),
|
||
OutTradeNo: wxcore.String(outTradeNo), // 唯一的订单号
|
||
Attach: wxcore.String(product.Description),
|
||
NotifyUrl: wxcore.String(fmt.Sprintf("%s/%s", config.ConfigData.WxPay.NotifyURL, platform)), // 回调地址,需替换为实际地址
|
||
Amount: &jsapi.Amount{
|
||
Total: wxcore.Int64(amount), // 订单金额,单位为分
|
||
},
|
||
Payer: &jsapi.Payer{
|
||
Openid: wxcore.String(openid), // 用户的 OpenID,通过前端传入
|
||
}}
|
||
// 发起预支付请求
|
||
svc := jsapi.JsapiApiService{Client: client}
|
||
resp, _, err = svc.PrepayWithRequestPayment(ctx, prepayReq)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return resp, nil
|
||
}
|
||
|
||
// 浏览器H5
|
||
func (a *OrderService) WechatH5Prepay(appid string, mchID string, product model.Product, outTradeNo string, amount int64, userIP string, client *wxcore.Client) (resp *h5.PrepayResponse, err error) {
|
||
prepayReq := h5.PrepayRequest{
|
||
Appid: wxcore.String(appid),
|
||
Mchid: wxcore.String(mchID),
|
||
Description: wxcore.String(product.ProductName),
|
||
OutTradeNo: wxcore.String(outTradeNo), // 唯一的订单号
|
||
Attach: wxcore.String(product.Description),
|
||
NotifyUrl: wxcore.String(config.ConfigData.WxPay.NotifyURL), // 回调地址,需替换为实际地址
|
||
Amount: &h5.Amount{
|
||
Total: wxcore.Int64(amount), // 订单金额,单位为分
|
||
},
|
||
SceneInfo: &h5.SceneInfo{
|
||
PayerClientIp: wxcore.String(userIP), // 可通过前端传入
|
||
H5Info: &h5.H5Info{
|
||
Type: wxcore.String("Wap"),
|
||
//AppName: wxcore.String("全能查"),
|
||
//AppUrl: wxcore.String("https://你的域名"),
|
||
},
|
||
},
|
||
}
|
||
|
||
// 发起预支付请求
|
||
svc := h5.H5ApiService{Client: client}
|
||
resp, _, err = svc.Prepay(context.Background(), prepayReq)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return resp, nil
|
||
}
|
||
func (a *OrderService) WeChatRefund(order model.PayOrder) (err error) {
|
||
var client *wxcore.Client
|
||
switch order.Platform {
|
||
case model.PlatformMPWEIXIN:
|
||
client = global.GlobalData.PayClient
|
||
case model.PlatformMPH5, model.PlatformTYDATA:
|
||
client = global.GlobalData.PayH5Client
|
||
}
|
||
outRefundNo := utils.GenerateOrderRefundNumber()
|
||
svc := refunddomestic.RefundsApiService{Client: client}
|
||
resp, result, err := svc.Create(ctx,
|
||
refunddomestic.CreateRequest{
|
||
OutTradeNo: wxcore.String(order.OutTradeNo),
|
||
OutRefundNo: wxcore.String(outRefundNo), //退款单号
|
||
NotifyUrl: wxcore.String(fmt.Sprintf("%s/%s", config.ConfigData.WxPay.RefundNotifyURL, order.Platform)),
|
||
Amount: &refunddomestic.AmountReq{
|
||
Currency: wxcore.String("CNY"),
|
||
Refund: wxcore.Int64(order.Amount),
|
||
Total: wxcore.Int64(order.Amount),
|
||
},
|
||
},
|
||
)
|
||
if err != nil {
|
||
// 处理错误
|
||
log.Printf("微信订单申请退款错误:%s", err)
|
||
return errors.New(fmt.Sprintf("微信订单申请退款错误:%s", err))
|
||
} else {
|
||
// 处理返回结果
|
||
log.Printf("微信订单申请退款 response status=%d resp=%s", result.Response.StatusCode, resp)
|
||
order.PayStatus = model.PayStatusUnderRefund
|
||
err = db.DB.Save(order).Error
|
||
if err != nil {
|
||
log.Printf("微信订单退状态修改失败 underRefund Error:%v", err)
|
||
return errors.New(fmt.Sprintf("微信订单退状态修改失败 underRefund Error:%v", err))
|
||
}
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 创建投诉回调URL
|
||
func (a *OrderService) RegisterComplaintNotificationURL(client *wxcore.Client, platform string) {
|
||
url := "https://api.mch.weixin.qq.com/v3/merchant-service/complaint-notifications"
|
||
|
||
// 请求体数据
|
||
requestBody := map[string]string{
|
||
"url": fmt.Sprintf("%s/%s/pay/complaint_callback/%s", config.ConfigData.Server.Domain, config.ConfigData.Server.Prefix, platform), // 你的回调URL
|
||
}
|
||
|
||
// 将请求体序列化为 JSON
|
||
requestBodyJson, err := json.Marshal(requestBody)
|
||
if err != nil {
|
||
log.Fatalf("【%s创建投诉回调URL】请求体JSON序列化失败: %v", platform, err)
|
||
}
|
||
|
||
// 发送POST请求
|
||
result, err := client.Post(context.Background(), url, requestBodyJson)
|
||
if err != nil {
|
||
// 尝试解析响应内容,获取详细错误信息
|
||
var response map[string]interface{}
|
||
if err := json.NewDecoder(result.Response.Body).Decode(&response); err != nil {
|
||
log.Fatalf("【%s创建投诉回调URL】解析错误响应失败: %v", platform, err)
|
||
}
|
||
// 检查错误代码和信息
|
||
if responseCode, ok := response["code"].(string); ok && responseCode == "PARAM_ERROR" {
|
||
if responseMessage, ok := response["message"].(string); ok && responseMessage == "数据已存在" {
|
||
log.Printf("【%s创建投诉回调URL】数据已存在,不需要重新创建: %+v\n", platform, response)
|
||
return
|
||
}
|
||
}
|
||
log.Fatalf("【%s创建投诉回调URL】请求失败: %v", platform, err)
|
||
}
|
||
|
||
// 处理响应
|
||
defer result.Response.Body.Close()
|
||
|
||
// 检查请求状态
|
||
if result.Response.StatusCode != http.StatusOK {
|
||
log.Fatalf("【%s创建投诉回调URL】请求失败,状态码: %v", platform, result.Response.StatusCode)
|
||
}
|
||
|
||
// 解析响应
|
||
var response map[string]interface{}
|
||
if err := json.NewDecoder(result.Response.Body).Decode(&response); err != nil {
|
||
log.Fatalf("【%s创建投诉回调URL】解析响应失败: %v", platform, err)
|
||
}
|
||
|
||
log.Printf("【%s创建投诉回调URL】投诉通知回调URL创建成功: %+v\n", platform, response)
|
||
return
|
||
}
|
||
|
||
func (a *OrderService) AliRefund(ctx context.Context, payOrder model.PayOrder) error {
|
||
// 创建退款请求参数
|
||
refund := alipay.TradeRefund{}
|
||
refund.OutTradeNo = payOrder.OutTradeNo
|
||
refund.RefundAmount = utils.ConvertCentsToYuan(int(payOrder.Amount)) // 退款金额,单位元
|
||
refund.OutRequestNo = fmt.Sprintf("ali_refund_%s", utils.GenerateOrderNumber()) // 生成的唯一退款请求号
|
||
|
||
// 发起退款请求
|
||
refundResp, err := global.GlobalData.AliPayClient.TradeRefund(ctx, refund)
|
||
if err != nil {
|
||
log.Printf("【阿里支付退款】退款请求错误:%v", err)
|
||
return fmt.Errorf("refund request failed")
|
||
}
|
||
|
||
if refundResp.IsSuccess() {
|
||
// 更新订单状态
|
||
err = db.DB.Model(&model.PayOrder{}).Where("out_trade_no = ?", payOrder.OutTradeNo).Updates(model.PayOrder{
|
||
PayStatus: model.PayStatusRefund,
|
||
}).Error
|
||
if err != nil {
|
||
log.Printf("【阿里支付退款】订单更新错误:%v", err)
|
||
return fmt.Errorf("order update failed")
|
||
}
|
||
return nil
|
||
} else {
|
||
log.Printf("【阿里支付退款】退款失败:%v", refundResp.SubMsg)
|
||
return fmt.Errorf("refund failed: %s", refundResp.SubMsg)
|
||
}
|
||
}
|