復盤微信支付金額不正確問題解決過程——PHP浮點型計算

問題

2017年9月份,商城項目在運行過程中,購買某商品時如果在下單時沒有完成付款,而是稍后再從“個人中心-我的訂單”發起付款,則無法調起微信支付界面

思路

  • 其他商品正常,說明導致問題的原因大概率是商品本身
  • 只有從會員中心發起的付款存在此問題,說明大概率是會員中心的代碼存在問題
  • 需要先觀察問題出現時“統一下單”是否能夠成功,檢查是否是參數問題導致訂單無法在微信端創建

觀察統一下單返回值

result_code=FAIL
err_code=OUT_TRADE_NO_USED
err_code_des=商戶訂單號重復

微信官方對于此問題的描述如下:

image.png

出現這個問題的時候建議核查訂單號是否重復提交,但實際上在這個使用場景下,我們是“故意”重復提交訂單號的。因為從會員中心發起支付的時候訂單已經創建了,系統會再次請求微信統一下單接口,即便如此,我們也沒有必要每一次請求支付都創建一個新的訂單號。

那為什么返回了這個錯誤

我先給出結論再描述排錯過程:

所謂的同一筆交易不能多次提交,實際上指的是在商品描述、標價金額不相同的情況下,用同一個訂單號訪問了統一下單接口。

image.png

這里的錯誤實際上是因為:從會員中心發起支付時“標價金額”與提交訂單時的不相同。

PHP浮點型運算

以下是某位程序員寫的微信支付代碼:

$total_fee = (int)($order_total * 100);

微信要求金額的單位必須為分,而數據庫中訂單金額單位是元,所以使用訂單金額*100是正確的做法。
訂單支付金額的計算非常復雜,所以單位轉化為分之后再轉化為整型,可以保證微信支付參數不出錯,也是正確的做法。

但這里面隱藏了一個問題,還記得我們問題發生的條件必須是“購買某商品時”嗎?如果單獨購買這個商品的話,訂單的金額是19.9。我們可以嘗試:

echo (int)(19.9 * 100);
// 結果為1989,而非1990

這就導致了訂單創建時給微信的支付數據是1990,而再次支付時卻是1980,所以接口返回了“訂單號重復”的錯誤。

為什么會少了1分錢呢?PHP的官方文檔中是這么說:


image.png

隨后我又實驗了很多數字,結果如下:

echo (int)(19.1 * 100);// 1910
echo (int)(19.2 * 100);// 1920
echo (int)(19.3 * 100);// 1930
echo (int)(19.4 * 100);// 1939 注意這里出現了問題
echo (int)(19.5 * 100);// 1950
echo (int)(19.6 * 100);// 1960
echo (int)(19.7 * 100);// 1970
echo (int)(19.8 * 100);// 1980

這個問題的產生,似乎存在規律,例如19.4、18.4和17.4轉化后是錯誤的,而8.4轉化后返回了正確的結果。
更有趣的是:

echo (int)((19.8+0.1) * 100);
// 1990 注意此時結果是正確的
var_dump(19.9 == (19.8 + 0.1));
// false

調試過程

實際上這種奇怪的問題排查起來沒有什么捷徑,無非就是打日志追蹤變量,最多也就是細心點罷了。

最終使用了一個比較討巧的方式解決了這個問題,將代碼改為了:

$total_fee = (int)(($order_total + 0.00001) * 100);

至于更加嚴謹的浮點數計算方法,今后遇到的時候,再研究吧。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 《非銀行支付機構網絡支付業務管理辦法》條款釋義 - 中國支付網 - 中國支付行業第一門戶網站2016年7月1日...
    菜菜苔閱讀 7,640評論 1 44
  • 井蛙不可以語于海者,拘于虛也;夏蟲不可以語于冰者,篤于時也;曲士不可以語于道者,束于教也。
    曾國鎮閱讀 372評論 0 0
  • 男生不可不讀王小波,女生不可不讀周國平。 這句話在上世紀九十年代是特流行的,事實上不管是男是女,都應讀一讀周國平的...
    金三月閱讀 93評論 0 0