f
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"jnc-server/app/main/api/internal/logic/pay"
|
||||
"jnc-server/app/main/api/internal/svc"
|
||||
"jnc-server/common/result"
|
||||
)
|
||||
|
||||
func YunYinSignPayCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := pay.NewYunYinSignPayCallbackLogic(r.Context(), svcCtx)
|
||||
err := l.YunYinSignPayCallback(w, r)
|
||||
result.HttpResult(r, w, nil, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"jnc-server/app/main/api/internal/logic/pay"
|
||||
"jnc-server/app/main/api/internal/svc"
|
||||
"jnc-server/app/main/api/internal/types"
|
||||
"jnc-server/common/result"
|
||||
"jnc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func YunYinSignPayRefundHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.YunYinSignPayRefundReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := pay.NewYunYinSignPayRefundLogic(r.Context(), svcCtx)
|
||||
resp, err := l.YunYinSignPayRefund(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -786,6 +786,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
Path: "/pay/wechat/refund_callback",
|
||||
Handler: pay.WechatPayRefundCallbackHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/pay/yunyinsign/callback",
|
||||
Handler: pay.YunYinSignPayCallbackHandler(serverCtx),
|
||||
},
|
||||
},
|
||||
rest.WithPrefix("/api/v1"),
|
||||
)
|
||||
@@ -809,6 +814,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
Path: "/pay/payment",
|
||||
Handler: pay.PaymentHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/pay/yunyinsign/refund",
|
||||
Handler: pay.YunYinSignPayRefundHandler(serverCtx),
|
||||
},
|
||||
}...,
|
||||
),
|
||||
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
|
||||
|
||||
191
app/main/api/internal/logic/pay/yunyinsignpaycallbacklogic.go
Normal file
191
app/main/api/internal/logic/pay/yunyinsignpaycallbacklogic.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"jnc-server/app/main/api/internal/svc"
|
||||
"jnc-server/pkg/lzkit/lzUtils"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type YunYinSignPayCallbackLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewYunYinSignPayCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *YunYinSignPayCallbackLogic {
|
||||
return &YunYinSignPayCallbackLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
// YunYinSignPayCallback 云印签支付回调处理
|
||||
func (l *YunYinSignPayCallbackLogic) YunYinSignPayCallback(w http.ResponseWriter, r *http.Request) error {
|
||||
// 检查云印签支付服务是否启用
|
||||
if l.svcCtx.YunYinSignPayService == nil {
|
||||
logx.Errorf("云印签支付服务未启用")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 解析表单数据
|
||||
if err := r.ParseForm(); err != nil {
|
||||
logx.Errorf("云印签回调,解析表单数据失败: %v", err)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取回调参数
|
||||
tradeState := r.FormValue("tradeState")
|
||||
orderType := r.FormValue("orderType")
|
||||
payOrderNo := r.FormValue("payOrderNo")
|
||||
payAmount := r.FormValue("payAmount")
|
||||
flowId := r.FormValue("flowId")
|
||||
sourceOrderCode := r.FormValue("sourceOrderCode")
|
||||
channelOrderNo := r.FormValue("channelOrderNo")
|
||||
paySuccessTime := r.FormValue("paySuccessTime")
|
||||
|
||||
// 记录回调日志
|
||||
logx.Infof("云印签回调通知,订单号: %s, 支付状态: %s, 订单类型: %s, 支付单号: %s, 金额: %s, 流程ID: %s",
|
||||
sourceOrderCode, tradeState, orderType, payOrderNo, payAmount, flowId)
|
||||
|
||||
// 只处理付款通知(orderType == "pay")
|
||||
if orderType != "pay" {
|
||||
logx.Infof("云印签回调,非付款通知,订单类型: %s, 订单号: %s", orderType, sourceOrderCode)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 如果没有 sourceOrderCode,无法关联订单,直接返回成功
|
||||
if sourceOrderCode == "" {
|
||||
logx.Errorf("云印签回调,缺少 sourceOrderCode 参数")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 根据 sourceOrderCode 查找订单
|
||||
order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, sourceOrderCode)
|
||||
if findOrderErr != nil {
|
||||
logx.Errorf("云印签回调,查找订单失败,订单号: %s, 错误: %v", sourceOrderCode, findOrderErr)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 幂等性检查:如果订单已经不是 pending 状态,说明已经处理过,直接返回成功
|
||||
if order.Status != "pending" {
|
||||
logx.Infof("云印签回调,订单已处理,订单号: %s, 当前状态: %s", sourceOrderCode, order.Status)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 验证支付状态:tradeState == "00000" 表示支付成功
|
||||
if tradeState != "00000" {
|
||||
logx.Infof("云印签回调,订单未支付成功,订单号: %s, 支付状态: %s", sourceOrderCode, tradeState)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 验证金额
|
||||
payAmountFloat, parseAmountErr := strconv.ParseFloat(payAmount, 64)
|
||||
if parseAmountErr != nil {
|
||||
logx.Errorf("云印签回调,金额解析失败,订单号: %s, 金额: %s, 错误: %v", sourceOrderCode, payAmount, parseAmountErr)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 金额比较(允许小数点误差)
|
||||
amountDiff := payAmountFloat - order.Amount
|
||||
if amountDiff < 0 {
|
||||
amountDiff = -amountDiff
|
||||
}
|
||||
if amountDiff > 0.01 { // 允许1分钱的误差
|
||||
logx.Errorf("云印签回调,金额不一致,订单号: %s, 订单金额: %.2f, 回调金额: %.2f", sourceOrderCode, order.Amount, payAmountFloat)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 解析支付成功时间
|
||||
var payTime time.Time
|
||||
if paySuccessTime != "" {
|
||||
// 尝试多种时间格式
|
||||
timeFormats := []string{
|
||||
"2006-01-02 15:04:05",
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02 15:04:05.000",
|
||||
"2006-01-02T15:04:05.000Z",
|
||||
}
|
||||
parsed := false
|
||||
for _, format := range timeFormats {
|
||||
if t, err := time.Parse(format, paySuccessTime); err == nil {
|
||||
payTime = t
|
||||
parsed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !parsed {
|
||||
// 如果解析失败,使用当前时间
|
||||
payTime = time.Now()
|
||||
logx.Infof("云印签回调,支付时间解析失败,使用当前时间,订单号: %s, 时间字符串: %s", sourceOrderCode, paySuccessTime)
|
||||
}
|
||||
} else {
|
||||
payTime = time.Now()
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
order.Status = "paid"
|
||||
order.PayTime = lzUtils.TimeToNullTime(payTime)
|
||||
if channelOrderNo != "" {
|
||||
order.PlatformOrderId = lzUtils.StringToNullString(channelOrderNo)
|
||||
}
|
||||
|
||||
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
|
||||
logx.Errorf("云印签回调,更新订单状态失败,订单号: %s, 错误: %v", sourceOrderCode, updateErr)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 更新云印签订单表的支付状态
|
||||
yunyinOrder, findYunyinErr := l.svcCtx.YunyinSignPayOrderModel.FindOneByOrderId(l.ctx, order.Id)
|
||||
if findYunyinErr == nil && yunyinOrder != nil {
|
||||
// 更新支付状态为已支付(1)
|
||||
if yunyinOrder.PayStatus != 1 {
|
||||
yunyinOrder.PayStatus = 1
|
||||
if updateYunyinErr := l.svcCtx.YunyinSignPayOrderModel.Update(l.ctx, yunyinOrder); updateYunyinErr != nil {
|
||||
logx.Errorf("云印签回调,更新云印签订单支付状态失败,订单ID: %s, 错误: %v", order.Id, updateYunyinErr)
|
||||
} else {
|
||||
logx.Infof("云印签回调,成功更新云印签订单支付状态,订单ID: %s", order.Id)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logx.Infof("云印签回调,未找到对应的云印签订单记录,订单ID: %s, 流程ID: %s", order.Id, flowId)
|
||||
}
|
||||
|
||||
// 发送异步任务处理后续流程
|
||||
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
|
||||
logx.Errorf("云印签回调,异步任务调度失败,订单ID: %s, 错误: %v", order.Id, asyncErr)
|
||||
// 不返回错误,因为订单已经更新成功
|
||||
}
|
||||
|
||||
logx.Infof("云印签回调处理成功,订单号: %s, 支付单号: %s, 渠道单号: %s", sourceOrderCode, payOrderNo, channelOrderNo)
|
||||
|
||||
// 返回 success,云印签要求返回纯字符串 success
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
153
app/main/api/internal/logic/pay/yunyinsignpayrefundlogic.go
Normal file
153
app/main/api/internal/logic/pay/yunyinsignpayrefundlogic.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"jnc-server/app/main/api/internal/svc"
|
||||
"jnc-server/app/main/api/internal/types"
|
||||
"jnc-server/app/main/model"
|
||||
"jnc-server/common/xerr"
|
||||
"jnc-server/pkg/lzkit/lzUtils"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
type YunYinSignPayRefundLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewYunYinSignPayRefundLogic(ctx context.Context, svcCtx *svc.ServiceContext) *YunYinSignPayRefundLogic {
|
||||
return &YunYinSignPayRefundLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
// YunYinSignPayRefund 云印签退款
|
||||
func (l *YunYinSignPayRefundLogic) YunYinSignPayRefund(req *types.YunYinSignPayRefundReq) (resp *types.YunYinSignPayRefundResp, err error) {
|
||||
// 检查云印签支付服务是否启用
|
||||
if l.svcCtx.YunYinSignPayService == nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "云印签支付服务未启用")
|
||||
}
|
||||
|
||||
// 验证参数:order_no 和 participate_id 至少提供一个
|
||||
if req.OrderNo == "" && req.ParticipateId == 0 {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "订单号和参与方ID至少需要提供一个")
|
||||
}
|
||||
|
||||
// 查找订单(如果提供了订单号)
|
||||
var order *model.Order
|
||||
if req.OrderNo != "" {
|
||||
order, err = l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找订单失败,订单号: %s, 错误: %v", req.OrderNo, err)
|
||||
}
|
||||
|
||||
// 验证订单支付平台
|
||||
if order.PaymentPlatform != "yunyinSignPay" && order.PaymentPlatform != "yunyinSignPay_wechat" && order.PaymentPlatform != "yunyinSignPay_alipay" {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "订单不是云印签支付订单,订单号: %s, 支付平台: %s", req.OrderNo, order.PaymentPlatform)
|
||||
}
|
||||
|
||||
// 验证订单状态
|
||||
if order.Status != "paid" {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "订单状态不允许退款,订单号: %s, 状态: %s", req.OrderNo, order.Status)
|
||||
}
|
||||
|
||||
// 验证退款金额
|
||||
if req.RefundAmount > order.Amount {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "退款金额不能超过订单金额,订单号: %s, 订单金额: %.2f, 退款金额: %.2f", req.OrderNo, order.Amount, req.RefundAmount)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用云印签退款接口
|
||||
// RefundPayeeBill 方法会自动处理:如果只提供了 sourceOrderCode,会查询收款单获取 participateId
|
||||
refundErr := l.svcCtx.YunYinSignPayService.RefundPayeeBill(
|
||||
l.ctx,
|
||||
req.OrderNo,
|
||||
req.ParticipateId,
|
||||
req.RefundAmount,
|
||||
req.RefundReason,
|
||||
)
|
||||
if refundErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "云印签退款失败: %v", refundErr)
|
||||
}
|
||||
|
||||
// 如果提供了订单号,更新订单状态和创建退款记录
|
||||
if req.OrderNo != "" && order != nil {
|
||||
// 检查是否全额退款
|
||||
isFullRefund := req.RefundAmount >= order.Amount-0.01 // 允许1分钱的误差
|
||||
|
||||
// 创建退款记录并更新订单状态
|
||||
err = l.createRefundRecordAndUpdateOrder(order, req, isFullRefund)
|
||||
if err != nil {
|
||||
logx.Errorf("创建退款记录或更新订单状态失败: %v", err)
|
||||
// 不返回错误,因为退款已经成功
|
||||
}
|
||||
|
||||
// 更新云印签订单表的支付状态
|
||||
yunyinOrder, findYunyinErr := l.svcCtx.YunyinSignPayOrderModel.FindOneByOrderId(l.ctx, order.Id)
|
||||
if findYunyinErr == nil && yunyinOrder != nil {
|
||||
if isFullRefund {
|
||||
// 全额退款,更新为已退款状态(2)
|
||||
yunyinOrder.PayStatus = 2
|
||||
}
|
||||
// 部分退款保持已支付状态(1),但可以通过其他字段记录退款金额
|
||||
if updateYunyinErr := l.svcCtx.YunyinSignPayOrderModel.Update(l.ctx, yunyinOrder); updateYunyinErr != nil {
|
||||
logx.Errorf("更新云印签订单状态失败: %v", updateYunyinErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logx.Infof("云印签退款成功,订单号: %s, 参与方ID: %d, 退款金额: %.2f", req.OrderNo, req.ParticipateId, req.RefundAmount)
|
||||
|
||||
return &types.YunYinSignPayRefundResp{
|
||||
Success: true,
|
||||
Message: "退款申请已提交",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// createRefundRecordAndUpdateOrder 创建退款记录并更新订单状态
|
||||
func (l *YunYinSignPayRefundLogic) createRefundRecordAndUpdateOrder(order *model.Order, req *types.YunYinSignPayRefundReq, isFullRefund bool) error {
|
||||
return l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
// 生成退款单号
|
||||
refundNo := fmt.Sprintf("refund-%s", order.OrderNo)
|
||||
|
||||
// 创建退款记录
|
||||
refund := &model.OrderRefund{
|
||||
Id: uuid.NewString(),
|
||||
RefundNo: refundNo,
|
||||
PlatformRefundId: sql.NullString{String: "", Valid: false}, // 云印签退款没有平台退款单号
|
||||
OrderId: order.Id,
|
||||
UserId: order.UserId,
|
||||
ProductId: order.ProductId,
|
||||
RefundAmount: req.RefundAmount,
|
||||
RefundReason: lzUtils.StringToNullString(req.RefundReason),
|
||||
Status: "success", // 云印签退款是异步的,这里标记为成功
|
||||
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
|
||||
}
|
||||
|
||||
if _, err := l.svcCtx.OrderRefundModel.Insert(ctx, session, refund); err != nil {
|
||||
return fmt.Errorf("创建退款记录失败: %v", err)
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
if isFullRefund {
|
||||
order.Status = "refunded"
|
||||
}
|
||||
// 部分退款保持 paid 状态
|
||||
|
||||
if _, err := l.svcCtx.OrderModel.Update(ctx, session, order); err != nil {
|
||||
return fmt.Errorf("更新订单状态失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -683,3 +683,172 @@ func (y *YunYinSignPayService) QueryPayeeBill(ctx context.Context, sourceOrderCo
|
||||
RefundAmount: billItem.RefundAmount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RefundPayeeBillRequest 退款请求
|
||||
type RefundPayeeBillRequest struct {
|
||||
SourceOrderCode string `json:"sourceOrderCode,omitempty"` // 来源订单号(与 participateId 二选一)
|
||||
ParticipateId int64 `json:"participateId,omitempty"` // 参与方ID(与 sourceOrderCode 二选一)
|
||||
RefundAmount float64 `json:"refundAmount"` // 退款金额(必填)
|
||||
RefundReason string `json:"refundReason,omitempty"` // 退款原因(可选)
|
||||
}
|
||||
|
||||
// RefundPayeeBillResponse 退款响应
|
||||
type RefundPayeeBillResponse struct {
|
||||
Code interface{} `json:"code"` // 返回码,可能是字符串"200"或数字200
|
||||
Msg string `json:"msg"` // 返回码的描述信息
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// GetCodeInt 获取 code 的 int 值
|
||||
func (r *RefundPayeeBillResponse) GetCodeInt() int {
|
||||
switch v := r.Code.(type) {
|
||||
case int:
|
||||
return v
|
||||
case float64:
|
||||
return int(v)
|
||||
case string:
|
||||
if v == "200" {
|
||||
return 200
|
||||
}
|
||||
return 0
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// RefundPayeeBill 发起退款
|
||||
func (y *YunYinSignPayService) RefundPayeeBill(ctx context.Context, sourceOrderCode string, participateId int64, refundAmount float64, refundReason string) error {
|
||||
// 1. 获取token和操作ID(带缓存)
|
||||
accessToken, err := y.GetAccessToken(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取云印签token失败: %v", err)
|
||||
}
|
||||
|
||||
operationUserId, err := y.GetUserId(ctx, accessToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取云印签操作ID失败: %v", err)
|
||||
}
|
||||
|
||||
// 2. 如果只提供了 sourceOrderCode,需要先查询收款单获取 participateId
|
||||
if participateId == 0 && sourceOrderCode != "" {
|
||||
// 查询收款单列表获取 participateId
|
||||
reqBody := QueryPayeeBillRequest{
|
||||
SourceOrderCode: sourceOrderCode,
|
||||
ListPageNo: 1,
|
||||
ListPageSize: 10,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化查询请求失败: %v", err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/signFlowBill/payeeBillList", y.config.ApiURL)
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建查询请求失败: %v", err)
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("appId", y.config.AppID)
|
||||
httpReq.Header.Set("accessToken", accessToken)
|
||||
httpReq.Header.Set("userId", operationUserId)
|
||||
httpReq.Header.Set("source", "pc")
|
||||
|
||||
resp, err := y.client.Do(httpReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取查询响应失败: %v", err)
|
||||
}
|
||||
|
||||
var queryResp QueryPayeeBillResponse
|
||||
if err := json.Unmarshal(body, &queryResp); err != nil {
|
||||
return fmt.Errorf("解析查询响应失败: %v, 响应内容: %s", err, string(body))
|
||||
}
|
||||
|
||||
codeInt := queryResp.GetCodeInt()
|
||||
if codeInt != 200 {
|
||||
return fmt.Errorf("查询收款单失败: %s", queryResp.Msg)
|
||||
}
|
||||
|
||||
if len(queryResp.Data) == 0 {
|
||||
return fmt.Errorf("未找到匹配的收款单记录,订单号: %s", sourceOrderCode)
|
||||
}
|
||||
|
||||
// 获取第一条记录的 participateId
|
||||
billItem := queryResp.Data[0]
|
||||
participateId = billItem.ParticipateID
|
||||
logx.Infof("通过订单号查询到参与方ID: %d", participateId)
|
||||
}
|
||||
|
||||
// 3. 验证参数
|
||||
if participateId == 0 {
|
||||
return fmt.Errorf("参与方ID不能为空,请提供 participateId 或 sourceOrderCode")
|
||||
}
|
||||
|
||||
if refundAmount <= 0 {
|
||||
return fmt.Errorf("退款金额必须大于0")
|
||||
}
|
||||
|
||||
// 4. 构建退款请求
|
||||
refundReq := RefundPayeeBillRequest{
|
||||
RefundAmount: refundAmount,
|
||||
RefundReason: refundReason,
|
||||
}
|
||||
|
||||
// 优先使用 participateId
|
||||
if participateId > 0 {
|
||||
refundReq.ParticipateId = participateId
|
||||
} else if sourceOrderCode != "" {
|
||||
// 如果 participateId 仍然为0,使用 sourceOrderCode
|
||||
refundReq.SourceOrderCode = sourceOrderCode
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(refundReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化退款请求失败: %v", err)
|
||||
}
|
||||
|
||||
// 5. 调用退款API
|
||||
url := fmt.Sprintf("%s/signFlowBill/refund", y.config.ApiURL)
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建退款请求失败: %v", err)
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("appId", y.config.AppID)
|
||||
httpReq.Header.Set("accessToken", accessToken)
|
||||
httpReq.Header.Set("userId", operationUserId)
|
||||
httpReq.Header.Set("source", "pc")
|
||||
|
||||
resp, err := y.client.Do(httpReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("退款请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取退款响应失败: %v", err)
|
||||
}
|
||||
|
||||
var refundResp RefundPayeeBillResponse
|
||||
if err := json.Unmarshal(body, &refundResp); err != nil {
|
||||
return fmt.Errorf("解析退款响应失败: %v, 响应内容: %s", err, string(body))
|
||||
}
|
||||
|
||||
// 6. 检查响应码
|
||||
codeInt := refundResp.GetCodeInt()
|
||||
if codeInt != 200 {
|
||||
return fmt.Errorf("退款失败: %s", refundResp.Msg)
|
||||
}
|
||||
|
||||
logx.Infof("云印签退款成功,参与方ID: %d, 退款金额: %.2f, 退款原因: %s", participateId, refundAmount, refundReason)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,3 +26,15 @@ type PaymentResp struct {
|
||||
PrepayId string `json:"prepay_id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
}
|
||||
|
||||
type YunYinSignPayRefundReq struct {
|
||||
OrderNo string `json:"order_no"` // 订单号(sourceOrderCode),与 participate_id 二选一
|
||||
ParticipateId int64 `json:"participate_id,omitempty"` // 参与方ID,与 order_no 二选一
|
||||
RefundAmount float64 `json:"refund_amount" validate:"required,gt=0"` // 退款金额,必须大于0
|
||||
RefundReason string `json:"refund_reason,omitempty"` // 退款原因
|
||||
}
|
||||
|
||||
type YunYinSignPayRefundResp struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user