Payment Plug-ins
You can add one or more payment plug-ins to Elastic Path Commerce. To add a provider, such as Authorize.net or AliPay, a back-end developer creates a Maven module and then implements an abstract plug-in class.
When you use your own payment plug-in within the Elastic Path Payments framework, you gain the following benefits:
- Avoid conflicts in versions between your payment plug-in and Elastic Path libraries
- Allow unlimited dependencies within your payment plug-ins
After you create a payment plug-in, you can activate and associate the plug-in with your store. For more information about activating and associating a plug-in, see Configuration of Elastic Path Commerce.
Set up a Plug-in Development Environment
Before you can create your payment plug-in within the development environment, you must determine some variables about your provider. You will use these variables as you create the plug-in.
{plugin}
A name for the plugin Maven artifact. Ensure that this is a unique name and that it ends with -plugin
, such as cyberSource-payment-provider-plugin
.
{your.company.group}
The Maven group identifier for the payment plug-in. This identifier is usually a company domain name with the plugin
suffix, such as com.example.plugins
.
{your/company/group}
The Maven group identifier for the payment plug-in. This identifier is created by starting with the company domain name, replacing the periods with slashes, and adding the plugin
suffix. For example, the identifier for the com.example.domain
is com/example/plugins
.
{ep-platform-version}
The Elastic Path version number that the payment plug-in supports, such as 0.0.0-SNAPSHOT
or 8.0.0
.
{plugin-version}
The version number of the payment plugin, such as 0.0.0-SNAPSHOT
or 1.0.0
.
Creating a Maven Module
In a new
{plugin}
directory, create apom.xml
file.Ensure that the
pom.xml
file contains the following parent, packaging and dependency information:<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.elasticpath</groupId> <artifactId>grandparent</artifactId> <version>111</version> </parent> <groupId>{your.company.group}</groupId> <artifactId>{plugin}</artifactId> <version>{plugin-version}</version> <packaging>jar</packaging> <properties> <org.springframework.version>4.3.19.RELEASE</org.springframework.version> <com.elasticpath.version>{ep-platform-version}</com.elasticpath.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>com.elasticpath</groupId> <artifactId>payment-plugin-connectivity</artifactId> <version>${com.elasticpath.version}</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptors> <descriptor>uberjar.xml</descriptor> </descriptors> <appendAssemblyId>false</appendAssemblyId> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
In the same new
{plugin}
directory, create anuberjar.xml
assembly descriptor file to ensure that your dependencies are bundled in theuberjar lib
directory.<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd"> <id>uberjar</id> <formats> <format>jar</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <fileSets> <fileSet> <directory>${project.build.outputDirectory}</directory> <outputDirectory>/</outputDirectory> </fileSet> </fileSets> <dependencySets> <dependencySet> <outputDirectory>/lib</outputDirectory> <useProjectArtifact>false</useProjectArtifact> <unpack>false</unpack> <scope>runtime</scope> </dependencySet> </dependencySets> </assembly>
In the
{plugin}/src/main/resources/META-INF/elasticpath/conf/spring/
directory, create aplugin.xml
file with the following:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="{your.company.group}"/> <context:annotation-config/> </beans>
Note The
{your.company.group}
value is the name of the Java package that contains the plug-in classes.
Implementing an Abstract Plug-in Class
In the
{plugin}/src/main/java/{your/company/group}
directory, implement thecom.elasticpath.plugin.payment.provider.AbstractPaymentProviderPlugin
class.package {your.company.group}; import java.util.Collections; import java.util.List; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import com.elasticpath.plugin.payment.provider.AbstractPaymentProviderPlugin; import com.elasticpath.plugin.payment.provider.PluginConfigurationKey; import com.elasticpath.plugin.payment.provider.capabilities.PaymentCapabilityRequestFailedException; import com.elasticpath.plugin.payment.provider.capabilities.PaymentCapabilityResponse; import com.elasticpath.plugin.payment.provider.capabilities.charge.ChargeCapability; import com.elasticpath.plugin.payment.provider.capabilities.charge.ChargeCapabilityRequest; import com.elasticpath.plugin.payment.provider.capabilities.charge.ReverseChargeCapability; import com.elasticpath.plugin.payment.provider.capabilities.charge.ReverseChargeCapabilityRequest; import com.elasticpath.plugin.payment.provider.capabilities.creation.PICCapability; import com.elasticpath.plugin.payment.provider.capabilities.creation.PaymentInstrumentCreationFields; import com.elasticpath.plugin.payment.provider.capabilities.creation.PaymentInstrumentCreationRequest; import com.elasticpath.plugin.payment.provider.capabilities.creation.PaymentInstrumentCreationResponse; import com.elasticpath.plugin.payment.provider.capabilities.credit.CreditCapability; import com.elasticpath.plugin.payment.provider.capabilities.credit.CreditCapabilityRequest; import com.elasticpath.plugin.payment.provider.capabilities.instructions.PICClientInteractionRequestCapability; import com.elasticpath.plugin.payment.provider.capabilities.instructions.PICInstructions; import com.elasticpath.plugin.payment.provider.capabilities.instructions.PICInstructionsFields; import com.elasticpath.plugin.payment.provider.capabilities.instructions.PICInstructionsRequest; import com.elasticpath.plugin.payment.provider.capabilities.reservation.CancelCapability; import com.elasticpath.plugin.payment.provider.capabilities.reservation.CancelCapabilityRequest; import com.elasticpath.plugin.payment.provider.capabilities.reservation.ModifyCapability; import com.elasticpath.plugin.payment.provider.capabilities.reservation.ModifyCapabilityRequest; import com.elasticpath.plugin.payment.provider.capabilities.reservation.ReserveCapability; import com.elasticpath.plugin.payment.provider.capabilities.reservation.ReserveCapabilityRequest; import com.elasticpath.plugin.payment.provider.dto.PICFieldsRequestContextDTO; import com.elasticpath.plugin.payment.provider.exception.ErrorMessage; import com.elasticpath.plugin.payment.provider.exception.PaymentInstrumentCreationFailedException; import com.elasticpath.plugin.payment.provider.exception.StructuredMessageType; @Component("yourPaymentProviderPlugin") @Scope("prototype") public class YourPaymentProviderPlugin extends AbstractPaymentProviderPlugin implements PICCapability, // required capability PICClientInteractionRequestCapability, ReserveCapability, ModifyCapability, CreditCapability, // required capability CancelCapability, ChargeCapability, // required capability ReverseChargeCapability { @Override public PaymentInstrumentCreationResponse createPaymentInstrument(PaymentInstrumentCreationRequest request) throws PaymentInstrumentCreationFailedException { throw new PaymentInstrumentCreationFailedException(Collections.singletonList(new ErrorMessage( StructuredMessageType.ERROR, "", "Not implemented", Collections.emptyMap()))); } @Override public PaymentInstrumentCreationFields getPaymentInstrumentCreationFields(final PICFieldsRequestContextDTO context) throws PaymentInstrumentCreationFailedException { return <fields required for your payment instrument creation and if this instrument is savable on profile> } @Override public String getPaymentVendorId() { return <your payment provider vendor name>; // e.g. CyberSource, PayPal, Stripe> } @Override public String getPaymentMethodId() { return <your payment method>; // e.g. Credit, Debit, Interac } @Override public List<PluginConfigurationKey> getConfigurationKeys() { return <your plugin configuration keys for CM>; } @Override public PICInstructionsFields getPaymentInstrumentCreationInstructionsFields(final PICFieldsRequestContextDTO context) throws PaymentInstrumentCreationFailedException { return <your instructions field names>; } @Override public PICInstructions getPaymentInstrumentCreationInstructions(final PICInstructionsRequest request) throws PaymentInstrumentCreationFailedException { return <your instructions: control and payload data maps> } @Override public PaymentCapabilityResponse charge(final ChargeCapabilityRequest request) throws PaymentCapabilityRequestFailedException { <charge user account > <respond with payment event data which will be persisted > } @Override public PaymentCapabilityResponse reverseCharge(final ReverseChargeCapabilityRequest request) throws PaymentCapabilityRequestFailedException { <reverse charge processing > <respond with payment event data which will be persisted > } @Override public PaymentCapabilityResponse credit(final CreditCapabilityRequest request) throws PaymentCapabilityRequestFailedException { <credit user account > <respond with payment event data which will be persisted > } @Override public PaymentCapabilityResponse cancel(final CancelCapabilityRequest request) throws PaymentCapabilityRequestFailedException { <cancel reservation> <respond with payment event data which will be persisted > } @Override public PaymentCapabilityResponse modify(final ModifyCapabilityRequest request) throws PaymentCapabilityRequestFailedException { <modify reservation> <respond with payment event data which will be persisted > } @Override public PaymentCapabilityResponse reserve(final ReserveCapabilityRequest request) throws PaymentCapabilityRequestFailedException { <process reservation> <respond with payment event data which will be persisted > } }
Note: You can customize the default payment plug-in that Elastic Path supplies out-of-the-box. For more information, see Purchase Order Plug-in section. You can also implement annotations to the Payments framework to enhance the payment flow. For more information about the types of annotations you can add, see the Additional Plug-in Behaviors section.
In the
{plugin}
directory, run the following command to build the payment plug-in:mvn clean install
Additional Plug-in Behaviors
You can implement additional payment plug-in behavior to the Payments framework to enhance the payment flow. For example, you can add single reserve per payment instrument (PI) or the billing address required capability.
Multiple partial charges per reservation
Tne @SupportsMultiplePartialCharges
annotation can be used if the payment gateway allows multiple partial charges (captures) to be processed against a single reservation (pre-authorization).
If the @SupportsMultiplePartialCharges
annotation is present, payment operations for an order with multiple shipments work as follows:
- A single reservation is made for the full amount of the order.
- A charge is applied for the electronic shipment total.
- Later, when the physical shipment ships, a charge is applied using the original reservation for the physical shipment total.
If the @SupportsMultiplePartialCharges
annotation is not present, payment operations for an order with multiple shipments work as follows:
- A single reservation is made for the full amount of the order.
- A charge is applied for the electronic shipment total.
- Elastic Path Commerce then assumes that the original reservation is consumed, so a new reservation is created for the remaining amount.
- Later, when the physical shipment ships, a charge is applied to the new reservation for the physical shipment total.
The ChargeCapabilityRequest
class includes a finalCharge
flag that tells the plugin if the charge request is the final one for the reservation. If this flag is set, the plugin can tell the payment gateway that the reservation can be closed out by the payment gateway.
Single reserve per payment instrument (PI)
The @SingleReservePerPI
annotation can be used when a provider creates a single-use token. Regardless of the number of shipments or modifications to the order, the Payments framework ensures that the single-use token processes a single reservation for the order. If an order contains both a physical and non-physical shipment, the capture is postponed until the final shipment is fulfilled to ensure that the token is used once.
The following capabilities are affected by the @SingleReservePerPI
annotation:
Modify
capability can only decrease, never increase the order amount.Charge
capability charges during the last shipment, when all shipments are either shipped or cancelled. Only orders that are shipped are charged.Cancel
capability can cancel any number of shipments when there are multiple shipments in the order.@singleReservePerPI
charges the reservation when there are no shipments left.
Billing Address Required
The @BillingAddressRequired
annotation can be used by the payment plug-in to prompt Elastic Path Commerce to provide a billing address during the Payment Instrument Creation (PIC) instructions and PIC phase. Address details that are added to the PIC form are saved to the shopper’s profile. When a payment provider uses the @BillingAddressRequired
annotation, Elastic Path Commerce sends a shopper’s billing address to the payment provider.
The following capabilities are affected when you use the @BillingAddressRequired
annotation:
PIC instructions
capability sends the billing address on file with the PIC request.PIC
capability ensures that the billing address is transmitted with the PIC and PIC instructions request.
Adding the Payment Plug-in
Add the payment plug-in to Cortex, Commerce Manager, and Integration Server. After you add the plug-in to the corresponding xml
files, you can build Elastic Path Commerce to include your plug-in.
To add the plug-in to Cortex, open the
extensions/cortex/ext-cortex-webapp/pom.xml
file, search for thecopy-plugin-artifacts
execution node at<project> --> <build> --> <plugins> --> <plugin> (maven-dependency-plugin) --> <executions> --> <execution> (copy-plugin-artifacts)
and add the following to the list of<configuration> --> <artifactItems>
:<artifactItem> <groupId>{your.company.group}</groupId> <artifactId>{plugin}</artifactId> <version>{plugin-version}</version> </artifactItem>
To add the plug-in to Commerce Manager, open the
extensions/cm/ext-cm-modules/ext-cm-webapp/pom.xml
file, search for thecopy-plugin-artifacts
execution node at<project> --> <build> --> <plugins> --> <plugin> (maven-dependency-plugin) --> <executions> --> <execution> (copy-plugin-artifacts)
and add the following to the list of<configuration> --> <artifactItems>
:<artifactItem> <groupId>{your.company.group}</groupId> <artifactId>{plugin}</artifactId> <version>{plugin-version}</version> </artifactItem>
To add the plug-in to Integration Server, open the
extensions/integration/ext-integration-webapp/pom.xml
file, search for thecopy-plugin-artifacts
execution node at<project> --> <build> --> <plugins> --> <plugin> (maven-dependency-plugin) --> <executions> --> <execution> (copy-plugin-artifacts)
and add the following to the list of<configuration> --> <artifactItems>
:<artifactItem> <groupId>{your.company.group}</groupId> <artifactId>{plugin}</artifactId> <version>{plugin-version}</version> </artifactItem>
Build Core Commerce
After you create, implement, and add your payment plug-in, build Core Commerce to include your payment plug-in. If you have previously built all the webapps, you only need to rebuild the following Maven modules:
ext-cortex-webapp
ext-integration-webapp
ext-cm-webapp
For information about how to build core commerce, see the Building Elastic Path Commerce Projects section.
Purchase Order Plug-in
Elastic Path Commerce includes a purchase order plug-in that is built using the Payments framework. Purchase orders are commonly used in B2B transactions. A purchase order (PO) number indicates a commitment by the buyer to pay the merchant at a later date. By default, the purchase order plug-in ensures that B2B buyers provide a PO number as their payment method for the order. The PO number is captured and kept with the order. The plug-in does not include purchase order validation, that is, the plug-in does not communicate with external business systems to validate the PO number, account limits, or account status. If you want to implement PO number validation, you can extend the plug-in.
The purchase order plug-in records the data against the orders and assumes that the client account is charged or refunded manually through another process outside of Elastic Path Commerce.
Default behaviour:
- Payment Instrument Creation (PIC) process validates that a buyer provides a PO number. If a PO number is not provided, the PIC creation process fails with an exception.
- Reserve process accepts any type of purchase order number, as there is no specific validation. The Reserve process records the number and the administrator can reference the transaction to associate an order with the purchase order number.
- Charge process accepts all requests to complete the order. The Charge process records the data with the Charge transaction. The Charge assumes that another manual process outside Elastic Path Commerce charges the client account.
- Credit process accepts all Refund requests recorded by the purchase order plug-in against the order. The system assumes that another manual process outside Elastic Path Commerce charges the client account.
You can activate and associate the plug-in with your store. For more information about activating and associating a plug-in, see Configuration of Elastic Path Commerce.