PAYATHON 2026

如何从 Laravel Cashier 获取下一次账单日期?

支付老李

结论

下一次账单日期通常要从支付平台的订阅对象中读取,不能直接通过 Cashier 的 Billable trait 获取。以 Laravel Cashier Stripe 为例,先取得 Stripe Subscription,再读取当前计费周期的结束时间 current_period_end:

use Carbon\Carbon;

$subscription = $user->subscription('default');
$stripeSubscription = $subscription->asStripeSubscription();

$nextBillingDate = Carbon::createFromTimestamp(
    $stripeSubscription->current_period_end
);

current_period_end 是 Unix 时间戳,通常对应当前订阅周期结束和下一个计费周期开始的时间。

在页面中显示

显示日期时,应将其明确转换为应用时区:

use Carbon\Carbon;

$subscription = $user->subscription('default');

if ($subscription) {
    $stripeSubscription = $subscription->asStripeSubscription();

    $nextBillingDate = Carbon::createFromTimestamp(
        $stripeSubscription->current_period_end
    )->setTimezone(config('app.timezone'));

    echo $nextBillingDate->format('Y-m-d H:i:s');
}

如果创建订阅时使用的名称不是 default,请替换为实际名称。例如:

$subscription = $user->subscription('main');

为什么不能直接从 Billable 获取

Cashier 的本地订阅记录主要保存订阅状态、套餐和取消时间等信息。下一次续费日期则由 Stripe 的计费周期决定,并可能受到以下因素影响:

  • 免费试用
  • 套餐切换
  • 按比例计费
  • 自定义 billing cycle anchor
  • 暂停或恢复订阅
  • Subscription Schedule
  • 在 Stripe 后台直接修改订阅

因此,这个日期通常应以远程 Stripe Subscription 中的数据为准。

需要注意的情况

订阅不存在

调用前应先检查用户是否有对应的订阅:

$subscription = $user->subscription('default');

if (!$subscription) {
    // 用户没有该订阅
}

订阅即将取消

如果用户设置了在当前周期结束时取消订阅,current_period_end 仍然存在,但此时它表示订阅结束日期,不是下一次扣费日期:

if ($subscription->onGracePeriod()) {
    $endsAt = $subscription->ends_at;
}

这种情况下,不应将该时间显示为“下次扣费时间”。

免费试用期

订阅处于试用期时,首次可能扣费的日期通常取决于 trial_end:

$stripeSubscription = $subscription->asStripeSubscription();

$timestamp = $stripeSubscription->trial_end
    ?? $stripeSubscription->current_period_end;

$billingDate = Carbon::createFromTimestamp($timestamp)
    ->setTimezone(config('app.timezone'));

字段位置可能因 Stripe API 版本变化

上述代码适用于 current_period_end 位于 Stripe Subscription 对象上的 Cashier 和 Stripe API 组合。较新的 Stripe API 版本可能会将计费周期信息放在 Subscription Item 上。如果订阅对象中没有该字段,需要检查实际返回的数据结构,并从对应的订阅项中读取:

$stripeSubscription = $subscription->asStripeSubscription();
$item = $stripeSubscription->items->data[0];

$nextBillingDate = Carbon::createFromTimestamp(
    $item->current_period_end
)->setTimezone(config('app.timezone'));

如果一个订阅包含多个计费周期不同的订阅项,就没有唯一的“下一次账单日期”。此时需要分别处理各个订阅项,或者查询 Stripe 的 upcoming invoice。

计费周期的结束时间也不代表银行卡一定会在该时刻成功扣款。失败重试、手动开票和异步支付都会影响实际付款时间。

备注:内容仅供参考。