LoginSignup
0

posted at

Organization

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

この記事は、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 さんに、環境変数の設定・読み込み方法をアドバイス頂きました。ありがとうございます!

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
What you can do with signing up
0