MyFatoorah Payment Gateway 小数价格与 QTY 更新问题
结论
KWD 支持三位小数,0.145、1.345 和 2.495 都是合法金额。如果价格变成整数,问题通常不在 KWD,而在金额传给 MyFatoorah 之前的处理过程,例如被转换成 Int、使用了错误的格式化方式,或先用二进制浮点数存储,之后又被取整。
QTY 没有更新,通常是因为第二次支付仍在使用第一次创建的支付请求、Invoice、Payment URL 或 SDK request 对象。取消支付后,需要根据当前购物车重新生成商品数组并创建新的支付请求,不能继续打开旧的支付页面。
价格为什么会丢失小数
先检查是否存在以下代码:
let price = Int("0.145") // 错误
let price = Int(0.145) // 错误,结果为 0
let price = String(format: "%.0f", 0.145) // 错误,强制保留 0 位小数
金额也不适合直接使用 Float:
let price: Float = 2.495
Float 和 Double 使用二进制浮点数,无法精确表示部分十进制金额。支付金额应优先使用 Decimal 或 NSDecimalNumber。
let price = Decimal(string: "2.495", locale: Locale(identifier: "en_US_POSIX"))
如果 SDK 的金额属性只接受 Double,可以在最后赋值给 SDK 时再转换:
guard let decimalPrice = Decimal(
string: "2.495",
locale: Locale(identifier: "en_US_POSIX")
) else {
return
}
let sdkPrice = NSDecimalNumber(decimal: decimalPrice).doubleValue
这次转换只是为了适配 SDK 的类型。购物车计算和持久化仍应使用 Decimal。
正确构造商品数据
发送给 MyFatoorah 的商品明细应直接取自当前购物车,包括每件商品的 Quantity 和 UnitPrice。请求数据大致如下:
{
"InvoiceValue": 5.860,
"CurrencyIso": "KWD",
"InvoiceItems": [
{
"ItemName": "Regular",
"Quantity": 2,
"UnitPrice": 0.145
},
{
"ItemName": "Sugar Free",
"Quantity": 4,
"UnitPrice": 0.145
},
{
"ItemName": "Corn Oil",
"Quantity": 1,
"UnitPrice": 2.495
}
]
}
总金额应按以下公式计算:
0.145 × 2 + 0.145 × 4 + 2.495 × 1 = 3.365 KWD
因此,这个示例中的 InvoiceValue 应为 3.365,不是 5.860。实际请求的总金额必须与商品明细的计算结果一致。
具体字段名称要以项目当前使用的 MyFatoorah SDK 或 API 文档为准。不同版本的模型名称可能不同,但金额和数量的处理方式相同。
Swift 中可以这样计算总金额:
struct CartItem {
let name: String
let price: Decimal
var quantity: Int
}
let items = [
CartItem(name: "Regular", price: Decimal(string: "0.145")!, quantity: 2),
CartItem(name: "Sugar Free", price: Decimal(string: "0.145")!, quantity: 4),
CartItem(name: "Corn Oil", price: Decimal(string: "2.495")!, quantity: 1)
]
let total = items.reduce(Decimal.zero) { result, item in
result + item.price * Decimal(item.quantity)
}
如果需要将金额固定为 KWD 的三位小数,可以在最终提交前进行十进制舍入:
func roundKWD(_ value: Decimal) -> Decimal {
var source = value
var result = Decimal()
NSDecimalRound(
&result,
&source,
3,
.plain
)
return result
}
let invoiceValue = roundKWD(total)
不要把格式化后的本地化字符串作为金额传给 API,例如:
1,345
1.345 د.ك
KD 1.345
API 中的金额应是数值,不能带货币符号或千位分隔符。
QTY 更新后必须重新创建支付请求
支付页面通常显示创建 Invoice 或支付会话时提交的数据。购物车在 App 内发生变化后,已经创建的支付页面不会自动同步。
以下处理会再次打开旧的支付页面:
if paymentURL != nil {
openPaymentPage(paymentURL!)
}
由于这里复用了旧 Invoice,页面仍会显示第一次提交的 QTY。
正确流程是:
用户修改购物车
↓
读取最新购物车数据
↓
重新构造 InvoiceItems
↓
重新计算 InvoiceValue
↓
创建新的支付请求或 Invoice
↓
使用新返回的支付信息打开 SDK screen
示意代码如下:
func startCheckout() {
let currentItems = cartStore.items
let invoiceItems = currentItems.map { item in
[
"ItemName": item.name,
"Quantity": item.quantity,
"UnitPrice": NSDecimalNumber(decimal: item.price)
] as [String: Any]
}
let total = currentItems.reduce(Decimal.zero) { result, item in
result + item.price * Decimal(item.quantity)
}
let request: [String: Any] = [
"CurrencyIso": "KWD",
"InvoiceValue": NSDecimalNumber(decimal: roundKWD(total)),
"InvoiceItems": invoiceItems
]
createNewPaymentRequest(request)
}
createNewPaymentRequest 只是代指项目中调用 MyFatoorah API 或 SDK 的实际代码,并不是某个特定版本的官方方法名。
用户取消支付时,应清除仅属于本次支付会话的数据:
func paymentDidCancel() {
currentPaymentURL = nil
currentInvoiceId = nil
currentPaymentRequest = nil
}
购物车不需要清除。用户再次点击支付时,再读取购物车的最新状态。
还需要检查对象复用问题
即使每次都重新打开 SDK screen,只要商品对象或请求对象仅创建过一次,提交的仍可能是旧数量。例如:
lazy var paymentRequest = buildPaymentRequest()
lazy 属性只初始化一次,不适合保存会随购物车变化的请求。应改为每次重新生成:
func makePaymentRequest(from items: [CartItem]) -> PaymentRequest {
// 根据当前 items 创建一个全新的请求
}
使用可变数组时,还要检查是否只更新了界面显示的 QTY,却没有更新支付请求使用的数据源:
displayedItems[index].quantity = 2
如果支付时读取的是另一个旧数组,数量仍不会变化:
let checkoutItems = originalItems
购物车 UI、总金额和支付请求应读取同一份最新数据。
如何定位数据在哪一步被改错
应在打开 SDK screen 前记录最终请求,而不是只打印服务端最初返回的商品数据:
for item in currentItems {
print(
"name=\(item.name), " +
"quantity=\(item.quantity), " +
"price=\(NSDecimalNumber(decimal: item.price).stringValue)"
)
}
print("invoiceValue=\(NSDecimalNumber(decimal: invoiceValue).stringValue)")
还要检查实际发送的 HTTP request body。如果请求中已经是:
{
"Quantity": 4,
"UnitPrice": 0.145
}
但支付页面仍显示旧值,应继续检查是否打开了旧的 Invoice ID 或 Payment URL。
如果实际请求中已经出现:
{
"Quantity": 1,
"UnitPrice": 0
}
说明问题发生在 App 的模型转换、缓存或序列化阶段,与支付页面无关。
注意事项
- KWD 通常按三位小数处理,不要使用只保留两位小数的货币格式。
- 不要先将金额乘以 100。只有接口明确要求传入最小货币单位时才能这样处理。KWD 使用三位小数,换算比例也不是普通两位小数货币使用的 100。
product_id通常是商户自己的商品标识,不能替代Quantity和UnitPrice。InvoiceValue应等于所有商品UnitPrice × Quantity的总和。- 取消支付不会修改已有 Invoice。商品数量变化后,需要创建新的支付会话。
- MyFatoorah 不同 SDK 版本的类名和属性可能不同。应以项目当前安装版本的接口定义为准,不要直接照搬其他版本的示例类名。
备注:内容仅供参考。
