サブスクリプション対応決済サービス Stripe と Laravel からサブスクリプションを操作する Laravel Cashier についてのメモ書き

Next.js で Stripe

stripe のインストール

npm i stripe @stripe/stripe-js @stripe/react-stripe-js
npm i -D @types/stripe

types/stripe.ts

export interface PaymentIntentRequest {
  amount: number;
  contentId: string;
}

export interface PaymentIntentResponse {
  clientSecret: string;
}
No.2564
10/24 17:35

edit

Stripe API で カード決済がエラーになるカード番号

テストで使用します

4000000000000341
No.1735
06/27 15:57

edit

Stripeの未払いインボイスを取得する

● Stripeの未払いインボイスを取得する

status = open が未払いインボイスです。
必ず $subscription_id が入っていることを確認しましょう。
(nullや 空文字の場合、全ての未払いインボイスを取得してしまいます)

        $open_invoices = null;
        if ( @$subscription_id ){
            $open_invoices = \Stripe\Invoice::all([
                'limit'        => 100 ,
                'subscription' => $subscription_id ,
                'status'       => 'open' ,
            ]);            
        }

● Stripeのインボイスを無効化する

インボイスを無効化するには、status = void にします。

No.1734
04/15 14:09

edit

Stripe の予定されているインボイスを取得する

● Subscription に紐づいている予定されているインボイス( Invoice pcoming )を取得する

		$stripe_upcoming_invoice = \Stripe\Invoice::upcoming([
			"subscription" => "sub_xxxxxxxxxxxxxxxxx" ,
		]);

● Customer に紐づいている予定されているインボイス( Invoice pcoming )を取得する

		$stripe_upcoming_invoice = \Stripe\Invoice::upcoming([
			"customer" => "cus_xxxxxxxxxxxxxx" ,
		]);
No.1637
04/15 14:09

edit

既存の Stripe Subscription の billing_cycle_anchor を変更する

既存の Stripe Subscription の billing_cycle_anchor を変更しようと、下記のようにすると

// update
\Stripe\Stripe::setApiKey( 'xxxxxxxxxxxxxxxxxxxxxxxxxxx' );
\Stripe\Subscription::update(
$subscription_id ,[
	  "billing_cycle_anchor" => $dt->timestamp,
]);

エラーとなります。

When updating an existing subscription, billing_cycle_anchor must be either unset, 'now', or 'unchanged'

● 既存の Stripe Subscription の billing_cycle_anchor を trial_end を使って変更する

次のように trial_end を使って変更することができます。

// update
\Stripe\Stripe::setApiKey( 'xxxxxxxxxxxxxxxxxxxxxxxxxxx' );
\Stripe\Subscription::update(
$subscription_id ,[
	  "trial_end" => $dt->timestamp,
]);
No.1635
12/03 14:28

edit

Stripe.js and Elementsを使用して登録済みのクレジットカード番号を変更する

● 公式サイトのチュートリアル

https://stripe.com/docs/recipes/updating-customer-cards#creating-an-update-your-card-form

● リファレンス

https://stripe.com/docs/stripe-js/reference

● 流れ

次のような画面の流れになります。

1. 入力画面(新しいクレジットカード番号を入力)
  ↓
(WEBアプリサーバへ送信)
  ↓
2. WEBアプリから Stripeへ更新実行
  ↓
3. 完了画面表示

● 1. 入力画面

<script src="https://js.stripe.com/v3/"></script>
<form action="" method="POST">
  <script
  src="https://checkout.stripe.com/checkout.js" class="stripe-button"
  data-key="YOUR-STRIPE_PUBLIC_KEY"
  data-image="/path/to/your/logo.png"
  data-name="お店の名前"
  data-panel-label="クレジットカード番号更新"
  data-label="クレジットカード番号更新"
  data-allow-remember-me=false
  data-locale="auto">
  </script>
</form>

↑ こちらを表示するとメールアドレス入力欄が表示されます。 メールアドレスをあらかじめセットして、ユーザーの入力を省くときは

  data-email="{{ $login_user->email }}"

などとして、email を渡します。

● 2. WEBアプリから Stripeへ更新実行

        // ===== Stripe =====
        \Stripe\Stripe::setApiKey( 'YOUR-STRIPE_SECRET_KEY' );

        $message = "";

        if (isset($_POST['stripeToken'])){
            // dd( $_POST['stripeToken'] );
          try {
            $cu = \Stripe\Customer::update(
              $login_user->stripe_id, // Stripe Customer
              [
                'source' => $_POST['stripeToken'] ,
              ]
            );

            $message = "クレジットカード情報が正常に更新されました";
          }
          catch(\Stripe\Exception\CardException $e) {

            // Use the variable $error to save any errors
            // To be displayed to the customer later in the page
            $body = $e->getJsonBody();
            $err  = $body['error'];
            $error = $err['message'];
          }
          // Add additional error handling here as needed
        }
        // ===== Stripe =====

● 3. 完了画面表示

お好きにお作りください。

No.1593
10/02 22:50

edit

Stripe API の Subscription データから 「このSubscription課金が有効かどうか?」をチェックする

● Stripe API の Subscription データから 「このSubscription課金が有効かどうか?」をチェックする

Stripe API から Subscription を取得すると、 次のようなデータが取得できます。

Stripe\Subscription Object
(
    [id] => sub_FfXXXXXXXXXXXX
    [object] => subscription
    [application_fee_percent] => 
    [billing] => charge_automatically
    [billing_cycle_anchor] => 1566446348
    [billing_thresholds] => 
    [cancel_at] => 1569124748
    [cancel_at_period_end] => 
    [canceled_at] => 1566446348
    [collection_method] => charge_automatically
    [created] => 1566359949
    [current_period_end] => 1569124748
    [current_period_start] => 1566446348
    [customer] => cus_xxxxxxxxxxxxxx
    [days_until_due] => 
    [default_payment_method] => 
    [default_source] => 
    [default_tax_rates] => Array
        (
        )

    [discount] => 
    [ended_at] => 
    [items] => Stripe\Collection Object
        (

このSubscription課金が有効かどうかをチェックするにはこの項目

・( ended_at == null )

上記条件が成り立つとき、サブスクリプションは有効です。

PHPでは次のようなコードで記述します。

\Stripe\Stripe::setApiKey("sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxx");
$subscription = \Stripe\Subscription::retrieve('sub_xxxxxxxxxxxxxx');
if( $StripeSubscription->ended_at == null ){
    // サブスクリプション有効
}

また

現在の支払期間の終了時に解約が予定されているか どうかをチェックするには

・( cancel_at_period_end == false )

上記条件が成り立つとき、現在の支払期間の終了時に解約されます。

またこちらの方法も有効です。

引用: http://bit.ly/2zdPxcj

初の支払いが成功したら、current_period_endに、有効期日を保存。月額課金だったら、課金日+1ヶ月(+α)を保存しておく。
ログイン時は、毎回current_period_endをチェックして、有効期日が過ぎていなければ、サービス利用可能とする。
定期的な支払いは、Webhookで、invoice.payment_succeededイベントを受け取る。これを受け取ったら、current_period_endを更新する。
No.1575
08/22 16:23

edit

UNIXタイムスタンプをマウスオーバーで馴染みのある表記に変更する Chrome拡張機能

● Stripe開発時に便利 UNIXタイムスタンプをマウスオーバーで馴染みのある表記に変更する Chrome拡張機能

*Unix timestamp to readable date/time

https://chrome.google.com/webstore/detail/unix-timestamp-to-readabl/cngfpffdiefnoblplbcdejppdpceaehm/related?hl=ja

No.1553
07/17 11:24

edit

No.1524
07/24 09:06

edit

Laravel Cashier で Stripe の WebHookで Invalid encoding: ISO-8859-1 エラーが出るときの対処法

● Laravel Cashier で Stripe の WebHookで Invalid encoding: ISO-8859-1 エラーが出るときの対処法

Stripe管理画面からエンドポイントURLを見直しましょう
最後のスラッシュを取り除きます

https://YOUR-SERVER.TLD/stripe/webhook/

  ↓

https://YOUR-SERVER.TLD/stripe/webhook
No.1513
06/24 10:44

edit

Stripe Subscription をキャンセルする

● Stripe Subscription をキャンセルする

Stripeのクラスから操作します。

\Stripe\Stripe::setApiKey("sk_test_xxxxxxxxxxv6KeEdZMj9xCPqxxxxxxxxxx");
$subscription = \Stripe\Subscription::retrieve('sub_49ty4767H20z6a');
$subscription->cancel();

● LaravelCashierを使ってStripe Subscription をキャンセルする

LaravelCashierには
・契約中の期間が終了するとともにキャンセルする cancel()
・今すぐキャンセルする cancelNow()
2つのメソッドが用意されています。

$user->subscription('main')->cancel();
$user->subscription('main')->cancelNow();

● 注意  Laravel Cashier を使っている時に「Stripeクラス」または「Stripe管理画面」からキャンセルすると、キャンセルした情報が Laravel Cashier からは取得できません。

これはなぜ起こるかと言うとLaravel Cashier は subscriptions テーブルの サブスクリプション終了日 ( ends_at ) の値を見ているからです。 ( Stripe 管理画面でキャンセルされるとローカルのDBの ends_at に値が入らない。) そこで、Stripe の キャンセルをチェックして、 Stripe管理画面でキャンセルされた場合にsubscriptions の ends_at に値を入れてあげます。

モデル app/Subscription.php


No.1504
06/11 18:29

edit

Laravel Cashier で 課金時に任意のパラメーター(metadata)を渡す

● Laravel Cashier で 課金時(1回課金)に任意のパラメーター(metadata)を渡す

次のようにすると Stripeには影響のない任意のパラメーター(metadata)を渡すことができます。

$stripe_invoice = $user->invoiceFor($item->name, $item->price_withtax_no, [
    'metadata' => [
        'myparam01'   => 'アイテムごとの任意のパラメーター01' ,
        'myparam02'   => 'アイテムごとの任意のパラメーター02' ,
    ]
],[
    'metadata' => [
        'myparam01'   => 'インボイスごとの任意のパラメーター01' ,
        'myparam02'   => 'インボイスごとの任意のパラメーター02' ,
    ]
]);

3番目の引数にに渡すと、「インボイスアイテムごと」4番目の引数にに渡すと、「インボイスごと」になります。

なお、インボイスとインボイスアイテムの関係は次のようにインボイスの下にインボイスアイテムがぶら下がる感じになっています。

インボイス
    ├── インボイスアイテム1
    └── インボイスアイテム2

Stripe API Invoiceitems( create ) : https://stripe.com/docs/api/invoiceitems/create
Stripe API Invoices( create ) : https://stripe.com/docs/api/invoices/create

サブスクリプション時に metadata を渡すには

 ->withMetadata([
]);

が使用できますが、条件によってはうまくデータが渡らないことがあります。 anchorBillingCycleOn() と同時に使うとうまく渡らないことがあります。

No.1503
06/12 01:46

edit

LaravelCashier

Laravel Cashier で Stripe Customerの操作を行う

● Laravel Cashier で Stripe Customerの操作を行う

・Stripe Customer の新規登録

// まだStripe Customer でない場合は新規登録する
if ( ! $user->hasStripeId() ){
    $user->createAsStripeCustomer([
        "description" => $user->email,
        "source"      => $stripe_token
    ]);
}
No.1501
05/06 15:53

edit

LaravelCashier

Laravel Cashierで実装されている機能(メソッド)一覧

Laravel Cashier( Billable トレイト )( SubscriptionBuilder )で実装されている機能はどういったものがあるでしょうか?

ファイルから public function を抜き出してみます。

● Laravel Cashier( Billable トレイト)のメソッド一覧

URL: https://github.com/laravel/cashier/blob/822b6535e755fd36dec8ecceb52cc8909c8a953e/src/Billable.php

・ customer関連

// ユーザーが既に Stripe Customer になっているかどうか判定する
//  @return bool
public function hasStripeId()
// ユーザーをStripe Customer として登録する
//  @return \Stripe\Customer
public function createAsStripeCustomer(array $options = [])
// ユーザーのStripe Customer 情報を更新する
//  @return \Stripe\Customer
public function updateStripeCustomer(array $options = [])
// ユーザーのStripe Customer 情報を取得して返す
// @return \Stripe\Customer
public function asStripeCustomer()

・ インボイス関連

// インボイス付き課金 を実行する
//  @return \Stripe\Invoice|bool
public function invoiceFor($description, $amount, array $tabOptions = [], array $invoiceOptions = [])


public function invoice(array $options = [])
public function upcomingInvoice()
public function findInvoice($id)
public function findInvoiceOrFail($id)
public function downloadInvoice($id, array $data)
public function invoices($includePending = false, $parameters = [])
public function invoicesIncludingPending(array $parameters = [])

・ その他

public function charge($amount, array $options = [])
public function refund($charge, array $options = [])
public function hasCardOnFile()
public function tab($description, $amount, array $options = [])
public function newSubscription($subscription, $plan)

// 現在サブスクリプションがトライアル中かどうかを判別する
public function onTrial($subscription = 'default', $plan = null)

// あるユーザーのサブスクリプションを全て取得
public function subscriptions()

// あるユーザーのサブスクリプションを「名前から」取得
public function subscription($subscription = 'default')


public function onGenericTrial()
public function subscribed($subscription = 'default', $plan = null)
public function cards($parameters = [])
public function defaultCard()
public function updateCard($token)
public function updateCardFromStripe()
public function deleteCards()
public function applyCoupon($coupon)
public function subscribedToPlan($plans, $subscription = 'default')
public function onPlan($plan)
public function preferredCurrency()
public function taxPercentage()

● Laravel Cashier( SubscriptionBuilder )のメソッド一覧

URL: https://github.com/laravel/cashier/blob/822b6535e755fd36dec8ecceb52cc8909c8a953e/src/SubscriptionBuilder.php

public function __construct($owner, $name, $plan)
public function quantity($quantity)
public function trialDays($trialDays)
public function trialUntil(Carbon $trialUntil)
public function skipTrial()
public function anchorBillingCycleOn($date)
public function withCoupon($coupon)

// サブスクリプション作成時にメタデータをセットする(インボイス全体のmetadataではなく、インボイスアイテムのmetadataに格納されます)
public function withMetadata($metadata)

public function add(array $options = [])
public function create($token = null, array $options = [])
No.1500
05/07 15:33

edit

LaravelCashier

Laravel Cashier で Stripe Web Hook を使用する

● 設定ファイルの作成

1. .env に キーを指定

Stripe管理画面 > 開発者 > Webhook
に表示されるキーを .env に記述します。

STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

2. config/service.php に .env の値を読み込むよう設定

    'stripe' => [
        'model' => App\User::class,
        'key' => env('STRIPE_PUBLIC_KEY'),
        'secret' => env('STRIPE_SECRET_KEY'),
        'webhook' => [
            'secret' => env('STRIPE_WEBHOOK_SECRET'),
            'tolerance' => env('STRIPE_WEBHOOK_TOLERANCE', 300),
        ],
    ],

● ルートの作成

routes/web.php

Route::get('stripe/webhook/test', 'WebhookController@test');
Route::post('stripe/webhook', 'WebhookController@handleWebhook');
test() :      テスト用メソッド
handleWebhook() : Stripeからのhookをこのメソッドで受け取ります

● コントローラーの作成

app/Http/Controllers/WebhookController.php

メソッド名の命名は決まっています。

メソッド名はCashierが期待する命名規則に沿う必要があります。特にメソッドはhandleのプレフィックスで始まり、処理したいStripeのWebフックの名前を「キャメルケース」にします。

メソッド名の例

invoice.payment_succeeded → handleInvoicePaymentSucceeded
invoice.payment_failed → handleInvoicePaymentFailed
customer.subscription.deleted → handleCustomerSubscriptionDeleted
<?php
namespace App\Http\Controllers;
use Laravel\Cashier\Http\Controllers\WebhookController as CashierController;
class WebhookController extends CashierController
{
    use MailTrait;
 
    /**
     * メール送信のテスト
     *
     */
    public function test()
    {
        $this->mailtoAdmin( 'TEST MAILの本文です' );
    }

    /**
     * インボイス支払い成功時の処理
     *
     * @param  array  $payload
     * @return void
     *
     */
    public function handleInvoicePaymentSucceeded( $payload )
    {
        $info = print_r($payload, TRUE);
        $this->mailtoAdmin($info);
    }
}

mailtoAdmin() メソッドは適宜作成してください。

● webhookのテスト

1. *.env の webhookキーを一時的にコメントアウト

STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx をコメントアウトする

2. https://YOUR-SERVER.TLD/stripe/webhook/test にアクセスして、メール送信が正常動作することを確認する

正常に動作することを確認できたら、コメントアウトを戻す。(戻すと先ほどの test メソッドは forbidden となります。)

3. Stripe 管理画面から webhook を追加する

Stripe管理画面に追加する設定情報は以下の例のように設定します イベントタイプは複数設定できます。

URL : https://YOUR-SERVER.TLD/stripe/webhook
イベントタイプ : 
invoice.payment_failed
invoice.payment_succeeded

( invoice.payment_succeeded : 決済成功時のHook) とします。

No.1499
04/12 14:38

edit

Stripe Web Hook の種類

● Stripe Web Hook の種類

次の135の Hook が用意されているようです。

account.updated
account.external_account.created
account.external_account.deleted
account.external_account.updated
balance.available
charge.captured
charge.expired
charge.failed
charge.pending
charge.refunded
charge.succeeded
charge.updated
charge.dispute.closed
charge.dispute.created
charge.dispute.funds_reinstated
charge.dispute.funds_withdrawn
charge.dispute.updated
charge.refund.updated
checkout.session.completed
coupon.created
coupon.deleted
coupon.updated
credit_note.created
credit_note.updated
credit_note.voided
customer.created
customer.deleted
customer.updated
customer.bank_account.deleted
customer.discount.created
customer.discount.deleted
customer.discount.updated
customer.source.created
customer.source.deleted
customer.source.expiring
customer.source.updated
customer.subscription.created
customer.subscription.deleted
customer.subscription.trial_will_end
customer.subscription.updated
customer.tax_id.created
customer.tax_id.deleted
customer.tax_id.updated
file.created
invoice.created
invoice.deleted
invoice.finalized
invoice.marked_uncollectible
invoice.payment_action_required
invoice.payment_failed
invoice.payment_succeeded
invoice.sent
invoice.upcoming
invoice.updated
invoice.voided
invoiceitem.created
invoiceitem.deleted
invoiceitem.updated
issuing_authorization.created
issuing_authorization.request
issuing_authorization.updated
issuing_card.created
issuing_card.updated
issuing_cardholder.created
issuing_cardholder.updated
issuing_dispute.created
issuing_dispute.updated
issuing_settlement.created
issuing_settlement.updated
issuing_transaction.created
issuing_transaction.updated
order.created
order.payment_failed
order.payment_succeeded
order.updated
order_return.created
payment_intent.amount_capturable_updated
payment_intent.created
payment_intent.payment_failed
payment_intent.succeeded
payment_method.attached
payment_method.card_automatically_updated
payment_method.detached
payout.canceled
payout.created
payout.failed
payout.paid
payout.updated
person.created
person.deleted
person.updated
plan.created
plan.deleted
plan.updated
product.created
product.deleted
product.updated
recipient.created
recipient.deleted
recipient.updated
reporting.report_run.failed
reporting.report_run.succeeded
reporting.report_type.updated
review.closed
review.opened
sigma.scheduled_query_run.created
sku.created
sku.deleted
sku.updated
source.canceled
source.chargeable
source.failed
source.mandate_notification
source.refund_attributes_required
source.transaction.created
source.transaction.updated
subscription_schedule.aborted
subscription_schedule.canceled
subscription_schedule.completed
subscription_schedule.created
subscription_schedule.expiring
subscription_schedule.released
subscription_schedule.updated
tax_rate.created
tax_rate.updated
topup.canceled
topup.created
topup.failed
topup.reversed
topup.succeeded
transfer.created
transfer.failed
transfer.paid
transfer.reversed
transfer.updated
No.1498
04/28 08:56

edit

Laravel で Stripeサブスクリプションを簡単にする Laravel Cashier を導入する

● Laravel Cashier

https://readouble.com/laravel/5.8/ja/billing.html

https://laravel.com/docs/5.8/billing

● インストール Laravel アプリケーションをセットアップ後 composer からインストールします

composer require laravel/cashier

● 決済通貨を円にする

モデルの boot() メソッドで設定します

    protected static function boot()
    {
	parent::boot();
	\Laravel\Cashier\Cashier::useCurrency('jpy', '¥');
    }

● ユーザーとサブスクリプションを紐付ける

$subscription_name = 'main';
$stripe_plan_id_name = 'plan_123456789';
$user->newSubscription($subscription_name, $stripe_plan_id_name)->create($stripe_token);

引数の説明

* サブスクリプション名($subscription_name)をどう命名するかですが、これは公式サイトにあるように main でOKです。
サブスクリプションのプランが例えば「ノーマル」と「ヘビー」と2つあって、途中で「ノーマル」から「ヘビー」に切り替わる場合も サブスクリプション名は変わりません。(代わりにプランIDが変更になります。)あるサービスでサブスクリプションが同時に2種類契約されるということはほぼないと思いますので、 main でOKだと思います。  
1ユーザーに複数のサブスクリプション契約を許す場合は、商品ごとのユニークな名前(商品ID)を渡しておけば良いと思います。
// $subscription_name : サブスクリプション名(任意の文字列・プランごとにユニークな名前が良い)(subscriptions テーブルの name カラムに格納されます )
// $plan : Stripe のプランID
// $token : ユーザーがカード番号を入れた後にStripeから自動発行されるトークン
newSubscription( string  $subscription  , string  $plan )->create( string $token );

● ユーザーとサブスクリプションを紐付ける時に細かいユーザー情報を登録する

(ユーザー情報を登録しておくとStripe管理画面を見た時に情報が沢山あるので分かりやすくなります)


$new_subscription = $user->newSubscription($subscription_name, $stripe_plan_id_name);
$new_subscription->create($stripe_token, [
    'name'    => "山田 太郎" ,
    'phone'   => '090-123-4567 ,
    'address' => [
        'postal_code' => '123-4567' ,
        'state'       => '東京都' ,
        'line1'       =>'住所1' ,
        'line2'       =>'住所2' ,
    ] ,
]);    

● ユーザーがサブスクリプションを紐付いているかどうか確認する

if ( $auth__user->subscribed( $subscription_name ) ){
    dd("すでに{$subscription_name}に登録済みです。”);
}

● 請求日を指定してサブスクリプションと紐付ける

$c = Carbon::parse('first day of next month'); // 月の初め(1日)をセット
$c->startOfDay(); // 時間 00:00:00 をセット

$user->newSubscription($subscription_name, $stripe_plan_id_name)
                        ->anchorBillingCycleOn( $c)       // 請求アンカーを指定する
                        ->create($stripe_token);

● 初期費用の1回課金を行う(インボイスつき)

$user->invoiceFor('商品名', 価格, [
    'quantity' => 50,
], [
    'tax_percent' => 21,
]);

引数は

【string 商品名】,【int 価格】,【array インボイスアイテムへの支払いオプション】,【array インボイス自身に対する支払いオプション】

です。

Stripe API Invoiceitems( create ) : https://stripe.com/docs/api/invoiceitems/create

No.1497
05/06 15:53

edit

LaravelCashier