add count easypay

This commit is contained in:
liangzai
2026-01-09 14:04:33 +08:00
parent f079980608
commit 2ca14de27e
14 changed files with 211 additions and 62 deletions

View File

@@ -87,6 +87,8 @@ EasyPay:
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"
CIDs: ["12200"] # 支付渠道ID数组可配置一个或多个多个时会按RotateDays轮询
# CIDs: ["12200", "12201", "12202"] # 多个渠道示例会按RotateDays天数轮询
RotateDays: 3 # 渠道轮询天数默认3天仅在配置了多个CIDs时生效
NotifyUrl: "https://www.dsjcq168.cn/api/v1/pay/easypay/callback"
ReturnUrl: "https://www.dsjcq168.cn/payment/result"

View File

@@ -87,6 +87,8 @@ EasyPay:
ApiURL: "https://zpayz.cn/"
PID: "2025123009590455"
PKEY: "f61pwaOj93lYpesM82ZnPAVwFojuSL7F"
CID: "12200"
CIDs: ["12200", "12303"] # 支付渠道ID数组可配置一个或多个
RotateMode: "count" # 轮询模式day天数轮询或 count次数轮询默认count
RotateDays: 3 # 渠道轮询天数默认3天仅在RotateMode=day时生效
NotifyUrl: "https://www.dsjcq168.cn/api/v1/pay/easypay/callback"
ReturnUrl: "https://www.dsjcq168.cn/payment/result"

View File

@@ -125,11 +125,13 @@ type PromotionConfig struct {
// EasyPayConfig 易支付配置
type EasyPayConfig struct {
Enabled bool // 是否启用易支付
ApiURL string // 接口地址
PID string // 商户ID
PKEY string // 商户密钥
CID string // 支付渠道ID
NotifyUrl string // 异步通知地址
ReturnUrl string // 页面跳转地址
Enabled bool // 是否启用易支付
ApiURL string // 接口地址
PID string // 商户ID
PKEY string // 商户密钥
CIDs []string // 支付渠道ID数组(可配置一个或多个)
RotateMode string // 轮询模式day天数轮询或 count次数轮询默认day
RotateDays int // 渠道轮询天数默认3天仅在RotateMode=day时生效
NotifyUrl string // 异步通知地址
ReturnUrl string // 页面跳转地址
}

View File

@@ -6,7 +6,6 @@ import (
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/common/globalkey"
"jnc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
@@ -66,7 +65,6 @@ func (l *AdminGetAgentRankingLogic) AdminGetAgentRanking(req *types.AdminGetAgen
func (l *AdminGetAgentRankingLogic) queryCommissionRanking(limit int) ([]types.AgentRankingItem, error) {
// 1. Query all commissions
builder := l.svcCtx.AgentCommissionModel.SelectBuilder()
builder = builder.Where("del_state = ?", globalkey.DelStateNo)
commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, builder, "")
if err != nil {
@@ -111,7 +109,7 @@ func (l *AdminGetAgentRankingLogic) queryCommissionRanking(limit int) ([]types.A
}
agents, err := l.svcCtx.AgentModel.FindAll(l.ctx,
l.svcCtx.AgentModel.SelectBuilder().Where("del_state = ?", globalkey.DelStateNo), "")
l.svcCtx.AgentModel.SelectBuilder(), "")
if err != nil {
return nil, err
}
@@ -162,7 +160,6 @@ func (l *AdminGetAgentRankingLogic) queryCommissionRanking(limit int) ([]types.A
func (l *AdminGetAgentRankingLogic) queryOrdersRanking(limit int) ([]types.AgentRankingItem, error) {
// 1. Query all orders
builder := l.svcCtx.AgentOrderModel.SelectBuilder()
builder = builder.Where("del_state = ?", globalkey.DelStateNo)
orders, err := l.svcCtx.AgentOrderModel.FindAll(l.ctx, builder, "")
if err != nil {
@@ -207,7 +204,7 @@ func (l *AdminGetAgentRankingLogic) queryOrdersRanking(limit int) ([]types.Agent
}
agents, err := l.svcCtx.AgentModel.FindAll(l.ctx,
l.svcCtx.AgentModel.SelectBuilder().Where("del_state = ?", globalkey.DelStateNo), "")
l.svcCtx.AgentModel.SelectBuilder(), "")
if err != nil {
return nil, err
}

View File

@@ -7,7 +7,6 @@ import (
"jnc-server/app/main/api/internal/svc"
"jnc-server/app/main/api/internal/types"
"jnc-server/app/main/model"
"jnc-server/common/globalkey"
"github.com/zeromicro/go-zero/core/logx"
)
@@ -29,7 +28,6 @@ func NewAdminGetProductDistributionLogic(ctx context.Context, svcCtx *svc.Servic
func (l *AdminGetProductDistributionLogic) AdminGetProductDistribution() (resp *types.AdminGetProductDistributionResp, err error) {
// 1. Query all commissions
builder := l.svcCtx.AgentCommissionModel.SelectBuilder()
builder = builder.Where("del_state = ?", globalkey.DelStateNo)
commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, builder, "")
if err != nil {
@@ -50,7 +48,7 @@ func (l *AdminGetProductDistributionLogic) AdminGetProductDistribution() (resp *
var products []*model.Product
if len(productIds) > 0 {
products, err = l.svcCtx.ProductModel.FindAll(l.ctx,
l.svcCtx.ProductModel.SelectBuilder().Where("del_state = ?", globalkey.DelStateNo), "")
l.svcCtx.ProductModel.SelectBuilder(), "")
if err != nil {
return nil, err
}

View File

@@ -2,7 +2,6 @@ package admin_agent
import (
"context"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/Masterminds/squirrel"
@@ -29,8 +28,7 @@ func NewAdminGetAgentOrderListLogic(ctx context.Context, svcCtx *svc.ServiceCont
}
func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGetAgentOrderListReq) (resp *types.AdminGetAgentOrderListResp, err error) {
builder := l.svcCtx.AgentOrderModel.SelectBuilder().
Where("del_state = ?", globalkey.DelStateNo)
builder := l.svcCtx.AgentOrderModel.SelectBuilder()
if req.AgentId != nil {
builder = builder.Where("agent_id = ?", *req.AgentId)

View File

@@ -7,7 +7,6 @@ import (
"time"
"jnc-server/app/main/model"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"jnc-server/pkg/lzkit/crypto"
@@ -188,8 +187,7 @@ func contains(str, substr string) bool {
// queryOrders 查询订单列表
func (l *AdminGetAgentOrdersListLogic) queryOrders(req *types.AdminGetAgentOrdersListReq) ([]*model.Order, int64, error) {
builder := l.svcCtx.OrderModel.SelectBuilder().
Where("`del_state` = ?", globalkey.DelStateNo)
builder := l.svcCtx.OrderModel.SelectBuilder()
// 订单筛选
if req.OrderNo != nil && *req.OrderNo != "" {
@@ -275,7 +273,7 @@ func (l *AdminGetAgentOrdersListLogic) queryCommissionsByOrderIds(orderIds []str
commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx,
l.svcCtx.AgentCommissionModel.SelectBuilder().
Where(fmt.Sprintf("`order_id` IN (%s) AND `del_state` = ?", inClause), append(stringsToInterfaces(orderIds), globalkey.DelStateNo)...),
Where(fmt.Sprintf("`order_id` IN (%s)", inClause), stringsToInterfaces(orderIds)...),
"")
if err != nil {
@@ -319,7 +317,7 @@ func (l *AdminGetAgentOrdersListLogic) queryAgentsByAgentIds(agentIds []string)
agents, err := l.svcCtx.AgentModel.FindAll(l.ctx,
l.svcCtx.AgentModel.SelectBuilder().
Where(fmt.Sprintf("`id` IN (%s) AND `del_state` = ?", inClause), append(stringsToInterfaces(ids), globalkey.DelStateNo)...),
Where(fmt.Sprintf("`id` IN (%s)", inClause), stringsToInterfaces(ids)...),
"")
if err != nil {
@@ -363,7 +361,7 @@ func (l *AdminGetAgentOrdersListLogic) queryUsersByUserIds(userIds []string) map
users, err := l.svcCtx.UserModel.FindAll(l.ctx,
l.svcCtx.UserModel.SelectBuilder().
Where(fmt.Sprintf("`id` IN (%s) AND `del_state` = ?", inClause), append(stringsToInterfaces(ids), globalkey.DelStateNo)...),
Where(fmt.Sprintf("`id` IN (%s)", inClause), stringsToInterfaces(ids)...),
"")
if err != nil {
@@ -407,7 +405,7 @@ func (l *AdminGetAgentOrdersListLogic) queryProductsByProductIds(productIds []st
products, err := l.svcCtx.ProductModel.FindAll(l.ctx,
l.svcCtx.ProductModel.SelectBuilder().
Where(fmt.Sprintf("`id` IN (%s) AND `del_state` = ?", inClause), append(stringsToInterfaces(ids), globalkey.DelStateNo)...),
Where(fmt.Sprintf("`id` IN (%s)", inClause), stringsToInterfaces(ids)...),
"")
if err != nil {
@@ -436,7 +434,7 @@ func (l *AdminGetAgentOrdersListLogic) queryQueriesByOrderIds(orderIds []string)
queries, err := l.svcCtx.QueryModel.FindAll(l.ctx,
l.svcCtx.QueryModel.SelectBuilder().
Where(fmt.Sprintf("`order_id` IN (%s) AND `del_state` = ?", inClause), append(stringsToInterfaces(orderIds), globalkey.DelStateNo)...),
Where(fmt.Sprintf("`order_id` IN (%s)", inClause), stringsToInterfaces(orderIds)...),
"")
if err != nil {

View File

@@ -2,7 +2,6 @@ package admin_agent
import (
"context"
"jnc-server/common/globalkey"
"jnc-server/common/xerr"
"github.com/pkg/errors"
@@ -28,8 +27,7 @@ func NewAdminGetAgentProductConfigListLogic(ctx context.Context, svcCtx *svc.Ser
}
func (l *AdminGetAgentProductConfigListLogic) AdminGetAgentProductConfigList(req *types.AdminGetAgentProductConfigListReq) (resp *types.AdminGetAgentProductConfigListResp, err error) {
builder := l.svcCtx.AgentProductConfigModel.SelectBuilder().
Where("del_state = ?", globalkey.DelStateNo)
builder := l.svcCtx.AgentProductConfigModel.SelectBuilder()
// 如果提供了产品ID直接过滤
if req.ProductId != nil {
@@ -38,7 +36,7 @@ func (l *AdminGetAgentProductConfigListLogic) AdminGetAgentProductConfigList(req
// 如果提供了产品名称,通过关联查询 product 表过滤
if req.ProductName != nil && *req.ProductName != "" {
builder = builder.Where("product_id IN (SELECT id FROM product WHERE product_name LIKE ? AND del_state = ?)", "%"+*req.ProductName+"%", globalkey.DelStateNo)
builder = builder.Where("product_id IN (SELECT id FROM product WHERE product_name LIKE ?)", "%"+*req.ProductName+"%")
}
// 分页查询

View File

@@ -96,7 +96,19 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
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)
// 获取用户ID用于次数轮询模式
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil {
// 如果获取用户ID失败使用空字符串会回退到天数轮询
userID = ""
}
easyPayResult, createOrderErr := l.svcCtx.EasyPayService.CreateEasyPayOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo, userID)
if createOrderErr == nil && easyPayResult != nil {
prepayData = easyPayResult.PayURL
// 将渠道ID存储到context中后续创建订单时使用
ctx = context.WithValue(ctx, "easypay_cid", easyPayResult.CID)
l.ctx = ctx
}
} else if req.PayMethod == "appleiap" {
prepayData = l.svcCtx.ApplePayService.GetIappayAppID(paymentTypeResp.outTradeNo)
}
@@ -228,6 +240,12 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
Amount: amount,
Status: "pending",
}
// 如果是易支付记录使用的渠道ID到备注字段
if req.PayMethod == "easypay_alipay" {
if cid, ok := l.ctx.Value("easypay_cid").(string); ok && cid != "" {
order.Remark = sql.NullString{String: fmt.Sprintf("易支付渠道号: %s", cid), Valid: true}
}
}
_, insertOrderErr := l.svcCtx.OrderModel.Insert(l.ctx, session, &order)
if insertOrderErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存订单失败: %+v", insertOrderErr)

View File

@@ -19,17 +19,126 @@ import (
// EasyPayService 易支付服务
type EasyPayService struct {
config config.EasyPayConfig
client *http.Client
config config.EasyPayConfig
client *http.Client
orderModel model.OrderModel // 订单模型,用于次数轮询模式查询用户订单数量
}
// getSelectedCID 获取当前应该使用的渠道ID
// ctx: 上下文用于次数轮询模式时访问Redis
// userID: 用户ID用于次数轮询模式时记录用户使用的渠道
func (e *EasyPayService) getSelectedCID(ctx context.Context, userID string) string {
// 如果没有配置 CID返回空字符串
if len(e.config.CIDs) == 0 {
return ""
}
// 如果只有一个渠道,直接返回
if len(e.config.CIDs) == 1 {
return e.config.CIDs[0]
}
// 根据轮询模式选择策略
rotateMode := e.config.RotateMode
if rotateMode == "" {
rotateMode = "day" // 默认天数轮询
}
switch rotateMode {
case "count":
// 次数轮询模式:按用户订单次数轮询
return e.selectCIDByCount(ctx, userID)
case "day":
// 天数轮询模式:按时间轮询
return e.selectCIDByRotation()
default:
// 默认使用天数轮询
logx.Infof("未知的轮询模式: %s使用默认天数轮询", rotateMode)
return e.selectCIDByRotation()
}
}
// selectCIDByRotation 按时间轮询策略选择CID
func (e *EasyPayService) selectCIDByRotation() string {
if len(e.config.CIDs) == 0 {
return ""
}
// 如果只有一个,直接返回
if len(e.config.CIDs) == 1 {
return e.config.CIDs[0]
}
// 获取轮询天数默认3天
rotateDays := e.config.RotateDays
if rotateDays <= 0 {
rotateDays = 3
}
// 计算从某个基准日期比如2020-01-01开始的天数
baseDate := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
now := time.Now()
daysSinceBase := int(now.Sub(baseDate).Hours() / 24)
// 按轮询天数计算当前应该使用的索引
rotationIndex := (daysSinceBase / rotateDays) % len(e.config.CIDs)
selectedCID := e.config.CIDs[rotationIndex]
logx.Infof("易支付渠道天数轮询选择: 总渠道数=%d, 轮询天数=%d, 当前索引=%d, 选择渠道=%s",
len(e.config.CIDs), rotateDays, rotationIndex, selectedCID)
return selectedCID
}
// selectCIDByCount 按次数轮询策略选择CID针对用户
func (e *EasyPayService) selectCIDByCount(ctx context.Context, userID string) string {
if len(e.config.CIDs) == 0 {
return ""
}
// 如果只有一个,直接返回
if len(e.config.CIDs) == 1 {
return e.config.CIDs[0]
}
// 如果没有用户ID回退到天数轮询
if userID == "" {
logx.Infof("次数轮询模式但用户ID为空回退到天数轮询")
return e.selectCIDByRotation()
}
// 查询该用户的易支付订单数量
orderCount := int64(0)
if e.orderModel != nil {
builder := e.orderModel.SelectBuilder().
Where("user_id = ?", userID).
Where("payment_platform = ?", "easypay_alipay")
count, err := e.orderModel.FindCount(ctx, builder, "id")
if err != nil {
logx.Errorf("查询用户易支付订单数量失败: %v使用索引0", err)
} else {
orderCount = count
}
}
// 根据订单数量计算应该使用的渠道索引订单数量从0开始所以第0个订单用索引0第1个订单用索引1以此类推
channelIndex := int(orderCount) % len(e.config.CIDs)
selectedCID := e.config.CIDs[channelIndex]
logx.Infof("易支付渠道次数轮询选择: 用户ID=%s, 总渠道数=%d, 用户订单数=%d, 选择索引=%d, 选择渠道=%s",
userID, len(e.config.CIDs), orderCount, channelIndex, selectedCID)
return selectedCID
}
// NewEasyPayService 创建易支付服务实例
func NewEasyPayService(c config.Config) *EasyPayService {
func NewEasyPayService(c config.Config, orderModel model.OrderModel) *EasyPayService {
return &EasyPayService{
config: c.EasyPay,
client: &http.Client{
Timeout: 30 * time.Second,
},
orderModel: orderModel,
}
}
@@ -189,7 +298,7 @@ func (e *EasyPayService) verifySign(params map[string]string, sign string) bool
}
// CreateEasyPayH5Order 创建易支付H5订单页面跳转方式
func (e *EasyPayService) CreateEasyPayH5Order(amount float64, subject string, outTradeNo string) (string, error) {
func (e *EasyPayService) CreateEasyPayH5Order(ctx context.Context, amount float64, subject string, outTradeNo string, userID string) (string, error) {
// 格式化金额,保留两位小数
moneyStr := fmt.Sprintf("%.2f", amount)
@@ -203,9 +312,9 @@ func (e *EasyPayService) CreateEasyPayH5Order(amount float64, subject string, ou
"return_url": e.config.ReturnUrl,
"sign_type": "MD5",
}
// 如果配置了渠道ID则添加
if e.config.CID != "" {
params["cid"] = e.config.CID
// 获取并添加渠道ID
if cid := e.getSelectedCID(ctx, userID); cid != "" {
params["cid"] = cid
}
// 生成签名
@@ -226,7 +335,7 @@ func (e *EasyPayService) CreateEasyPayH5Order(amount float64, subject string, ou
}
// CreateEasyPayAppOrder 创建易支付APP订单API方式
func (e *EasyPayService) CreateEasyPayAppOrder(ctx context.Context, amount float64, subject string, outTradeNo string, clientIP string) (string, error) {
func (e *EasyPayService) CreateEasyPayAppOrder(ctx context.Context, amount float64, subject string, outTradeNo string, clientIP string, userID string) (string, error) {
// 格式化金额,保留两位小数
moneyStr := fmt.Sprintf("%.2f", amount)
@@ -241,9 +350,9 @@ func (e *EasyPayService) CreateEasyPayAppOrder(ctx context.Context, amount float
"device": "pc",
"sign_type": "MD5",
}
// 如果配置了渠道ID则添加
if e.config.CID != "" {
params["cid"] = e.config.CID
// 获取并添加渠道ID
if cid := e.getSelectedCID(ctx, userID); cid != "" {
params["cid"] = cid
}
// 生成签名
@@ -302,14 +411,25 @@ func (e *EasyPayService) CreateEasyPayAppOrder(ctx context.Context, amount float
return "", fmt.Errorf("未获取到支付链接")
}
// CreateEasyPayOrderResult 易支付订单创建结果
type CreateEasyPayOrderResult struct {
PayURL string // 支付URL
CID string // 使用的渠道ID
}
// CreateEasyPayOrder 根据平台类型创建易支付订单
func (e *EasyPayService) CreateEasyPayOrder(ctx context.Context, amount float64, subject string, outTradeNo string) (string, error) {
func (e *EasyPayService) CreateEasyPayOrder(ctx context.Context, amount float64, subject string, outTradeNo string, userID string) (*CreateEasyPayOrderResult, error) {
// 获取选中的渠道ID
selectedCID := e.getSelectedCID(ctx, userID)
// 根据 ctx 中的 platform 判断平台
platform, platformOk := ctx.Value("platform").(string)
if !platformOk {
return "", fmt.Errorf("无效的支付平台")
return nil, fmt.Errorf("无效的支付平台")
}
var payURL string
var err error
switch platform {
case model.PlatformApp:
// APP平台使用API方式
@@ -317,13 +437,22 @@ func (e *EasyPayService) CreateEasyPayOrder(ctx context.Context, amount float64,
if ip, ok := ctx.Value("client_ip").(string); ok {
clientIP = ip
}
return e.CreateEasyPayAppOrder(ctx, amount, subject, outTradeNo, clientIP)
payURL, err = e.CreateEasyPayAppOrder(ctx, amount, subject, outTradeNo, clientIP, userID)
case model.PlatformH5:
// H5平台使用页面跳转方式
return e.CreateEasyPayH5Order(amount, subject, outTradeNo)
payURL, err = e.CreateEasyPayH5Order(ctx, amount, subject, outTradeNo, userID)
default:
return "", fmt.Errorf("不支持的支付平台: %s", platform)
return nil, fmt.Errorf("不支持的支付平台: %s", platform)
}
if err != nil {
return nil, err
}
return &CreateEasyPayOrderResult{
PayURL: payURL,
CID: selectedCID,
}, nil
}
// HandleEasyPayNotification 处理易支付回调通知

View File

@@ -186,7 +186,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
// 根据配置决定是否初始化易支付服务
var easyPayService *service.EasyPayService
if c.EasyPay.Enabled {
easyPayService = service.NewEasyPayService(c)
easyPayService = service.NewEasyPayService(c, orderModel)
logx.Info("易支付服务已启用")
} else {
logx.Info("易支付服务已禁用")