实现分销网后台基础功能与独立前端
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
package distribution
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
ModeTwoPartyDistributorReceive = "two_party_distributor_receive"
|
||||
ModeTwoPartyPlatformReceive = "two_party_platform_receive"
|
||||
ModeThreeParty = "three_party"
|
||||
|
||||
ServiceFeeUnitPercent = "percent"
|
||||
ServiceFeeUnitFixedAmount = "fixed_amount"
|
||||
ServiceFeeUnitRatio = "ratio"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidAmount = errors.New("invalid distribution amount")
|
||||
ErrInvalidRate = errors.New("invalid distribution rate")
|
||||
ErrInvalidServiceUnit = errors.New("invalid service fee unit")
|
||||
ErrUnsupportedMode = errors.New("unsupported distribution mode")
|
||||
)
|
||||
|
||||
// Rule 是后台可编辑的分账规则。比例字段按参与方角色命名,避免方向歧义。
|
||||
type Rule struct {
|
||||
Mode string
|
||||
PlatformReceiveRatio float64
|
||||
PlatformSellRatio float64
|
||||
DistributorReceiveRatio float64
|
||||
DistributorSellRatio float64
|
||||
DistributorARatio float64
|
||||
DistributorBRatio float64
|
||||
ServiceFeeValue float64
|
||||
ServiceFeeUnit string
|
||||
PaymentFeeBPS int64
|
||||
TaxEnabled bool
|
||||
TaxRateBPS int64
|
||||
}
|
||||
|
||||
// SettlementInput 使用支付单和退款单的整数分快照计算分销费用。
|
||||
type SettlementInput struct {
|
||||
GrossPaidAmountCent int64
|
||||
RefundAmountCent int64
|
||||
MarkupAmountCent int64
|
||||
GrossProfitByParty []PartyProfitInput
|
||||
}
|
||||
|
||||
type PartyProfitInput struct {
|
||||
PartyRole string
|
||||
IncomeAmountCent int64
|
||||
CostAmountCent int64
|
||||
}
|
||||
|
||||
type PartyTaxResult struct {
|
||||
PartyRole string `json:"party_role"`
|
||||
GrossProfitCent int64 `json:"gross_profit_cent"`
|
||||
TaxAmountCent int64 `json:"tax_amount_cent"`
|
||||
}
|
||||
|
||||
type SettlementResult struct {
|
||||
GrossPaidAmountCent int64
|
||||
RefundAmountCent int64
|
||||
NetPaidAmountCent int64
|
||||
PaymentFeeCent int64
|
||||
ServiceFeeCent int64
|
||||
MarkupAmountCent int64
|
||||
SplitBaseAmountCent int64
|
||||
TaxAmountCent int64
|
||||
TaxByParty []PartyTaxResult
|
||||
}
|
||||
|
||||
// DefaultRule 返回文档中约定的第一版默认值;调用方应将其写入规则表而非写死到业务流程。
|
||||
func DefaultRule(mode string) (Rule, error) {
|
||||
base := Rule{
|
||||
Mode: mode, ServiceFeeValue: 2.5, ServiceFeeUnit: ServiceFeeUnitRatio,
|
||||
PaymentFeeBPS: 50, TaxEnabled: false, TaxRateBPS: 100,
|
||||
}
|
||||
switch mode {
|
||||
case ModeTwoPartyDistributorReceive:
|
||||
base.DistributorReceiveRatio = 3.5
|
||||
base.PlatformSellRatio = 4
|
||||
case ModeTwoPartyPlatformReceive:
|
||||
base.PlatformReceiveRatio = 3.5
|
||||
base.DistributorSellRatio = 4
|
||||
case ModeThreeParty:
|
||||
base.DistributorARatio = 3.5
|
||||
base.DistributorBRatio = 4
|
||||
default:
|
||||
return Rule{}, ErrUnsupportedMode
|
||||
}
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// CalculateSettlement 先扣支付手续费,再计算服务费和分成基数;退款金额不能超过支付金额。
|
||||
func CalculateSettlement(rule Rule, input SettlementInput) (SettlementResult, error) {
|
||||
if input.GrossPaidAmountCent < 0 || input.RefundAmountCent < 0 || input.MarkupAmountCent < 0 || input.RefundAmountCent > input.GrossPaidAmountCent {
|
||||
return SettlementResult{}, ErrInvalidAmount
|
||||
}
|
||||
if rule.PaymentFeeBPS < 0 || rule.PaymentFeeBPS > 10000 || rule.TaxRateBPS < 0 || rule.TaxRateBPS > 10000 || rule.ServiceFeeValue < 0 {
|
||||
return SettlementResult{}, ErrInvalidRate
|
||||
}
|
||||
if rule.ServiceFeeUnit != ServiceFeeUnitPercent && rule.ServiceFeeUnit != ServiceFeeUnitFixedAmount && rule.ServiceFeeUnit != ServiceFeeUnitRatio {
|
||||
return SettlementResult{}, ErrInvalidServiceUnit
|
||||
}
|
||||
netPaid := input.GrossPaidAmountCent - input.RefundAmountCent
|
||||
paymentFee := roundHalfUp(float64(netPaid) * float64(rule.PaymentFeeBPS) / 10000)
|
||||
serviceFee, err := serviceFeeCent(rule, netPaid)
|
||||
if err != nil {
|
||||
return SettlementResult{}, err
|
||||
}
|
||||
splitBase := netPaid - paymentFee - serviceFee
|
||||
if splitBase < 0 {
|
||||
splitBase = 0
|
||||
}
|
||||
result := SettlementResult{
|
||||
GrossPaidAmountCent: input.GrossPaidAmountCent,
|
||||
RefundAmountCent: input.RefundAmountCent,
|
||||
NetPaidAmountCent: netPaid,
|
||||
PaymentFeeCent: paymentFee,
|
||||
ServiceFeeCent: serviceFee,
|
||||
MarkupAmountCent: input.MarkupAmountCent,
|
||||
SplitBaseAmountCent: splitBase,
|
||||
}
|
||||
if rule.TaxEnabled {
|
||||
result.TaxByParty = make([]PartyTaxResult, 0, len(input.GrossProfitByParty))
|
||||
for _, party := range input.GrossProfitByParty {
|
||||
profit := party.IncomeAmountCent - party.CostAmountCent
|
||||
tax := roundHalfUp(float64(max(profit, 0)) * float64(rule.TaxRateBPS) / 10000)
|
||||
result.TaxAmountCent += tax
|
||||
result.TaxByParty = append(result.TaxByParty, PartyTaxResult{PartyRole: party.PartyRole, GrossProfitCent: profit, TaxAmountCent: tax})
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func serviceFeeCent(rule Rule, netPaid int64) (int64, error) {
|
||||
switch rule.ServiceFeeUnit {
|
||||
case ServiceFeeUnitPercent:
|
||||
return roundHalfUp(float64(netPaid) * rule.ServiceFeeValue / 100), nil
|
||||
case ServiceFeeUnitFixedAmount:
|
||||
return roundHalfUp(rule.ServiceFeeValue * 100), nil
|
||||
case ServiceFeeUnitRatio:
|
||||
if rule.ServiceFeeValue <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return roundHalfUp(float64(netPaid) / rule.ServiceFeeValue), nil
|
||||
default:
|
||||
return 0, ErrInvalidServiceUnit
|
||||
}
|
||||
}
|
||||
|
||||
func roundHalfUp(value float64) int64 {
|
||||
return int64(math.Floor(value + 0.5))
|
||||
}
|
||||
|
||||
func max(a, b int64) int64 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user