5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SymfonyAdvent Calendar 2022

Day 22

Symfonyを使って、Stripeのサブスクリプション申し込みフローを構築する

Posted at

この記事は、Symfony Advent Calendar 202222日目の記事です。

Symfonyを利用してアプリケーションを開発する際の、Stripe.js / Stripe PHP SDKの組み込み方法を簡単に紹介します。

プロジェクトのセットアップ

Symfonyでのプロジェクトセットアップには、Composerを利用します。

$ composer create-project symfony/skeleton first-symfony-app
$ cd first-symfony-app
$ composer install

今回は、traditional web applicationとして構築しますので、次のステップも追加しましょう。

$ composer require webapp

Dockerの設定を更新するか聞かれます。今回はnを選びましたが、必要に応じて追加してもらいましょう。

    This may create/update docker-compose.yml or update Dockerfile (if it exists).

    Do you want to include Docker configuration from recipes?
    [y] Yes
    [n] No
    [p] Yes permanently, never ask again for this project
    [x] No permanently, never ask again for this project
    (defaults to y): n

セットアップしたアプリを動かすには、symfonyコマンドを利用しましょう。

$ symfony server:start                                                                                                                        
 [OK] Web server listening                                                                                              
      The Web server is using PHP FPM 8.2.0                                                                             
      http://127.0.0.1:8000    

サブスクリプション申し込みのためのページを追加する

http://127.0.0.1:8000/ にアクセスすると、Symfonyで用意されている画面が表示されます。

スクリーンショット 2022-12-22 8.51.59.png

ただしこれはTOPページではなく、ログを見るとHTTP 404が返っています。

[Web Server ] Dec 22 08:51:31 |WARN   | SERVER GET  (404) / ip="127.0.0.1"

そのため、コントローラーやビューを追加して、画面を用意しましょう。

Symfony CLIで、ファイルを作成する

コントローラーとビューは、CLIから作成できます。

$ symfony console make:controller SubscriptionController

 created: src/Controller/SubscriptionController.php
 created: templates/subscription/index.html.twig

           
  Success! 
           

 Next: Open your new controller class and add some pages!

http://127.0.0.1:8000/subscription にアクセスすると、ページが確認できます。

スクリーンショット 2022-12-22 8.56.35.png

src/Controller/SubscriptionController.phpを次のように変更することで、http://127.0.0.1:8000/にページを表示させることもできます。

-    #[Route('/subscription', name: 'app_subscription')]
+    #[Route('/', name: 'app_subscription')]
    public function index(): Response

templates/subscription/index.html.twigを変更すると、表示内容を更新できます。

    <h1>Hello {{ controller_name }}! ✅</h1>
    <h2>You can edit this page.</h2>

スクリーンショット 2022-12-22 9.03.02.png

SymfonyでStripe SDKを利用する

ページの用意ができましたので、Stripe SDKを組み込みましょう。

Stripe SDKのインストール

まずはSDKをインストールします。

$ composer require stripe/stripe-php

APIキーを環境変数から読み込む

.envにStripeのAPIキーを設定しましょう。

+STRIPE_PUBLISHABLE_API_KEY=pk_test_から始まる公開可能キー
+STRIPE_SECRET_API_KEY=sk_test_から始まるシークレットキー

config/services.yamlを編集して、環境変数を登録します。

parameters:
+    app.stripe.publishable_api_key: '%env(resolve:STRIPE_PUBLISHABLE_API_KEY)%'
+    app.stripe.secret_api_key: '%env(resolve:STRIPE_SECRET_API_KEY)%'

services:
    # default config

これでコントローラーから環境変数を取得できるようになります。

src/Controller/SubscriptionController.phpを次のように変更しましょう。

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class SubscriptionController extends AbstractController
{
    #[Route('/subscription', name: 'app_subscription')]
    public function index(): Response
    {
+        $stripe_pk = $this->getParameter('app.stripe.publishable_api_key');
+        $stripe = new \Stripe\StripeClient( $this->getParameter('app.stripe.secret_api_key') );
        return $this->render('subscription/index.html.twig', [
            'controller_name' => 'SubscriptionController',
+            'stripe_pk' => $stripe_pk,
        ]);
    }
}

Stripe SDKで、サブスクリプションを作成する

Stripe SDKの準備ができましたので、サブスクリプションを作成しましょう。

Stripe SDKを実行する場所について
今回は便宜上、コントローラー上に直接実装します。
実際の開発では、Stripe・Stripe Subscription用のサービスを作成することをお勧めします。

src/Controller/SubscriptionController.phpを次のように変更しましょう。


        $stripe_pk = $this->getParameter('app.stripe.secret_api_key');
        $stripe = new \Stripe\StripeClient( $this->getParameter('app.stripe.secret_api_key') );
+        $customer = $stripe->customers->create();
+        $product = $stripe->products->create([
+            'name' => 'Symfony demo'
+        ]);
+        $subscription = $stripe->subscriptions->create([
+            'customer' => $customer->id,
+            'items' => [
+                [
+                    'price_data' => [
+                        'unit_amount' => 1000,
+                        'currency' => 'jpy',
+                        'recurring' => [
+                            'interval' => 'month',
+                        ],
+                        'product' => $product->id,
+                    ],
+                    'quantity' => 1,
+                ]
+            ],
+            'payment_behavior' => 'default_incomplete',
+            'expand' => [
+                'latest_invoice.payment_intent',
+            ],
+            'payment_settings' => [
+                'save_default_payment_method' => 'on_subscription',
+            ],
+        ]);
        return $this->render('subscription/index.html.twig', [
            'controller_name' => 'SubscriptionController',
            'stripe_pk' => $stripe_pk,
+            'subscription_pi_secret' => $subscription->latest_invoice->payment_intent->client_secret,
        ]);

これでhttp://localhost:8000/subscriptionにアクセスする度に、サブスクリプションなどのデータが作成されます。

templates/subscription/index.html.twigを次のように変更すると、作成したサブスクリプションの決済に必要なClient Secretが表示されます。

    <h1>Hello {{ controller_name }}! ✅</h1>
+    <pre><code>{{ subscription_pi_secret }}</pre></code>

    This friendly message is coming from:
    <ul>

スクリーンショット 2022-12-22 13.47.04.png

[Tips]作成済みの料金・顧客・商品データを利用する

$stripe->customers->create$stripe->products->createを毎回実行しますので、読み込みの度に新しいデータが作成されます。

すでにCustomerやProduct, PriceのIDがある方は作成済みのリソースのIDをお使いください。


-        $customer = $stripe->customers->create();
-        $product = $stripe->products->create([
-            'name' => 'Symfony demo'
-        ]);
        $subscription = $stripe->subscriptions->create([
+            'customer' => 'cus_xxx',
-            'customer' => $customer->id,  
            'items' => [
                [
+                   'price' => 'price_xxx',
-                    'price_data' => [
-                        'unit_amount' => 1000,
-                        'currency' => 'jpy',
-                        'recurring' => [
-                            'interval' => 'month',
-                        ],
-                        'product' => 'prod_xxx',
-                    ],
                    'quantity' => 1,
                ]
            ],
            'payment_behavior' => 'default_incomplete',
            'expand' => [
                'latest_invoice.payment_intent',
            ],
            'payment_settings' => [
                'save_default_payment_method' => 'on_subscription',
            ],
        ]);

サブスクリプションのClient Secretを利用して、決済フォームを実装する

最後に、顧客が決済情報を入力するフォームを追加しましょう。

Encoreを利用して、WebpackでJSファイルをバンドルすることもできますが、今回は便宜上こちらを使わずに実装します。

templates/subscription/index.html.twigを次のように変更しましょう。

...
</div>

+<form id="stripe-subscription-form">
+    <div id="stripe-element"></div>
+    <button type='submit'>Subscrive</button>
+</form>
{% endblock %}

+{% block javascripts %}
+<script>
+    let stripe
+    let elements
+    window.onload = function() {
+        presentPaymentElement();
+        addFormSubmitEvent();
+    }
+
+    function presentPaymentElement() {
+        stripe = new Stripe('{{ stripe_pk }}');
+        elements = stripe.elements({
+            appearance: {
+                theme: 'stripe',
+            },
+            clientSecret: '{{ subscription_pi_secret }}'
+        });
+        const paymentElement = elements.create('payment', {
+            layout: 'accordion'
+        });
+        paymentElement.mount('#stripe-element');
+    }

+    function addFormSubmitEvent() {
+        document
+            .getElementById('stripe-subscription-form')
+            .addEventListener('submit', async function(e) {
+                e.preventDefault();
+                if (!stripe || !elements) return;
+                const { error, paymentIntent } = await stripe.confirmPayment({
+                    elements,
+                    redirect: 'if_required'
+                });
+                if (error) {
+                    window.alert(JSON.stringify(error));
+                } else {
+                    console.log(paymentIntent);
+                    window.alert('done');
+                }
+            });
+    }
+</script>
+{% endblock %}

変更を保存すると、決済フォームが表示されます。

スクリーンショット 2022-12-22 14.16.26.png

Stripeが用意している「テストカード番号」を利用して、決済をテストしましょう。

成功メッセージが表示されれば、実装完了です。

スクリーンショット 2022-12-22 14.16.16.png

Documents

Special Thanks

Twitterにて @ippey_s さんに、環境変数の設定・読み込み方法をアドバイス頂きました。ありがとうございます!

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?