laravel cashier defaultPaymentMethod()のふるまい

## 概要

laravel cashierが提供する`特定のユーザーモデル->defaultPaymentMethod()`メソッドのふるまいを簡単にまとめる

## メソッド定義場所と内容

当該のメソッドは`vendor/laravel/cashier/src/Concerns/ManagesPaymentMethods.php`に定義されている。

```vendor/laravel/cashier/src/Concerns/ManagesPaymentMethods.php
/**
 * Get the default payment method for the customer.
 *
 * @return \Laravel\Cashier\PaymentMethod|\Stripe\Card|\Stripe\BankAccount|null
 */
public function defaultPaymentMethod()
{
    if (! $this->hasStripeId()) {
        return;
    }

    /** @var \Stripe\Customer */
    $customer = $this->asStripeCustomer(['default_source', 'invoice_settings.default_payment_method']);

    if ($customer->invoice_settings->default_payment_method) {
        return new PaymentMethod($this, $customer->invoice_settings->default_payment_method);
    }

    // If we can't find a payment method, try to return a legacy source...
    return $customer->default_source;
}
```

PHPDocを確認するとどうやら下記のいずれかが返るらしい

- laravel cashierが定義するPaymentMethodオブジェクト
- StripeのPHP SDKが定義するCardオブジェクト
- StripeのPHP SDKが定義するBankAccount
- null

:::note warn
上記を見た感じ「デフォルトの支払い方法が設定されていない場合nullが返る」と思いそうだが、これは間違いである。
nullが返るのは「当該のユーザーがStripeの顧客IDを持っていないとき」である。
:::

処理を一つ一つ見ていこう
まず下記を見てみる。これは「当該のユーザーがStripeの顧客IDを持っているか」をチェックしている。この場合の「ユーザーがStripeの顧客IDを持っているか」は「usersテーブルのstripe_idカラムがnullか否か」でチェックしている。チェックしているメソッドは`vendor/laravel/cashier/src/Concerns/ManagesCustomer.php`に定義されている。（vendorディレクトリに移動し、`grep -ilr "function hasStripeId(" ./*`を実行してhasStripeId()のメソッド定義を探した。）

「当該のユーザーがStripeの顧客IDを持っていない」場合、defaultPaymentMethod()はnullを返す。

```php
if (! $this->hasStripeId()) {
    return;
}
```

次の処理に移る
これは単純にusersテーブルのstripe_idの値を使ってStripeの顧客情報を取得している。（ただし、オブションとして`default_source`と`invoice_settings.default_payment_method`を指定しているのでCustomerオブジェクトに付随して2種類のデフォルト支払い情報が返されている。）StripeのPHP SDKが定義するCustomerオブジェクトが返される。当該のオブジェクトの元になるクラスは`vendor/stripe/stripe-php/lib/Customer.php`で定義されている。

```php
/** @var \Stripe\Customer */
$customer = $this->asStripeCustomer(['default_source', 'invoice_settings.default_payment_method']);
```

次の処理に移る
これは先に取得したCustomerオブジェクトの`invoice_settings.default_payment_method`の値に注目している。（当該の値の説明は後述する）当該の値がnullではない場合PaymentMethodのオブジェクトを作成して返す。

```php
if ($customer->invoice_settings->default_payment_method) {
    return new PaymentMethod($this, $customer->invoice_settings->default_payment_method);
}
```

最後の処理に移る
これはCustomerオブジェクトの`invoice_settings.default_payment_method`がnullだったので**別**
のデフォルト支払い情報（default_source）を返している。登録されていない場合nullが入っているっぽいのでnullが返る場合もある。

```php
// If we can't find a payment method, try to return a legacy source...
return $customer->default_source;
```

## invoice_settings.default_payment_methodとdefault_source

### invoice_settings.default_payment_method

端的に言うとPayment Intents APIという新しい仕組みを用いて登録されたデフォルトの支払い方法の値が入っている。
基本的にこれから開発する場合はこっちにデフォルトの支払い情報が入っている。

### default_source

Charges APIという古い仕組みを用いて登録されたデフォルトの支払い方法の値が入っている。おそらくバージョンの後方互換をもたせるためにこの値も取るようになっている模様、、。
これから開発する人でクレカ情報を登録する場合、まずこのキーにデフォルトの支払い方法の値が入ることは無いと思っていいはず。（特殊なことをやる場合は入るかも・・？）

## ざっくりまとめ

`defaultPaymentMethod()`がnullを返すパターンは「デフォルトの支払い方法が設定されていない場合」だけではなく「顧客としてStripeに登録されていないとき」も含まれるので注意する。

~~仕様や要件によるが`hasDefaultPaymentMethod()`で先にデフォルトの支払い方法が設定されているかどうかをチェックしたほうが良さそう。~~
嘘でした

https://qiita.com/miriwo/items/a36fb5ca9bbdbe6612ec

`hasPaymentMethod()`を使ったほうが良さそうです。

## 参考文献

https://stripe.com/docs/api/invoices/create#create_invoice-default_source

https://stripe.com/docs/api/customers/object

https://readouble.com/laravel/10.x/ja/billing.html#check-for-a-payment-method