f
This commit is contained in:
@@ -1,20 +1,21 @@
|
||||
package admin_order
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/app/main/model"
|
||||
"qnc-server/common/xerr"
|
||||
paylogic "qnc-server/app/main/api/internal/logic/pay"
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/app/main/model"
|
||||
"qnc-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
"github.com/google/uuid"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -44,6 +45,10 @@ func (l *AdminRefundOrderLogic) AdminRefundOrder(req *types.AdminRefundOrderReq)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if model.IsXpayOrder(order) {
|
||||
return l.handleXpayRefund(order, req)
|
||||
}
|
||||
|
||||
// 根据支付平台处理退款
|
||||
switch order.PaymentPlatform {
|
||||
case PaymentPlatformAlipay:
|
||||
@@ -107,6 +112,23 @@ func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *type
|
||||
}
|
||||
}
|
||||
|
||||
// handleXpayRefund 处理小程序虚拟支付退款
|
||||
func (l *AdminRefundOrderLogic) handleXpayRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
|
||||
if req.RefundAmount != order.Amount {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("虚拟支付订单仅支持全额退款"), "")
|
||||
}
|
||||
if err := paylogic.RefundXpayQueryOrder(l.ctx, l.svcCtx, order); err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "虚拟支付退款失败: %v", err)
|
||||
}
|
||||
refundNo := l.generateRefundNo(order.OrderNo)
|
||||
_ = l.createRefundRecordOnly(order, req, refundNo, "", model.OrderRefundStatusSuccess)
|
||||
return &types.AdminRefundOrderResp{
|
||||
Status: model.OrderStatusRefunded,
|
||||
RefundNo: refundNo,
|
||||
Amount: req.RefundAmount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handleWechatRefund 处理微信退款
|
||||
func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
|
||||
// 调用微信退款接口
|
||||
@@ -133,18 +155,18 @@ func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *type
|
||||
func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, orderStatus, refundStatus string) error {
|
||||
return l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
// 创建退款记录
|
||||
refund := &model.OrderRefund{
|
||||
Id: uuid.NewString(),
|
||||
RefundNo: refundNo,
|
||||
PlatformRefundId: l.createNullString(platformRefundId),
|
||||
OrderId: order.Id,
|
||||
UserId: order.UserId,
|
||||
ProductId: order.ProductId,
|
||||
RefundAmount: req.RefundAmount,
|
||||
RefundReason: l.createNullString(req.RefundReason),
|
||||
Status: refundStatus, // 使用传入的状态,不再硬编码
|
||||
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
|
||||
}
|
||||
refund := &model.OrderRefund{
|
||||
Id: uuid.NewString(),
|
||||
RefundNo: refundNo,
|
||||
PlatformRefundId: l.createNullString(platformRefundId),
|
||||
OrderId: order.Id,
|
||||
UserId: order.UserId,
|
||||
ProductId: order.ProductId,
|
||||
RefundAmount: req.RefundAmount,
|
||||
RefundReason: l.createNullString(req.RefundReason),
|
||||
Status: refundStatus, // 使用传入的状态,不再硬编码
|
||||
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
|
||||
}
|
||||
|
||||
if _, err := l.svcCtx.OrderRefundModel.Insert(ctx, session, refund); err != nil {
|
||||
return fmt.Errorf("创建退款记录失败: %v", err)
|
||||
@@ -169,18 +191,18 @@ func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Or
|
||||
|
||||
// createRefundRecordOnly 仅创建退款记录,不更新订单状态(用于退款失败的情况)
|
||||
func (l *AdminRefundOrderLogic) createRefundRecordOnly(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, refundStatus string) error {
|
||||
refund := &model.OrderRefund{
|
||||
Id: uuid.NewString(),
|
||||
RefundNo: refundNo,
|
||||
PlatformRefundId: l.createNullString(platformRefundId),
|
||||
OrderId: order.Id,
|
||||
UserId: order.UserId,
|
||||
ProductId: order.ProductId,
|
||||
RefundAmount: req.RefundAmount,
|
||||
RefundReason: l.createNullString(req.RefundReason),
|
||||
Status: refundStatus,
|
||||
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
|
||||
}
|
||||
refund := &model.OrderRefund{
|
||||
Id: uuid.NewString(),
|
||||
RefundNo: refundNo,
|
||||
PlatformRefundId: l.createNullString(platformRefundId),
|
||||
OrderId: order.Id,
|
||||
UserId: order.UserId,
|
||||
ProductId: order.ProductId,
|
||||
RefundAmount: req.RefundAmount,
|
||||
RefundReason: l.createNullString(req.RefundReason),
|
||||
Status: refundStatus,
|
||||
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
|
||||
}
|
||||
|
||||
_, err := l.svcCtx.OrderRefundModel.Insert(l.ctx, nil, refund)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package admin_order
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"qnc-server/app/main/api/internal/logic/pay"
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/app/main/model"
|
||||
"qnc-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type AdminXpayDeliverLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewAdminXpayDeliverLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminXpayDeliverLogic {
|
||||
return &AdminXpayDeliverLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AdminXpayDeliverLogic) AdminXpayDeliver(req *types.AdminXpayDeliverReq) (resp *types.AdminXpayDeliverResp, err error) {
|
||||
order, findErr := l.svcCtx.OrderModel.FindOne(l.ctx, req.Id)
|
||||
if findErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("订单不存在"), "%v", findErr)
|
||||
}
|
||||
|
||||
if !model.IsXpayOrder(order) {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("仅支持小程序虚拟支付订单"), "")
|
||||
}
|
||||
|
||||
if l.svcCtx.XpayService == nil || !l.svcCtx.XpayService.Enabled() {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("虚拟支付未启用"), "")
|
||||
}
|
||||
|
||||
result, deliverErr := pay.DeliverXpayQueryOrder(l.ctx, l.svcCtx, order.OrderNo)
|
||||
if deliverErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "补发货失败: %v", deliverErr)
|
||||
}
|
||||
|
||||
return &types.AdminXpayDeliverResp{
|
||||
Credited: result.Credited,
|
||||
Notified: result.Notified,
|
||||
WechatDetail: result.WechatDetail,
|
||||
Errors: result.Errors,
|
||||
Message: result.Message,
|
||||
}, nil
|
||||
}
|
||||
@@ -42,8 +42,8 @@ func (l *PaymentCheckLogic) PaymentCheck(req *types.PaymentCheckReq) (resp *type
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单失败: %v", findErr)
|
||||
}
|
||||
|
||||
// xpay 轮询:pending 时主动查微信单
|
||||
if order.Status == "pending" && order.PaymentScene == "wxmini" &&
|
||||
// xpay 轮询:pending 时主动查微信单并到账(不通知微信发货)
|
||||
if order.Status == model.OrderStatusPending && model.IsXpayOrder(order) &&
|
||||
l.svcCtx.XpayService != nil && l.svcCtx.XpayService.Enabled() {
|
||||
if syncErr := l.syncXpayOrderStatus(order); syncErr != nil {
|
||||
l.Errorf("[xpay] 轮询查单失败 order_no=%s err=%v", req.OrderNo, syncErr)
|
||||
@@ -68,27 +68,14 @@ func (l *PaymentCheckLogic) syncXpayOrderStatus(order *model.Order) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sessionKey, err := l.svcCtx.XpayService.GetSessionKey(l.ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
status, err := l.svcCtx.XpayService.QueryOrder(l.ctx, openid, order.OrderNo, sessionKey)
|
||||
status, err := l.svcCtx.XpayService.QueryOrder(l.ctx, openid, order.OrderNo, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if service.IsXpayPaidStatus(status.Status) {
|
||||
credited, fulfillErr := fulfillQueryOrderPaid(l.ctx, l.svcCtx, order, "", status.PaidFee)
|
||||
if fulfillErr != nil {
|
||||
return fulfillErr
|
||||
}
|
||||
if credited {
|
||||
wxOrderID := ""
|
||||
_ = l.svcCtx.XpayService.NotifyProvideGoods(l.ctx, openid, order.OrderNo, wxOrderID, sessionKey)
|
||||
_ = l.svcCtx.XpayService.MarkNotified(l.ctx, order.OrderNo)
|
||||
}
|
||||
return nil
|
||||
_, fulfillErr := fulfillQueryOrderPaid(l.ctx, l.svcCtx, order, status.WxOrderID, status.PaidFee)
|
||||
return fulfillErr
|
||||
}
|
||||
|
||||
if service.IsXpayClosedStatus(status.Status) {
|
||||
|
||||
@@ -67,7 +67,7 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
|
||||
}
|
||||
|
||||
case "query":
|
||||
paymentTypeResp, err = l.QueryOrderPayment(req, session)
|
||||
paymentTypeResp, err = l.QueryOrderPayment(req, session, useXpay)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -225,7 +225,7 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) {
|
||||
func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Session, useXpay bool) (resp *PaymentTypeResp, err error) {
|
||||
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if getUidErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取用户信息失败, %+v", getUidErr)
|
||||
@@ -271,12 +271,43 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
|
||||
if user.Inside == 1 {
|
||||
amount = 0.01
|
||||
}
|
||||
|
||||
// 同一 queryId 重复发起支付:复用已有订单,避免 order_no 唯一键冲突导致二次支付失败
|
||||
if existingOrder, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, outTradeNo); findOrderErr == nil {
|
||||
switch existingOrder.Status {
|
||||
case model.OrderStatusPaid:
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("订单已支付,请前往报告查看"), "订单号: %s", outTradeNo)
|
||||
case model.OrderStatusPending:
|
||||
if existingOrder.Status != model.OrderStatusPending {
|
||||
existingOrder.Status = model.OrderStatusPending
|
||||
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, session, existingOrder); updateErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "重置订单状态失败: %+v", updateErr)
|
||||
}
|
||||
}
|
||||
return &PaymentTypeResp{
|
||||
amount: existingOrder.Amount,
|
||||
outTradeNo: outTradeNo,
|
||||
description: product.ProductName,
|
||||
orderID: existingOrder.Id,
|
||||
productEn: product.ProductEn,
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("订单状态异常,请重新发起查询"), "status=%s", existingOrder.Status)
|
||||
}
|
||||
} else if !errors.Is(findOrderErr, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单失败: %+v", findOrderErr)
|
||||
}
|
||||
|
||||
paymentPlatform := req.PayMethod
|
||||
if useXpay {
|
||||
paymentPlatform = model.PaymentPlatformWechatXpay
|
||||
}
|
||||
order := model.Order{
|
||||
Id: uuid.NewString(),
|
||||
OrderNo: outTradeNo,
|
||||
UserId: userID,
|
||||
ProductId: product.Id,
|
||||
PaymentPlatform: req.PayMethod,
|
||||
PaymentPlatform: paymentPlatform,
|
||||
PaymentScene: resolvePaymentScene(l.ctx),
|
||||
Amount: amount,
|
||||
Status: "pending",
|
||||
@@ -376,18 +407,9 @@ func (l *PaymentLogic) shouldUseXpay(req *types.PaymentReq) bool {
|
||||
func resolvePaymentScene(ctx context.Context) string {
|
||||
platform, err := ctxdata.GetPlatformFromCtx(ctx)
|
||||
if err != nil {
|
||||
return "app"
|
||||
}
|
||||
switch platform {
|
||||
case model.PlatformWxMini:
|
||||
return "wxmini"
|
||||
case model.PlatformWxH5:
|
||||
return "wxh5"
|
||||
case model.PlatformH5:
|
||||
return "h5"
|
||||
default:
|
||||
return "app"
|
||||
return model.PaymentSceneApp
|
||||
}
|
||||
return model.PaymentSceneFromPlatform(platform)
|
||||
}
|
||||
|
||||
// AgentVipOrderPayment 代理会员充值订单(已废弃,新系统使用升级功能替代)
|
||||
|
||||
87
app/main/api/internal/logic/pay/xpay_deliver.go
Normal file
87
app/main/api/internal/logic/pay/xpay_deliver.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"qnc-server/app/main/api/internal/service"
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/model"
|
||||
)
|
||||
|
||||
// XpayDeliverResult 同步 xpay 支付状态(查微信单 → 本地到账 → 异步查报告)
|
||||
type XpayDeliverResult struct {
|
||||
Credited bool `json:"credited"`
|
||||
Notified bool `json:"notified"`
|
||||
WechatDetail string `json:"wechat_detail"`
|
||||
Errors []string `json:"errors"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// DeliverXpayQueryOrder 按商户订单号同步微信虚拟支付到账(不通知微信发货,发货在报告成功后)
|
||||
func DeliverXpayQueryOrder(ctx context.Context, svcCtx *svc.ServiceContext, orderNo string) (*XpayDeliverResult, error) {
|
||||
order, err := svcCtx.OrderModel.FindOneByOrderNo(ctx, orderNo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &XpayDeliverResult{WechatDetail: "ok"}
|
||||
|
||||
if order.Status == model.OrderStatusPaid {
|
||||
resp.Message = "订单已支付,无需同步"
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if !model.IsXpayOrder(order) {
|
||||
resp.Errors = append(resp.Errors, "非小程序虚拟支付订单")
|
||||
resp.Message = "非小程序虚拟支付订单"
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if svcCtx.XpayService == nil || !svcCtx.XpayService.Enabled() {
|
||||
resp.Errors = append(resp.Errors, "虚拟支付未启用")
|
||||
resp.Message = "虚拟支付未启用"
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
openid, err := svcCtx.XpayService.GetWxMiniOpenID(ctx, svcCtx.UserAuthModel, order.UserId)
|
||||
if err != nil {
|
||||
resp.Errors = append(resp.Errors, err.Error())
|
||||
resp.Message = "获取用户 openid 失败"
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
status, qErr := svcCtx.XpayService.QueryOrder(ctx, openid, order.OrderNo, "")
|
||||
if qErr != nil {
|
||||
resp.Errors = append(resp.Errors, qErr.Error())
|
||||
resp.WechatDetail = qErr.Error()
|
||||
resp.Message = "查询微信订单失败"
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if !service.IsXpayPaidStatus(status.Status) {
|
||||
resp.Errors = append(resp.Errors, "微信侧订单未支付")
|
||||
resp.Message = "微信侧订单未支付"
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
platformOrderID := status.WxOrderID
|
||||
credited, fulfillErr := fulfillQueryOrderPaid(ctx, svcCtx, order, platformOrderID, status.PaidFee)
|
||||
resp.Credited = credited
|
||||
if fulfillErr != nil {
|
||||
resp.Errors = append(resp.Errors, fulfillErr.Error())
|
||||
}
|
||||
|
||||
resp.Message = buildXpayDeliverMessage(resp)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func buildXpayDeliverMessage(r *XpayDeliverResult) string {
|
||||
if len(r.Errors) > 0 {
|
||||
return strings.Join(r.Errors, ";")
|
||||
}
|
||||
if r.Credited {
|
||||
return "同步成功,订单已到账并开始查询报告"
|
||||
}
|
||||
return "未执行到账"
|
||||
}
|
||||
48
app/main/api/internal/logic/pay/xpay_notify.go
Normal file
48
app/main/api/internal/logic/pay/xpay_notify.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/model"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
// NotifyXpayGoodsAfterReport 报告生成成功后通知微信虚拟支付已发货(合规/结算)
|
||||
func NotifyXpayGoodsAfterReport(ctx context.Context, svcCtx *svc.ServiceContext, order *model.Order) {
|
||||
if order == nil || !model.IsXpayOrder(order) {
|
||||
return
|
||||
}
|
||||
if svcCtx.XpayService == nil || !svcCtx.XpayService.Enabled() {
|
||||
return
|
||||
}
|
||||
|
||||
already, err := svcCtx.XpayService.AlreadyNotified(ctx, order.OrderNo)
|
||||
if err != nil {
|
||||
logx.WithContext(ctx).Errorf("[xpay] 检查发货通知状态失败 order_no=%s err=%v", order.OrderNo, err)
|
||||
return
|
||||
}
|
||||
if already {
|
||||
return
|
||||
}
|
||||
|
||||
openid, err := svcCtx.XpayService.GetWxMiniOpenID(ctx, svcCtx.UserAuthModel, order.UserId)
|
||||
if err != nil {
|
||||
logx.WithContext(ctx).Errorf("[xpay] 报告成功后通知发货:获取 openid 失败 order_no=%s err=%v", order.OrderNo, err)
|
||||
return
|
||||
}
|
||||
|
||||
sessionKey, _ := svcCtx.XpayService.GetSessionKey(ctx, order.UserId)
|
||||
wxOrderID := ""
|
||||
if order.PlatformOrderId.Valid {
|
||||
wxOrderID = order.PlatformOrderId.String
|
||||
}
|
||||
|
||||
if notifyErr := svcCtx.XpayService.NotifyProvideGoods(ctx, openid, order.OrderNo, wxOrderID, sessionKey); notifyErr != nil {
|
||||
logx.WithContext(ctx).Errorf("[xpay] 报告成功后 notify_provide_goods 失败 order_no=%s err=%v", order.OrderNo, notifyErr)
|
||||
return
|
||||
}
|
||||
_ = svcCtx.XpayService.MarkNotified(ctx, order.OrderNo)
|
||||
logx.WithContext(ctx).Infof("[xpay] 报告成功后已通知微信发货 order_no=%s", order.OrderNo)
|
||||
}
|
||||
83
app/main/api/internal/logic/pay/xpay_refund.go
Normal file
83
app/main/api/internal/logic/pay/xpay_refund.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/model"
|
||||
"qnc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
// RefundXpayQueryOrder 对 xpay 查询订单发起全额退款并更新本地订单状态
|
||||
func RefundXpayQueryOrder(ctx context.Context, svcCtx *svc.ServiceContext, order *model.Order) error {
|
||||
if svcCtx.XpayService == nil || !svcCtx.XpayService.Enabled() {
|
||||
return fmt.Errorf("虚拟支付未启用")
|
||||
}
|
||||
openid, err := svcCtx.XpayService.GetWxMiniOpenID(ctx, svcCtx.UserAuthModel, order.UserId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 openid 失败: %w", err)
|
||||
}
|
||||
|
||||
refundFeeFen := lzUtils.ToWechatAmount(order.Amount)
|
||||
refundOrderID := buildXpayRefundOrderID(order.OrderNo)
|
||||
if refundErr := svcCtx.XpayService.RefundOrder(ctx, openid, order.OrderNo, refundOrderID, refundFeeFen); refundErr != nil {
|
||||
return refundErr
|
||||
}
|
||||
|
||||
return svcCtx.OrderModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
|
||||
order.Status = model.OrderStatusRefunded
|
||||
if updateErr := svcCtx.OrderModel.UpdateWithVersion(transCtx, session, order); updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
return svcCtx.AgentService.ReverseAgentSettlementOnOrderRefund(transCtx, session, order.Id)
|
||||
})
|
||||
}
|
||||
|
||||
func buildXpayRefundOrderID(orderNo string) string {
|
||||
const prefix = "RF_"
|
||||
id := prefix + orderNo
|
||||
if len(id) > 32 {
|
||||
id = id[:32]
|
||||
}
|
||||
if len(id) < 8 {
|
||||
id = id + "00000000"
|
||||
id = id[:8]
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// TryRefundOnQueryFailure 查询失败时按支付渠道退款
|
||||
func TryRefundOnQueryFailure(ctx context.Context, svcCtx *svc.ServiceContext, order *model.Order) {
|
||||
var refundErr error
|
||||
switch {
|
||||
case model.IsXpayOrder(order):
|
||||
refundErr = RefundXpayQueryOrder(ctx, svcCtx, order)
|
||||
case order.PaymentPlatform == model.PaymentPlatformWechat:
|
||||
refundErr = svcCtx.WechatPayService.WeChatRefund(ctx, order.OrderNo, order.Amount, order.Amount)
|
||||
default:
|
||||
refund, err := svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount)
|
||||
if err != nil {
|
||||
refundErr = err
|
||||
} else if !refund.IsSuccess() {
|
||||
refundErr = fmt.Errorf("支付宝退款失败: %s", refund.Msg)
|
||||
} else {
|
||||
transErr := svcCtx.OrderModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
|
||||
order.Status = model.OrderStatusRefunded
|
||||
if err := svcCtx.OrderModel.UpdateWithVersion(transCtx, session, order); err != nil {
|
||||
return err
|
||||
}
|
||||
return svcCtx.AgentService.ReverseAgentSettlementOnOrderRefund(transCtx, session, order.Id)
|
||||
})
|
||||
if transErr != nil {
|
||||
refundErr = transErr
|
||||
}
|
||||
}
|
||||
}
|
||||
if refundErr != nil {
|
||||
logx.WithContext(ctx).Errorf("[refund] 查询失败自动退款未成功 order_no=%s platform=%s err=%v", order.OrderNo, order.PaymentPlatform, refundErr)
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/common/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type XpayAdminDeliverLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewXpayAdminDeliverLogic(ctx context.Context, svcCtx *svc.ServiceContext) *XpayAdminDeliverLogic {
|
||||
return &XpayAdminDeliverLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
type XpayAdminDeliverResp struct {
|
||||
Credited bool `json:"credited"`
|
||||
Notified bool `json:"notified"`
|
||||
WechatDetail string `json:"wechat_detail"`
|
||||
Errors []string `json:"errors"`
|
||||
}
|
||||
|
||||
func (l *XpayAdminDeliverLogic) XpayAdminDeliver(req *types.XpayAdminDeliverReq, r *http.Request) (*XpayAdminDeliverResp, error) {
|
||||
auth := r.Header.Get("Authorization")
|
||||
token := strings.TrimPrefix(auth, "Bearer ")
|
||||
if token == "" || token != l.svcCtx.Config.WechatXpay.AdminToken {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("未授权"), "")
|
||||
}
|
||||
|
||||
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("订单不存在"), "")
|
||||
}
|
||||
|
||||
resp := &XpayAdminDeliverResp{WechatDetail: "ok"}
|
||||
|
||||
if order.Status == "paid" {
|
||||
resp.Credited = false
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
openid, err := l.svcCtx.XpayService.GetWxMiniOpenID(l.ctx, l.svcCtx.UserAuthModel, order.UserId)
|
||||
if err != nil {
|
||||
resp.Errors = append(resp.Errors, err.Error())
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
sessionKey, err := l.svcCtx.XpayService.GetSessionKey(l.ctx, order.UserId)
|
||||
if err != nil {
|
||||
resp.Errors = append(resp.Errors, "session_key 不可用: "+err.Error())
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
status, qErr := l.svcCtx.XpayService.QueryOrder(l.ctx, openid, order.OrderNo, sessionKey)
|
||||
if qErr != nil {
|
||||
resp.Errors = append(resp.Errors, qErr.Error())
|
||||
resp.WechatDetail = qErr.Error()
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if !serviceIsPaid(status.Status) {
|
||||
resp.Errors = append(resp.Errors, "微信侧订单未支付")
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
credited, fulfillErr := fulfillQueryOrderPaid(l.ctx, l.svcCtx, order, "", status.PaidFee)
|
||||
resp.Credited = credited
|
||||
if fulfillErr != nil {
|
||||
resp.Errors = append(resp.Errors, fulfillErr.Error())
|
||||
}
|
||||
|
||||
if notifyErr := l.svcCtx.XpayService.NotifyProvideGoods(l.ctx, openid, order.OrderNo, "", sessionKey); notifyErr != nil {
|
||||
resp.Notified = false
|
||||
resp.WechatDetail = notifyErr.Error()
|
||||
resp.Errors = append(resp.Errors, notifyErr.Error())
|
||||
} else {
|
||||
resp.Notified = true
|
||||
_ = l.svcCtx.XpayService.MarkNotified(l.ctx, order.OrderNo)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func serviceIsPaid(status int) bool {
|
||||
return status == 2 || status == 3 || status == 4
|
||||
}
|
||||
@@ -21,8 +21,14 @@ func fulfillQueryOrderPaid(ctx context.Context, svcCtx *svc.ServiceContext, orde
|
||||
|
||||
orderFen := lzUtils.ToWechatAmount(order.Amount)
|
||||
if wechatPaidFen > 0 && wechatPaidFen != orderFen {
|
||||
logx.WithContext(ctx).Errorf("[xpay] 金额不一致 order_no=%s order_fen=%d wechat_fen=%d", order.OrderNo, orderFen, wechatPaidFen)
|
||||
return false, nil
|
||||
if model.IsXpayOrder(order) {
|
||||
order.Amount = lzUtils.RoundMoney(float64(wechatPaidFen) / 100)
|
||||
orderFen = wechatPaidFen
|
||||
logx.WithContext(ctx).Infof("[xpay] 同步微信实付金额 order_no=%s amount=%.2f fen=%d", order.OrderNo, order.Amount, wechatPaidFen)
|
||||
} else {
|
||||
logx.WithContext(ctx).Errorf("[xpay] 金额不一致 order_no=%s order_fen=%d wechat_fen=%d", order.OrderNo, orderFen, wechatPaidFen)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
order.Status = "paid"
|
||||
@@ -30,8 +36,8 @@ func fulfillQueryOrderPaid(ctx context.Context, svcCtx *svc.ServiceContext, orde
|
||||
if platformOrderID != "" {
|
||||
order.PlatformOrderId = sql.NullString{String: platformOrderID, Valid: true}
|
||||
}
|
||||
if order.PaymentScene == "" || order.PaymentScene == "app" {
|
||||
order.PaymentScene = "wxmini"
|
||||
if order.PaymentScene == "" || order.PaymentScene == model.PaymentSceneApp {
|
||||
order.PaymentScene = model.PaymentSceneMiniProgram
|
||||
}
|
||||
|
||||
if updateErr := svcCtx.OrderModel.UpdateWithVersion(ctx, nil, order); updateErr != nil {
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
@@ -33,8 +35,14 @@ func NewWxMiniAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WxMini
|
||||
}
|
||||
|
||||
func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMiniAuthResp, err error) {
|
||||
code := strings.TrimSpace(req.Code)
|
||||
if code == "" || code == "the code is a mock one" || strings.Contains(code, " ") {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("微信登录凭证无效,请重新打开小程序"),
|
||||
"无效的微信 code: %q", req.Code)
|
||||
}
|
||||
|
||||
// 1. 获取session_key和openid
|
||||
sessionKeyResp, err := l.GetSessionKey(req.Code)
|
||||
sessionKeyResp, err := l.GetSessionKey(code)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err)
|
||||
}
|
||||
@@ -104,10 +112,10 @@ func (l *WxMiniAuthLogic) GetSessionKey(code string) (*SessionKeyResp, error) {
|
||||
appID = l.svcCtx.Config.WechatMini.AppID
|
||||
appSecret = l.svcCtx.Config.WechatMini.AppSecret
|
||||
|
||||
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
||||
appID, appSecret, code)
|
||||
apiURL := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
||||
url.QueryEscape(appID), url.QueryEscape(appSecret), url.QueryEscape(code))
|
||||
|
||||
resp, err := http.Get(url)
|
||||
resp, err := http.Get(apiURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user