PAYATHON 2026

如何高效确定 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 版本及其具体实现。不过,将远程查询移出循环通常更稳妥。

备注:内容仅供参考。