PaymentFailuresService.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. declare(strict_types=1);
  7. namespace Magento\Sales\Model\Service;
  8. use Magento\Backend\App\Area\FrontNameResolver;
  9. use Magento\Framework\App\Config\ScopeConfigInterface;
  10. use Magento\Framework\App\ObjectManager;
  11. use Magento\Framework\Mail\Template\TransportBuilder;
  12. use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
  13. use Magento\Framework\Translate\Inline\StateInterface;
  14. use Magento\Quote\Api\CartRepositoryInterface;
  15. use Magento\Quote\Api\Data\CartInterface as Quote;
  16. use Magento\Sales\Api\PaymentFailuresInterface;
  17. use Magento\Store\Model\ScopeInterface;
  18. use Magento\Store\Model\Store;
  19. use Psr\Log\LoggerInterface;
  20. /**
  21. * Service is responsible for handling failed payment transactions.
  22. *
  23. * It depends on Stores > Configuration > Sales > Checkout > Payment Failed Emails configuration.
  24. *
  25. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  26. */
  27. class PaymentFailuresService implements PaymentFailuresInterface
  28. {
  29. /**
  30. * Store config
  31. *
  32. * @var ScopeConfigInterface
  33. */
  34. private $scopeConfig;
  35. /**
  36. * @var StateInterface
  37. */
  38. private $inlineTranslation;
  39. /**
  40. * @var TransportBuilder
  41. */
  42. private $transportBuilder;
  43. /**
  44. * @var TimezoneInterface
  45. */
  46. private $localeDate;
  47. /**
  48. * @var CartRepositoryInterface
  49. */
  50. private $cartRepository;
  51. /**
  52. * @var LoggerInterface
  53. */
  54. private $logger;
  55. /**
  56. * @param ScopeConfigInterface $scopeConfig
  57. * @param StateInterface $inlineTranslation
  58. * @param TransportBuilder $transportBuilder
  59. * @param TimezoneInterface $localeDate
  60. * @param CartRepositoryInterface $cartRepository
  61. * @param LoggerInterface|null $logger
  62. */
  63. public function __construct(
  64. ScopeConfigInterface $scopeConfig,
  65. StateInterface $inlineTranslation,
  66. TransportBuilder $transportBuilder,
  67. TimezoneInterface $localeDate,
  68. CartRepositoryInterface $cartRepository,
  69. LoggerInterface $logger = null
  70. ) {
  71. $this->scopeConfig = $scopeConfig;
  72. $this->inlineTranslation = $inlineTranslation;
  73. $this->transportBuilder = $transportBuilder;
  74. $this->localeDate = $localeDate;
  75. $this->cartRepository = $cartRepository;
  76. $this->logger = $logger ?: ObjectManager::getInstance()->get(LoggerInterface::class);
  77. }
  78. /**
  79. * Sends an email about failed transaction.
  80. *
  81. * @param int $cartId
  82. * @param string $message
  83. * @param string $checkoutType
  84. * @return PaymentFailuresInterface
  85. */
  86. public function handle(
  87. int $cartId,
  88. string $message,
  89. string $checkoutType = 'onepage'
  90. ): PaymentFailuresInterface {
  91. $this->inlineTranslation->suspend();
  92. $quote = $this->cartRepository->get($cartId);
  93. $template = $this->getConfigValue('checkout/payment_failed/template', $quote);
  94. $receiver = $this->getConfigValue('checkout/payment_failed/receiver', $quote);
  95. $sendTo = [
  96. [
  97. 'email' => $this->getConfigValue('trans_email/ident_' . $receiver . '/email', $quote),
  98. 'name' => $this->getConfigValue('trans_email/ident_' . $receiver . '/name', $quote),
  99. ],
  100. ];
  101. $copyMethod = $this->getConfigValue('checkout/payment_failed/copy_method', $quote);
  102. $copyTo = $this->getConfigEmails($quote);
  103. $bcc = [];
  104. if (!empty($copyTo)) {
  105. switch ($copyMethod) {
  106. case 'bcc':
  107. $bcc = $copyTo;
  108. break;
  109. case 'copy':
  110. foreach ($copyTo as $email) {
  111. $sendTo[] = ['email' => $email, 'name' => null];
  112. }
  113. break;
  114. }
  115. }
  116. foreach ($sendTo as $recipient) {
  117. $transport = $this->transportBuilder
  118. ->setTemplateIdentifier($template)
  119. ->setTemplateOptions([
  120. 'area' => FrontNameResolver::AREA_CODE,
  121. 'store' => Store::DEFAULT_STORE_ID,
  122. ])
  123. ->setTemplateVars($this->getTemplateVars($quote, $message, $checkoutType))
  124. ->setFrom($this->getSendFrom($quote))
  125. ->addTo($recipient['email'], $recipient['name'])
  126. ->addBcc($bcc)
  127. ->getTransport();
  128. try {
  129. $transport->sendMessage();
  130. } catch (\Exception $e) {
  131. $this->logger->critical($e->getMessage());
  132. }
  133. }
  134. $this->inlineTranslation->resume();
  135. return $this;
  136. }
  137. /**
  138. * Returns mail template variables.
  139. *
  140. * @param Quote $quote
  141. * @param string $message
  142. * @param string $checkoutType
  143. * @return array
  144. */
  145. private function getTemplateVars(Quote $quote, string $message, string $checkoutType): array
  146. {
  147. return [
  148. 'reason' => $message,
  149. 'checkoutType' => $checkoutType,
  150. 'dateAndTime' => $this->getLocaleDate(),
  151. 'customer' => $this->getCustomerName($quote),
  152. 'customerEmail' => $quote->getBillingAddress()->getEmail(),
  153. 'billingAddress' => $quote->getBillingAddress(),
  154. 'shippingAddress' => $quote->getShippingAddress(),
  155. 'shippingMethod' => $this->getConfigValue(
  156. 'carriers/' . $this->getShippingMethod($quote) . '/title',
  157. $quote
  158. ),
  159. 'paymentMethod' => $this->getConfigValue(
  160. 'payment/' . $this->getPaymentMethod($quote) . '/title',
  161. $quote
  162. ),
  163. 'items' => implode('<br />', $this->getQuoteItems($quote)),
  164. 'total' => $quote->getCurrency()->getStoreCurrencyCode() . ' ' . $quote->getGrandTotal(),
  165. ];
  166. }
  167. /**
  168. * Returns scope config value by config path.
  169. *
  170. * @param string $configPath
  171. * @param Quote $quote
  172. * @return mixed
  173. */
  174. private function getConfigValue(string $configPath, Quote $quote)
  175. {
  176. return $this->scopeConfig->getValue(
  177. $configPath,
  178. ScopeInterface::SCOPE_STORE,
  179. $quote->getStoreId()
  180. );
  181. }
  182. /**
  183. * Returns shipping method from quote.
  184. *
  185. * @param Quote $quote
  186. * @return string
  187. */
  188. private function getShippingMethod(Quote $quote): string
  189. {
  190. $shippingMethod = '';
  191. $shippingInfo = $quote->getShippingAddress()->getShippingMethod();
  192. if ($shippingInfo) {
  193. $data = explode('_', $shippingInfo);
  194. $shippingMethod = $data[0];
  195. }
  196. return $shippingMethod;
  197. }
  198. /**
  199. * Returns payment method title from quote.
  200. *
  201. * @param Quote $quote
  202. * @return string
  203. */
  204. private function getPaymentMethod(Quote $quote): string
  205. {
  206. $paymentMethod = $quote->getPayment()->getMethod() ?? '';
  207. return $paymentMethod;
  208. }
  209. /**
  210. * Returns quote visible items.
  211. *
  212. * @param Quote $quote
  213. * @return array
  214. */
  215. private function getQuoteItems(Quote $quote): array
  216. {
  217. $items = [];
  218. foreach ($quote->getAllVisibleItems() as $item) {
  219. $itemData = $item->getProduct()->getName() . ' x ' . $item->getQty() . ' ' .
  220. $quote->getCurrency()->getStoreCurrencyCode() . ' ' .
  221. $item->getProduct()->getFinalPrice($item->getQty());
  222. $items[] = $itemData;
  223. }
  224. return $items;
  225. }
  226. /**
  227. * Gets email values by configuration path.
  228. *
  229. * @param Quote $quote
  230. * @return array|false
  231. */
  232. private function getConfigEmails(Quote $quote)
  233. {
  234. $configData = $this->getConfigValue('checkout/payment_failed/copy_to', $quote);
  235. if (!empty($configData)) {
  236. return explode(',', $configData);
  237. }
  238. return false;
  239. }
  240. /**
  241. * Returns sender identity.
  242. *
  243. * @param Quote $quote
  244. * @return string
  245. */
  246. private function getSendFrom(Quote $quote): string
  247. {
  248. return $this->getConfigValue('checkout/payment_failed/identity', $quote);
  249. }
  250. /**
  251. * Returns current locale date and time
  252. *
  253. * @return string
  254. */
  255. private function getLocaleDate(): string
  256. {
  257. return $this->localeDate->formatDateTime(
  258. new \DateTime(),
  259. \IntlDateFormatter::MEDIUM,
  260. \IntlDateFormatter::MEDIUM
  261. );
  262. }
  263. /**
  264. * Returns customer name.
  265. *
  266. * @param Quote $quote
  267. * @return string
  268. */
  269. private function getCustomerName(Quote $quote): string
  270. {
  271. $customer = __('Guest')->render();
  272. if (!$quote->getCustomerIsGuest()) {
  273. $customer = $quote->getCustomer()->getFirstname() . ' ' .
  274. $quote->getCustomer()->getLastname();
  275. }
  276. return $customer;
  277. }
  278. }