| Current Path : /var/www/magento.test.indacotrentino.com/www/vendor/stripe/module-payments/Helper/ |
| Current File : /var/www/magento.test.indacotrentino.com/www/vendor/stripe/module-payments/Helper/Generic.php |
<?php
namespace StripeIntegration\Payments\Helper;
use Magento\Framework\Validator\Exception;
use StripeIntegration\Payments\Model\Config;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Store\Model\ScopeInterface;
use Magento\Authorization\Model\UserContextInterface;
use Magento\Framework\Exception\LocalizedException;
use StripeIntegration\Payments\Helper\Data as DataHelper;
class Generic
{
public $magentoCustomerId = null;
public $urlBuilder = null;
protected $cards = [];
public $orderComments = [];
public $currentCustomer = null;
public $productRepository = null;
public $bundleProductOptions = [];
public $quoteHasSubscriptions = [];
private $scopeConfig;
private $backendSessionQuote;
private $request;
private $appState;
private $storeManager;
private $order;
private $invoice;
private $invoiceService;
private $creditmemo;
private $customerSession;
private $checkoutSession;
private $resource;
private $coreRegistry;
private $adminOrderAddressForm;
private $customerRegistry;
private $messageManager;
private $productFactory;
private $quoteFactory;
private $orderFactory;
private $cache;
private $userContext;
private $orderSender;
private $priceCurrency;
private $customerRepositoryInterface;
private $orderCommentSender;
private $creditmemoFactory;
private $creditmemoService;
private $paymentExtensionFactory;
private $customerCollection;
private $apiFactory;
private $addressHelper;
private $convert;
private $quoteRepository;
private $orderCollectionFactory;
private $couponFactory;
private $stripeCouponFactory;
private $checkoutHelper;
private $ruleRepository;
private $invoiceSender;
private $quoteHelper;
private $taxConfig;
private $transactionSearchResultFactory;
private $orderRepository;
private $invoiceRepository;
private $transactionRepository;
private $creditmemoRepository;
private $shipmentRepository;
private $orderPaymentRepository;
private $sessionManager;
private $multishippingQuoteFactory;
private $productMetadata;
private $products;
private $quotes;
private $orders;
private $_order;
private $_hasTrialSubscriptions;
private $hasOnlyTrialSubscriptions;
private $restController;
private $subscriptionHelper = null;
public function __construct(
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Magento\Backend\Model\Session\Quote $backendSessionQuote,
\Magento\Framework\App\Request\Http $request,
\Magento\Framework\App\State $appState,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Sales\Model\Order $order,
\Magento\Sales\Model\Order\Invoice $invoice,
\Magento\Sales\Model\Service\InvoiceService $invoiceService,
\Magento\Sales\Model\Order\Creditmemo $creditmemo,
\Magento\Customer\Model\Session $customerSession,
\Magento\Checkout\Model\Session $checkoutSession,
\Magento\Framework\App\ResourceConnection $resource,
\Magento\Framework\Registry $coreRegistry,
\Magento\Sales\Block\Adminhtml\Order\Create\Form\Address $adminOrderAddressForm,
\Magento\Customer\Model\CustomerRegistry $customerRegistry,
\Magento\Framework\Message\ManagerInterface $messageManager,
\Magento\Catalog\Model\ProductFactory $productFactory,
\Magento\Quote\Model\QuoteFactory $quoteFactory,
\Magento\Sales\Api\Data\OrderInterfaceFactory $orderFactory,
\Magento\Framework\UrlInterface $urlBuilder,
\Magento\Framework\App\CacheInterface $cache,
\Magento\Authorization\Model\UserContextInterface $userContext,
\Magento\Sales\Model\Order\Email\Sender\OrderSender $orderSender,
\Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency,
\Magento\Customer\Api\CustomerRepositoryInterface $customerRepositoryInterface,
\Magento\Sales\Model\Order\Email\Sender\OrderCommentSender $orderCommentSender,
\Magento\Sales\Model\Order\CreditmemoFactory $creditmemoFactory,
\Magento\Sales\Model\Service\CreditmemoService $creditmemoService,
\Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory,
\StripeIntegration\Payments\Model\ResourceModel\StripeCustomer\Collection $customerCollection,
\Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
\StripeIntegration\Payments\Helper\ApiFactory $apiFactory,
\StripeIntegration\Payments\Helper\Address $addressHelper,
\StripeIntegration\Payments\Helper\Convert $convert,
\Magento\Quote\Api\CartRepositoryInterface $quoteRepository,
\Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory,
\Magento\SalesRule\Model\CouponFactory $couponFactory,
\StripeIntegration\Payments\Model\CouponFactory $stripeCouponFactory,
\Magento\Checkout\Helper\Data $checkoutHelper,
\Magento\SalesRule\Api\RuleRepositoryInterface $ruleRepository,
\Magento\Sales\Api\Data\TransactionSearchResultInterfaceFactory $transactionSearchResultFactory,
\Magento\Sales\Model\Order\Email\Sender\InvoiceSender $invoiceSender,
\StripeIntegration\Payments\Helper\Quote $quoteHelper,
\Magento\Tax\Model\Config $taxConfig,
\Magento\Sales\Api\OrderRepositoryInterface $orderRepository,
\Magento\Sales\Api\InvoiceRepositoryInterface $invoiceRepository,
\Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository,
\Magento\Sales\Api\CreditmemoRepositoryInterface $creditmemoRepository,
\Magento\Sales\Api\ShipmentRepositoryInterface $shipmentRepository,
\Magento\Sales\Api\OrderPaymentRepositoryInterface $orderPaymentRepository,
\Magento\Framework\Session\SessionManagerInterface $sessionManager,
\StripeIntegration\Payments\Model\Multishipping\QuoteFactory $multishippingQuoteFactory,
\Magento\Framework\App\ProductMetadataInterface $productMetadata,
\StripeIntegration\Payments\Plugin\Webapi\Controller\Rest $restController
) {
$this->scopeConfig = $scopeConfig;
$this->backendSessionQuote = $backendSessionQuote;
$this->request = $request;
$this->appState = $appState;
$this->storeManager = $storeManager;
$this->order = $order;
$this->invoice = $invoice;
$this->invoiceService = $invoiceService;
$this->creditmemo = $creditmemo;
$this->customerSession = $customerSession;
$this->checkoutSession = $checkoutSession;
$this->resource = $resource;
$this->coreRegistry = $coreRegistry;
$this->adminOrderAddressForm = $adminOrderAddressForm;
$this->customerRegistry = $customerRegistry;
$this->messageManager = $messageManager;
$this->productFactory = $productFactory;
$this->quoteFactory = $quoteFactory;
$this->orderFactory = $orderFactory;
$this->urlBuilder = $urlBuilder;
$this->cache = $cache;
$this->userContext = $userContext;
$this->orderSender = $orderSender;
$this->priceCurrency = $priceCurrency;
$this->customerRepositoryInterface = $customerRepositoryInterface;
$this->orderCommentSender = $orderCommentSender;
$this->creditmemoFactory = $creditmemoFactory;
$this->creditmemoService = $creditmemoService;
$this->paymentExtensionFactory = $paymentExtensionFactory;
$this->customerCollection = $customerCollection;
$this->productRepository = $productRepository;
$this->apiFactory = $apiFactory;
$this->addressHelper = $addressHelper;
$this->convert = $convert;
$this->quoteRepository = $quoteRepository;
$this->orderCollectionFactory = $orderCollectionFactory;
$this->couponFactory = $couponFactory;
$this->stripeCouponFactory = $stripeCouponFactory;
$this->checkoutHelper = $checkoutHelper;
$this->ruleRepository = $ruleRepository;
$this->invoiceSender = $invoiceSender;
$this->quoteHelper = $quoteHelper;
$this->taxConfig = $taxConfig;
$this->transactionSearchResultFactory = $transactionSearchResultFactory;
$this->orderRepository = $orderRepository;
$this->invoiceRepository = $invoiceRepository;
$this->transactionRepository = $transactionRepository;
$this->creditmemoRepository = $creditmemoRepository;
$this->shipmentRepository = $shipmentRepository;
$this->orderPaymentRepository = $orderPaymentRepository;
$this->sessionManager = $sessionManager;
$this->multishippingQuoteFactory = $multishippingQuoteFactory;
$this->productMetadata = $productMetadata;
$this->restController = $restController;
}
protected function getBackendSessionQuote()
{
return $this->backendSessionQuote->getQuote();
}
public function isSecure()
{
return $this->request->isSecure();
}
public function getSessionQuote()
{
return $this->checkoutSession->getQuote();
}
public function getCheckoutSession()
{
return $this->checkoutSession;
}
public function getQuote($quoteId = null)
{
// Admin area new order page
if ($this->isAdmin())
return $this->getBackendSessionQuote();
// Front end checkout
$quote = $this->getSessionQuote();
// API Request
if (empty($quote) || !is_numeric($quote->getGrandTotal()))
{
try
{
if ($quoteId)
$quote = $this->quoteRepository->get($quoteId);
else if ($this->quoteHelper->quoteId) {
$quote = $this->quoteRepository->get($this->quoteHelper->quoteId);
}
}
catch (\Exception $e)
{
}
}
return $quote;
}
public function getStoreId()
{
if ($this->isAdmin())
{
if ($this->request->getParam('order_id', null))
{
// Viewing an order
$order = $this->orderRepository->get($this->request->getParam('order_id', null));
return $order->getStoreId();
}
if ($this->request->getParam('invoice_id', null))
{
// Viewing an invoice
$invoice = $this->invoiceRepository->get($this->request->getParam('invoice_id', null));
return $invoice->getStoreId();
}
else if ($this->request->getParam('creditmemo_id', null))
{
// Viewing a credit memo
$creditmemo = $this->creditmemoRepository->get($this->request->getParam('creditmemo_id', null));
return $creditmemo->getStoreId();
}
else if ($this->request->getParam('shipment_id', null))
{
// Viewing a shipment
$shipment = $this->shipmentRepository->get($this->request->getParam('shipment_id', null));
return $shipment->getStoreId();
}
else
{
// Creating a new order
$quote = $this->getBackendSessionQuote();
return $quote->getStoreId();
}
}
else
{
return $this->storeManager->getStore()->getId();
}
}
public function getCurrentStore()
{
return $this->storeManager->getStore();
}
public function loadProductBySku($sku)
{
try
{
return $this->productRepository->get($sku);
}
catch (\Exception $e)
{
return null;
}
}
public function loadProductById($productId)
{
if (!isset($this->products))
$this->products = [];
if (!is_numeric($productId))
return null;
if (!empty($this->products[$productId]))
return $this->products[$productId];
$this->products[$productId] = $this->productFactory->create()->load($productId);
return $this->products[$productId];
}
public function loadQuoteById($quoteId)
{
if (!isset($this->quotes))
$this->quotes = [];
if (!is_numeric($quoteId))
return null;
if (!empty($this->quotes[$quoteId]))
return $this->quotes[$quoteId];
$this->quotes[$quoteId] = $this->quoteFactory->create()->load($quoteId);
return $this->quotes[$quoteId];
}
public function loadOrderByIncrementId($incrementId, $useCache = true)
{
if (!isset($this->orders))
$this->orders = [];
if (empty($incrementId))
return null;
if (!empty($this->orders[$incrementId]) && $useCache)
return $this->orders[$incrementId];
try
{
$orderModel = $this->orderFactory->create();
$order = $orderModel->loadByIncrementId($incrementId);
if ($order && $order->getId())
return $this->orders[$incrementId] = $order;
}
catch (\Exception $e)
{
return null;
}
}
public function loadOrderById($orderId)
{
return $this->orderFactory->create()->load($orderId);
}
public function loadCustomerById($customerId)
{
return $this->customerRepositoryInterface->getById($customerId);
}
public function isAdmin()
{
$areaCode = $this->appState->getAreaCode();
return $areaCode == \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE;
}
public function isGraphQLRequest()
{
$areaCode = $this->appState->getAreaCode();
return ($areaCode == "graphql");
}
public function isAPIRequest()
{
$areaCode = $this->appState->getAreaCode();
switch ($areaCode)
{
case 'webapi_rest': // \Magento\Framework\App\Area::AREA_WEBAPI_REST:
case 'webapi_soap': // \Magento\Framework\App\Area::AREA_WEBAPI_SOAP:
case 'graphql': // \Magento\Framework\App\Area::AREA_GRAPHQL: - Magento 2.1 doesn't have the constant
return true;
default:
return false;
}
}
public function isCustomerLoggedIn()
{
return $this->customerSession->isLoggedIn();
}
public function getCustomerId()
{
// If we are in the back office
if ($this->isAdmin())
{
// About to refund/invoice an order
if ($order = $this->coreRegistry->registry('current_order'))
return $order->getCustomerId();
// About to capture an invoice
if ($invoice = $this->coreRegistry->registry('current_invoice'))
return $invoice->getCustomerId();
// Creating a new order from admin
if ($this->adminOrderAddressForm && $this->adminOrderAddressForm->getCustomerId())
return $this->adminOrderAddressForm->getCustomerId();
}
// If we are on the REST API
else if ($this->userContext->getUserType() == UserContextInterface::USER_TYPE_CUSTOMER)
{
return $this->userContext->getUserId();
}
// If we are on the checkout page
else if ($this->customerSession->isLoggedIn())
{
return $this->customerSession->getCustomerId();
}
// A webhook has instantiated this object
else if (!empty($this->magentoCustomerId))
{
return $this->magentoCustomerId;
}
return null;
}
public function getMagentoCustomer()
{
if ($this->customerSession->getCustomer()->getEntityId())
return $this->customerSession->getCustomer();
$customerId = $this->getCustomerId();
if (!$customerId) return;
$customer = $this->customerRegistry->retrieve($customerId);
if ($customer->getEntityId())
return $customer;
return null;
}
public function isGuest()
{
return !$this->customerSession->isLoggedIn();
}
// Should return the email address of guest customers
public function getCustomerEmail()
{
$customer = $this->getMagentoCustomer();
if (!$customer)
$customer = $this->getGuestCustomer();
if (!$customer)
return null;
if (!$customer->getEmail())
return null;
return trim(strtolower($customer->getEmail()));
}
public function getGuestCustomer($order = null)
{
if ($order)
{
return $this->getAddressFrom($order, 'billing');
}
else if (isset($this->_order))
{
return $this->getAddressFrom($this->_order, 'billing');
}
else
return null;
}
public function getMultiCurrencyAmount($payment, $baseAmount)
{
$order = $payment->getOrder();
$grandTotal = $order->getGrandTotal();
$baseGrandTotal = $order->getBaseGrandTotal();
$rate = $order->getBaseToOrderRate();
if ($rate == 0) $rate = 1;
// Full capture, ignore currency rate in case it changed
if ($baseAmount == $baseGrandTotal)
return $grandTotal;
// Partial capture, consider currency rate but don't capture more than the original amount
else if (is_numeric($rate))
return min($baseAmount * $rate, $grandTotal);
// Not a multicurrency capture
else
return $baseAmount;
}
public function getAddressFrom($order, $addressType = 'shipping')
{
if (!$order) return null;
$addresses = $order->getAddresses();
if (!empty($addresses))
{
foreach ($addresses as $address)
{
if ($address["address_type"] == $addressType)
return $address;
}
}
else if ($addressType == "shipping" && $order->getShippingAddress() && $order->getShippingAddress()->getStreet(1))
{
return $order->getShippingAddress();
}
else if ($addressType == "billing" && $order->getBillingAddress() && $order->getBillingAddress()->getStreet(1))
{
return $order->getBillingAddress();
}
return null;
}
// Do not use Config::isSubscriptionsEnabled(), a circular dependency injection will appear
public function isSubscriptionsEnabled()
{
$storeId = $this->getStoreId();
$data = $this->scopeConfig->getValue("payment/stripe_payments_subscriptions/active", ScopeInterface::SCOPE_STORE, $storeId);
return (bool)$data;
}
/**
* Description
* @param object $item
* @return \Magento\Catalog\Model\Product|null
*/
public function getSubscriptionProductFromOrderItem($item)
{
if (!in_array($item->getProductType(), ["simple", "virtual"]))
return null;
$product = $this->loadProductById($item->getProductId());
if ($product && $this->getSubscriptionsHelper()->isSubscriptionOptionEnabled($product->getId()))
{
return $product;
}
return null;
}
public function getSubscriptionProductFromQuoteItem($quoteItem)
{
if (!in_array($quoteItem->getProductType(), ["simple", "virtual"]))
return null;
$productId = $quoteItem->getProductId();
if (empty($productId))
return null;
$product = $this->loadProductById($productId);
if (!$product || !$this->getSubscriptionsHelper()->isSubscriptionOptionEnabled($product->getId()))
return null;
return $product;
}
/**
* Description
* @param array<\Magento\Sales\Model\Order\Item> $items
* @return bool
*/
public function hasSubscriptionsIn($items, $returnSubscriptions = false)
{
if (!$this->isSubscriptionsEnabled())
return false;
if (empty($items))
return false;
foreach ($items as $item)
{
$product = $this->getSubscriptionProductFromOrderItem($item);
if ($product)
return true;
}
return false;
}
public function hasTrialSubscriptions($quote = null)
{
if (isset($this->_hasTrialSubscriptions) && $this->_hasTrialSubscriptions)
return true;
if (!$quote)
$quote = $this->getQuote();
$items = $quote->getAllItems();
return $this->_hasTrialSubscriptions = $this->hasTrialSubscriptionsIn($items);
}
/**
* Description
* @param array<\Magento\Sales\Model\Order\Item> $items
* @return bool
*/
public function hasTrialSubscriptionsIn($items)
{
if (!$this->isSubscriptionsEnabled())
return false;
foreach ($items as $item)
{
$product = $this->getSubscriptionProductFromOrderItem($item);
if (!$product)
continue;
$subscriptionOptionDetails = $this->getSubscriptionsHelper()->getSubscriptionOptionDetails($product->getId());
if (!$subscriptionOptionDetails)
continue;
$trial = $subscriptionOptionDetails->getSubTrial();
if (is_numeric($trial) && $trial > 0)
return true;
else
continue;
}
return false;
}
public function hasOnlyTrialSubscriptionsIn($items)
{
if (!$this->isSubscriptionsEnabled())
return false;
$foundAtLeastOneTrialSubscriptionProduct = false;
foreach ($items as $item)
{
if (!in_array($item->getProductType(), ["simple", "virtual", "downloadable", "giftcard"]))
continue;
$product = $this->loadProductById($item->getProductId());
if ($product)
{
$subscriptionOptionDetails = $this->getSubscriptionsHelper()->getSubscriptionOptionDetails($product->getId());
if (!$subscriptionOptionDetails)
continue;
$trial = $subscriptionOptionDetails->getSubTrial();
if (is_numeric($trial) && $trial > 0)
{
$foundAtLeastOneTrialSubscriptionProduct = true;
}
else
{
return false;
}
}
}
return $foundAtLeastOneTrialSubscriptionProduct;
}
public function hasSubscriptions($quote = null)
{
if (empty($quote))
$quote = $this->getQuote();
if (empty($quote))
return false;
$quoteId = $quote->getId();
if (isset($this->quoteHasSubscriptions[$quoteId]))
return $this->quoteHasSubscriptions[$quoteId];
$items = $quote->getAllItems();
return $this->quoteHasSubscriptions[$quoteId] = $this->hasSubscriptionsIn($items);
}
public function hasOnlyTrialSubscriptions($quote = null)
{
if (!$quote)
$quote = $this->getQuote();
if ($quote && $quote->getId() && isset($this->hasOnlyTrialSubscriptions[$quote->getId()]))
return $this->hasOnlyTrialSubscriptions[$quote->getId()];
$items = $quote->getAllItems();
return $this->hasOnlyTrialSubscriptions[$quote->getId()] = $this->hasOnlyTrialSubscriptionsIn($items);
}
public function isAuthorizationExpired($charge)
{
if (!$charge->refunded)
return false;
if (empty($charge->refunds->data[0]->reason))
return false;
if ($charge->refunds->data[0]->reason == "expired_uncaptured_charge")
return true;
return false;
}
public function addWarning($msg)
{
if (is_string($msg))
$msg = __($msg);
$this->messageManager->addWarningMessage($msg);
}
public function addError($msg)
{
if (is_string($msg))
$msg = __($msg);
$this->messageManager->addErrorMessage( $msg );
}
public function addSuccess($msg)
{
if (is_string($msg))
$msg = __($msg);
$this->messageManager->addSuccessMessage( $msg );
}
public function logError($msg, $trace = null)
{
if (!$this->isAuthenticationRequiredMessage($msg))
{
$entry = Config::module() . ": " . $msg;
if ($trace)
$entry .= "\n$trace";
\StripeIntegration\Payments\Helper\Logger::log($entry);
}
}
public function logInfo($msg)
{
$entry = Config::module() . ": " . $msg;
\StripeIntegration\Payments\Helper\Logger::logInfo($entry);
}
public function isStripeAPIKeyError($msg)
{
$pos1 = stripos($msg, "Invalid API key provided");
$pos2 = stripos($msg, "No API key provided");
if ($pos1 !== false || $pos2 !== false)
return true;
return false;
}
public function cleanError($msg)
{
if ($this->isStripeAPIKeyError($msg))
return "Invalid Stripe API key provided.";
return $msg;
}
public function isMultiShipping($quote = null)
{
if (empty($quote))
$quote = $this->getQuote();
if (empty($quote))
return false;
return $quote->getIsMultiShipping();
}
public function shouldLogExceptionTrace($e)
{
if (empty($e))
return false;
$msg = $e->getMessage();
if ($this->isAuthenticationRequiredMessage($msg))
return false;
if (get_class($e) == \Stripe\Exception\CardException::class) // i.e. card declined, insufficient funds etc
return false;
if (get_class($e) == \Magento\Framework\Exception\CouldNotSaveException::class)
{
switch ($msg)
{
case "Your card was declined.":
return false;
default:
break;
}
}
return true;
}
public function dieWithError($msg, $e = null)
{
$this->logError($msg);
if ($this->shouldLogExceptionTrace($e))
{
if ($e->getMessage() != $msg)
$this->logError($e->getMessage());
$this->logError($e->getTraceAsString());
}
if ($this->isAdmin())
{
throw new CouldNotSaveException(__($msg));
}
else if ($this->isAPIRequest())
{
if ($this->isGraphQLRequest())
{
throw new \Magento\Framework\GraphQl\Exception\GraphQlInputException(__($msg));
}
else
{
$this->restController->setDisplay(true);
throw new CouldNotSaveException(__($this->cleanError($msg)), $e);
}
}
else if ($this->isMultiShipping())
{
throw new \Magento\Framework\Exception\LocalizedException(__($msg), $e);
}
else
{
$error = $this->cleanError($msg);
throw new \Exception($error);
}
}
public function isValidToken($token)
{
if (!is_string($token))
return false;
if (!strlen($token))
return false;
if (strpos($token, "_") === FALSE)
return false;
return true;
}
public function captureOrder($order)
{
foreach($order->getInvoiceCollection() as $invoice)
{
$invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_ONLINE);
$invoice->capture();
$this->invoiceRepository->save($invoice);
}
}
public function getInvoiceAmounts($invoice, $details)
{
$currency = strtolower($details['currency']);
$amount = $this->convert->stripeAmountToMagentoAmount($details['amount'], $currency);
$baseAmount = round(floatval($amount / $invoice->getBaseToOrderRate()), 2);
if (!empty($details["shipping"]))
{
$shipping = $this->convert->stripeAmountToMagentoAmount($details['shipping'], $currency);
$baseShipping = round(floatval($shipping / $invoice->getBaseToOrderRate()), 2);
}
else
{
$shipping = 0;
$baseShipping = 0;
}
if (!empty($details["tax"]))
{
$tax = $this->convert->stripeAmountToMagentoAmount($details['tax'], $currency);
$baseTax = round(floatval($tax / $invoice->getBaseToOrderRate()), 2);
}
else
{
$tax = 0;
$baseTax = 0;
}
return [
"amount" => $amount,
"base_amount" => $baseAmount,
"shipping" => $shipping,
"base_shipping" => $baseShipping,
"tax" => $tax,
"base_tax" => $baseTax
];
}
// Used for partial invoicing triggered from a partial Stripe dashboard capture
public function adjustInvoiceAmounts(&$invoice, $details)
{
if (!is_array($details))
return;
$amounts = $this->getInvoiceAmounts($invoice, $details);
$amount = $amounts['amount'];
$baseAmount = $amounts['base_amount'];
if ($invoice->getGrandTotal() != $amount)
{
if (!empty($amounts['shipping']))
$invoice->setShippingAmount($amounts['shipping']);
if (!empty($amounts['base_shipping']))
$invoice->setBaseShippingAmount($amounts['base_shipping']);
if (!empty($amounts['tax']))
$invoice->setTaxAmount($amounts['tax']);
if (!empty($amounts['base_tax']))
$invoice->setBaseTaxAmount($amounts['base_tax']);
$invoice->setGrandTotal($amount);
$invoice->setBaseGrandTotal($baseAmount);
$subtotal = 0;
$baseSubtotal = 0;
$items = $invoice->getAllItems();
foreach ($items as $item)
{
$subtotal += $item->getRowTotal();
$baseSubtotal += $item->getBaseRowTotal();
}
$invoice->setSubtotal($subtotal);
$invoice->setBaseSubtotal($baseSubtotal);
}
}
public function invoiceSubscriptionOrder($order, $transactionId = null, $captureCase = \Magento\Sales\Model\Order\Invoice::CAPTURE_ONLINE, $amount = null, $save = true)
{
$invoice = $this->invoiceService->prepareInvoice($order);
$invoice->setRequestedCaptureCase($captureCase);
if ($transactionId)
{
$invoice->setTransactionId($transactionId);
$order->getPayment()->setLastTransId($transactionId);
}
$this->adjustInvoiceAmounts($invoice, $amount);
$invoice->register();
$comment = __("Captured payment of %1 through Stripe.", $order->formatPrice($invoice->getGrandTotal()));
$order->addStatusToHistory($status = 'processing', $comment, $isCustomerNotified = false);
if ($save)
{
$this->saveInvoice($invoice);
$this->saveOrder($order);
$this->sendInvoiceEmail($invoice);
}
return $invoice;
}
public function invoiceOrder($order, $transactionId = null, $captureCase = \Magento\Sales\Model\Order\Invoice::CAPTURE_ONLINE, $amount = null, $save = true)
{
// This will kick in with "Authorize Only" mode orders, but not with "Authorize & Capture"
if ($order->canInvoice())
{
$invoice = $this->invoiceService->prepareInvoice($order);
$invoice->setRequestedCaptureCase($captureCase);
if ($transactionId)
{
$invoice->setTransactionId($transactionId);
$order->getPayment()->setLastTransId($transactionId);
}
$this->adjustInvoiceAmounts($invoice, $amount);
$invoice->register();
if ($save)
{
$this->saveInvoice($invoice);
$this->saveOrder($order);
$this->sendInvoiceEmail($invoice);
}
return $invoice;
}
// Invoices have already been generated with either Authorize Only or Authorize & Capture, but have not actually been captured because
// the source is not chargeable yet. These should have a pending status.
else
{
foreach($order->getInvoiceCollection() as $invoice)
{
$canCapture = $invoice->canCapture(); // Used in Authorize Only mode
$isOpen = $invoice->getState() == \Magento\Sales\Model\Order\Invoice::STATE_OPEN; // Used with admin area Stripe Billing asynchronous payments
if ($canCapture || $isOpen)
{
$invoice->setRequestedCaptureCase($captureCase);
$this->adjustInvoiceAmounts($invoice, $amount);
if ($transactionId && !$invoice->getTransactionId())
{
$invoice->setTransactionId($transactionId);
$order->getPayment()->setLastTransId($transactionId);
}
$invoice->pay();
if ($save)
{
$this->saveInvoice($invoice);
$this->saveOrder($order);
$this->sendInvoiceEmail($invoice);
}
return $invoice;
}
}
}
return null;
}
// Pending orders are the ones that were placed with an asynchronous payment method, such as SOFORT or SEPA Direct Debit,
// which may finalize the charge after several days or weeks
public function invoicePendingOrder($order, $transactionId = null, $amount = null)
{
if (!$order->canInvoice())
throw new \Exception("Order #" . $order->getIncrementId() . " cannot be invoiced.");
$invoice = $this->invoiceService->prepareInvoice($order);
if ($transactionId)
{
$captureCase = \Magento\Sales\Model\Order\Invoice::NOT_CAPTURE;
$invoice->setTransactionId($transactionId);
$order->getPayment()->setLastTransId($transactionId);
}
else
{
$captureCase = \Magento\Sales\Model\Order\Invoice::CAPTURE_ONLINE;
}
$invoice->setRequestedCaptureCase($captureCase);
$this->adjustInvoiceAmounts($invoice, $amount);
$invoice->register();
$this->sendInvoiceEmail($invoice);
$this->saveInvoice($invoice);
$this->saveOrder($order);
return $invoice;
}
public function cancelOrCloseOrder($order, $refundInvoices = false, $refundOffline = true)
{
$canceled = false;
// When in Authorize & Capture, uncaptured invoices exist, so we should cancel them first
foreach($order->getInvoiceCollection() as $invoice)
{
if ($invoice->getState() == \Magento\Sales\Model\Order\Invoice::STATE_CANCELED)
continue;
if ($invoice->canCancel())
{
$invoice->cancel();
$this->saveInvoice($invoice);
$canceled = true;
}
else if ($refundInvoices)
{
$creditmemo = $this->creditmemoFactory->createByOrder($order);
$creditmemo->setInvoice($invoice);
$this->creditmemoService->refund($creditmemo, $refundOffline);
$this->saveCreditmemo($creditmemo);
$canceled = true;
}
}
// When there are no invoices, the order can be canceled
if ($order->canCancel())
{
$order->cancel();
$canceled = true;
}
$this->saveOrder($order);
return $canceled;
}
public function maskError($msg)
{
if (stripos($msg, "You must verify a phone number on your Stripe account") === 0)
return $msg;
return false;
}
// Removes decorative strings that Magento adds to the transaction ID
public function cleanToken($token)
{
if (empty($token))
return null;
return preg_replace('/-.*$/', '', $token);
}
public function formatStripePrice($price, string $currency)
{
$precision = $this->convert->getCurrencyPrecision($currency);
$magentoAmount = $this->convert->stripeAmountToMagentoAmount($price, $currency);
return $this->priceCurrency->format($magentoAmount, false, $precision, null, strtoupper($currency));
}
public function getUrl($path, $additionalParams = [])
{
$params = ['_secure' => $this->request->isSecure()];
return $this->urlBuilder->getUrl($path, $params + $additionalParams);
}
public function getRequest()
{
return $this->request;
}
public function updateBillingAddress($token)
{
if (strpos($token, "pm_") === 0)
{
$paymentMethod = \Stripe\PaymentMethod::retrieve($token);
$quote = $this->getQuote();
$magentoBillingDetails = $this->addressHelper->getStripeAddressFromMagentoAddress($quote->getBillingAddress());
$paymentMethodBillingDetails = [
"address" => [
"city" => $paymentMethod->billing_details->address->city,
"line1" => $paymentMethod->billing_details->address->line1,
"line2" => $paymentMethod->billing_details->address->line2,
"country" => $paymentMethod->billing_details->address->country,
"postal_code" => $paymentMethod->billing_details->address->postal_code,
"state" => $paymentMethod->billing_details->address->state
],
"phone" => $paymentMethod->billing_details->phone,
"name" => $paymentMethod->billing_details->name,
"email" => $paymentMethod->billing_details->email
];
if ($paymentMethodBillingDetails != $magentoBillingDetails || $paymentMethodBillingDetails["address"] != $magentoBillingDetails["address"])
{
\Stripe\PaymentMethod::update(
$paymentMethod->id,
['billing_details' => $magentoBillingDetails]
);
}
}
}
public function sendNewOrderEmailFor($order, $forceSend = false)
{
if (empty($order) || !$order->getId())
return;
if (!$order->getEmailSent() && $forceSend)
{
$order->setCanSendNewEmailFlag(true);
}
// Send the order email
if ($order->getCanSendNewEmailFlag())
{
try
{
$this->orderSender->send($order);
return true;
}
catch (\Exception $e)
{
$this->logError($e->getMessage());
$this->logError($e->getTraceAsString());
}
}
return false;
}
public function sendInvoiceEmail($invoice)
{
try
{
$this->invoiceSender->send($invoice);
return true;
}
catch (\Exception $e)
{
$this->logError($e->getMessage(), $e->getTraceAsString());
}
return false;
}
// An assumption is made that Webhooks->initStripeFrom($order) has already been called
// to set the store and currency before the conversion, as the pricingHelper uses those
public function getFormattedStripeAmount($amount, $currency, $order)
{
$orderAmount = $this->convertStripeAmountToOrderAmount($amount, $currency, $order);
return $this->addCurrencySymbol($orderAmount, $currency);
}
public function convertBaseAmountToStoreAmount($baseAmount)
{
$store = $this->storeManager->getStore();
return $store->getBaseCurrency()->convert($baseAmount, $store->getCurrentCurrencyCode());
}
public function convertBaseAmountToOrderAmount($baseAmount, $order, $stripeCurrency, $precision = 4)
{
$currency = $order->getOrderCurrencyCode();
if (strtolower($stripeCurrency) == strtolower($order->getOrderCurrencyCode()))
{
$rate = $order->getBaseToOrderRate();
if (empty($rate))
return $baseAmount; // The base currency and the order currency are the same
return round(floatval($baseAmount * $rate), $precision);
}
else
{
$store = $this->storeManager->getStore();
$amount = $store->getBaseCurrency()->convert($baseAmount, $stripeCurrency);
return round(floatval($amount), $precision);
}
// $rate = $this->currencyFactory->create()->load($order->getBaseCurrencyCode())->getAnyRate($currency);
}
public function convertMagentoAmountToStripeAmount($amount, $currency)
{
if (empty($amount) || !is_numeric($amount) || $amount < 0)
return 0;
return $this->convert->magentoAmountToStripeAmount($amount, $currency);
}
public function convertOrderAmountToBaseAmount($amount, $currency, $order)
{
if (strtolower($currency) == strtolower($order->getOrderCurrencyCode()))
$rate = $order->getBaseToOrderRate();
else
throw new \Exception("Currency code $currency was not used to place order #" . $order->getIncrementId());
// $rate = $this->currencyFactory->create()->load($order->getBaseCurrencyCode())->getAnyRate($currency);
if (empty($rate))
return $amount; // The base currency and the order currency are the same
return round(floatval($amount / $rate), 2);
}
public function convertStripeAmountToBaseOrderAmount($amount, $currency, $order)
{
if (strtolower($currency) != strtolower($order->getOrderCurrencyCode()))
throw new \Exception("The order currency does not match the Stripe currency");
$magentoAmount = $this->convert->stripeAmountToMagentoAmount($amount, $currency);
$baseAmount = round($magentoAmount / (float)$order->getBaseToOrderRate(), 2);
return $baseAmount;
}
public function convertStripeAmountToOrderAmount($amount, $currency, $order)
{
if (strtolower($currency) != strtolower($order->getOrderCurrencyCode()))
throw new \Exception("The order currency does not match the Stripe currency");
return $this->convert->stripeAmountToMagentoAmount($amount, $currency);
}
public function convertStripeAmountToQuoteAmount($amount, $currency, $quote)
{
if (strtolower($currency) != strtolower($quote->getQuoteCurrencyCode()))
throw new \Exception("The quote currency does not match the Stripe currency");
return $this->convert->stripeAmountToMagentoAmount($amount, $currency);
}
public function convertStripeAmountToBaseQuoteAmount($amount, $currency, $quote)
{
if (strtolower($currency) != strtolower($quote->getQuoteCurrencyCode()))
throw new \Exception("The order currency does not match the Stripe currency");
$precision = $this->convert->getCurrencyPrecision($currency);
$magentoAmount = $this->convert->stripeAmountToMagentoAmount($amount, $currency);
$baseAmount = round(floatval($magentoAmount / $quote->getBaseToQuoteRate()), $precision);
return $baseAmount;
}
public function getCurrentCurrencyCode()
{
return $this->storeManager->getStore()->getCurrentCurrency()->getCode();
}
public function getBaseCurrencyCode()
{
return $this->storeManager->getStore()->getBaseCurrencyCode();
}
public function addCurrencySymbol($amount, $currencyCode = null)
{
if (empty($currencyCode))
$currencyCode = $this->getCurrentCurrencyCode();
$precision = $this->convert->getCurrencyPrecision($currencyCode);
return $this->priceCurrency->format($amount, false, $precision, null, strtoupper($currencyCode));
}
public function getClearSourceInfo($data)
{
$info = [];
$remove = ['mandate_url', 'fingerprint', 'client_token', 'data_string'];
foreach ($data as $key => $value)
{
if (!in_array($key, $remove))
$info[$key] = $value;
}
// Remove Klarna pay fields
$startsWith = ["pay_"];
foreach ($info as $key => $value)
{
foreach ($startsWith as $part)
{
if (strpos($key, $part) === 0)
unset($info[$key]);
}
}
return $info;
}
public function notifyCustomer($order, $comment)
{
$order->addStatusToHistory($status = false, $comment, $isCustomerNotified = true);
$order->setCustomerNote($comment);
try
{
$this->orderCommentSender->send($order, $notify = true, $comment);
}
catch (\Exception $e)
{
$this->logError("Order email sending failed: " . $e->getMessage());
}
}
public function sendNewOrderEmailWithComment($order, $comment)
{
$order->addStatusToHistory($status = false, $comment, $isCustomerNotified = true);
$this->orderComments[$order->getIncrementId()] = $comment;
$order->setEmailSent(false);
$this->orderSender->send($order, true);
}
public function isAuthenticationRequiredMessage($message)
{
return (strpos($message, "Authentication Required: ") !== false);
}
public function getMultishippingOrdersDescription($quote, $orders)
{
$customerName = $quote->getCustomerFirstname() . " " . $quote->getCustomerLastname();
$orderIncrementIds = [];
foreach ($orders as $order)
$orderIncrementIds[] = "#" . $order->getIncrementId();
$description = __("Multishipping orders %1 by %2", implode(", ", $orderIncrementIds), $customerName);
return $description;
}
public function getOrderDescription($order)
{
if ($order->getCustomerIsGuest())
{
$customer = $this->getGuestCustomer($order);
$customerName = $customer->getFirstname() . ' ' . $customer->getLastname();
}
else
$customerName = $order->getCustomerName();
if ($this->hasSubscriptionsIn($order->getAllItems()))
$subscription = "subscription ";
else
$subscription = "";
if ($this->isMultiShipping())
$description = "Multi-shipping {$subscription}order #" . $order->getRealOrderId() . " by $customerName";
else
$description = "{$subscription}order #" . $order->getRealOrderId() . " by $customerName";
return ucfirst($description);
}
public function getQuoteDescription($quote)
{
if ($quote->getCustomerIsGuest())
{
$customer = $this->getGuestCustomer($quote);
$customerName = $customer->getFirstname() . ' ' . $customer->getLastname();
}
else
$customerName = $quote->getCustomerName();
if (!empty($customerName))
$description = __("Cart %1 by %2", $quote->getId(), $customerName);
else
$description = __("Cart %1", $quote->getId());
return $description;
}
public function supportsSubscriptions(?string $method)
{
if (empty($method))
return false;
return in_array($method, ["stripe_payments", "stripe_payments_checkout", "stripe_payments_express"]);
}
public function isStripeCheckoutMethod(?string $method)
{
if (empty($method))
return false;
return in_array($method, ["stripe_payments_checkout"]);
}
public function getLevel3DataFrom($order)
{
if (empty($order))
return null;
$merchantReference = $order->getIncrementId();
if (empty($merchantReference))
return null;
$currency = $order->getOrderCurrencyCode();
$data = [
"merchant_reference" => $merchantReference,
"line_items" => $this->getLevel3DataLineItemsFrom($order)
];
if (!$order->getIsVirtual())
{
$data["shipping_address_zip"] = $order->getShippingAddress()->getPostcode();
$data["shipping_amount"] = $this->convert->magentoAmountToStripeAmount($order->getShippingInclTax(), $currency);
}
$data = array_merge($data, $this->getLevel3AdditionalDataFrom($order));
return $data;
}
public function getLevel3DataLineItemsFrom($order)
{
$items = [];
$quoteItems = $order->getAllVisibleItems();
foreach ($quoteItems as $item)
{
$amount = $item->getPrice();
$currency = $order->getOrderCurrencyCode();
$tax = $this->convert->magentoAmountToStripeAmount($item->getTaxAmount(), $currency);
$discount = $this->convert->magentoAmountToStripeAmount($item->getDiscountAmount(), $currency);
$items[] = [
"product_code" => substr($item->getSku(), 0, 12),
"product_description" => substr($item->getName(), 0, 26),
"unit_cost" => $this->convert->magentoAmountToStripeAmount($amount, $currency),
"quantity" => $item->getQtyOrdered(),
"tax_amount" => $tax,
"discount_amount" => $discount
];
}
return $items;
}
public function getLevel3AdditionalDataFrom($order)
{
// You can overwrite to add the shipping_from_zip or customer_reference parameters here
return [];
}
public function getCustomerModel()
{
if ($this->currentCustomer)
return $this->currentCustomer;
$this->currentCustomer = \Magento\Framework\App\ObjectManager::getInstance()->get('StripeIntegration\Payments\Model\StripeCustomer');
if ($this->currentCustomer->getStripeId())
return $this->currentCustomer;
$pk = $this->getPublishableKey();
if (empty($pk))
return $this->currentCustomer;
$customerId = $this->getCustomerId();
$model = null;
if (is_numeric($customerId) && $customerId > 0)
{
$model = $this->customerCollection->getByCustomerId($customerId, $pk);
if ($model && $model->getId() && $model->existsInStripe())
{
$model->updateSessionId();
$this->currentCustomer = $model;
}
}
else
{
$stripeCustomerId = $this->sessionManager->getStripeCustomerId();
$model = null;
if ($stripeCustomerId)
{
$model = $this->customerCollection->getByStripeCustomerIdAndPk($stripeCustomerId, $pk);
}
else
{
$sessionId = $this->sessionManager->getSessionId();
$model = $this->customerCollection->getBySessionId($sessionId, $pk);
}
if ($model && $model->getId() && $model->existsInStripe())
$this->currentCustomer = $model;
}
if (!$this->currentCustomer)
{
$this->currentCustomer = \Magento\Framework\App\ObjectManager::getInstance()->create('StripeIntegration\Payments\Model\StripeCustomer');
}
return $this->currentCustomer;
}
public function getCustomerModelByStripeId($stripeId)
{
return $this->customerCollection->getByStripeCustomerId($stripeId);
}
public function getPublishableKey()
{
$storeId = $this->getStoreId();
$mode = $this->scopeConfig->getValue("payment/stripe_payments_basic/stripe_mode", \Magento\Store\Model\ScopeInterface::SCOPE_STORE, $storeId);
$pk = $this->scopeConfig->getValue("payment/stripe_payments_basic/stripe_{$mode}_pk", \Magento\Store\Model\ScopeInterface::SCOPE_STORE, $storeId);
if (empty($pk))
return null;
return trim($pk);
}
public function getStripeUrl($liveMode, $objectType, $id)
{
if ($liveMode)
return "https://dashboard.stripe.com/$objectType/$id";
else
return "https://dashboard.stripe.com/test/$objectType/$id";
}
public function holdOrder(&$order)
{
$order->setHoldBeforeState($order->getState());
$order->setHoldBeforeStatus($order->getStatus());
$order->setState(\Magento\Sales\Model\Order::STATE_HOLDED)
->setStatus($order->getConfig()->getStateDefaultStatus(\Magento\Sales\Model\Order::STATE_HOLDED));
$comment = __("Order placed under manual review by Stripe Radar.");
$order->addStatusToHistory(false, $comment, false);
return $order;
}
public function addOrderComment($msg, $order, $isCustomerNotified = false)
{
if ($order)
$order->addCommentToStatusHistory($msg);
}
public function overrideInvoiceActionComment(\Magento\Sales\Model\Order\Payment $payment, $msg)
{
try
{
$extensionAttributes = $payment->getExtensionAttributes();
if ($extensionAttributes === null)
{
$extensionAttributes = $this->paymentExtensionFactory->create();
$payment->setExtensionAttributes($extensionAttributes);
}
if (!method_exists($extensionAttributes, "setNotificationMessage"))
{
throw new \Exception("Older version of Magento detected.");
}
$extensionAttributes->setNotificationMessage($msg);
}
catch (\Exception $e)
{
// Magento 2.3.5 and older did not support the notification_message extension attribute, which will cause an exception
// https://github.com/magento/magento2/blob/2.3.6/app/code/Magento/Sales/etc/extension_attributes.xml#L17
}
}
public function overrideCancelActionComment(\Magento\Sales\Model\Order\Payment $payment, $msg)
{
$payment->setMessage($msg);
$this->overrideInvoiceActionComment($payment, $msg);
}
public function capture($token, $payment, $amount, $useSavedCard = false)
{
$token = $this->cleanToken($token);
$order = $payment->getOrder();
if ($token == "cannot_capture_subscriptions")
{
$msg = __("Subscription items cannot be captured online. Capturing offline instead.");
$this->addWarning($msg);
$this->overrideInvoiceActionComment($payment, $msg);
return;
}
try
{
$paymentObject = $ch = null;
$finalAmount = $amountToCapture = 0;
if (strpos($token, 'pi_') === 0)
{
$pi = \Stripe\PaymentIntent::retrieve($token);
if (empty($pi->charges->data[0]) || $pi->status == "requires_action")
$this->dieWithError(__("The payment for this order has not been authorized yet."));
$ch = $pi->charges->data[0];
$paymentObject = $pi;
$amountToCapture = "amount_to_capture";
}
else if (strpos($token, 'ch_') === 0)
{
$ch = \Stripe\Charge::retrieve($token);
$paymentObject = $ch;
$amountToCapture = "amount";
}
else
{
$this->dieWithError(__("We do not know how to capture payments with a token of this format."));
}
$currency = $ch->currency;
if ($currency == strtolower($order->getOrderCurrencyCode()))
$finalAmount = $this->getMultiCurrencyAmount($payment, $amount);
else if ($currency == strtolower($order->getBaseCurrencyCode()))
$finalAmount = $amount;
else
$this->dieWithError(__("Cannot capture payment because it was created using a different currency (%1).", $ch->currency));
$stripeAmount = $this->convert->magentoAmountToStripeAmount($finalAmount, $currency);
if ($this->isAuthorizationExpired($ch))
{
if ($useSavedCard)
{
return $this->apiFactory->create()->reCreateCharge($payment, $amount, $ch);
}
else
return $this->dieWithError("The payment authorization with the customer's bank has expired. If you wish to create a new payment using a saved card, please enable Expired Authorizations from Configuration → Sales → Payment Methods → Stripe → Card Payments → Expired Authorizations.");
}
else if ($ch->refunded)
{
$this->dieWithError("The amount for this invoice has been refunded in Stripe.");
}
else if ($ch->captured)
{
$capturedAmount = $ch->amount - $ch->amount_refunded;
$humanReadableAmount = $this->formatStripePrice($stripeAmount - $capturedAmount, $ch->currency);
if ($order->getInvoiceCollection()->getSize() > 0)
{
foreach ($order->getInvoiceCollection() as $invoice)
{
if ($invoice->getState() == \Magento\Sales\Model\Order\Invoice::STATE_PAID)
{
if ($invoice->getGrandTotal() < $order->getGrandTotal()) // Is this a partial invoice?
{
if ($useSavedCard)
{
$this->apiFactory->create()->reCreateCharge($payment, $amount, $ch);
return;
}
else
{
return $this->dieWithError("The payment has already been partially captured, and the remaining amount has been released. If you wish to create a new payment using a saved card, please enable Expired Authorizations from Configuration → Sales → Payment Methods → Stripe → Card Payments → Expired Authorizations.");
}
}
else
{
// In theory we should never get in here because Magento cannot Invoice orders which have already been fully invoiced..
$msg = __("%1 could not be captured online because it was already captured via Stripe. Capturing %1 offline instead.", $humanReadableAmount);
$this->addWarning($msg);
$this->addOrderComment($msg, $order);
return;
}
}
}
}
if ($this->hasTrialSubscriptionsIn($order->getAllItems()))
{
$msg = __("%1 could not be captured online because this cart includes subscriptions which are trialing. Capturing %1 offline instead.", $humanReadableAmount);
}
else if (($stripeAmount - $capturedAmount) == 0)
{
// Case with a regular item and a subscription with PaymentElement, before the webhook arrives.
$humanReadableAmount = $this->formatStripePrice($stripeAmount, $ch->currency);
$msg = __("%1 has already captured via Stripe. The invoice was in Pending status, likely because a webhook could not be delivered to your website. Capturing %1 offline instead.", $humanReadableAmount);
}
else
$msg = __("%1 could not be captured online because it was already captured via Stripe. Capturing %1 offline instead.", $humanReadableAmount);
$this->addWarning($msg);
$this->overrideInvoiceActionComment($payment, $msg);
}
else // status == pending
{
$availableAmount = $ch->amount;
if ($availableAmount < $stripeAmount)
{
$available = $this->formatStripePrice($availableAmount, $ch->currency);
$requested = $this->formatStripePrice($stripeAmount, $ch->currency);
if ($this->hasSubscriptionsIn($order->getAllItems()))
$msg = __("Capturing %1 instead of %2 because subscription items cannot be captured.", $available, $requested);
else
$msg = __("The maximum available amount to capture is %1, but a capture of %2 was requested. Will capture %1 instead.", $available, $requested);
$this->addWarning($msg);
$this->addOrderComment($msg, $order);
$stripeAmount = $availableAmount;
}
$key = "admin_captured_" . $paymentObject->id;
try
{
$this->cache->save($value = "1", $key, ["stripe_payments"], $lifetime = 60 * 60);
$paymentObject->capture(array($amountToCapture => $stripeAmount));
}
catch (\Exception $e)
{
$this->cache->remove($key);
throw $e;
}
}
}
catch (\Exception $e)
{
$this->dieWithError($e->getMessage(), $e);
}
}
public function deduplicatePaymentMethod($customerId, $paymentMethodId, $paymentMethodType, $fingerprint, $stripeClient)
{
if ($paymentMethodType != "card" || empty($fingerprint) || empty($customerId) || empty($paymentMethodId))
return;
try
{
switch ($paymentMethodType)
{
case "card":
$subscriptions = [];
$data = $stripeClient->subscriptions->all(['limit' => 100, 'customer' => $customerId]);
foreach ($data->autoPagingIterator() as $subscription)
$subscriptions[] = $subscription;
$collection = $stripeClient->paymentMethods->all([
'customer' => $customerId,
'type' => $paymentMethodType
]);
foreach ($collection->data as $paymentMethod)
{
if ($paymentMethod['id'] == $paymentMethodId || $paymentMethod['card']['fingerprint'] != $fingerprint)
continue;
// Update subscriptions which use the card that will be deleted
foreach ($subscriptions as $subscription)
{
if ($subscription->default_payment_method == $paymentMethod['id'])
{
try
{
$stripeClient->subscriptions->update($subscription->id, ['default_payment_method' => $paymentMethodId]);
}
catch (\Exception $e)
{
$this->logError($e->getMessage(), $e->getTraceAsString());
}
}
}
// Detach the card from the customer
try
{
$stripeClient->paymentMethods->detach($paymentMethod['id']);
}
catch (\Exception $e)
{
$this->logError($e->getMessage());
$this->logError($e->getTraceAsString());
}
}
break;
default:
break;
}
}
catch (\Exception $e)
{
$this->logError($e->getMessage());
$this->logError($e->getTraceAsString());
}
}
public function getPRAPIMethodType()
{
if (empty($_SERVER['HTTP_USER_AGENT']))
return null;
$userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
if (strpos($userAgent, 'chrome') !== false)
return 'Google Pay';
if (strpos($userAgent, 'safari') !== false)
return 'Apple Pay';
if (strpos($userAgent, 'edge') !== false)
return 'Microsoft Pay';
if (strpos($userAgent, 'opera') !== false)
return 'Opera Browser Wallet';
if (strpos($userAgent, 'firefox') !== false)
return 'Firefox Browser Wallet';
if (strpos($userAgent, 'samsung') !== false)
return 'Samsung Browser Wallet';
if (strpos($userAgent, 'qqbrowser') !== false)
return 'QQ Browser Wallet';
return null;
}
public function getPaymentLocation($location)
{
if (stripos($location, 'product') === 0)
return "Product Page";
switch ($location) {
case 'cart':
return "Shopping Cart Page";
case 'checkout':
return "Checkout Page";
case 'minicart':
return "Mini cart";
default:
return "Unknown";
}
}
public function getCustomerOrders($customerId, $statuses = [], $paymentMethodId = null)
{
$collection = $this->orderCollectionFactory->create($customerId)
->addAttributeToSelect('*')
->join(
['pi' => $this->resource->getTableName('stripe_payment_intents')],
'main_table.customer_id = pi.customer_id and main_table.increment_id = pi.order_increment_id',
[]
)
->setOrder(
'created_at',
'desc'
);
if (!empty($statuses))
$collection->addFieldToFilter('main_table.status', ['in' => $statuses]);
if (!empty($paymentMethodId))
$collection->addFieldToFilter('pi.pm_id', ['eq' => $paymentMethodId]);
return $collection;
}
public function loadCouponByCouponCode($couponCode)
{
return $this->couponFactory->create()->loadByCode($couponCode);
}
public function loadRuleByRuleId($ruleId)
{
return $this->ruleRepository->getById($ruleId);
}
public function loadStripeCouponByRuleId($ruleId)
{
return $this->stripeCouponFactory->create()->load($ruleId, 'rule_id');
}
public function sendPaymentFailedEmail($quote, $msg)
{
try
{
if (!$quote)
$quote = $this->getQuote();
$this->checkoutHelper->sendPaymentFailedEmail($quote, $msg);
}
catch (\Exception $e)
{
$this->logError($e->getMessage(), $e->getTraceAsString());
}
}
public function isRecurringOrder($method)
{
try
{
$info = $method->getInfoInstance();
if (!$info)
return false;
return $info->getAdditionalInformation("is_recurring_subscription");
}
catch (\Exception $e)
{
return false;
}
return false;
}
public function resetPaymentData($payment)
{
if ($payment->getPaymentId())
return;
// Reset a previously initialized 3D Secure session
$payment->setAdditionalInformation('stripejs_token', null)
->setAdditionalInformation('token', null)
->setAdditionalInformation("is_recurring_subscription", null)
->setAdditionalInformation("is_migrated_subscription", null)
->setAdditionalInformation("remove_initial_fee", null)
->setAdditionalInformation("customer_stripe_id", null)
->setAdditionalInformation("payment_element", null)
->setAdditionalInformation("payment_location", null)
->setAdditionalInformation("cvc_token", null)
->setAdditionalInformation("client_secret", null)
->setAdditionalInformation("manual_authentication", null)
->setAdditionalInformation("is_subscription_update", null);
}
protected function isSavedPaymentMethod(\Stripe\PaymentMethod $paymentMethod)
{
if (empty($paymentMethod->customer))
{
return false;
}
// With subscriptions, the payment method is created with PaymentElement and saved automatically before the order is placed.
// We don't want to perform CVC checks if this is a freshly created payment method.
$age = time() - $paymentMethod->created;
if ($age < (4 * 60))
{
return false;
}
return true;
}
public function assignPaymentData($payment, $data)
{
$this->resetPaymentData($payment);
if ($this->isMultiShipping())
{
$payment->setAdditionalInformation("payment_location", "Multishipping checkout");
}
else if ($this->isAdmin())
{
$payment->setAdditionalInformation("payment_location", "Admin area");
}
else if (!empty($data['is_migrated_subscription']))
{
$payment->setAdditionalInformation("payment_location", "CLI migrated subscription order");
}
else if (!empty($data['is_recurring_subscription']))
{
$payment->setAdditionalInformation("payment_location", "Recurring subscription order");
}
else if (!empty($data['is_prapi']))
{
$payment->setAdditionalInformation('is_prapi', true);
$payment->setAdditionalInformation('prapi_location', $data['prapi_location']);
if (!empty($data['prapi_title']))
{
$payment->setAdditionalInformation('prapi_title', $data['prapi_title']);
$location = $data['prapi_title'] . " via " . $data['prapi_location'] . " page";
$payment->setAdditionalInformation("payment_location", $location);
}
else
{
$payment->setAdditionalInformation("payment_location", "Wallet payment");
}
}
else if (!empty($data['is_subscription_update']))
{
$payment->setAdditionalInformation("is_subscription_update", true);
}
if (!empty($data['payment_element']))
{
// Used by the new checkout flow
$token = (isset($data['payment_method']) ? $data['payment_method'] : null);
$payment->setAdditionalInformation('payment_element', $data['payment_element']);
$payment->setAdditionalInformation('token', $token);
if (!empty($data['manual_authentication']))
{
$payment->setAdditionalInformation('manual_authentication', $data['manual_authentication']);
}
// Token is null when using a new payment method, and is set when using a saved payment method
if ($token)
{
$config = DataHelper::getSingleton(\StripeIntegration\Payments\Model\Config::class);
if ($config->reCheckCVCForSavedCards())
{
$payment->setAdditionalInformation('cvc_token', null);
$stripePaymentMethod = DataHelper::getSingleton(\StripeIntegration\Payments\Model\Stripe\PaymentMethod::class);
$paymentMethod = $stripePaymentMethod->fromPaymentMethodId($token)->getStripeObject();
if ($paymentMethod->type == "card" && $this->isSavedPaymentMethod($paymentMethod))
{
if (!empty($data['cvc_token']))
{
$payment->setAdditionalInformation('cvc_token', $data['cvc_token']);
}
else
{
throw new LocalizedException(__("Your card has been declined. Please check your CVC and try again."));
}
}
}
}
if ($this->isMultiShipping())
{
if (!empty($data['cc_stripejs_token'])) {
// Used in the Magento admin, Wallet button, GraphQL API and REST API
$card = explode(':', $data['cc_stripejs_token']);
$data['cc_stripejs_token'] = $card[0];
$payment->setAdditionalInformation('token', $card[0]);
$quoteId = $payment->getQuoteId();
$multishippingQuoteModel = $this->multishippingQuoteFactory->create();
$multishippingQuoteModel->load($quoteId, 'quote_id');
$multishippingQuoteModel->setQuoteId($quoteId);
$multishippingQuoteModel->setPaymentMethodId($card[0]);
$multishippingQuoteModel->save();
}
}
}
else if (!empty($data['cc_stripejs_token']))
{
// Used in the Magento admin, Wallet button, GraphQL API and REST API
$card = explode(':', $data['cc_stripejs_token']);
$data['cc_stripejs_token'] = $card[0];
$payment->setAdditionalInformation('token', $card[0]);
if (isset($data['save_payment_method']) && $data['save_payment_method'])
$payment->setAdditionalInformation('save_payment_method', true);
if ($this->isMultiShipping())
{
$quoteId = $payment->getQuoteId();
$multishippingQuoteModel = $this->multishippingQuoteFactory->create();
$multishippingQuoteModel->load($quoteId, 'quote_id');
$multishippingQuoteModel->setQuoteId($quoteId);
$multishippingQuoteModel->setPaymentMethodId($card[0]);
$multishippingQuoteModel->save();
}
}
else if (!empty($data['is_recurring_subscription']))
$payment->setAdditionalInformation('is_recurring_subscription', $data['is_recurring_subscription']);
if (!empty($data['is_migrated_subscription']))
$payment->setAdditionalInformation('is_migrated_subscription', true);
}
public function shippingIncludesTax($store = null)
{
return $this->taxConfig->shippingPriceIncludesTax($store);
}
public function priceIncludesTax($store = null)
{
return $this->taxConfig->priceIncludesTax($store);
}
/**
* Transaction interface types
* const TYPE_PAYMENT = 'payment';
* const TYPE_ORDER = 'order';
* const TYPE_AUTH = 'authorization';
* const TYPE_CAPTURE = 'capture';
* const TYPE_VOID = 'void';
* const TYPE_REFUND = 'refund';
**/
public function addTransaction($order, $transactionId, $transactionType = "capture", $parentTransactionId = null)
{
try
{
$payment = $order->getPayment();
if ($parentTransactionId)
{
$payment->setTransactionId($transactionId . "-$transactionType");
$payment->setParentTransactionId($parentTransactionId);
}
else
{
$payment->setTransactionId($transactionId);
$payment->setParentTransactionId(null);
}
$transaction = $payment->addTransaction($transactionType, null, true);
return $transaction;
}
catch (Exception $e)
{
$this->logError($e->getMessage(), $e->getTraceAsString());
}
}
public function getOrderTransactions($order)
{
$transactions = $this->transactionSearchResultFactory->create()->addOrderIdFilter($order->getId());
return $transactions->getItems();
}
// $orderItemQtys = [$orderItem->getId() => int $qty, ...]
public function invoiceOrderItems($order, $orderItemQtys, $save = true)
{
if (empty($orderItemQtys))
return null;
$invoice = $this->invoiceService->prepareInvoice($order, $orderItemQtys);
$invoice->register();
$order->setIsInProcess(true);
if ($save)
{
$this->saveInvoice($invoice);
$this->saveOrder($order);
}
return $invoice;
}
public function isPendingCheckoutOrder($order)
{
$method = $order->getPayment()->getMethod();
if (!$this->isStripeCheckoutMethod($method))
return false;
if ($order->getState() != "pending_payment")
return false;
if ($order->getPayment()->getLastTransId())
return false;
return true;
}
public function clearCache()
{
$this->products = [];
$this->orders = [];
$this->quoteHasSubscriptions = [];
$this->quotes = [];
return $this;
}
public function saveOrder($order)
{
return $this->orderRepository->save($order);
}
public function saveInvoice($invoice)
{
return $this->invoiceRepository->save($invoice);
}
public function saveQuote($quote)
{
return $this->quoteRepository->save($quote);
}
public function saveTransaction($transaction)
{
return $this->transactionRepository->save($transaction);
}
public function saveCreditmemo($creditmemo)
{
return $this->creditmemoRepository->save($creditmemo);
}
public function savePayment($payment)
{
return $this->orderPaymentRepository->save($payment);
}
public function saveProduct($product)
{
return $this->productRepository->save($product);
}
public function setProcessingState($order, $comment = null, $isCustomerNotified = false)
{
$state = \Magento\Sales\Model\Order::STATE_PROCESSING;
$status = $order->getConfig()->getStateDefaultStatus($state);
if ($comment)
$order->setState($state)->addStatusToHistory($status, $comment, $isCustomerNotified);
else
$order->setState($state)->setStatus($status);
}
public function setOrderState($order, $state, $comment = null, $isCustomerNotified = false)
{
$status = $order->getConfig()->getStateDefaultStatus($state);
if ($comment)
$order->setState($state)->addStatusToHistory($status, $comment, $isCustomerNotified);
else
$order->setState($state)->setStatus($status);
}
public function getStripeApiTimeDifference()
{
$timeDifference = $this->cache->load("stripe_api_time_difference");
if (!is_numeric($timeDifference))
{
$localTime = time();
$product = \Stripe\Product::create([
'name' => 'Time Query',
'type' => 'service'
]);
$timeDifference = $product->created - ($localTime + 1); // The 1 added second accounts for the delay in creating the product
$this->cache->save($timeDifference, $key = "stripe_api_time_difference", $tags = ["stripe_payments"], $lifetime = 24 * 60 * 60);
$product->delete();
}
return $timeDifference;
}
public function isTransactionId($transactionId)
{
if (empty($transactionId))
return false;
$isPaymentIntent = (strpos($transactionId, "pi_") === 0);
$isCharge = (strpos($transactionId, "ch_") === 0);
return ($isPaymentIntent || $isCharge);
}
public function getOrdersByTransactionId($transactionId)
{
$orders = [];
if (!$this->isTransactionId($transactionId))
return $orders;
$transactions = $this->transactionSearchResultFactory->create()->addFieldToFilter('txn_id', $transactionId);
foreach ($transactions as $transaction)
{
if (!$transaction->getOrderId())
continue;
$orderId = $transaction->getOrderId();
if (isset($orders[$orderId]))
continue;
$order = $this->loadOrderById($orderId);
if ($order && $order->getId())
$orders[$orderId] = $order;
}
return $orders;
}
public function getCache()
{
return $this->cache;
}
public function removeTransactions($order)
{
$order->getPayment()->setLastTransId(null);
$order->getPayment()->setTransactionId(null);
$order->getPayment()->save();
}
public function magentoVersion($operator, $version)
{
$magentoVersion = $this->productMetadata->getVersion();
return version_compare($magentoVersion, $version, $operator);
}
public function getSubscriptionsHelper()
{
if (!$this->subscriptionHelper) {
$this->subscriptionHelper = DataHelper::getSingleton(\StripeIntegration\Payments\Helper\Subscriptions::class);
}
return $this->subscriptionHelper;
}
}