问题描述
在提交订单时候,输入充值金额和优惠码,后台会返回具体的订单信息,如下图,支付金额应该是1 * (1 - 0.09) = 0.91(这个是理想状态),但是表单显示的是0.90999997,
然后点击确认的时候,它会进行支付请求,最终报错如下,错误表达就是参数无效
解决过程
去支付宝沙箱官网看参数设置,主要是下面这五个参数
out_trade_no必选string(64)
【描述】商户订单号。
由商家自定义,64个字符以内,仅支持字母、数字、下划线且需保证在商户端不重复。
【示例值】20150320010101001
total_amount必选price(11)
【描述】订单总金额,单位为元,精确到小数点后两位,取值范围为 [0.01,100000000]。金额不能为0。
【示例值】88.88
subject必选string(256)
【描述】订单标题。
注意:不可使用特殊字符,如 /,=,& 等。
【示例值】Iphone6 16G
product_code必选string(64)
【描述】销售产品码,与支付宝签约的产品码名称。注:目前电脑支付场景下仅支持FAST_INSTANT_TRADE_PAY
【示例值】FAST_INSTANT_TRADE_PAY
time_expire可选string(32)
【描述】订单绝对超时时间。
格式为yyyy-MM-dd HH:mm:ss。超时时间范围:1m~15d。
注:time_expire和timeout_express两者只需传入一个或者都不传,两者均传入时,优先使用time_expire。
【示例值】2016-12-31 10:05:01
其中total_amount这个参数它要求精确到小数点后两位,再看我订单返回的值是0.90999997,就是这个参数导致支付失败
精度丢失说明:
浮点数精度丢失问题源于计算机如何表示和处理浮点数。计算机内部使用二进制(base-2)系统表示数字,而某些十进制小数无法被精确地表示为二进制小数。这导致在浮点数运算过程中出现精度丢失问题。
验证示例如下:
解决方法
使用BigDecimal
public OrderVo returnOrderInfo(Long uid, Long amount, String codes) {
if(codes != null && (codes.length() != 49 || (codes.charAt(codes.length() - 1) != '0' && codes.charAt(codes.length() - 1) != '1'))){
throw new RuntimeException("优惠码格式错误");
}
try{
Float discountRate = null;
BigDecimal realAmount = null;
BigDecimal amounts = new BigDecimal(amount.toString());
if(StringUtils.isNoneBlank(codes)){
// 校验优惠码
BaseResponse<PromotionVo> baseResponse = orderPromotionFeign.checkPromotionCode(uid, codes);
if(baseResponse.getCode() != 0){
throw new RuntimeException("优惠码不可用");
}
discountRate = baseResponse.getData().getDiscountRate();
}
if(discountRate == null){
realAmount = amounts;
}else {
BigDecimal discount = new BigDecimal(discountRate.toString());
realAmount = amounts.multiply(BigDecimal.ONE.subtract(discount));
}
realAmount = realAmount.setScale(2, BigDecimal.ROUND_HALF_UP);
// 生成订单info
OrderVo orderVo = OrderVo.builder()
.subject("购买憨币")
.amount(amount)
.realAmount(realAmount.floatValue())
.discountRate(discountRate)
.codes(codes)
.build();
log.info("订单信息:{}", orderVo);
return orderVo;
}catch (Exception e){
throw new RuntimeException("获取订单信息失败");
}
}
*注,在使用BigDecimal的时候,使用字符串构造 BigDecimal,不然还会引起精度问题,如下:
使用字符串构造 BigDecimal: