This commit is contained in:
2025-12-31 16:54:17 +08:00
parent 4a48add783
commit a30d77845d
12 changed files with 688 additions and 20 deletions

View File

@@ -22,6 +22,10 @@ service main {
// 微信退款回调
@handler WechatPayRefundCallback
post /pay/wechat/refund_callback
// 易支付回调
@handler EasyPayCallback
get /pay/easypay/callback
}
@server (

View File

@@ -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"

View File

@@ -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"

View File

@@ -15,6 +15,7 @@ type Config struct {
Alipay AlipayConfig
Wxpay WxpayConfig
Applepay ApplepayConfig
EasyPay EasyPayConfig
Tianyuanapi TianyuanapiConfig
SystemConfig SystemConfig
WechatH5 WechatH5Config
@@ -33,20 +34,20 @@ type JwtAuth struct {
RefreshAfter int64
}
type VerifyCode struct {
AccessKeyID string
AccessKeySecret string
EndpointURL string
SignName string
TemplateCode string
ValidTime int
ComplaintTemplate string // 投诉通知短信模板
AccessKeyID string
AccessKeySecret string
EndpointURL string
SignName string
TemplateCode string
ValidTime int
ComplaintTemplate string // 投诉通知短信模板
}
type Encrypt struct {
SecretKey string
}
type AlipayConfig struct {
Enabled bool // 是否启用支付宝支付
Enabled bool // 是否启用支付宝支付
AppID string
PrivateKey string
AlipayPublicKey string
@@ -58,7 +59,7 @@ type AlipayConfig struct {
ReturnURL string
}
type WxpayConfig struct {
Enabled bool // 是否启用微信支付
Enabled bool // 是否启用微信支付
AppID string
MchID string
MchCertificateSerialNumber string
@@ -71,7 +72,7 @@ type WxpayConfig struct {
RefundNotifyUrl string
}
type ApplepayConfig struct {
Enabled bool // 是否启用Apple支付
Enabled bool // 是否启用Apple支付
ProductionVerifyURL string
SandboxVerifyURL string // 沙盒环境的验证 URL
Sandbox bool
@@ -84,7 +85,7 @@ type SystemConfig struct {
ThreeVerify bool
}
type WechatH5Config struct {
Enabled bool // 是否启用微信公众号登录
Enabled bool // 是否启用微信公众号登录
AppID string
AppSecret string
}
@@ -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 // 页面跳转地址
}

View 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)
}
}

View File

@@ -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",

View File

@@ -18,10 +18,11 @@ import (
)
const (
PaymentPlatformAlipay = "alipay"
PaymentPlatformWechat = "wechat"
OrderStatusPaid = "paid"
RefundNoPrefix = "refund-"
PaymentPlatformAlipay = "alipay"
PaymentPlatformWechat = "wechat"
PaymentPlatformEasyPay = "easypay_alipay"
OrderStatusPaid = "paid"
RefundNoPrefix = "refund-"
)
type AdminRefundOrderLogic struct {
@@ -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 {

View 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
}

View File

@@ -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"
@@ -38,12 +40,61 @@ func (l *PaymentCheckLogic) PaymentCheck(req *types.PaymentCheckReq) (resp *type
Status: order.Status,
}, nil
}
// 查询订单(包括代理订单)
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
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,

View File

@@ -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)
}

View 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"
}

View File

@@ -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,