237 lines
8.6 KiB
Go
237 lines
8.6 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) 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:
|
|||
|
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)
|
|||
|
}
|
|||
|
}
|