98 lines
2.4 KiB
Go
98 lines
2.4 KiB
Go
package paymentutil
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ParseIntegerAmount parses an integer amount expressed in the smallest
|
|
// currency unit. Floating point is intentionally not involved.
|
|
func ParseIntegerAmount(value string) (int64, error) {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return 0, errors.New("支付金额为空")
|
|
}
|
|
return strconv.ParseInt(value, 10, 64)
|
|
}
|
|
|
|
// ParseDecimalAmount converts a major-unit decimal string to an integer amount
|
|
// using a power-of-ten scale, without using floating point.
|
|
func ParseDecimalAmount(value string, scale int64) (int64, error) {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" || scale <= 0 {
|
|
return 0, errors.New("支付金额或换算比例无效")
|
|
}
|
|
if strings.HasPrefix(value, "-") {
|
|
return 0, errors.New("支付金额不能为负数")
|
|
}
|
|
parts := strings.Split(value, ".")
|
|
if len(parts) > 2 || parts[0] == "" {
|
|
return 0, fmt.Errorf("支付金额格式错误: %s", value)
|
|
}
|
|
digits, err := scaleDigits(scale)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
whole, err := strconv.ParseInt(parts[0], 10, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
fraction := ""
|
|
if len(parts) == 2 {
|
|
fraction = parts[1]
|
|
}
|
|
if len(fraction) > digits {
|
|
return 0, fmt.Errorf("支付金额精度超过 %d 位", digits)
|
|
}
|
|
fraction += strings.Repeat("0", digits-len(fraction))
|
|
fractionValue := int64(0)
|
|
if fraction != "" {
|
|
fractionValue, err = strconv.ParseInt(fraction, 10, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
if whole > (int64(^uint64(0)>>1)-fractionValue)/scale {
|
|
return 0, errors.New("支付金额超出范围")
|
|
}
|
|
return whole*scale + fractionValue, nil
|
|
}
|
|
|
|
func FormatDecimalAmount(amount, scale int64) (string, error) {
|
|
if amount <= 0 || scale <= 0 {
|
|
return "", errors.New("支付金额或换算比例无效")
|
|
}
|
|
digits, err := scaleDigits(scale)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if digits == 0 {
|
|
return strconv.FormatInt(amount, 10), nil
|
|
}
|
|
return fmt.Sprintf("%d.%0*d", amount/scale, digits, amount%scale), nil
|
|
}
|
|
|
|
func scaleDigits(scale int64) (int, error) {
|
|
digits := 0
|
|
for n := scale; n > 1 && n%10 == 0; n /= 10 {
|
|
digits++
|
|
}
|
|
if scale <= 0 || scale != pow10(digits) {
|
|
return 0, errors.New("支付金额换算比例必须是 10 的幂")
|
|
}
|
|
return digits, nil
|
|
}
|
|
|
|
func pow10(digits int) int64 {
|
|
value := int64(1)
|
|
for i := 0; i < digits; i++ {
|
|
if value > (int64(^uint64(0)>>1))/10 {
|
|
return 0
|
|
}
|
|
value *= 10
|
|
}
|
|
return value
|
|
}
|