订单退款金额分配算法

目录

业务需求

算法说明:

代码实现

示例输出:


业务需求

一个订单由多种支付方式,每个 sku 可以被 多种支付方式支付,给一个退款金额,如何分配到每种支付方式和 sku。

算法说明

  1. 双层分配机制

    • 第一层:在支付方式之间分配总退款金额

    • 第二层:在每个支付方式内部将分配到的金额进一步分配到各个SKU

  2. 智能余数分配

    • 使用整数除法进行基础分配

    • 余数按照剩余金额降序排列进行分配

    • 每次分配1分直到余数分配完毕

  3. 防超退措施

    • 严格验证分配金额不超过剩余金额

    • 使用排序确保优先处理大额余数

    • 双重校验保证数据一致性

代码实现

以下是使用Go语言实现的退款分配算法,该算法采用智能余数分配策略,确保在支付方式和SKU层面均不会出现超退问题。

package main

import (
	"errors"
	"fmt"
	"sort"
)

type SkuRefund struct {
	SkuID       string
	TotalAmount int // 该SKU在该支付方式中的总金额(分)
	Refunded    int // 已退金额(分)
}

type PaymentMethod struct {
	ID          string
	TotalAmount int          // 支付方式总金额(分)
	Refunded    int          // 已退金额(分)
	Skus        []*SkuRefund // 包含的SKU列表
}

type RefundResult struct {
	PaymentMethodRefunds map[string]int            // 支付方式ID -> 退款金额
	SkuRefunds           map[string]map[string]int // 支付方式ID -> (SKUID -> 退款金额)
}

func CalculateRefund(paymentMethods []*PaymentMethod, refundAmount int) (*RefundResult, error) {
	pmAllocations, err := allocatePaymentMethods(paymentMethods, refundAmount)
	if err != nil {
		return nil, err
	}

	result := &RefundResult{
		PaymentMethodRefunds: make(map[string]int),
		SkuRefunds:           make(map[string]map[string]int),
	}

	for _, pm := range paymentMethods {
		amount := pmAllocations[pm.ID]
		if amount == 0 {
			continue
		}

		skuAllocations, err := allocateSkus(pm.Skus, amount)
		if err != nil {
			return nil, fmt.Errorf("payment method %s: %w", pm.ID, err)
		}

		result.PaymentMethodRefunds[pm.ID] = amount
		result.SkuRefunds[pm.ID] = skuAllocations
	}

	return result, nil
}

func allocatePaymentMethods(paymentMethods []*PaymentMethod, totalRefund int) (map[string]int, error) {
	totalRemain := 0
	pmRemains := make(map[string]int)
	for _, pm := range paymentMethods {
		remain := pm.TotalAmount - pm.Refunded
		if remain < 0 {
			return nil, errors.New("negative remaining in payment method")
		}
		pmRemains[pm.ID] = remain
		totalRemain += remain
	}

	if totalRemain < totalRefund {
		return nil, errors.New("refund amount exceeds total remaining")
	}

	// 计算基础分配和余数
	allocations := make(map[string]int)
	sumBase := 0
	bases := make(map[string]int)

	for _, pm := range paymentMethods {
		remain := pmRemains[pm.ID]
		if remain == 0 {
			continue
		}
		base := (totalRefund * remain) / totalRemain
		bases[pm.ID] = base
		sumBase += base
	}

	remainder := totalRefund - sumBase

	// 排序支付方式(按剩余金额降序,ID升序)
	sortedPMs := make([]*PaymentMethod, len(paymentMethods))
	copy(sortedPMs, paymentMethods)
	sort.Slice(sortedPMs, func(i, j int) bool {
		ri := sortedPMs[i].TotalAmount - sortedPMs[i].Refunded
		rj := sortedPMs[j].TotalAmount - sortedPMs[j].Refunded
		if ri == rj {
			return sortedPMs[i].ID < sortedPMs[j].ID
		}
		return ri > rj
	})

	// 分配余数
	for remainder > 0 {
		allocated := false
		for _, pm := range sortedPMs {
			if remainder <= 0 {
				break
			}
			remain := pmRemains[pm.ID]
			if bases[pm.ID] < remain {
				bases[pm.ID]++
				remainder--
				allocated = true
			}
		}
		if !allocated && remainder > 0 {
			return nil, errors.New("remainder allocation failed")
		}
	}

	// 构建结果并验证
	for _, pm := range paymentMethods {
		allocated := bases[pm.ID]
		if allocated > pmRemains[pm.ID] {
			return nil, fmt.Errorf("allocation exceeded for payment method %s", pm.ID)
		}
		allocations[pm.ID] = allocated
	}

	return allocations, nil
}

func allocateSkus(skus []*SkuRefund, totalRefund int) (map[string]int, error) {
	totalRemain := 0
	skuRemains := make(map[string]int)
	for _, sku := range skus {
		remain := sku.TotalAmount - sku.Refunded
		if remain < 0 {
			return nil, errors.New("negative remaining in sku")
		}
		skuRemains[sku.SkuID] = remain
		totalRemain += remain
	}

	if totalRemain < totalRefund {
		return nil, errors.New("sku refund exceeds total remaining")
	}

	// 计算基础分配和余数
	allocations := make(map[string]int)
	sumBase := 0
	bases := make(map[string]int)

	for _, sku := range skus {
		remain := skuRemains[sku.SkuID]
		if remain == 0 {
			continue
		}
		base := (totalRefund * remain) / totalRemain
		bases[sku.SkuID] = base
		sumBase += base
	}

	remainder := totalRefund - sumBase

	// 排序SKU(按剩余金额降序,ID升序)
	sortedSkus := make([]*SkuRefund, len(skus))
	copy(sortedSkus, skus)
	sort.Slice(sortedSkus, func(i, j int) bool {
		ri := sortedSkus[i].TotalAmount - sortedSkus[i].Refunded
		rj := sortedSkus[j].TotalAmount - sortedSkus[j].Refunded
		if ri == rj {
			return sortedSkus[i].SkuID < sortedSkus[j].SkuID
		}
		return ri > rj
	})

	// 分配余数
	for remainder > 0 {
		allocated := false
		for _, sku := range sortedSkus {
			if remainder <= 0 {
				break
			}
			remain := skuRemains[sku.SkuID]
			if bases[sku.SkuID] < remain {
				bases[sku.SkuID]++
				remainder--
				allocated = true
			}
		}
		if !allocated && remainder > 0 {
			return nil, errors.New("sku remainder allocation failed")
		}
	}

	// 构建结果并验证
	for _, sku := range skus {
		allocated := bases[sku.SkuID]
		if allocated > skuRemains[sku.SkuID] {
			return nil, fmt.Errorf("allocation exceeded for sku %s", sku.SkuID)
		}
		allocations[sku.SkuID] = allocated
	}

	return allocations, nil
}

// 示例用法
func main() {
	// 创建测试支付方式
	paymentMethods := []*PaymentMethod{
		{
			ID:          "pm1",
			TotalAmount: 100,
			Refunded:    33 + 33,
			Skus: []*SkuRefund{
				{SkuID: "sku1", TotalAmount: 50, Refunded: 17 + 16},
				{SkuID: "sku2", TotalAmount: 50, Refunded: 16 + 17},
			},
		},
		{
			ID:          "pm2",
			TotalAmount: 200,
			Refunded:    67 + 67,
			Skus: []*SkuRefund{
				{SkuID: "sku1", TotalAmount: 100, Refunded: 34 + 33},
				{SkuID: "sku2", TotalAmount: 100, Refunded: 33 + 34},
			},
		},
	}

	// 计算退款100分
	result, err := CalculateRefund(paymentMethods, 100)
	if err != nil {
		panic(err)
	}

	fmt.Println("支付方式退款:")
	for pm, amount := range result.PaymentMethodRefunds {
		fmt.Printf("%s: %d分\n", pm, amount)
	}

	fmt.Println("\nSKU退款:")
	for pm, skus := range result.SkuRefunds {
		fmt.Printf("支付方式 %s:\n", pm)
		for sku, amount := range skus {
			fmt.Printf("  %s: %d分\n", sku, amount)
		}
	}
}

示例输出

支付方式退款:
pm1: 34分    
pm2: 66分    
             
SKU退款:     
支付方式 pm2:
  sku1: 33分 
  sku2: 33分 
支付方式 pm1:
  sku1: 17分 
  sku2: 17分 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值