如何高效确定 default payment method?
支付阿杰
结论
不要在循环里反复调用 hasDefaultPaymentMethod() 和 defaultPaymentMethod()。先取得默认支付方式的 ID,再遍历所有支付方式并逐一比较。
当前写法很可能每次循环都会查询 Stripe,因此 payment method 越多,页面加载时间越长。默认状态通常保存在 Customer 配置中,并不是 PaymentMethod 对象自身的字段,所以 paymentMethods() 返回的数据没有 default key 很正常。
推荐写法
$defaultPaymentMethod = $user->defaultPaymentMethod();
$defaultPaymentMethodId = $defaultPaymentMethod?->id;
$paymentMethods = $user->paymentMethods()
->map(function ($paymentMethod) use ($defaultPaymentMethodId) {
return [
'id' => $paymentMethod->id,
'card' => [
'last4' => $paymentMethod->card->last4,
'exp_year' => $paymentMethod->card->exp_year,
'exp_month' => $paymentMethod->card->exp_month,
'brand' => $paymentMethod->card->brand,
],
'default' => $paymentMethod->id === $defaultPaymentMethodId,
];
})
->sortByDesc('default')
->values();
这里不需要再单独调用 hasDefaultPaymentMethod()。如果用户没有默认支付方式,defaultPaymentMethod() 通常会返回 null,使用 null-safe operator ?-> 就能安全处理。
如果项目使用的 PHP 版本低于 8.0,可以这样写:
$defaultPaymentMethod = $user->defaultPaymentMethod();
$defaultPaymentMethodId = $defaultPaymentMethod
? $defaultPaymentMethod->id
: null;
进一步减少排序开销
如果只是想把默认支付方式放到第一项,可以在遍历时分别保存默认项和其他项,省去 sortByDesc() 或 usort():
$defaultPaymentMethod = $user->defaultPaymentMethod();
$defaultPaymentMethodId = $defaultPaymentMethod
? $defaultPaymentMethod->id
: null;
$default = null;
$others = [];
foreach ($user->paymentMethods() as $paymentMethod) {
$method = [
'id' => $paymentMethod->id,
'card' => [
'last4' => $paymentMethod->card->last4,
'exp_year' => $paymentMethod->card->exp_year,
'exp_month' => $paymentMethod->card->exp_month,
'brand' => $paymentMethod->card->brand,
],
'default' => $paymentMethod->id === $defaultPaymentMethodId,
];
if ($method['default']) {
$default = $method;
} else {
$others[] = $method;
}
}
$paymentMethods = $default !== null
? array_merge([$default], $others)
: $others;
这段代码只遍历一次,时间复杂度是 O(n),同时避免了重复的远程请求和不必要的 JSON 编解码。
注意事项
default应始终初始化为false或直接保存比较结果。否则,当用户没有默认支付方式时,后续排序可能会访问不存在的数组键。- 不要仅为了把数组转换成对象而使用
json_decode(json_encode(...))。API 响应可以直接返回数组,Laravel 会自动将其序列化为 JSON。 - 如果同一个请求后面还需要使用默认支付方式,请复用已经取得的
$defaultPaymentMethod,不要再次调用defaultPaymentMethod()。 - 查询次数取决于项目使用的 Laravel Cashier 版本及其具体实现。不过,将远程查询移出循环通常更稳妥。
备注:内容仅供参考。
