【EC-CUBE4】PAY.JPの定期購入を使って全品送料無料の有料プレミアム会員(サブスクリプション)みたいなのを実装する方法

EC-CUBE4でPAY.JPの定期購入を使って全品送料無料の有料プレミアム会員(サブスクリプション)みたいなのを実装する方法です。

必要なライブラリをインストール

bin/console eccube:composer:require payjp/payjp-php

PAY.JPのアカウントとプランを作成

PAY.JPのアカウントを作成して、APIキーの公開鍵を取得をしてください。

プランについては、例えば月額1000円といったものを作成してください。

プランを作成してプランIDを取得してください。

そのプランに契約して毎月1000円を払い続けている場合はずっと送料無料になるといったものを作ります。

定期課金IDを保存するカラムをCustomerテーブルに追加

<?php

namespace Customize\Entity;

use Customize\Entity\Master\CustomerType;
use Doctrine\ORM\Mapping as ORM;
use Eccube\Annotation\EntityExtension;

/**
 * @EntityExtension("Eccube\Entity\Customer")
 */
trait CustomerTrait
{
    /**
     * PAY.JP発行の定期課金ID
     *
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $subscription_id;

    /**
     * PAY.JPから取得した定期課金ステータス
     *
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $subscription_status;

    public function getSubscriptionId(): ?string
    {
        return $this->subscription_id;
    }

    public function setSubscriptionId(?string $subscription_id): self
    {
        $this->subscription_id = $subscription_id;

        return $this;
    }

    public function getSubscriptionStatus(): ?string
    {
        return $this->subscription_status;
    }

    public function setSubscriptionStatus(?string $subscription_status): self
    {
        $this->subscription_status = $subscription_status;

        return $this;
    }
}

有料会員登録と解約するためのコントローラー作成

APIの公開キーやプランIDは開発環境の場合は.env、本番環境の場合はhttpd.conf や、 .htaccess ファイルに設定したほうが良いかと思います。

下記コントローラーは例外処理が甘いので動作チェックして必要な処理を追加してください。

<?php


namespace Customize\Controller;


use Eccube\Controller\AbstractController;
use Payjp\Customer;
use Payjp\Payjp;
use Payjp\Subscription;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class SubscriptionController extends AbstractController
{
    // APIの公開キーを設定
    const PUBLIC_KEY = 'sk_test_**************************';

    // 定期購入プランIDを設定
    const PLAN = 'pln_**************************';

    public function __construct()
    {
        Payjp::setApiKey(self::PUBLIC_KEY);
    }

    /**
     * 有料会員登録
     *
     * @Route("/mypage/subscription", name="mypage_subscription")
     *
     * @param Request $request
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
     */
    public function subscription(Request $request)
    {
        $Customer = $this->getUser();

        if ($Customer instanceof \Eccube\Entity\Customer) {
            $token = $request->request->get("payjp-token");

            try {
                $PayJpCustomer = Customer::retrieve($Customer->getId());
            } catch(\Exception $e) {
                $PayJpCustomer = Customer::create([
                    "email" => $Customer->getEmail(),
                    "id" => $Customer->getId(),
                    "card" => $token
                ]);
            }

            try {
                $subscription = Subscription::create([
                    "customer" => $PayJpCustomer["id"],
                    "plan" => self::PLAN,
                    "prorate" => true //日割り課金
                ]);
            } catch (\Exception $e) {
                $c = Customer::retrieve($PayJpCustomer["id"]);
                $c->delete();
                throw new $e;
            }

            $Customer->setSubscriptionId($subscription["id"]);
            $Customer->setSubscriptionStatus($subscription["status"]);
            $this->entityManager->persist($Customer);
            $this->entityManager->flush();
        }

        return $this->redirectToRoute("mypage_change");
    }

    /**
     * 有料会員解約
     *
     * @Route("/mypage/subscription/delete", name="mypage_subscription_delete")
     */
    public function delete()
    {
        $Customer = $this->getUser();

        if ($Customer instanceof \Eccube\Entity\Customer) {
            if($Customer->getSubscriptionId()) {
                $su = Subscription::retrieve($Customer->getSubscriptionId());
                $su->delete([
                    "prorate" => true //日割り設定
                ]);

                if($su["deleted"]) {
                    $Customer->setSubscriptionId(null);
                    $Customer->setSubscriptionStatus(null);
                    $this->entityManager->persist($Customer);
                    $this->entityManager->flush();

                }
            }
        }

        return $this->redirectToRoute("mypage_change");
    }
}

マイページのどこかに有料会員登録と解約のボタンを設置してください

PAY.JPはクレジットカード情報のトークン化対応しているので、以下のようにPAY.JPが用意しているjsを設置するだけで有料会員登録ボタンが表示されます。

                        <div class="ec-welcomeMsg">
                            {% if app.user.subscriptionStatus == 'active' %}
                                <a href="{{ url('mypage_subscription_delete') }}">解約</a>
                            {% elseif app.user.subscriptionStatus == 'paused' %}
                                停止中
                            {% else %}
                                <form action="{{ url('mypage_subscription') }}" method="post">
                                    <script src="https://checkout.pay.jp/" class="payjp-button" data-text="有料会員に登録" data-key="pk_test_*****************************"></script>
                                </form>
                            {% endif %}
                        </div>

有料会員は送料無料にするとかの処理

有料会員は送料無料にするとかの処理はPurchaseFlowを使えば簡単にできます。

引き落としの失敗やカードが不正などで定期課金が停止されたユーザーを一括で無料会員に変更

引き落としの失敗やカードが不正などで定期課金が停止されたユーザーを一括で無料会員に変更するコマンドです。

以下のコマンドをcronで定期的に実行してください。

<?php

namespace Customize\Command;

use Doctrine\ORM\EntityManagerInterface;
use Eccube\Entity\Customer;
use Payjp\Payjp;
use Payjp\Subscription;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * 引き落としの失敗やカードが不正などで定期課金が停止されたユーザーを一括で無料会員に変更するコマンド
 *
 *
 * Class SubscriptionsCommand
 * @package Customize\Command
 */
class SubscriptionsCommand extends Command
{
    // APIの公開キーを設定
    const PUBLIC_KEY = 'sk_test_*************************';

    // 定期購入プランIDを設定
    const PLAN = 'pln_*************************';

    protected static $defaultName = 'app:subscriptions';

    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    public function __construct(
        EntityManagerInterface $entityManager
    )
    {
        parent::__construct();

        $this->entityManager = $entityManager;
    }

    protected function configure()
    {
        $this
            ->setDescription('定期購入停止中のユーザーを無料会員に変更')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = new SymfonyStyle($input, $output);

        Payjp::setApiKey(self::PUBLIC_KEY);
        $subscriptions = Subscription::all([
            "status" => "paused" //停止中のみ取得
        ]);

        foreach($subscriptions["data"] as $data) {
            $Customer = $this->entityManager->getRepository(Customer::class)->findOneBy([
                "subscription_id" => $data["id"]
            ]);
            // Subscriptionステータをpausedにしてユーザーに定期課金停止中をお知らせする
            $Customer->setSubscriptionStatus($data["status"]);
            $this->entityManager->persist($Customer);
        }

        $this->entityManager->flush();

        $io->success('success.');
    }
}

上記コードは停止中ユーザーを10件しか取得できませんので、すべての停止中ユーザーを取得するよう工夫する必要があります。

停止している定期課金を再開する処理なども必要かと思いますが、とりあえず以上で完成とします。

細かい処理や設定は大幅に省いていますので独自で実装してください。

あと今回の記事に関しては自己責任でご参考ください。

この記事を参考・利用したことによる直接的、付随的、結果的、間接的、あるいは懲罰的な損害、経費、損失、または 債務について、いかなる責任も負いかねます。

お気軽にコメントをどうぞ

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください