この記事は、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で用意されている画面が表示されます。
ただしこれは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 にアクセスすると、ページが確認できます。
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>
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>
[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 %}
変更を保存すると、決済フォームが表示されます。
Stripeが用意している「テストカード番号」を利用して、決済をテストしましょう。
成功メッセージが表示されれば、実装完了です。
Documents
Special Thanks
Twitterにて @ippey_s さんに、環境変数の設定・読み込み方法をアドバイス頂きました。ありがとうございます!