fix
This commit is contained in:
@@ -22,6 +22,10 @@ service main {
|
||||
// 微信退款回调
|
||||
@handler WechatPayRefundCallback
|
||||
post /pay/wechat/refund_callback
|
||||
|
||||
// 易支付回调
|
||||
@handler EasyPayCallback
|
||||
get /pay/easypay/callback
|
||||
}
|
||||
|
||||
@server (
|
||||
|
||||
@@ -82,3 +82,11 @@ Authorization:
|
||||
Promotion:
|
||||
PromotionDomain: "http://localhost:8888" # 推广域名(用于生成短链)
|
||||
OfficialDomain: "http://localhost:5678" # 正式站点域名(短链重定向的目标域名)
|
||||
EasyPay:
|
||||
Enabled: true
|
||||
ApiURL: "https://zpayz.cn/"
|
||||
PID: "2025123009590455"
|
||||
PKEY: "f61pwaOj93lYpesM82ZnPAVwFojuSL7F"
|
||||
CID: "12200"
|
||||
NotifyUrl: "https://6m4685017o.goho.co/api/v1/pay/easypay/callback"
|
||||
ReturnUrl: "http://localhost:5678/payment/result"
|
||||
|
||||
@@ -82,3 +82,11 @@ Authorization:
|
||||
Promotion:
|
||||
PromotionDomain: "https://p.dsjcq168.cn" # 推广域名(用于生成短链)
|
||||
OfficialDomain: "https://www.dsjcq168.cn" # 正式站点域名(短链重定向的目标域名)
|
||||
EasyPay:
|
||||
Enabled: true
|
||||
ApiURL: "https://zpayz.cn/"
|
||||
PID: "2025123009590455"
|
||||
PKEY: "f61pwaOj93lYpesM82ZnPAVwFojuSL7F"
|
||||
CID: "12200"
|
||||
NotifyUrl: "https://www.dsjcq168.cn/api/v1/pay/easypay/callback"
|
||||
ReturnUrl: "https://www.dsjcq168.cn/payment/result"
|
||||
|
||||
@@ -15,6 +15,7 @@ type Config struct {
|
||||
Alipay AlipayConfig
|
||||
Wxpay WxpayConfig
|
||||
Applepay ApplepayConfig
|
||||
EasyPay EasyPayConfig
|
||||
Tianyuanapi TianyuanapiConfig
|
||||
SystemConfig SystemConfig
|
||||
WechatH5 WechatH5Config
|
||||
@@ -121,3 +122,14 @@ type PromotionConfig struct {
|
||||
PromotionDomain string // 推广域名(用于生成短链)
|
||||
OfficialDomain string // 正式站点域名(短链重定向的目标域名)
|
||||
}
|
||||
|
||||
// EasyPayConfig 易支付配置
|
||||
type EasyPayConfig struct {
|
||||
Enabled bool // 是否启用易支付
|
||||
ApiURL string // 接口地址
|
||||
PID string // 商户ID
|
||||
PKEY string // 商户密钥
|
||||
CID string // 支付渠道ID
|
||||
NotifyUrl string // 异步通知地址
|
||||
ReturnUrl string // 页面跳转地址
|
||||
}
|
||||
|
||||
17
app/main/api/internal/handler/pay/easypaycallbackhandler.go
Normal file
17
app/main/api/internal/handler/pay/easypaycallbackhandler.go
Normal file
@@ -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 EasyPayCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := pay.NewEasyPayCallbackLogic(r.Context(), svcCtx)
|
||||
err := l.EasyPayCallback(w, r)
|
||||
result.HttpResult(r, w, nil, err)
|
||||
}
|
||||
}
|
||||
@@ -771,6 +771,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
Path: "/pay/alipay/callback",
|
||||
Handler: pay.AlipayCallbackHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/pay/easypay/callback",
|
||||
Handler: pay.EasyPayCallbackHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/pay/wechat/callback",
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
const (
|
||||
PaymentPlatformAlipay = "alipay"
|
||||
PaymentPlatformWechat = "wechat"
|
||||
PaymentPlatformEasyPay = "easypay_alipay"
|
||||
OrderStatusPaid = "paid"
|
||||
RefundNoPrefix = "refund-"
|
||||
)
|
||||
@@ -50,6 +51,8 @@ func (l *AdminRefundOrderLogic) AdminRefundOrder(req *types.AdminRefundOrderReq)
|
||||
return l.handleAlipayRefund(order, req)
|
||||
case PaymentPlatformWechat:
|
||||
return l.handleWechatRefund(order, req)
|
||||
case PaymentPlatformEasyPay:
|
||||
return l.handleEasyPayRefund(order, req)
|
||||
default:
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("不支持的支付平台"), "AdminRefundOrder, 不支持的支付平台: %s", order.PaymentPlatform)
|
||||
}
|
||||
@@ -130,6 +133,40 @@ func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *type
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handleEasyPayRefund 处理易支付退款
|
||||
func (l *AdminRefundOrderLogic) handleEasyPayRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
|
||||
// 检查易支付服务是否启用
|
||||
if l.svcCtx.EasyPayService == nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 易支付服务未启用")
|
||||
}
|
||||
|
||||
// 调用易支付退款接口
|
||||
err := l.svcCtx.EasyPayService.Refund(l.ctx, order.OrderNo, req.RefundAmount)
|
||||
if err != nil {
|
||||
// 易支付退款失败,创建失败记录但不更新订单状态
|
||||
refundNo := l.generateRefundNo(order.OrderNo)
|
||||
createErr := l.createRefundRecordOnly(order, req, refundNo, "", model.OrderRefundStatusFailed)
|
||||
if createErr != nil {
|
||||
logx.Errorf("创建易支付退款失败记录时出错: %v", createErr)
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 易支付退款失败 err: %v", err)
|
||||
}
|
||||
|
||||
// 易支付退款成功,创建成功记录
|
||||
refundNo := l.generateRefundNo(order.OrderNo)
|
||||
// 易支付退款是同步的,直接标记为成功
|
||||
err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, "", model.OrderStatusRefunded, model.OrderRefundStatusSuccess)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.AdminRefundOrderResp{
|
||||
Status: model.OrderStatusRefunded,
|
||||
RefundNo: refundNo,
|
||||
Amount: req.RefundAmount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// createRefundRecordAndUpdateOrder 创建退款记录并更新订单状态
|
||||
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 {
|
||||
|
||||
122
app/main/api/internal/logic/pay/easypaycallbacklogic.go
Normal file
122
app/main/api/internal/logic/pay/easypaycallbacklogic.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"jnc-server/app/main/api/internal/service"
|
||||
"jnc-server/pkg/lzkit/lzUtils"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"jnc-server/app/main/api/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type EasyPayCallbackLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewEasyPayCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *EasyPayCallbackLogic {
|
||||
return &EasyPayCallbackLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *EasyPayCallbackLogic) EasyPayCallback(w http.ResponseWriter, r *http.Request) error {
|
||||
// 检查易支付服务是否启用
|
||||
if l.svcCtx.EasyPayService == nil {
|
||||
logx.Errorf("易支付服务未启用")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return nil
|
||||
}
|
||||
|
||||
notification, err := l.svcCtx.EasyPayService.HandleEasyPayNotification(r)
|
||||
if err != nil {
|
||||
logx.Errorf("易支付回调处理失败: %v", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 根据订单号前缀判断订单类型
|
||||
orderNo := notification.OutTradeNo
|
||||
if strings.HasPrefix(orderNo, "Q_") {
|
||||
// 查询订单处理
|
||||
return l.handleQueryOrderPayment(w, notification)
|
||||
} else if strings.HasPrefix(orderNo, "A_") {
|
||||
// 旧系统会员充值订单(已废弃,新系统使用升级功能)
|
||||
// 直接返回成功,避免旧订单影响
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
} else if strings.HasPrefix(orderNo, "U_") {
|
||||
// 系统简化:移除升级功能,直接返回成功
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
} else {
|
||||
// 兼容旧订单,假设没有前缀的是查询订单
|
||||
return l.handleQueryOrderPayment(w, notification)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理查询订单支付
|
||||
func (l *EasyPayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter, notification *service.EasyPayNotification) error {
|
||||
order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, notification.OutTradeNo)
|
||||
if findOrderErr != nil {
|
||||
logx.Errorf("易支付回调,查找订单失败: %+v", findOrderErr)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
if order.Status != "pending" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 验证支付状态
|
||||
if !l.svcCtx.EasyPayService.IsPaymentSuccess(notification) {
|
||||
logx.Infof("易支付回调,订单未支付成功,订单号: %s, 状态: %s", notification.OutTradeNo, notification.TradeStatus)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 验证金额(转换为字符串比较)
|
||||
amountStr := lzUtils.ToAlipayAmount(order.Amount)
|
||||
if amountStr != notification.Money {
|
||||
logx.Errorf("易支付回调,金额不一致,订单号: %s, 订单金额: %s, 回调金额: %s", notification.OutTradeNo, amountStr, notification.Money)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
order.Status = "paid"
|
||||
order.PayTime = lzUtils.TimeToNullTime(time.Now())
|
||||
order.PlatformOrderId = lzUtils.StringToNullString(notification.TradeNo)
|
||||
|
||||
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
|
||||
logx.Errorf("易支付回调,修改订单信息失败: %+v", updateErr)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 发送异步任务处理后续流程
|
||||
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
|
||||
logx.Errorf("异步任务调度失败: %v", asyncErr)
|
||||
// 不返回错误,因为订单已经更新成功
|
||||
}
|
||||
|
||||
// 返回success,易支付要求返回纯字符串success
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("success"))
|
||||
return nil
|
||||
}
|
||||
@@ -2,10 +2,12 @@ package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"jnc-server/app/main/api/internal/svc"
|
||||
"jnc-server/app/main/api/internal/types"
|
||||
"jnc-server/common/xerr"
|
||||
"jnc-server/pkg/lzkit/lzUtils"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
@@ -44,6 +46,55 @@ func (l *PaymentCheckLogic) PaymentCheck(req *types.PaymentCheckReq) (resp *type
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单失败: %v", err)
|
||||
}
|
||||
|
||||
// 如果订单状态是 pending 且支付平台是易支付,主动查询易支付订单状态
|
||||
if order.Status == "pending" && order.PaymentPlatform == "easypay_alipay" {
|
||||
// 检查易支付服务是否启用
|
||||
if l.svcCtx.EasyPayService != nil {
|
||||
// 主动查询易支付订单状态
|
||||
queryResp, queryErr := l.svcCtx.EasyPayService.QueryOrderStatus(l.ctx, req.OrderNo)
|
||||
if queryErr != nil {
|
||||
logx.Errorf("主动查询易支付订单状态失败,订单号: %s, 错误: %v", req.OrderNo, queryErr)
|
||||
// 查询失败不影响返回,继续返回当前订单状态
|
||||
} else {
|
||||
// 如果易支付返回订单已支付(status == 1),更新本地订单状态
|
||||
if queryResp.Status == 1 {
|
||||
logx.Infof("主动查询发现易支付订单已支付,订单号: %s,开始更新订单状态", req.OrderNo)
|
||||
|
||||
// 重新查询订单(获取最新版本号)
|
||||
order, err = l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
|
||||
if err != nil {
|
||||
logx.Errorf("更新订单状态前重新查询订单失败: %v", err)
|
||||
} else if order.Status == "pending" {
|
||||
// 更新订单状态
|
||||
order.Status = "paid"
|
||||
order.PayTime = lzUtils.TimeToNullTime(time.Now())
|
||||
if queryResp.TradeNo != "" {
|
||||
order.PlatformOrderId = lzUtils.StringToNullString(queryResp.TradeNo)
|
||||
}
|
||||
|
||||
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
|
||||
logx.Errorf("主动查询后更新订单状态失败: %v", updateErr)
|
||||
} else {
|
||||
logx.Infof("主动查询后成功更新订单状态为已支付,订单号: %s", req.OrderNo)
|
||||
|
||||
// 发送异步任务处理后续流程
|
||||
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
|
||||
logx.Errorf("主动查询后发送异步任务失败: %v", asyncErr)
|
||||
}
|
||||
|
||||
// 返回更新后的状态
|
||||
return &types.PaymentCheckResp{
|
||||
Type: "query",
|
||||
Status: "paid",
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &types.PaymentCheckResp{
|
||||
Type: "query",
|
||||
Status: order.Status,
|
||||
|
||||
@@ -5,14 +5,14 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"jnc-server/app/main/api/internal/svc"
|
||||
"jnc-server/app/main/api/internal/types"
|
||||
"jnc-server/app/main/model"
|
||||
"jnc-server/common/ctxdata"
|
||||
"jnc-server/common/xerr"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
@@ -92,6 +92,11 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
|
||||
prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
|
||||
} else if req.PayMethod == "alipay" {
|
||||
prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
|
||||
} else if req.PayMethod == "easypay_alipay" {
|
||||
if l.svcCtx.EasyPayService == nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "易支付服务未启用")
|
||||
}
|
||||
prepayData, createOrderErr = l.svcCtx.EasyPayService.CreateEasyPayOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
|
||||
} else if req.PayMethod == "appleiap" {
|
||||
prepayData = l.svcCtx.ApplePayService.GetIappayAppID(paymentTypeResp.outTradeNo)
|
||||
}
|
||||
|
||||
388
app/main/api/internal/service/easyPayService.go
Normal file
388
app/main/api/internal/service/easyPayService.go
Normal file
@@ -0,0 +1,388 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"jnc-server/app/main/api/internal/config"
|
||||
"jnc-server/app/main/model"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
// EasyPayService 易支付服务
|
||||
type EasyPayService struct {
|
||||
config config.EasyPayConfig
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewEasyPayService 创建易支付服务实例
|
||||
func NewEasyPayService(c config.Config) *EasyPayService {
|
||||
return &EasyPayService{
|
||||
config: c.EasyPay,
|
||||
client: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// EasyPayOrderResponse API接口支付响应
|
||||
type EasyPayOrderResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
TradeNo string `json:"trade_no,omitempty"`
|
||||
OId string `json:"O_id,omitempty"`
|
||||
PayUrl string `json:"payurl,omitempty"`
|
||||
Qrcode string `json:"qrcode,omitempty"`
|
||||
Img string `json:"img,omitempty"`
|
||||
}
|
||||
|
||||
// EasyPayQueryResponse 查询订单响应
|
||||
type EasyPayQueryResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
TradeNo string `json:"trade_no,omitempty"`
|
||||
OutTradeNo string `json:"out_trade_no,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Pid string `json:"pid,omitempty"`
|
||||
Addtime string `json:"addtime,omitempty"`
|
||||
Endtime string `json:"endtime,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Money string `json:"money,omitempty"`
|
||||
Status int `json:"status,omitempty"`
|
||||
Param string `json:"param,omitempty"`
|
||||
Buyer string `json:"buyer,omitempty"`
|
||||
}
|
||||
|
||||
// EasyPayRefundResponse 退款响应
|
||||
type EasyPayRefundResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// EasyPayNotification 支付回调通知
|
||||
type EasyPayNotification struct {
|
||||
Pid string
|
||||
Name string
|
||||
Money string
|
||||
OutTradeNo string
|
||||
TradeNo string
|
||||
Param string
|
||||
TradeStatus string
|
||||
Type string
|
||||
Sign string
|
||||
SignType string
|
||||
}
|
||||
|
||||
// generateSign 生成MD5签名
|
||||
func (e *EasyPayService) generateSign(params map[string]string) string {
|
||||
// 排除 sign、sign_type 和空值
|
||||
filteredParams := make(map[string]string)
|
||||
for k, v := range params {
|
||||
if k != "sign" && k != "sign_type" && v != "" {
|
||||
filteredParams[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// 按参数名ASCII码从小到大排序
|
||||
keys := make([]string, 0, len(filteredParams))
|
||||
for k := range filteredParams {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// 拼接成URL键值对格式
|
||||
var parts []string
|
||||
for _, k := range keys {
|
||||
parts = append(parts, fmt.Sprintf("%s=%s", k, filteredParams[k]))
|
||||
}
|
||||
queryString := strings.Join(parts, "&")
|
||||
|
||||
// 拼接商户密钥并MD5加密
|
||||
signString := queryString + e.config.PKEY
|
||||
hash := md5.Sum([]byte(signString))
|
||||
return fmt.Sprintf("%x", hash) // 转为小写
|
||||
}
|
||||
|
||||
// verifySign 验证签名
|
||||
func (e *EasyPayService) verifySign(params map[string]string, sign string) bool {
|
||||
calculatedSign := e.generateSign(params)
|
||||
return strings.EqualFold(calculatedSign, sign)
|
||||
}
|
||||
|
||||
// CreateEasyPayH5Order 创建易支付H5订单(页面跳转方式)
|
||||
func (e *EasyPayService) CreateEasyPayH5Order(amount float64, subject string, outTradeNo string) (string, error) {
|
||||
// 格式化金额,保留两位小数
|
||||
moneyStr := fmt.Sprintf("%.2f", amount)
|
||||
|
||||
params := map[string]string{
|
||||
"name": subject,
|
||||
"money": moneyStr,
|
||||
"type": "alipay",
|
||||
"out_trade_no": outTradeNo,
|
||||
"notify_url": e.config.NotifyUrl,
|
||||
"pid": e.config.PID,
|
||||
"return_url": e.config.ReturnUrl,
|
||||
"sign_type": "MD5",
|
||||
}
|
||||
// 如果配置了渠道ID,则添加
|
||||
if e.config.CID != "" {
|
||||
params["cid"] = e.config.CID
|
||||
}
|
||||
|
||||
// 生成签名
|
||||
sign := e.generateSign(params)
|
||||
params["sign"] = sign
|
||||
|
||||
// 构建支付URL
|
||||
baseURL := strings.TrimSuffix(e.config.ApiURL, "/")
|
||||
payURL := fmt.Sprintf("%s/submit.php", baseURL)
|
||||
|
||||
// 构建查询字符串
|
||||
values := url.Values{}
|
||||
for k, v := range params {
|
||||
values.Set(k, v)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s?%s", payURL, values.Encode()), nil
|
||||
}
|
||||
|
||||
// CreateEasyPayAppOrder 创建易支付APP订单(API方式)
|
||||
func (e *EasyPayService) CreateEasyPayAppOrder(ctx context.Context, amount float64, subject string, outTradeNo string, clientIP string) (string, error) {
|
||||
// 格式化金额,保留两位小数
|
||||
moneyStr := fmt.Sprintf("%.2f", amount)
|
||||
|
||||
params := map[string]string{
|
||||
"pid": e.config.PID,
|
||||
"type": "alipay",
|
||||
"out_trade_no": outTradeNo,
|
||||
"notify_url": e.config.NotifyUrl,
|
||||
"name": subject,
|
||||
"money": moneyStr,
|
||||
"clientip": clientIP,
|
||||
"device": "pc",
|
||||
"sign_type": "MD5",
|
||||
}
|
||||
// 如果配置了渠道ID,则添加
|
||||
if e.config.CID != "" {
|
||||
params["cid"] = e.config.CID
|
||||
}
|
||||
|
||||
// 生成签名
|
||||
sign := e.generateSign(params)
|
||||
params["sign"] = sign
|
||||
|
||||
// 构建请求URL
|
||||
baseURL := strings.TrimSuffix(e.config.ApiURL, "/")
|
||||
apiURL := fmt.Sprintf("%s/mapi.php", baseURL)
|
||||
|
||||
// 构建form-data请求
|
||||
values := url.Values{}
|
||||
for k, v := range params {
|
||||
values.Set(k, v)
|
||||
}
|
||||
|
||||
// 发送POST请求
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", apiURL, strings.NewReader(values.Encode()))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建请求失败: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp, err := e.client.Do(req)
|
||||
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 orderResp EasyPayOrderResponse
|
||||
if err := json.Unmarshal(body, &orderResp); err != nil {
|
||||
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
|
||||
}
|
||||
|
||||
if orderResp.Code != 1 {
|
||||
return "", fmt.Errorf("创建订单失败: %s", orderResp.Msg)
|
||||
}
|
||||
|
||||
// 优先返回支付URL,如果没有则返回二维码
|
||||
if orderResp.PayUrl != "" {
|
||||
return orderResp.PayUrl, nil
|
||||
}
|
||||
if orderResp.Qrcode != "" {
|
||||
return orderResp.Qrcode, nil
|
||||
}
|
||||
if orderResp.Img != "" {
|
||||
return orderResp.Img, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("未获取到支付链接")
|
||||
}
|
||||
|
||||
// CreateEasyPayOrder 根据平台类型创建易支付订单
|
||||
func (e *EasyPayService) CreateEasyPayOrder(ctx context.Context, amount float64, subject string, outTradeNo string) (string, error) {
|
||||
// 根据 ctx 中的 platform 判断平台
|
||||
platform, platformOk := ctx.Value("platform").(string)
|
||||
if !platformOk {
|
||||
return "", fmt.Errorf("无效的支付平台")
|
||||
}
|
||||
|
||||
switch platform {
|
||||
case model.PlatformApp:
|
||||
// APP平台使用API方式
|
||||
clientIP := ""
|
||||
if ip, ok := ctx.Value("client_ip").(string); ok {
|
||||
clientIP = ip
|
||||
}
|
||||
return e.CreateEasyPayAppOrder(ctx, amount, subject, outTradeNo, clientIP)
|
||||
case model.PlatformH5:
|
||||
// H5平台使用页面跳转方式
|
||||
return e.CreateEasyPayH5Order(amount, subject, outTradeNo)
|
||||
default:
|
||||
return "", fmt.Errorf("不支持的支付平台: %s", platform)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleEasyPayNotification 处理易支付回调通知
|
||||
func (e *EasyPayService) HandleEasyPayNotification(r *http.Request) (*EasyPayNotification, error) {
|
||||
// 解析GET参数
|
||||
params := make(map[string]string)
|
||||
for k, v := range r.URL.Query() {
|
||||
if len(v) > 0 {
|
||||
params[k] = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
// 获取签名
|
||||
sign, ok := params["sign"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("缺少签名参数")
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
if !e.verifySign(params, sign) {
|
||||
return nil, fmt.Errorf("签名验证失败")
|
||||
}
|
||||
|
||||
// 构建通知对象
|
||||
notification := &EasyPayNotification{
|
||||
Pid: params["pid"],
|
||||
Name: params["name"],
|
||||
Money: params["money"],
|
||||
OutTradeNo: params["out_trade_no"],
|
||||
TradeNo: params["trade_no"],
|
||||
Param: params["param"],
|
||||
TradeStatus: params["trade_status"],
|
||||
Type: params["type"],
|
||||
Sign: sign,
|
||||
SignType: params["sign_type"],
|
||||
}
|
||||
|
||||
return notification, nil
|
||||
}
|
||||
|
||||
// QueryOrderStatus 查询订单状态
|
||||
func (e *EasyPayService) QueryOrderStatus(ctx context.Context, outTradeNo string) (*EasyPayQueryResponse, error) {
|
||||
// 构建查询URL
|
||||
baseURL := strings.TrimSuffix(e.config.ApiURL, "/")
|
||||
queryURL := fmt.Sprintf("%s/api.php?act=order&pid=%s&key=%s&out_trade_no=%s",
|
||||
baseURL, e.config.PID, e.config.PKEY, url.QueryEscape(outTradeNo))
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", queryURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建请求失败: %v", err)
|
||||
}
|
||||
|
||||
resp, err := e.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
var queryResp EasyPayQueryResponse
|
||||
if err := json.Unmarshal(body, &queryResp); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
|
||||
}
|
||||
|
||||
if queryResp.Code != 1 {
|
||||
return nil, fmt.Errorf("查询订单失败: %s", queryResp.Msg)
|
||||
}
|
||||
|
||||
return &queryResp, nil
|
||||
}
|
||||
|
||||
// Refund 申请退款
|
||||
func (e *EasyPayService) Refund(ctx context.Context, outTradeNo string, refundAmount float64) error {
|
||||
// 格式化金额,保留两位小数
|
||||
moneyStr := fmt.Sprintf("%.2f", refundAmount)
|
||||
|
||||
params := map[string]string{
|
||||
"pid": e.config.PID,
|
||||
"key": e.config.PKEY,
|
||||
"out_trade_no": outTradeNo,
|
||||
"money": moneyStr,
|
||||
}
|
||||
|
||||
// 构建请求URL
|
||||
baseURL := strings.TrimSuffix(e.config.ApiURL, "/")
|
||||
refundURL := fmt.Sprintf("%s/api.php?act=refund", baseURL)
|
||||
|
||||
// 构建form-data请求
|
||||
values := url.Values{}
|
||||
for k, v := range params {
|
||||
values.Set(k, v)
|
||||
}
|
||||
|
||||
// 发送POST请求
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", refundURL, strings.NewReader(values.Encode()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建请求失败: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp, err := e.client.Do(req)
|
||||
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 EasyPayRefundResponse
|
||||
if err := json.Unmarshal(body, &refundResp); err != nil {
|
||||
return fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
|
||||
}
|
||||
|
||||
if refundResp.Code != 1 {
|
||||
return fmt.Errorf("退款失败: %s", refundResp.Msg)
|
||||
}
|
||||
|
||||
logx.Infof("易支付退款成功,订单号: %s, 退款金额: %s", outTradeNo, moneyStr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsPaymentSuccess 判断支付是否成功
|
||||
func (e *EasyPayService) IsPaymentSuccess(notification *EasyPayNotification) bool {
|
||||
return notification.TradeStatus == "TRADE_SUCCESS"
|
||||
}
|
||||
@@ -74,6 +74,7 @@ type ServiceContext struct {
|
||||
AlipayService *service.AliPayService
|
||||
WechatPayService *service.WechatPayService
|
||||
ApplePayService *service.ApplePayService
|
||||
EasyPayService *service.EasyPayService
|
||||
ApiRequestService *service.ApiRequestService
|
||||
AsynqServer *asynq.Server
|
||||
AsynqService *service.AsynqService
|
||||
@@ -181,6 +182,15 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
||||
} else {
|
||||
logx.Info("Apple支付服务已禁用")
|
||||
}
|
||||
|
||||
// 根据配置决定是否初始化易支付服务
|
||||
var easyPayService *service.EasyPayService
|
||||
if c.EasyPay.Enabled {
|
||||
easyPayService = service.NewEasyPayService(c)
|
||||
logx.Info("易支付服务已启用")
|
||||
} else {
|
||||
logx.Info("易支付服务已禁用")
|
||||
}
|
||||
apiRequestService := service.NewApiRequestService(c, featureModel, productFeatureModel, tianyuanapi)
|
||||
verificationService := service.NewVerificationService(c, tianyuanapi, apiRequestService)
|
||||
asynqService := service.NewAsynqService(c)
|
||||
@@ -261,6 +271,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
||||
AlipayService: alipayService,
|
||||
WechatPayService: wechatPayService,
|
||||
ApplePayService: applePayService,
|
||||
EasyPayService: easyPayService,
|
||||
ApiRequestService: apiRequestService,
|
||||
AsynqServer: asynqServer,
|
||||
AsynqService: asynqService,
|
||||
|
||||
Reference in New Issue
Block a user