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

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($payload, JSON_FORCE_OBJECT);
  202. }
  203. public function createUpdateCheckoutSessionPayload($Order)
  204. {
  205. $router = $this->container->get('router');
  206. // NOTE: AmazonPayAPI仕様上、0円の決済は許容しない.
  207. if($Order->getPaymentTotal() == 0) {
  208. throw AmazonPaymentException::create(AmazonPaymentException::ZERO_PAYMENT);
  209. }
  210. $config = $this->configRepository->get();
  211. if ($config->getSale() == $this->eccubeConfig['amazon_pay_v2']['sale']['authori']) {
  212. $paymentIntent = 'Authorize';
  213. } elseif ($config->getSale() == $this->eccubeConfig['amazon_pay_v2']['sale']['capture']) {
  214. $paymentIntent = 'AuthorizeWithCapture';
  215. }
  216. $payload = [
  217. 'webCheckoutDetails' => [
  218. 'checkoutResultReturnUrl' => $router->generate('amazon_pay_shopping_checkout_result', [], UrlGeneratorInterface::ABSOLUTE_URL),
  219. ],
  220. 'paymentDetails' => [
  221. 'paymentIntent' => $paymentIntent,
  222. 'canHandlePendingAuthorization' => false,
  223. 'chargeAmount' => [
  224. 'amount' => (int)$Order->getPaymentTotal(),
  225. 'currencyCode' => "JPY"
  226. ],
  227. //"softDescriptor" => "softDescriptor"
  228. ],
  229. 'merchantMetadata' => [
  230. 'merchantReferenceId' => $this->payoutSellerOrderId($Order->getId()),
  231. 'noteToBuyer' => ''
  232. // "customInformation" => "customInformation"
  233. ],
  234. "platformId" => "A1LODGGQOBGE66"
  235. ];
  236. // 店舗名が50文字を超えるとAmazon側でエラーとなるため考慮する
  237. if (mb_strlen($this->BaseInfo->getShopName()) < 51) {
  238. $payload['merchantMetadata']['merchantStoreName'] = $this->BaseInfo->getShopName();
  239. }
  240. return json_encode($payload, JSON_FORCE_OBJECT);
  241. }
  242. public function createCompleteCheckoutSessionPayload($Order)
  243. {
  244. $payload = [
  245. 'chargeAmount' => [
  246. 'amount' => (int)$Order->getPaymentTotal(),
  247. 'currencyCode' => 'JPY',
  248. ]
  249. ];
  250. return json_encode($payload, JSON_FORCE_OBJECT);
  251. }
  252. public function createCaptureChargePayload($Order, $billingAmount = null)
  253. {
  254. $payload = [
  255. 'captureAmount' => [
  256. 'amount' => is_null($billingAmount) ? (int)$Order->getPaymentTotal() : $billingAmount,
  257. 'currencyCode' => 'JPY',
  258. ]
  259. ];
  260. return json_encode($payload, JSON_FORCE_OBJECT);
  261. }
  262. public function createCancelChargePayload($cancellationReason = null)
  263. {
  264. $payload = [
  265. 'cancellationReason' => $cancellationReason
  266. ];
  267. return json_encode($payload, JSON_FORCE_OBJECT);
  268. }
  269. public function createCloseChargePermissionPayload($closureReason = null, $cancelPendingCharges = null)
  270. {
  271. $payload = [
  272. 'closureReason' => $closureReason,
  273. 'cancelPendingCharges' => $cancelPendingCharges
  274. ];
  275. return json_encode($payload, JSON_FORCE_OBJECT);
  276. }
  277. public function createCreateRefundPayload($chargeId, $refundAmount)
  278. {
  279. $payload = [
  280. 'chargeId' => $chargeId,
  281. 'refundAmount' => [
  282. 'amount' => $refundAmount,
  283. 'currencyCode' => $this->eccubeConfig['amazon_pay_v2']['api']['payload']['currency_code'],
  284. ]
  285. ];
  286. return json_encode($payload, JSON_FORCE_OBJECT);
  287. }
  288. public function createCreateChargePayload($chargePermissionId, $paymentTotal, $CaptureNow = false, $canHandlePendingAuthorization = false)
  289. {
  290. $payload = [
  291. 'chargePermissionId' => $chargePermissionId,
  292. 'chargeAmount' => [
  293. 'amount' => $paymentTotal,
  294. 'currencyCode' => $this->eccubeConfig['amazon_pay_v2']['api']['payload']['currency_code']
  295. ],
  296. 'captureNow' => $CaptureNow,
  297. 'canHandlePendingAuthorization' => $canHandlePendingAuthorization
  298. ];
  299. return json_encode($payload, JSON_FORCE_OBJECT);
  300. }
  301. public function updateCheckoutSession($Order, $amazonCheckoutSessionId)
  302. {
  303. $client = new AmazonPayClient($this->getAmazonPayConfig());
  304. $result = $client->updateCheckoutSession($amazonCheckoutSessionId, $this->createUpdateCheckoutSessionPayload($Order));
  305. return json_decode($result['response']);
  306. }
  307. public function signaturePayload($payload)
  308. {
  309. $client = new AmazonPayClient($this->getAmazonPayConfig());
  310. $signature = $client->generateButtonSignature($payload);
  311. return $signature;
  312. }
  313. public function getCheckoutSession($amazonCheckoutSessionId)
  314. {
  315. $client = new AmazonPayClient($this->getAmazonPayConfig());
  316. $result = $client->getCheckoutSession($amazonCheckoutSessionId);
  317. return json_decode($result['response']);
  318. }
  319. public function completeCheckoutSession($Order, $amazonCheckoutSessionId)
  320. {
  321. $client = new AmazonPayClient($this->getAmazonPayConfig());
  322. $result = $client->completeCheckoutSession($amazonCheckoutSessionId, $this->createCompleteCheckoutSessionPayload($Order));
  323. $response = json_decode($result['response']);
  324. logs('amazon_pay_v2')->info('▼completeCheckoutSession http-status = ' . $result['status'] . ', order_id = ' . $Order->getId());
  325. if ($result['status'] == 200 || $result['status'] == 202) {
  326. if ($response->statusDetails->state == 'Completed') {
  327. return $response;
  328. }
  329. } elseif (isset($response->reasonCode)) {
  330. logs('amazon_pay_v2')->info('▼completeCheckoutSession reasonCode = ' . $response->reasonCode . ', order_id = ' . $Order->getId());
  331. if ($response->reasonCode == 'CheckoutSessionCanceled') {
  332. // チェックアウトセッションを再取得し理由コードを取得する
  333. $checkoutSession = $this->getCheckoutSession($amazonCheckoutSessionId);
  334. if ($checkoutSession && isset($checkoutSession->statusDetails->reasonCode)) {
  335. $errorCode = AmazonPaymentException::getErrorCode($checkoutSession->statusDetails->reasonCode);
  336. logs('amazon_pay_v2')->info('▼completeCheckoutSession statusDetails = ' . var_export($checkoutSession->statusDetails, true));
  337. // 購入者が決済をキャンセルしたなら受注もキャンセルにする
  338. $this->cancelOrder($Order);
  339. logs('amazon_pay_v2')->info('▼completeCheckoutSession 受注をキャンセルしました' . 'order_id = ' . $Order->getId());
  340. if ($errorCode) {
  341. throw AmazonPaymentException::create($errorCode);
  342. }
  343. }
  344. }
  345. }
  346. // 条件に合致しない場合は全てAmazonException()
  347. throw new AmazonException();
  348. }
  349. /**
  350. * 受注をキャンセルする
  351. *
  352. * @param $Order キャンセル対象の受注
  353. */
  354. private function cancelOrder($Order)
  355. {
  356. // 在庫・使用ポイント戻しはexecutePurchaseFlowで実施されるため
  357. // 自前実装不要
  358. $OrderStatus = $this->orderStatusRepository->find($this->orderStatusRepository->find(OrderStatus::CANCEL));
  359. $Order->setOrderStatus($OrderStatus);
  360. $AmazonStatus = $this->amazonStatusRepository->find(AmazonStatus::CANCEL);
  361. $Order->setAmazonPayV2AmazonStatus($AmazonStatus);
  362. $this->entityManager->flush();
  363. }
  364. public function captureCharge($chargeId, $Order, $billingAmount = null)
  365. {
  366. $client = new AmazonPayClient($this->getAmazonPayConfig());
  367. $headers = ['x-amz-pay-Idempotency-Key' => uniqid()];
  368. $result = $client->captureCharge($chargeId, $this->createCaptureChargePayload($Order, $billingAmount), $headers);
  369. return json_decode($result['response']);
  370. }
  371. /**
  372. * 売上をキャンセル
  373. *
  374. * @param string $chargeId Amazon注文参照ID
  375. * @return array or string リクエスト結果
  376. */
  377. public function cancelCharge($chargeId, $cancellationReason = null)
  378. {
  379. $payload = $this->createCancelChargePayload($cancellationReason);
  380. $client = new AmazonPayClient($this->getAmazonPayConfig());
  381. $result = $client->cancelCharge($chargeId, $payload);
  382. return json_decode($result['response']);
  383. }
  384. /**
  385. * 注文取消
  386. */
  387. public function closeChargePermission($chargePermissionId, $closureReason = null, $cancelPendingCharges = true)
  388. {
  389. $payload = $this->createCloseChargePermissionPayload($closureReason, $cancelPendingCharges);
  390. $client = new AmazonPayClient($this->getAmazonPayConfig());
  391. $result = $client->closeChargePermission($chargePermissionId, $payload);
  392. return json_decode($result['response']);
  393. }
  394. /**
  395. * 請求済み売り上げを返金
  396. *
  397. * @param string $amazonCaptureId Amazon取引ID
  398. * @param string $chargeId 注文ID
  399. * @param integer $refundAmoun 返金金額
  400. * @return array or string リクエスト結果
  401. */
  402. public function createRefund($chargeId, $refundAmount, $softDescriptor = null, $idempotencyKey = null)
  403. {
  404. $payload = $this->createCreateRefundPayload($chargeId, $refundAmount);
  405. if (null != $softDescriptor) {
  406. $payload = array_merge($payload, ["softDescriptor" => $softDescriptor]);
  407. }
  408. if ($idempotencyKey == null) {
  409. $idempotencyKey = uniqid();
  410. }
  411. $headers = ['x-amz-pay-Idempotency-Key' => $idempotencyKey];
  412. $client = new AmazonPayClient($this->getAmazonPayConfig());
  413. $result = $client->createRefund($payload, $headers);
  414. return json_decode($result['response']);
  415. }
  416. /**
  417. * 購入を確定(オーソリのリクエスト)
  418. *
  419. * @param string $amazonOrderReferenceId Amazon注文参照ID
  420. * @param integer $order_id 注文ID
  421. * @param integer $payment_total 受注金額合計
  422. * @return array or string リクエスト結果
  423. */
  424. public function createCharge($chargePermissionId, $paymentTotal, $CaptureNow = false, $softDescriptor = null, $canHandlePendingAuthorization = false, $merchantMetadataMerchantReferenceId = null, $idempotencyKey = null)
  425. {
  426. $payload = $this->createCreateChargePayload($chargePermissionId, $paymentTotal, $CaptureNow, $canHandlePendingAuthorization);
  427. if (null != $merchantMetadataMerchantReferenceId) {
  428. $payload = array_merge($payload, [
  429. "merchantMetadata" => [
  430. "merchantReferenceId"=> $merchantMetadataMerchantReferenceId
  431. ]
  432. ]);
  433. }
  434. if (null != $softDescriptor) {
  435. $payload = array_merge($payload, ["softDescriptor" => $softDescriptor]);
  436. }
  437. if ($idempotencyKey == null) {
  438. $idempotencyKey = uniqid();
  439. }
  440. $headers = ['x-amz-pay-Idempotency-Key' => $idempotencyKey];
  441. $client = new AmazonPayClient($this->getAmazonPayConfig());
  442. $result = $client->createCharge($payload, $headers);
  443. return json_decode($result['response']);
  444. }
  445. /**
  446. * 請求情報取得
  447. * @param string $chargeId Amazon請求情報参照ID
  448. * @return array or string リクエスト結果
  449. */
  450. public function getCharge($chargeId)
  451. {
  452. $client = new AmazonPayClient($this->getAmazonPayConfig());
  453. $result = $client->getCharge($chargeId);
  454. return json_decode($result['response']);
  455. }
  456. public function createSigninPayload($returnUrl)
  457. {
  458. $Config = $this->configRepository->get();
  459. $payload = [
  460. 'signInReturnUrl' => $returnUrl,
  461. 'storeId' => $Config->getClientId(),
  462. ];
  463. return json_encode($payload, JSON_FORCE_OBJECT);
  464. }
  465. /**
  466. * 購入者情報取得
  467. * @param string $buyerToken 購入者情報トークン
  468. * @param array $headers ヘッダー
  469. * @return array or string リクエスト結果
  470. */
  471. public function getBuyer($buyerToken, $headers = null)
  472. {
  473. $client = new AmazonPayClient($this->getAmazonPayConfig());
  474. $result = $client->getBuyer($buyerToken, $headers);
  475. if ($result['status'] != 200) {
  476. throw new AmazonException();
  477. }
  478. return json_decode($result['response']);
  479. }
  480. /**
  481. * 取得したbuyerIdに一致した会員でログインを行う
  482. *
  483. * @param Request $request
  484. * @param string $buyerId
  485. * @return bool
  486. */
  487. public function loginWithBuyerId(Request $request, $buyerId)
  488. {
  489. // buyerIdで会員を検索する
  490. $Customers = $this->customerRepository->getNonWithdrawingCustomers(['v2_amazon_user_id' => $buyerId]);
  491. if (empty($Customers[0]) || !$Customers[0] instanceof \Eccube\Entity\Customer) {
  492. return false;
  493. }
  494. // ログイン処理
  495. $token = new UsernamePasswordToken($Customers[0], 'customer', ['ROLE_USER']);
  496. $this->tokenStorage->setToken($token);
  497. $request->getSession()->migrate(true);
  498. // 未ログインカートとログイン済みカートのマージ処理
  499. $this->cartService->mergeFromPersistedCart();
  500. foreach ($this->cartService->getCarts() as $Cart) {
  501. $this->purchaseFlow->validate($Cart, new PurchaseContext($Cart, $Customers[0]));
  502. }
  503. $this->cartService->save();
  504. return true;
  505. }
  506. }