This is a revised article initially written in September 2014 once I began to play with Magento 2 for the primary time. Since there have been loads of modifications in Magento 2, this article additionally needed some refreshment. We will look over the backend, and proceed with the development of a easy module. For this job, I picked up the payment gateway API integration. This comparatively easy job will assist us show some key modifications in Magento 2.
More exactly, we will likely be specializing in implementation of Stripe payment gateway. Even although Stripe has a wealthy set of options, right here we will likely be focusing solely on most elementary functionalities to get you began with Magento 2 extension development.
NOTE: Magento 2 source code is below fixed modifications. Although code was examined on early Magento 2.1., it’s now outdated. Stay tuned for an replace.
Module setup
As you in all probability already know, rather a lot has modified in Magento 2, and Module setup and code structure is just not an exception. One of the primary issues you will discover is absence of code pools that we’re used to from earlier version. Inside the app/code folder we’ll create our namespace folder. In the tip, our file/folder structure would appear like this:
Before we proceed, there’s yet one more factor that must be taken care of. Stripe comes with its own set of PHP libraries for integration, they usually should be included as nicely. This, nonetheless, must be managed by Composer. If you check out composer.json, you will discover require line. By putting in this pattern extension via composer, Stripe library will likely be positioned below vendor/stripe folder, and will likely be out there via autoloader in our code.
Next huge change has been launched in XML configuration files. Magento 2 has launched XML scheme for every configuration type that must be adopted, or in any other case the module is not going to work. First one that we are going to create, is module.xml that replaces configuration which was beforehand positioned below app/etc/modules/namespace_modulename.xml. As in magento 1, it’s used to declare module and its dependencies:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
<module name="Simplemagento_Stripe" setup_version="1.0.0">
<sequence>
<module name="Magento_Sales" />
<module name="Magento_Payment" />
<module name="Magento_Directory" />
<module name="Magento_Config" />
</sequence>
</module>
</config>
There are additionally some modifications in different config files, however I’ll let you have enjoyable exploring them yourselves.
Payment implementation
So far, we’ve created our module file structure, we’ve created module configuration files and we’ve built-in our library files. It is now time to proceed with the payment integration.
Anyone who built-in payment gateway in Magento is aware of the significance of implementing correct admin settings, because the system will deal with many of the issues robotically. Lets check out our etc/adminhtml/system.xml file:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../Magento/Config/etc/system_file.xsd">
<system>
<section id="payment">
<group id="Simplemagento_stripe" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Stripe</label>
<comment>
<![CDATA[<a href="https://stripe.com/" target="_blank">Click here to sign up for Stripe account</a>]]>
</comment>
<field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" presentInWebsite="1" showInStore="0">
<label>Enabled</label>
<source_model>MagentoConfigModelConfigSourceYesno</source_model>
</field>
<field id="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Title</label>
</field>
<field id="api_key" translate="label" type="obscure" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Api Key</label>
<backend_model>MagentoConfigModelConfigBackendEncrypted</backend_model>
</field>
<field id="debug" translate="label" type="select" sortOrder="4" showInDefault="1" presentInWebsite="1" showInStore="0">
<label>Debug</label>
<source_model>MagentoConfigModelConfigSourceYesno</source_model>
</field>
<field id="cctypes" translate="label" type="multiselect" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Credit Card Types</label>
<source_model>SimplemagentoStripeModelSourceCctype</source_model>
</field>
<field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Sort Order</label>
</field>
<field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Payment from Applicable Countries</label>
<source_model>MagentoPaymentModelConfigSourceAllspecificcountries</source_model>
</field>
<field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Payment from Specific Countries</label>
<source_model>MagentoDirectoryModelConfigSourceCountry</source_model>
</field>
<field id="min_order_total" translate="label" type="text" sortOrder="98" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Minimum Order Total</label>
</field>
<field id="max_order_total" translate="label" type="text" sortOrder="99" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Maximum Order Total</label>
<comment>Leave empty to disable limit</comment>
</field>
</group>
</section>
</system>
</config>
There are solely three fields that we need to deal with via our code: api_key, min_order_total and max_order_total. As I beforehand mentioned, Magento will deal with the remainder via abstract classes by default.
Speaking of classes, it’s lastly time to implement our Payment class. Due to the character of Stripe, we will likely be extending MagentoPaymentModelMethodCc. Besides setting ordinary config via protected variables, we additionally should move Stripe library to our class to respect dependency injection and testability. Therefore we’ll start our class with the next snippet:
namespace SimplemagentoStripeMannequin;
class Payment extends MagentoPaymentModelMethodCc
{
const CODE = 'Simplemagento_stripe';
protected $_code = self::CODE;
protected $_isGateway = true;
protected $_canCapture = true;
protected $_canCapturePartial = true;
protected $_canRefund = true;
protected $_canRefundInvoicePartial = true;
protected $_stripeApi = false;
protected $_countryFactory;
protected $_minAmount = null;
protected $_maxAmount = null;
protected $_supportedCurrencyCodes = array('USD');
protected $_debugReplacePrivateDataKeys = ['number', 'exp_month', 'exp_year', 'cvc'];
public function __construct(
MagentoFrameworkModelContext $context,
MagentoFrameworkRegistry $registry,
MagentoFrameworkApiExtensionAttributesFactory $extensionFactory,
MagentoFrameworkApiAttributeValueFactory $customAttributeFactory,
MagentoPaymentHelperData $paymentData,
MagentoFrameworkAppConfigScopeConfigInterface $scopeConfig,
MagentoPaymentModelMethodLogger $logger,
MagentoFrameworkModuleModuleListInterface $moduleList,
MagentoFrameworkStdlibDateTimeTimezoneInterface $localeDate,
MagentoDirectoryModelCountryFactory $countryFactory,
StripeStripe $stripe,
array $data = array()
)
At this level, we’ve covered nearly all variations in module development between Magento 2 and 1. From this level on, many of the code can be the identical between these two variations, so far as our Stripe integration is anxious.
Lets proceed with implementation of our most essential function, capture():
/**
* Payment capturing
*
* @param MagentoPaymentModelInfoInterface $payment
* @param float $amount
* @return $this
* @throws MagentoFrameworkValidatorException
*/
public function capture(MagentoPaymentModelInfoInterface $payment, $amount)
{
//throw new MagentoFrameworkValidatorException(__('Inside Stripe, throwing donuts :]'));
/** @var MagentoSalesModelOrder $order */
$order = $payment->getOrder();
/** @var MagentoSalesModelOrderAddress $billing */
$billing = $order->getBillingAddress();
try catch (Exception $e) {
$this->debugData(['request' => $requestData, 'exception' => $e->getMessage()]);
$this->_logger->error(__('Payment capturing error.'));
throw new MagentoFrameworkValidatorException(__('Payment capturing error.'));
}
return $this;
}
As ordinary, we’ll fetch billing info via payment object. Credit card information is than handed to Stripes API which handles the remainder. In case of success, we’ll add this transaction to Magento’s list of transactions, and we’re principally performed right here. It is essential to notice right here, that transaction id must be set to transaction ID obtained by the payment gateway, since this will likely be used afterward.
Another essential characteristic for payment method is means to subject a refund from Magento admin. So lets proceed and implement our refund() function:
/**
* Payment refund
*
* @param MagentoPaymentModelInfoInterface $payment
* @param float $amount
* @return $this
* @throws MagentoFrameworkValidatorException
*/
public function refund(MagentoPaymentModelInfoInterface $payment, $amount)
{
$transactionId = $payment->getParentTransactionId();
try catch (Exception $e) {
$this->debugData((*2*));
$this->_logger->error(__('Payment refunding error.'));
throw new MagentoFrameworkValidatorException(__('Payment refunding error.'));
}
$payment
->setTransactionId($transactionId . '-' . MagentoSalesModelOrderPaymentTransaction::TYPE_REFUND)
->setParentTransactionId($transactionId)
->setIsTransactionClosed(1)
->setShouldCloseParentTransaction(1);
return $this;
}
Basically, we’re fetching transaction ID, which is then handed to API which handles refund communications. All we need to do right here is to correctly deal with errors and mark transactions. And sure, by dealing with errors I do imply throwing Exception from inside catch block, in order to inform Magento of an error. Reason for try..catch block in the primary place was to sanitize data, since response from server may need delicate info. This additionally applies to capture performance.
In Magento 2, checkout has been rewritten as JS consumer aspect application, that’s speaking with core system via API. Considering that, PHP half itself is just not sufficient for integration to work. We will proceed by including two extra JS files via layout update XML(test hyperlink, it’s to giant to be listed right here). Following files have been added:
- view/frontend/web/js/view/payment/stripe-payments.js
- view/frontend/web/js/view/payment/method-renderer/stripe-method.js
Their goal is to offer configuration for UI element used in checkout. For now, we’ll go away like that, explaining how this works, requires its own article.
And that’s principally it. There could also be a number of extra issues to tune up, however our module is prepared. Full code will be retrieved at our GIT repository here. An don’t overlook to regulate our repository. We plan to implement integration with tokens to keep away from sending bank card information to server.
So far we’ve covered many of the basic items which might be required for module development. However, our module is actually easy: it lacks any template files, controllers, blocks, composer config, etc. But we’ll cover all that in our future articles. This is only a warmup to get you began. I hope it was helpful, and that you just loved studying it.