<?php
/*
* This file is part of Customize of EC-CUBE by Kenji Nakanishi
*
* Copyright(c) 2021 Kenji Nakanishi. All Rights Reserved.
*
* https://www.facebook.com/web.kenji.nakanishi
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Plugin\SheebDlc42Subscription;
use Doctrine\ORM\EntityManagerInterface;
use Eccube\Common\EccubeConfig;
use Eccube\Entity\Master\OrderStatus;
use Eccube\Entity\Order;
use Eccube\Event\EccubeEvents;
use Eccube\Event\EventArgs;
use Eccube\Event\TemplateEvent;
use Eccube\Repository\Master\OrderStatusRepository;
use Plugin\SheebDlc42Subscription\Entity\Config;
use Plugin\SheebDlc42Subscription\Entity\PaymentStatus;
use Plugin\SheebDlc42Subscription\Repository\ConfigRepository;
use Plugin\SheebDlc42Subscription\Repository\PaymentStatusRepository;
use Plugin\SheebDlc42Subscription\Service\MailService;
use Plugin\SheebDlc42Subscription\Service\Method\CreditCard;
use Plugin\SheebDlc42Subscription\Service\PaymentService;
use Symfony\Component\Asset\Packages;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\RouterInterface;
class Event implements EventSubscriberInterface
{
/**
* リッスンしたいサブスクライバのイベント名の配列を返します。
* 配列のキーはイベント名、値は以下のどれかをしてします。
* - 呼び出すメソッド名
* - 呼び出すメソッド名と優先度の配列
* - 呼び出すメソッド名と優先度の配列の配列
* 優先度を省略した場合は0
*
* 例:
* - array('eventName' => 'methodName')
* - array('eventName' => array('methodName', $priority))
* - array('eventName' => array(array('methodName1', $priority), array('methodName2')))
*
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
// 管理画面
'@admin/index.twig' => 'alertUnable',
'@admin/Order/index.twig' => 'alertUnable',
'@admin/Store/plugin.twig' => 'alertUnable',
'@admin/Order/edit.twig' => 'onAdminOrderEditTwig',
// フロント画面
'Shopping/confirm.twig' => ['onShoppingConfirmCreditCardTwig'],
// 受注正常終了
EccubeEvents::FRONT_SHOPPING_COMPLETE_INITIALIZE => ['onFrontShoppingCompleteInitialize'],
];
}
/**
* @var ConfigRepository
*/
protected $configRepository;
/**
* @var PaymentStatusRepository
*/
protected $paymentStatusRepository;
/**
* @var OrderStatusRepository
*/
protected $orderStatusRepository;
/**
* @var EccubeConfig
*/
protected $eccubeConfig;
/**
* @var EntityManagerInterface
*/
protected $entityManager;
/**
* @var RouterInterface
*/
protected $router;
/**
* @var Session
*/
protected $session;
/**
* @var MailService
*/
protected $mailService;
public function __construct(
ConfigRepository $configRepository,
PaymentStatusRepository $paymentStatusRepository,
OrderStatusRepository $orderStatusRepository,
EccubeConfig $eccubeConfig,
EntityManagerInterface $entityManager,
RouterInterface $router,
SessionInterface $session,
MailService $mailService
) {
$this->configRepository = $configRepository;
$this->paymentStatusRepository = $paymentStatusRepository;
$this->orderStatusRepository = $orderStatusRepository;
$this->eccubeConfig = $eccubeConfig;
$this->entityManager = $entityManager;
$this->router = $router;
$this->session = $session;
$this->mailService = $mailService;
}
public function alertUnable(TemplateEvent $event)
{
// 本体プラグインが非活性の場合はアラート
$unable = PluginManager::unableMainPluginMessage($this->entityManager);
if (!empty($unable)) {
$this->session->getFlashBag()->add('eccube.admin.sheeb_dlc42_subscription.error', $unable);
$event->addSnippet('@SheebDlc42Subscription/admin/alert.twig');
}
}
/**
* 管理画面: Order Edit
*
* @param TemplateEvent $event
* @return void
*/
public function onAdminOrderEditTwig(TemplateEvent $event)
{
/** @var Order */
$Order = $event->getParameter('Order');
// Stripe管理画面URL
$stripeConsoleURL = '';
$paymentIntentId = $Order->getSheebDlc42SubscriptionPaymentIntentId();
if (!empty($paymentIntentId)) {
$stripeConsoleURL = 'https://dashboard.stripe.com';
if ($Order->getSheebDlc42SubscriptionMode() !== PaymentService::MODE_PRODUCTION) {
$stripeConsoleURL .= '/test';
}
$stripeConsoleURL .= "/payments/{$paymentIntentId}";
}
$event->setParameter('stripe_console_url', $stripeConsoleURL);
// 有効UI
$PaymentStatus = $Order->getSheebDlc42SubscriptionPaymentStatus();
$isEnableWaiting = empty($PaymentStatus) ? false : $PaymentStatus->isWaiting();
$isEnableActualSales = empty($PaymentStatus) ? false : $PaymentStatus->isActualSales() || $PaymentStatus->isPartiallyRefunded();
$isEnableProvisionalSales = empty($PaymentStatus) ? false : $PaymentStatus->isProvisionalSales();
$event->setParameter('is_enable_waiting', $isEnableWaiting);
$event->setParameter('is_enable_actual_sales', $isEnableActualSales);
$event->setParameter('is_enable_provisional_sales', $isEnableProvisionalSales);
if ($isEnableActualSales) {
// 実売上 OR 一部返金済み
$paymentService = PaymentService::getInstance(
$this->configRepository->get(),
$this->eccubeConfig->get('currency')
);
// 返金済み金額
$refundedAmount = $paymentService->getPaymentIntentRefundTotal($Order);
$event->setParameter('refunded_amount', $refundedAmount);
// 最大返金指定可能額
$event->setParameter('refundable_amount', $Order->getPaymentTotal() - $refundedAmount);
} elseif ($isEnableProvisionalSales) {
// 仮売上
}
// Twig読み込み
$event->addSnippet('@SheebDlc42Subscription/admin/order_edit.twig');
}
/**
* Shopping/confirm.twig
* 購入手続き確認画面
*
* 支払方法が「クレジットカード決済」の場合
*/
public function onShoppingConfirmCreditCardTwig(TemplateEvent $event)
{
// 公開可能キーをフロントに渡す
$Config = $this->configRepository->get();
$event->setParameter('public_key', PaymentService::getPublicKey($Config));
// Twig追加
$event->addAsset('@SheebDlc42Subscription/credit_confirm/assets.twig');
$event->addSnippet('@SheebDlc42Subscription/credit_confirm/elements.twig');
$event->addSnippet('@SheebDlc42Subscription/credit_confirm/logic.twig');
}
/**
* EC-CUBE決済処理が正常終了したら、Stripe決済を完了させる
*
* @param EventArgs $event
* @return void
*/
public function onFrontShoppingCompleteInitialize(EventArgs $event)
{
/** @var Order $Order */
$Order = $event->getArgument('Order');
// ■ クレジット決済以外は無視
if ($Order->getPayment()->getMethodClass() !== CreditCard::class) {
return;
}
// ■ 処理済みの場合は無視(支払ステータス: 未決済以外は全て無視)
if ($Order->getSheebDlc42SubscriptionPaymentStatus()->getId() !== PaymentStatus::OUTSTANDING) {
return;
}
// ■ 処理済みの場合は無視(対応状況: 新規受付以外は無視))
if ($Order->getOrderStatus()->getId() !== OrderStatus::NEW) {
return;
}
// ■ 素材準備
$Config = $this->configRepository->get();
$paymentService = PaymentService::getInstance(
$Config,
$this->eccubeConfig->get('currency')
);
// ■ 支払ステータス: 仮売上
(function (Order $Order) {
// 支払ステータスを実売上にするのは Webhook で確定成功をStripeから通知を受けた時とする
$PaymentStatus = $this->paymentStatusRepository->find(PaymentStatus::PROVISIONAL_SALES);
$Order->setSheebDlc42SubscriptionPaymentStatus($PaymentStatus);
// Webhookよりもステータス変更を先に確実に行う必要があるため、paymentByOrder よりも先に保存
$this->entityManager->persist($Order);
$this->entityManager->flush($Order);
})($Order);
// ■ PaymentMethodを利用して、PaymentIntentを作成
$paymentResult = (function (Order $Order, PaymentService $paymentService) {
// 3D Secure が承認された後にリダイレクトされるURLを準備
$baseURI = empty($_SERVER['HTTPS']) ? 'http://' : 'https://';
$baseURI .= $_SERVER['HTTP_HOST'];
$returnURI = $baseURI . $this->router->generate('sheeb_dlc42_subscription_payment_3d_secure_redirect');
// PaymentIntentを作成
$paymentResult = $paymentService->paymentByOrder($Order, $returnURI);
$this->entityManager->persist($Order);
$this->entityManager->flush($Order);
return $paymentResult;
})($Order, $paymentService);
// ■ Statusに応じた処理
$response = (function (EventArgs $event, PaymentService $paymentService, $paymentResult) {
// 正常系ステータス
$allowStatuses = [
'succeeded',
'requires_capture',
];
if ($paymentResult['status'] === 'requires_action') {
// 3Dセキュアが必要な場合、3Dセキュア画面に遷移する
return $this->redirect3dSecure($event, $paymentResult, $paymentService);
} elseif (!in_array($paymentResult['status'], $allowStatuses)) {
// 売上処理に失敗した場合、エラー画面に遷移する
$message = trans('sheeb_dlc42_subscription.shopping.error.payment');
return $this->redirectError($event, $paymentService, $message);
}
return null;
})($event, $paymentService, $paymentResult);
if ($response instanceof Response) {
$event->setResponse($response);
return;
}
// ■ 売上を確定
(function (Config $Config, Order $Order, PaymentService $paymentService) {
// 必要がなければ終了
if ($Config->getPaymentPattern() !== PaymentService::PAYMENT_PATTERN_CREDIT_AND_SALES) {
return;
}
// 確定処理
$paymentService->capture($Order);
// 本受注メールの送信
$this->mailService->sendSheebDlc42SubscriptionOrderMail($Order);
$this->entityManager->flush();
})($Config, $Order, $paymentService);
// ■ 必要があれば 支払方法を削除
(function (Order $Order, PaymentService $paymentService) {
// 必要がなければ終了
if ($Order->getSheebDlc42SubscriptionCanRegisterCard()) {
return;
}
// 支払方法を削除
$paymentService->removeRegisteredCard(
$Order->getSheebDlc42SubscriptionStripeCustomerId(),
$Order->getSheebDlc42SubscriptionPaymentMethodId()
);
})($Order, $paymentService);
}
/**
* 3D Secure 認証ページに遷移
*
* @param EventArgs $event
* @param [type] $paymentResult
* @return void
*/
private function redirect3dSecure(EventArgs $event, $paymentResult, PaymentService $paymentService)
{
/** @var Order $Order */
$Order = $event->getArgument('Order');
// 支払ステータス: ユーザー認証待ち
$PaymentStatus = $this->paymentStatusRepository->find(PaymentStatus::WAITING);
$Order->setSheebDlc42SubscriptionPaymentStatus($PaymentStatus);
// 対応状況: 購入処理中
$OrderStatus = $this->orderStatusRepository->find(OrderStatus::PROCESSING);
$Order->setOrderStatus($OrderStatus);
if (!isset($paymentResult['nextAction'])) {
return $this->redirectError($event, $paymentService);
}
if (!isset($paymentResult['nextAction']['redirect_to_url'])) {
return $this->redirectError($event, $paymentService);
}
if (!isset($paymentResult['nextAction']['redirect_to_url']['url'])) {
return $this->redirectError($event, $paymentService);
}
if (!isset($paymentResult['token'])) {
return $this->redirectError($event, $paymentService);
}
// Status更新
$this->entityManager->persist($Order);
$this->entityManager->flush($Order);
$iframeURL = $paymentResult['nextAction']['redirect_to_url']['url'];
$this->session->set(PaymentService::PAYMENT_ID, $paymentResult['id']);
$this->session->set(PaymentService::PAYMENT_TOKEN, $paymentResult['token']);
$this->session->set(PaymentService::SECURE_REDIRECT_URL, $iframeURL);
// ■ 3Dセキュア画面
return new RedirectResponse(
$this->router->generate('sheeb_dlc42_subscription_payment_3d_secure')
);
}
/**
* 購入エラーページに遷移
*
* @param EventArgs $event
* @return void
*/
private function redirectError(EventArgs $event, PaymentService $paymentService, string $errorMessage = '')
{
/** @var Order $Order */
$Order = $event->getArgument('Order');
// 受注ステータス: キャンセル
$OrderStatus = $this->orderStatusRepository->find(OrderStatus::CANCEL);
$Order->setOrderStatus($OrderStatus);
// 決済ステータス: 未決済
$PaymentStatus = $this->paymentStatusRepository->find(PaymentStatus::OUTSTANDING);
$Order->setSheebDlc42SubscriptionPaymentStatus($PaymentStatus);
$this->entityManager->persist($Order);
$this->entityManager->flush($Order);
// 在庫数を元に戻す
$paymentService->rollbackStock($event->getArgument('Order'), $this->entityManager);
$this->entityManager->flush();
// エラーページにアラートメッセージを追加
if (!empty($errorMessage)) {
$this->session->getFlashBag()->add('eccube.front.error', $errorMessage);
}
// ■ エラー画面
return new RedirectResponse(
$this->router->generate('shopping_error')
);
}
}