Como Crear una pasarela de pago en Magento 2
Configuración del módulo
La configuración del Módulo se hace dentro de la carpeta de app/code crearemos nuestro espacio de carpetas. Al final nuestra estructura de archivos/carpetas se vería así:
Antes de continuar, hay una cosa más que debe ser atendida. Stripe viene con su propio conjunto de bibliotecas PHP para la integración, y también deben incluirse. Esto, sin embargo, debe ser gestionado por Composer. Si echa un vistazo a composer.json , notará línea requerida . Al instalar esta extensión se muestra a través del compositor, la biblioteca Stripe se colocará en la carpeta vendor/stripe y estará disponible a través del autoloader en nuestro código.
El siguiente gran cambio se ha introducido en los archivos de configuración XML. Magento 2 ha introducido un esquema XML para cada tipo de configuración que se debe seguir, o de lo contrario el módulo no funcionará. El primero que crearemos es module.xml que reemplaza la configuración que se colocó anteriormente en app/etc/modules/namespace_modulename.xml . Se utiliza para declarar el módulo y sus dependencias:
<br /> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"><br /> <module name="Inchoo_Stripe" setup_version="1.0.0"><br /> <sequence><br /> <module name="Magento_Sales" /><br /> <module name="Magento_Payment" /><br /> <module name="Magento_Directory" /><br /> <module name="Magento_Config" /><br /> </sequence><br /> </module><br /> </config><br />
Implementación de pago
Hasta ahora, hemos creado nuestra estructura de archivos de módulos, hemos creado archivos de configuración de módulos y hemos integrado nuestros archivos de biblioteca. Ahora es el momento de proceder con la integración de pago.
Cualquiera que haya integrado la pasarela de pago en Magento sabe la importancia de implementar la configuración de administración adecuada, ya que el sistema manejará la mayoría de las cosas automáticamente. Veamos nuestro archivo etc/adminhtml/system.xml :
<br /> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../Magento/Config/etc/system_file.xsd"><br /> <system></p> <section id="payment"> <group id="inchoo_stripe" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1"><br /> <label>Stripe</label><br /> <comment><br /> <![CDATA[<a href="https://stripe.com/" target="_blank">Click here to sign up for Stripe account</a>]]><br /> </comment><br /> <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0"><br /> <label>Enabled</label><source_model>Magento\Config\Model\Config\Source\Yesno</source_model><br /> </field><br /> <field id="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1"><br /> <label>Title</label><br /> </field><br /> <field id="api_key" translate="label" type="obscure" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0"><br /> <label>Api Key</label><br /> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model><br /> </field><br /> <field id="debug" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="0"><br /> <label>Debug</label><source_model>Magento\Config\Model\Config\Source\Yesno</source_model><br /> </field><br /> <field id="cctypes" translate="label" type="multiselect" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0"><br /> <label>Credit Card Types</label><source_model>Inchoo\Stripe\Model\Source\Cctype</source_model><br /> </field><br /> <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"><br /> <label>Sort Order</label><br /> </field><br /> <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"><br /> <label>Payment from Applicable Countries</label><source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model><br /> </field><br /> <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1" showInStore="0"><br /> <label>Payment from Specific Countries</label><source_model>Magento\Directory\Model\Config\Source\Country</source_model><br /> </field><br /> <field id="min_order_total" translate="label" type="text" sortOrder="98" showInDefault="1" showInWebsite="1" showInStore="0"><br /> <label>Minimum Order Total</label><br /> </field><br /> <field id="max_order_total" translate="label" type="text" sortOrder="99" showInDefault="1" showInWebsite="1" showInStore="0"><br /> <label>Maximum Order Total</label><br /> <comment>Leave empty to disable limit</comment><br /> </field><br /> </group><br /> </section> <p> </system><br /> </config><br />
Solo hay tres campos que debemos manejar a través de nuestro código: api_key , min_order_total y max_order_total . Como dije anteriormente, Magento manejará el resto a través de clases abstractas por defecto.
Hablando de clases, finalmente es hora de implementar nuestra clase de Pago. Debido a la naturaleza de Stripe, estaremos extendiendo \Magento\Payment\Model\Method\Cc . Además de configurar la configuración habitual a través de variables protegidas, también tenemos que pasar la biblioteca Stripe a nuestra clase para respetar la inyección de dependencias y la capacidad de prueba. Por lo tanto comenzaremos nuestra clase con el siguiente fragmento de código:
<br /> namespace Inchoo\Stripe\Model;</p> <p>class Payment extends \Magento\Payment\Model\Method\Cc<br /> {<br /> const CODE = 'inchoo_stripe';</p> <p> protected $_code = self::CODE;</p> <p> protected $_isGateway = true;<br /> protected $_canCapture = true;<br /> protected $_canCapturePartial = true;<br /> protected $_canRefund = true;<br /> protected $_canRefundInvoicePartial = true;</p> <p> protected $_stripeApi = false;</p> <p> protected $_countryFactory;</p> <p> protected $_minAmount = null;<br /> protected $_maxAmount = null;<br /> protected $_supportedCurrencyCodes = array('USD');</p> <p> protected $_debugReplacePrivateDataKeys = ['number', 'exp_month', 'exp_year', 'cvc'];</p> <p> public function __construct(<br /> \Magento\Framework\Model\Context $context,<br /> \Magento\Framework\Registry $registry,<br /> \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,<br /> \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory,<br /> \Magento\Payment\Helper\Data $paymentData,<br /> \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,<br /> \Magento\Payment\Model\Method\Logger $logger,<br /> \Magento\Framework\Module\ModuleListInterface $moduleList,<br /> \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,<br /> \Magento\Directory\Model\CountryFactory $countryFactory,<br /> \Stripe\Stripe $stripe,<br /> array $data = array()<br /> ) {<br /> parent::__construct(<br /> $context,<br /> $registry,<br /> $extensionFactory,<br /> $customAttributeFactory,<br /> $paymentData,<br /> $scopeConfig,<br /> $logger,<br /> $moduleList,<br /> $localeDate,<br /> null,<br /> null,<br /> $data<br /> );</p> <p> $this->_countryFactory = $countryFactory;</p> <p> $this->_stripeApi = $stripe;<br /> $this->_stripeApi->setApiKey(<br /> $this->getConfigData('api_key')<br /> );</p> <p> $this->_minAmount = $this->getConfigData('min_order_total');<br /> $this->_maxAmount = $this->getConfigData('max_order_total');<br /> }<br />
Continuemos con la implementación de nuestra función más importante, capture() :
<br /> /**<br /> * Payment capturing<br /> *<br /> * @param \Magento\Payment\Model\InfoInterface $payment<br /> * @param float $amount<br /> * @return $this<br /> * @throws \Magento\Framework\Validator\Exception<br /> */<br /> public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount)<br /> {<br /> //throw new \Magento\Framework\Validator\Exception(__('Inside Stripe, throwing donuts :]'));</p> <p> /** @var \Magento\Sales\Model\Order $order */<br /> $order = $payment->getOrder();</p> <p> /** @var \Magento\Sales\Model\Order\Address $billing */<br /> $billing = $order->getBillingAddress();</p> <p> try {<br /> $requestData = [<br /> 'amount' => $amount * 100,<br /> 'currency' => strtolower($order->getBaseCurrencyCode()),<br /> 'description' => sprintf('#%s, %s', $order->getIncrementId(), $order->getCustomerEmail()),<br /> 'card' => [<br /> 'number' => $payment->getCcNumber(),<br /> 'exp_month' => sprintf('%02d',$payment->getCcExpMonth()),<br /> 'exp_year' => $payment->getCcExpYear(),<br /> 'cvc' => $payment->getCcCid(),<br /> 'name' => $billing->getName(),<br /> 'address_line1' => $billing->getStreetLine(1),<br /> 'address_line2' => $billing->getStreetLine(2),<br /> 'address_city' => $billing->getCity(),<br /> 'address_zip' => $billing->getPostcode(),<br /> 'address_state' => $billing->getRegion(),<br /> 'address_country' => $billing->getCountryId(),<br /> // To get full localized country name, use this instead:<br /> // 'address_country' => $this->_countryFactory->create()->loadByCode($billing->getCountryId())->getName(),<br /> ]<br /> ];</p> <p> $charge = \Stripe\Charge::create($requestData);<br /> $payment<br /> ->setTransactionId($charge->id)<br /> ->setIsTransactionClosed(0);</p> <p> } catch (\Exception $e) {<br /> $this->debugData(['request' => $requestData, 'exception' => $e->getMessage()]);<br /> $this->_logger->error(__('Payment capturing error.'));<br /> throw new \Magento\Framework\Validator\Exception(__('Payment capturing error.'));<br /> }</p> <p> return $this;<br /> }<br />
Como de costumbre, buscaremos información de facturación a través del objeto de payment . La información de la tarjeta de crédito se pasa a la API Stripe que se encarga del resto. En caso de éxito, agregaremos esta transacción a la lista de transacciones de Magento, y básicamente hemos terminado aquí. Es importante tener en cuenta que el ID de transacción debe configurarse como el ID de transacción recibido por la pasarela de pago, ya que se usará más adelante.
Otra característica importante del método de pago es la capacidad de emitir un reembolso del administrador de Magento. Así que vamos a proceder e implementar nuestra función de refund() :
/**<br /> * Payment refund<br /> *<br /> * @param \Magento\Payment\Model\InfoInterface $payment<br /> * @param float $amount<br /> * @return $this<br /> * @throws \Magento\Framework\Validator\Exception<br /> */<br /> public function refund(\Magento\Payment\Model\InfoInterface $payment, $amount)<br /> {<br /> $transactionId = $payment->getParentTransactionId();</p> <p> try {<br /> \Stripe\Charge::retrieve($transactionId)->refund();<br /> } catch (\Exception $e) {<br /> $this->debugData(['transaction_id' => $transactionId, 'exception' => $e->getMessage()]);<br /> $this->_logger->error(__('Payment refunding error.'));<br /> throw new \Magento\Framework\Validator\Exception(__('Payment refunding error.'));<br /> }</p> <p> $payment<br /> ->setTransactionId($transactionId . '-' . \Magento\Sales\Model\Order\Payment\Transaction::TYPE_REFUND)<br /> ->setParentTransactionId($transactionId)<br /> ->setIsTransactionClosed(1)<br /> ->setShouldCloseParentTransaction(1);</p> <p> return $this;<br /> }<br />
Básicamente, estamos obteniendo el ID de la transacción, que luego se pasa a la API que maneja las comunicaciones de reembolso. Todo lo que necesitamos hacer aquí es manejar adecuadamente los errores y marcar las transacciones. Y sí, al manejar los errores me refiero a lanzar Excepción desde el bloque catch interno, para notificar un error a Magento. La razón para el bloque try..catch en primer lugar fue para limpiar los datos, ya que la respuesta del servidor podría tener información confidencial. Esto también se aplica a la funcionalidad de captura.
En Magento 2, el proceso de pago se ha reescrito como una aplicación del lado del cliente JS, que se comunica con el sistema central a través de la API. Teniendo en cuenta eso, la parte de PHP en sí no es suficiente para que la integración funcione. Continuaremos agregando dos archivos JS más a través de la actualización de diseño XML (ver enlace, es demasiado grande para ser listado aquí). Se han agregado los siguientes archivos: