app/Plugin/AmazonPayV2_42/Service/AmazonRequestService.php line 157

Open in your IDE?
  1. <?php
  2. /*
  3.  * Amazon Pay V2 for EC-CUBE4.2
  4.  * Copyright(c) 2023 EC-CUBE CO.,LTD. all rights reserved.
  5.  *
  6.  * https://www.ec-cube.co.jp/
  7.  *
  8.  * This program is not free software.
  9.  * It applies to terms of service.
  10.  *
  11.  */
  12. namespace Plugin\AmazonPayV2_42\Service;
  13. use Doctrine\ORM\EntityManagerInterface;
  14. use Eccube\Common\EccubeConfig;
  15. use Eccube\Entity\Master\OrderStatus;
  16. use Eccube\Repository\BaseInfoRepository;
  17. use Eccube\Repository\CustomerRepository;
  18. use Eccube\Repository\Master\OrderStatusRepository;
  19. use Eccube\Service\CartService;
  20. use Eccube\Service\PurchaseFlow\PurchaseContext;
  21. use Eccube\Service\PurchaseFlow\PurchaseFlow;
  22. use Eccube\Service\PurchaseFlow\Processor\StockReduceProcessor;
  23. use Eccube\Service\PurchaseFlow\Processor\PointProcessor;
  24. use Plugin\AmazonPayV2_42\Entity\Master\AmazonStatus;
  25. use Plugin\AmazonPayV2_42\Exception\AmazonException;
  26. use Plugin\AmazonPayV2_42\Exception\AmazonPaymentException;
  27. use Plugin\AmazonPayV2_42\Repository\ConfigRepository;
  28. use Plugin\AmazonPayV2_42\Amazon\Pay\API\Client as AmazonPayClient;
  29. use Plugin\AmazonPayV2_42\Repository\Master\AmazonStatusRepository;
  30. use GuzzleHttp\Client;
  31. use Guzzle\Http\Exception\BadResponseException;
  32. use Guzzle\Http\Exception\CurlException;
  33. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  34. use Psr\Container\ContainerInterface;
  35. use Symfony\Component\HttpFoundation\Request;
  36. use Symfony\Component\HttpFoundation\RequestStack;
  37. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  38. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  39. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  40. use Carbon\Carbon;
  41. class AmazonRequestService extends AbstractController
  42. {
  43.     /**
  44.      * @var EntityManagerInterface
  45.      */
  46.     protected $entityManager;
  47.     /**
  48.      * @var BaseInfoRepository
  49.      */
  50.     protected $baseInfoRepository;
  51.     /**
  52.      * @var CustomerRepository
  53.      */
  54.     protected $customerRepository;
  55.     /**
  56.      * @var CartService
  57.      */
  58.     protected $cartService;
  59.     /**
  60.      * @var PurchaseFlow
  61.      */
  62.     protected $purchaseFlow;
  63.     /**
  64.      * @var eccubeConfig
  65.      */
  66.     protected $eccubeConfig;
  67.     /**
  68.      * @var ConfigRepository
  69.      */
  70.     protected $configRepository;
  71.     /**
  72.      * @var Config
  73.      */
  74.     protected $Config;
  75.     /**
  76.      * @var array
  77.      */
  78.     protected $amazonApi;
  79.     /**
  80.      * @var array
  81.      */
  82.     protected $amazonApiConfig;
  83.     /**
  84.      * @var Session
  85.      */
  86.     protected $session;
  87.     /**
  88.      * @var TokenStorageInterface
  89.      */
  90.     protected $tokenStorage;
  91.     /**
  92.      * @var ContainerInterface
  93.      */
  94.     protected $container;
  95.     /**
  96.      * @var PointProcessor
  97.      */
  98.     private $pointProcessor;
  99.     /**
  100.      * @var StockReduceProcessor
  101.      */
  102.     private $stockReduceProcessor;
  103.     /**
  104.      * @var AmazonStatusRepository
  105.      */
  106.     private $amazonStatusRepository;
  107.     /**
  108.      * @var OrderStatusRepository
  109.      */
  110.     private $orderStatusRepository;
  111.     public function __construct(
  112.         EntityManagerInterface $entityManager,
  113.         BaseInfoRepository $baseInfoRepository,
  114.         CustomerRepository $customerRepository,
  115.         CartService $cartService,
  116.         PurchaseFlow $cartPurchaseFlow,
  117.         EccubeConfig $eccubeConfig,
  118.         ConfigRepository $configRepository,
  119.         RequestStack $requestStack,
  120.         TokenStorageInterface $tokenStorage,
  121.         OrderStatusRepository $orderStatusRepository,
  122.         AmazonStatusRepository $amazonStatusRepository,
  123.         StockReduceProcessor $stockReduceProcessor,
  124.         PointProcessor $pointProcessor,
  125.         ContainerInterface $container
  126.     ) {
  127.         $this->entityManager $entityManager;
  128.         $this->BaseInfo $baseInfoRepository->get();
  129.         $this->customerRepository $customerRepository;
  130.         $this->cartService $cartService;
  131.         $this->purchaseFlow $cartPurchaseFlow;
  132.         $this->eccubeConfig $eccubeConfig;
  133.         $this->configRepository $configRepository;
  134.         $this->session $requestStack->getSession();
  135.         $this->tokenStorage $tokenStorage;
  136.         $this->orderStatusRepository $orderStatusRepository;
  137.         $this->amazonStatusRepository $amazonStatusRepository;
  138.         $this->stockReduceProcessor $stockReduceProcessor;
  139.         $this->pointProcessor $pointProcessor;
  140.         $this->container $container;
  141.         $this->Config $this->configRepository->get();
  142.         if (
  143.             $this->Config->getAmazonAccountMode() == $this->eccubeConfig['amazon_pay_v2']['account_mode']['owned'] &&
  144.             $this->Config->getEnv() == $this->eccubeConfig['amazon_pay_v2']['env']['prod']
  145.         ) {
  146.             $this->amazonApi $this->eccubeConfig['amazon_pay_v2']['api']['prod'];
  147.         } else {
  148.             $this->amazonApi $this->eccubeConfig['amazon_pay_v2']['api']['sandbox'];
  149.         }
  150.         $this->amazonApiConfig $this->eccubeConfig['amazon_pay_v2']['api']['config'];
  151.     }
  152.     private function payoutSellerOrderId($orderId$request_type '')
  153.     {
  154.         $request_attr $request_type === '' '' strtoupper($request_type) . '_';
  155.         $prefix '';
  156.         $iniFile dirname(__FILE__) . '/../amazon_pay_config.ini';
  157.         if (file_exists($iniFile)) {
  158.             $arrInit parse_ini_file($iniFile);
  159.             $prefix $arrInit['prefix'];
  160.         }
  161.         $prefix $prefix === '' '' $prefix '_';
  162.         $timestamp '';
  163.         if ($this->Config->getAmazonAccountMode() === $this->eccubeConfig['amazon_pay_v2']['account_mode']['shared']) {
  164.             $timestamp Carbon::now()->timestamp;
  165.         }
  166.         $timestamp $timestamp === '' '' $timestamp '_';
  167.         return $timestamp $prefix $request_attr $orderId;
  168.     }
  169.     protected function getAmazonPayConfig()
  170.     {
  171.         $Config $this->configRepository->get();
  172.         $config = [
  173.             'public_key_id' => $Config->getPublicKeyId(),
  174.             'private_key'   => $this->eccubeConfig->get('kernel.project_dir') . '/' $Config->getPrivateKeyPath(),
  175.             'sandbox'       => $Config->getEnv() == $this->eccubeConfig['amazon_pay_v2']['env']['sandbox'] ? true false,                        // true (Sandbox) or false (Production) boolean
  176.             'region'        => 'jp'                         // Must be one of: 'us', 'eu', 'jp'
  177.         ];
  178.         return $config;
  179.     }
  180.     public function createCheckoutSessionPayload($cart_key)
  181.     {
  182.         $Config $this->configRepository->get();
  183.         $router $this->container->get('router');
  184.         $payload = [
  185.             'webCheckoutDetails' => [
  186.                 'checkoutReviewReturnUrl' => $router->generate('amazon_checkout_review', ['cart' => $cart_key], UrlGeneratorInterface::ABSOLUTE_URL),
  187.             ],
  188.             'paymentDetails' => [
  189.                 'allowOvercharge' => true//増額許可
  190.             ],
  191.             'storeId' => $Config->getClientId(),
  192.             'deliverySpecifications' => [
  193.                 'addressRestrictions' => [
  194.                     'type' => 'Allowed',
  195.                     'restrictions' => [
  196.                         'JP' => [],
  197.                     ],
  198.                 ],
  199.             ],
  200.         ];
  201.         return json_encode($payloadJSON_FORCE_OBJECT);
  202.     }
  203.     public function createUpdateCheckoutSessionPayload($Order)
  204.     {
  205.         $router $this->container->get('router');
  206.         
  207.         // NOTE: AmazonPayAPI仕様上、0円の決済は許容しない.
  208.         if($Order->getPaymentTotal() == 0) {
  209.             throw AmazonPaymentException::create(AmazonPaymentException::ZERO_PAYMENT);
  210.         }
  211.         $config $this->configRepository->get();
  212.         if ($config->getSale() == $this->eccubeConfig['amazon_pay_v2']['sale']['authori']) {
  213.             $paymentIntent 'Authorize';
  214.         } elseif ($config->getSale() == $this->eccubeConfig['amazon_pay_v2']['sale']['capture']) {
  215.             $paymentIntent 'AuthorizeWithCapture';
  216.         }
  217.         $payload = [
  218.             'webCheckoutDetails' => [
  219.                 'checkoutResultReturnUrl' => $router->generate('amazon_pay_shopping_checkout_result', [], UrlGeneratorInterface::ABSOLUTE_URL),
  220.             ],
  221.             'paymentDetails' => [
  222.                 'paymentIntent' => $paymentIntent,
  223.                 'canHandlePendingAuthorization' => false,
  224.                 'chargeAmount' => [
  225.                     'amount' => (int)$Order->getPaymentTotal(),
  226.                     'currencyCode' => "JPY"
  227.                 ],
  228.                 //"softDescriptor" => "softDescriptor"
  229.             ],
  230.             'merchantMetadata' => [
  231.                 'merchantReferenceId' => $this->payoutSellerOrderId($Order->getId()),
  232.                 'noteToBuyer' => ''
  233.                 // "customInformation" => "customInformation"
  234.             ],
  235.             "platformId" => "A1LODGGQOBGE66"
  236.         ];
  237.         // 店舗名が50文字を超えるとAmazon側でエラーとなるため考慮する
  238.         if (mb_strlen($this->BaseInfo->getShopName()) < 51) {
  239.             $payload['merchantMetadata']['merchantStoreName'] = $this->BaseInfo->getShopName();
  240.         }
  241.         return json_encode($payloadJSON_FORCE_OBJECT);
  242.     }
  243.     public function createCompleteCheckoutSessionPayload($Order)
  244.     {
  245.         $payload = [
  246.             'chargeAmount' => [
  247.                 'amount' => (int)$Order->getPaymentTotal(),
  248.                 'currencyCode' => 'JPY',
  249.             ]
  250.         ];
  251.         return json_encode($payloadJSON_FORCE_OBJECT);
  252.     }
  253.     public function createCaptureChargePayload($Order$billingAmount null)
  254.     {
  255.         $payload = [
  256.             'captureAmount' => [
  257.                 'amount' => is_null($billingAmount) ? (int)$Order->getPaymentTotal() : $billingAmount,
  258.                 'currencyCode' => 'JPY',
  259.             ]
  260.         ];
  261.         return json_encode($payloadJSON_FORCE_OBJECT);
  262.     }
  263.     public function createCancelChargePayload($cancellationReason null)
  264.     {
  265.         $payload = [
  266.             'cancellationReason' => $cancellationReason
  267.         ];
  268.         return json_encode($payloadJSON_FORCE_OBJECT);
  269.     }
  270.     public function createCloseChargePermissionPayload($closureReason null$cancelPendingCharges null)
  271.     {
  272.         $payload = [
  273.             'closureReason' => $closureReason,
  274.             'cancelPendingCharges' => $cancelPendingCharges
  275.         ];
  276.         return json_encode($payloadJSON_FORCE_OBJECT);
  277.     }
  278.     public function createCreateRefundPayload($chargeId$refundAmount)
  279.     {
  280.         $payload = [
  281.             'chargeId' => $chargeId,
  282.             'refundAmount' => [
  283.                 'amount' => $refundAmount,
  284.                 'currencyCode' => $this->eccubeConfig['amazon_pay_v2']['api']['payload']['currency_code'],
  285.             ]
  286.         ];
  287.         return json_encode($payloadJSON_FORCE_OBJECT);
  288.     }
  289.     public function createCreateChargePayload($chargePermissionId$paymentTotal$CaptureNow false$canHandlePendingAuthorization false)
  290.     {
  291.         $payload = [
  292.             'chargePermissionId' => $chargePermissionId,
  293.             'chargeAmount' => [
  294.                 'amount' => $paymentTotal,
  295.                 'currencyCode' => $this->eccubeConfig['amazon_pay_v2']['api']['payload']['currency_code']
  296.             ],
  297.             'captureNow' => $CaptureNow,
  298.             'canHandlePendingAuthorization' => $canHandlePendingAuthorization
  299.         ];
  300.         return json_encode($payloadJSON_FORCE_OBJECT);
  301.     }
  302.     public function updateCheckoutSession($Order$amazonCheckoutSessionId)
  303.     {
  304.         $client = new AmazonPayClient($this->getAmazonPayConfig());
  305.         $result $client->updateCheckoutSession($amazonCheckoutSessionId$this->createUpdateCheckoutSessionPayload($Order));
  306.         return json_decode($result['response']);
  307.     }
  308.     public function signaturePayload($payload)
  309.     {
  310.         $client = new AmazonPayClient($this->getAmazonPayConfig());
  311.         $signature $client->generateButtonSignature($payload);
  312.         return $signature;
  313.     }
  314.     public function getCheckoutSession($amazonCheckoutSessionId)
  315.     {
  316.         $client = new AmazonPayClient($this->getAmazonPayConfig());
  317.         $result $client->getCheckoutSession($amazonCheckoutSessionId);
  318.         return json_decode($result['response']);
  319.     }
  320.     public function completeCheckoutSession($Order$amazonCheckoutSessionId)
  321.     {
  322.         $client = new AmazonPayClient($this->getAmazonPayConfig());
  323.         $result $client->completeCheckoutSession($amazonCheckoutSessionId$this->createCompleteCheckoutSessionPayload($Order)); 
  324.         $response json_decode($result['response']);
  325.         logs('amazon_pay_v2')->info('▼completeCheckoutSession http-status = ' $result['status'] . ', order_id = ' $Order->getId());
  326.         if ($result['status'] == 200 || $result['status'] == 202) {
  327.                 if ($response->statusDetails->state == 'Completed') {
  328.                     return $response;
  329.                 }
  330.         } elseif (isset($response->reasonCode)) {
  331.             logs('amazon_pay_v2')->info('▼completeCheckoutSession reasonCode = ' $response->reasonCode ', order_id = ' $Order->getId());
  332.             if ($response->reasonCode == 'CheckoutSessionCanceled') {
  333.                 // チェックアウトセッションを再取得し理由コードを取得する
  334.                 $checkoutSession $this->getCheckoutSession($amazonCheckoutSessionId);
  335.                 if ($checkoutSession && isset($checkoutSession->statusDetails->reasonCode)) {
  336.                     $errorCode AmazonPaymentException::getErrorCode($checkoutSession->statusDetails->reasonCode);
  337.                     logs('amazon_pay_v2')->info('▼completeCheckoutSession statusDetails = ' var_export($checkoutSession->statusDetailstrue));
  338.                     // 購入者が決済をキャンセルしたなら受注もキャンセルにする
  339.                     $this->cancelOrder($Order);
  340.                     logs('amazon_pay_v2')->info('▼completeCheckoutSession 受注をキャンセルしました' 'order_id = ' $Order->getId());
  341.                     
  342.                     if ($errorCode) {
  343.                         throw AmazonPaymentException::create($errorCode);
  344.                     }
  345.                 }
  346.             }
  347.         }
  348.         // 条件に合致しない場合は全てAmazonException()
  349.         throw new AmazonException();
  350.     }
  351.     /**
  352.      * 受注をキャンセルする
  353.      * 
  354.      * @param $Order キャンセル対象の受注
  355.      */
  356.     private function cancelOrder($Order)
  357.     {   
  358.         // 在庫・使用ポイント戻しはexecutePurchaseFlowで実施されるため
  359.         // 自前実装不要
  360.         $OrderStatus $this->orderStatusRepository->find($this->orderStatusRepository->find(OrderStatus::CANCEL));
  361.         $Order->setOrderStatus($OrderStatus);
  362.         $AmazonStatus $this->amazonStatusRepository->find(AmazonStatus::CANCEL);
  363.         $Order->setAmazonPayV2AmazonStatus($AmazonStatus);
  364.         $this->entityManager->flush();
  365.     }
  366.     public function captureCharge($chargeId$Order$billingAmount null)
  367.     {
  368.         $client = new AmazonPayClient($this->getAmazonPayConfig());
  369.         $headers = ['x-amz-pay-Idempotency-Key' => uniqid()];
  370.         $result $client->captureCharge($chargeId$this->createCaptureChargePayload($Order$billingAmount), $headers);
  371.         return json_decode($result['response']);
  372.     }
  373.     /**
  374.      * 売上をキャンセル
  375.      *
  376.      * @param string $chargeId Amazon注文参照ID
  377.      * @return array or string リクエスト結果
  378.      */
  379.     public function cancelCharge($chargeId$cancellationReason null)
  380.     {
  381.         $payload $this->createCancelChargePayload($cancellationReason);
  382.         $client = new AmazonPayClient($this->getAmazonPayConfig());
  383.         $result $client->cancelCharge($chargeId$payload);
  384.         return json_decode($result['response']);
  385.     }
  386.     /**
  387.      * 注文取消
  388.      */
  389.     public function closeChargePermission($chargePermissionId$closureReason null$cancelPendingCharges true)
  390.     {
  391.         $payload $this->createCloseChargePermissionPayload($closureReason$cancelPendingCharges);
  392.         $client = new AmazonPayClient($this->getAmazonPayConfig());
  393.         $result $client->closeChargePermission($chargePermissionId$payload);
  394.         return json_decode($result['response']);
  395.     }
  396.     /**
  397.      * 請求済み売り上げを返金
  398.      *
  399.      * @param string $amazonCaptureId Amazon取引ID
  400.      * @param string $chargeId 注文ID
  401.      * @param integer $refundAmoun 返金金額
  402.      * @return array or string リクエスト結果
  403.      */
  404.     public function createRefund($chargeId$refundAmount$softDescriptor null$idempotencyKey null)
  405.     {
  406.         $payload $this->createCreateRefundPayload($chargeId$refundAmount);
  407.         if (null != $softDescriptor) {
  408.             $payload array_merge($payload, ["softDescriptor" => $softDescriptor]);
  409.         }
  410.         if ($idempotencyKey == null) {
  411.             $idempotencyKey uniqid();
  412.         }
  413.         $headers = ['x-amz-pay-Idempotency-Key' => $idempotencyKey];
  414.         $client = new AmazonPayClient($this->getAmazonPayConfig());
  415.         $result $client->createRefund($payload$headers);
  416.         return json_decode($result['response']);
  417.     }
  418.     /**
  419.      * 購入を確定(オーソリのリクエスト)
  420.      *
  421.      * @param string $amazonOrderReferenceId Amazon注文参照ID
  422.      * @param integer $order_id 注文ID
  423.      * @param integer $payment_total 受注金額合計
  424.      * @return array or string リクエスト結果
  425.      */
  426.     public function createCharge($chargePermissionId$paymentTotal$CaptureNow false$softDescriptor null$canHandlePendingAuthorization false$merchantMetadataMerchantReferenceId null$idempotencyKey null)
  427.     {
  428.         $payload $this->createCreateChargePayload($chargePermissionId$paymentTotal$CaptureNow$canHandlePendingAuthorization);
  429.         if (null != $merchantMetadataMerchantReferenceId) {
  430.             $payload array_merge($payload, [
  431.                 "merchantMetadata" => [
  432.                     "merchantReferenceId"=> $merchantMetadataMerchantReferenceId
  433.                 ]
  434.             ]);
  435.         }
  436.         if (null != $softDescriptor) {
  437.             $payload array_merge($payload, ["softDescriptor" => $softDescriptor]);
  438.         }
  439.         if ($idempotencyKey == null) {
  440.             $idempotencyKey uniqid();
  441.         }
  442.         $headers = ['x-amz-pay-Idempotency-Key' => $idempotencyKey];
  443.         $client = new AmazonPayClient($this->getAmazonPayConfig());
  444.         $result $client->createCharge($payload$headers);
  445.         return json_decode($result['response']);
  446.     }
  447.     /**
  448.      * 請求情報取得
  449.      * @param string $chargeId Amazon請求情報参照ID
  450.      * @return array or string リクエスト結果
  451.      */
  452.     public function getCharge($chargeId)
  453.     {
  454.         $client = new AmazonPayClient($this->getAmazonPayConfig());
  455.         $result $client->getCharge($chargeId);
  456.         return json_decode($result['response']);
  457.     }
  458.     public function createSigninPayload($returnUrl)
  459.     {
  460.         $Config $this->configRepository->get();
  461.         $payload = [
  462.             'signInReturnUrl' => $returnUrl,
  463.             'storeId' => $Config->getClientId(),
  464.         ];
  465.         return json_encode($payloadJSON_FORCE_OBJECT);
  466.     }
  467.     /**
  468.      * 購入者情報取得
  469.      * @param string $buyerToken 購入者情報トークン
  470.      * @param array $headers ヘッダー
  471.      * @return array or string リクエスト結果
  472.      */
  473.     public function getBuyer($buyerToken$headers null)
  474.     {
  475.         $client = new AmazonPayClient($this->getAmazonPayConfig());
  476.         $result $client->getBuyer($buyerToken$headers);
  477.         if ($result['status'] != 200) {
  478.             throw new AmazonException();
  479.         }
  480.         return json_decode($result['response']);
  481.     }
  482.     /**
  483.      * 取得したbuyerIdに一致した会員でログインを行う
  484.      *
  485.      * @param Request $request
  486.      * @param string $buyerId
  487.      * @return bool
  488.      */
  489.     public function loginWithBuyerId(Request $request$buyerId)
  490.     {
  491.         // buyerIdで会員を検索する
  492.         $Customers $this->customerRepository->getNonWithdrawingCustomers(['v2_amazon_user_id' => $buyerId]);
  493.         if (empty($Customers[0]) || !$Customers[0] instanceof \Eccube\Entity\Customer) {
  494.             return false;
  495.         }
  496.         // ログイン処理
  497.         $token = new UsernamePasswordToken($Customers[0], 'customer', ['ROLE_USER']);
  498.         $this->tokenStorage->setToken($token);
  499.         $request->getSession()->migrate(true);
  500.         // 未ログインカートとログイン済みカートのマージ処理
  501.         $this->cartService->mergeFromPersistedCart();
  502.         foreach ($this->cartService->getCarts() as $Cart) {
  503.             $this->purchaseFlow->validate($Cart, new PurchaseContext($Cart$Customers[0]));
  504.         }
  505.         $this->cartService->save();
  506.         return true;
  507.     }
  508. }