增加支付功能
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
beelog "github.com/beego/beego/v2/core/logs"
|
||||
)
|
||||
|
||||
/* =============================================================
|
||||
* 推广佣金:规则匹配 -> 金额计算 -> 佣金单生成(幂等)-> 发放登记
|
||||
* 佣金是平台自有资金支出,与租户收款资金流解耦。
|
||||
* ============================================================= */
|
||||
|
||||
// OnOrderPaid 支付成功后生成佣金单(幂等:同一支付单 + 同一推广方只生成一张)
|
||||
// 无推广方 / 无匹配规则 / 佣金金额为 0 时返回 (nil, nil)。
|
||||
func OnOrderPaid(ctx context.Context, order *models.PlatformPaymentOrder) (*models.PlatformCommissionOrder, error) {
|
||||
if order == nil || order.PromoterID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 幂等查重
|
||||
exist := &models.PlatformCommissionOrder{}
|
||||
err := models.Orm.QueryTable(new(models.PlatformCommissionOrder)).
|
||||
Filter("pay_no", order.PayNo).
|
||||
Filter("promoter_id", order.PromoterID).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(exist)
|
||||
if err == nil {
|
||||
return exist, nil
|
||||
} else if err != orm.ErrNoRows {
|
||||
return nil, fmt.Errorf("查询佣金单失败: %w", err)
|
||||
}
|
||||
|
||||
rule, err := matchCommissionRule(order.OrderType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rule == nil {
|
||||
return nil, nil
|
||||
}
|
||||
amount := calcCommissionAmount(rule, order.Amount)
|
||||
if amount <= 0 {
|
||||
beelog.Warn("佣金规则 %s 计算金额为 0,跳过(订单 %s)", rule.RuleName, order.PayNo)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
row := &models.PlatformCommissionOrder{
|
||||
CommissionNo: NextCommissionNo(),
|
||||
PayNo: order.PayNo,
|
||||
OutTradeNo: order.OutTradeNo,
|
||||
RuleID: rule.ID,
|
||||
RuleName: rule.RuleName,
|
||||
CalcType: rule.CalcType,
|
||||
PromoterID: order.PromoterID,
|
||||
PromoterName: order.PromoterName,
|
||||
PromoterType: order.PromoterType,
|
||||
BaseAmount: order.Amount,
|
||||
Amount: amount,
|
||||
Status: models.CommissionStatusPayable,
|
||||
Remark: fmt.Sprintf("支付单 %s 支付成功自动生成", order.PayNo),
|
||||
}
|
||||
if _, err := models.Orm.Insert(row); err != nil {
|
||||
if strings.Contains(err.Error(), "Duplicate") || strings.Contains(err.Error(), "1062") {
|
||||
// 并发生成命中唯一键:查回已有记录
|
||||
return exist, nil
|
||||
}
|
||||
return nil, fmt.Errorf("创建佣金单失败: %w", err)
|
||||
}
|
||||
beelog.Info("佣金单已生成: %s 支付单 %s 推广方 %s 金额 %d 分", row.CommissionNo, order.PayNo, order.PromoterName, amount)
|
||||
return row, nil
|
||||
}
|
||||
|
||||
// matchCommissionRule 匹配启用中的佣金规则(同类型规则取最新一条)
|
||||
func matchCommissionRule(orderType string) (*models.PlatformCommissionRule, error) {
|
||||
var rules []models.PlatformCommissionRule
|
||||
_, err := models.Orm.QueryTable(new(models.PlatformCommissionRule)).
|
||||
Filter("status", 1).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("-id").
|
||||
All(&rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range rules {
|
||||
if rules[i].OrderType == models.CommissionOrderTypeAll || rules[i].OrderType == orderType {
|
||||
return &rules[i], nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// calcCommissionAmount 按规则计算佣金(分),向下取整
|
||||
func calcCommissionAmount(rule *models.PlatformCommissionRule, baseAmount int64) int64 {
|
||||
if rule == nil || baseAmount <= 0 {
|
||||
return 0
|
||||
}
|
||||
switch rule.CalcType {
|
||||
case models.CommissionCalcPercent:
|
||||
return baseAmount * rule.Value / 10000 // value = 百分比 × 100
|
||||
case models.CommissionCalcFixed:
|
||||
return rule.Value
|
||||
case models.CommissionCalcTiered:
|
||||
for _, tier := range parseTiers(rule.TierJSON) {
|
||||
if baseAmount < tier.Min {
|
||||
continue
|
||||
}
|
||||
if tier.Max != nil && baseAmount >= *tier.Max {
|
||||
continue
|
||||
}
|
||||
return baseAmount * tier.Rate / 10000
|
||||
}
|
||||
return 0
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func parseTiers(raw *string) []models.CommissionTier {
|
||||
tiers := []models.CommissionTier{}
|
||||
if raw == nil || *raw == "" {
|
||||
return tiers
|
||||
}
|
||||
_ = json.Unmarshal([]byte(*raw), &tiers)
|
||||
return tiers
|
||||
}
|
||||
|
||||
/* ---------------- 佣金台账查询与发放 ---------------- */
|
||||
|
||||
// CommissionListInput 台账查询条件
|
||||
type CommissionListInput struct {
|
||||
Promoter string
|
||||
Status string
|
||||
StartTime string // YYYY-MM-DD
|
||||
EndTime string
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// ListCommissions 佣金台账分页
|
||||
func ListCommissions(in CommissionListInput) ([]models.PlatformCommissionOrder, int64, error) {
|
||||
qs := models.Orm.QueryTable(new(models.PlatformCommissionOrder)).Filter("delete_time__isnull", true)
|
||||
if in.Promoter != "" {
|
||||
qs = qs.Filter("promoter_name__icontains", in.Promoter)
|
||||
}
|
||||
if in.Status != "" {
|
||||
qs = qs.Filter("status", in.Status)
|
||||
}
|
||||
if in.StartTime != "" {
|
||||
if t, err := time.ParseInLocation("2006-01-02", in.StartTime, time.Local); err == nil {
|
||||
qs = qs.Filter("create_time__gte", t)
|
||||
}
|
||||
}
|
||||
if in.EndTime != "" {
|
||||
if t, err := time.ParseInLocation("2006-01-02", in.EndTime, time.Local); err == nil {
|
||||
qs = qs.Filter("create_time__lt", t.AddDate(0, 0, 1))
|
||||
}
|
||||
}
|
||||
total, err := qs.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if in.Page <= 0 {
|
||||
in.Page = 1
|
||||
}
|
||||
if in.PageSize <= 0 || in.PageSize > 200 {
|
||||
in.PageSize = 10
|
||||
}
|
||||
var rows []models.PlatformCommissionOrder
|
||||
_, err = qs.OrderBy("-id").Limit(in.PageSize, (in.Page-1)*in.PageSize).All(&rows)
|
||||
return rows, total, err
|
||||
}
|
||||
|
||||
// CommissionStats 台账统计(应付 / 已付 / 待处理笔数)
|
||||
type CommissionStats struct {
|
||||
PayableAmount int64 `json:"payable_amount"`
|
||||
PaidAmount int64 `json:"paid_amount"`
|
||||
PendingCount int64 `json:"pending_count"`
|
||||
}
|
||||
|
||||
// GetCommissionStats 统计口径:
|
||||
//
|
||||
// 应付总额 = status=payable 的金额合计;已付总额 = status=settled;待处理笔数 = payable + tax_processing。
|
||||
func GetCommissionStats() (*CommissionStats, error) {
|
||||
type row struct {
|
||||
Total int64 `orm:"column(total)"`
|
||||
Cnt int64 `orm:"column(cnt)"`
|
||||
}
|
||||
stats := &CommissionStats{}
|
||||
pay := row{}
|
||||
if err := models.Orm.Raw(
|
||||
"SELECT COALESCE(SUM(amount),0) AS total, COUNT(*) AS cnt FROM yz_platform_commission_order WHERE delete_time IS NULL AND status = ?",
|
||||
models.CommissionStatusPayable).QueryRow(&pay); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.PayableAmount = pay.Total
|
||||
stats.PendingCount = pay.Cnt
|
||||
|
||||
paid := row{}
|
||||
if err := models.Orm.Raw(
|
||||
"SELECT COALESCE(SUM(amount),0) AS total, COUNT(*) AS cnt FROM yz_platform_commission_order WHERE delete_time IS NULL AND status = ?",
|
||||
models.CommissionStatusSettled).QueryRow(&paid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.PaidAmount = paid.Total
|
||||
|
||||
tax := row{}
|
||||
if err := models.Orm.Raw(
|
||||
"SELECT COALESCE(SUM(amount),0) AS total, COUNT(*) AS cnt FROM yz_platform_commission_order WHERE delete_time IS NULL AND status = ?",
|
||||
models.CommissionStatusTaxProcessing).QueryRow(&tax); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.PendingCount += tax.Cnt
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// MarkCommissionPaidInput 标记已付入参
|
||||
type MarkCommissionPaidInput struct {
|
||||
ID uint64
|
||||
PayMethod string
|
||||
PayVoucher string
|
||||
Remark string
|
||||
OperatorID string
|
||||
OperatorName string
|
||||
}
|
||||
|
||||
// MarkCommissionPaid 发放登记:应付/税务处理中 -> 已付
|
||||
func MarkCommissionPaid(ctx context.Context, in MarkCommissionPaidInput) (*models.PlatformCommissionOrder, error) {
|
||||
row := &models.PlatformCommissionOrder{}
|
||||
if err := models.Orm.QueryTable(new(models.PlatformCommissionOrder)).
|
||||
Filter("id", in.ID).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(row); err != nil {
|
||||
return nil, fmt.Errorf("佣金单不存在")
|
||||
}
|
||||
if row.Status == models.CommissionStatusSettled {
|
||||
return nil, errors.New("该佣金单已是已付状态")
|
||||
}
|
||||
now := time.Now()
|
||||
_, err := models.Orm.QueryTable(new(models.PlatformCommissionOrder)).
|
||||
Filter("id", row.ID).
|
||||
Update(orm.Params{
|
||||
"status": models.CommissionStatusSettled,
|
||||
"pay_method": in.PayMethod,
|
||||
"pay_voucher": in.PayVoucher,
|
||||
"paid_at": now,
|
||||
"paid_user_id": in.OperatorID,
|
||||
"paid_user_name": in.OperatorName,
|
||||
"remark": in.Remark,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return GetCommissionByID(in.ID)
|
||||
}
|
||||
|
||||
// GetCommissionByID 按 ID 查询佣金单
|
||||
func GetCommissionByID(id uint64) (*models.PlatformCommissionOrder, error) {
|
||||
row := &models.PlatformCommissionOrder{}
|
||||
err := models.Orm.QueryTable(new(models.PlatformCommissionOrder)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(row)
|
||||
return row, err
|
||||
}
|
||||
Reference in New Issue
Block a user