Payment Plugins
Payment Plugin Development
You can create your own payment plugins for Self Managed Commerce. To add a plugin, a back-end developer creates a Maven module and then implements an abstract plugin class.
When you use your own payment plugin within the payments framework, you gain the following benefits:
- Avoid conflicts in versions between your payment plugin and Elastic Path libraries
- Allow unlimited dependencies within your payment plugins
After you create a payment plugin, you can activate and associate the plugin with your store. For more information about activating and associating a plugin, see Configuration of Self Managed Commerce.
Set up a Plugin Development Environment
Before you can create your payment plugin within the development environment, you must determine some variables about your provider. You will use these variables as you create the plugin.
{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 plugin. 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 plugin. 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 plugin 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 plugin classes.
Implementing an Abstract Plugin 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 plugin that Elastic Path supplies out-of-the-box. For more information, see Purchase Order Plugin 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 Plugin Behaviors section.
In the
{plugin}
directory, run the following command to build the payment plugin:mvn clean install
Additional Plugin Behaviors
You can implement additional payment plugin 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.
Request hold
Payment plugins can be implemented to request that the order be held. In the implementation of the reserve
method, call setRequestHold(true)
on the PaymentCapabilityResponse
class. This will be read by the PaymentRequestHoldStrategyImpl
class, which will add a hold titled "Hold requested by payment plugin".
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.
- Self Managed 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 plugin to prompt Self Managed 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, Self Managed 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 a Payment Plugin
These instructions describe how to make a payment plugin available to Cortex, Commerce Manager, and Integration Server. After you add the plugin to the corresponding xml
files, you can build Self Managed Commerce to include your plugin.
To add the plugin 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 plugin 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 plugin 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 plugin, build Core Commerce to include your payment plugin. 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 Self Managed Commerce Projects section.
Purchase Order Payment Plugin
Self Managed Commerce includes a purchase order plugin 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 plugin 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 plugin does not include purchase order validation, that is, the plugin 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 plugin.
The purchase order plugin records the data against the orders and assumes that the client account is charged or refunded manually through another process outside of Self Managed 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 Self Managed Commerce charges the client account.
- Credit process accepts all Refund requests recorded by the purchase order plugin against the order. The system assumes that another manual process outside Self Managed Commerce charges the client account.
You can activate and associate the plugin to your store. For more information about activating and associating a plugin, see Configuration of Self Managed Commerce.
Gift Certificate Payment Plugin
Self Managed Commerce includes a gift certificate plugin that is built using the Payments framework. This plugin allows shoppers to pay for some or all of their order using a gift certificate that was purchased through Cortex.
The requestinstructionsform resource for a payment method enabled by the gift certificate plugin allows shoppers to retrieve details about a gift certificate with a specified code. A sample response is shown below:
{
"self": {
"type": "paymentinstructions.order-payment-instructions",
"uri": "/paymentinstructions/paymentmethods/orders/mobee/ge2ggylcgm3tcljwmq3toljumnrweljyme4dmllggjsgkolbgrrdszjqgy=/m5uwm5ddmvzhi2lgnfrwc5dffvrw63tgnftq=/instructions/qgvgmyllmuwwm2lfnrska=/q2tgc3lpovxhjjzsgaydalrqgcxgc3lpovxhillenfzxa3dbpgvegqkegiydambogaykoytbnrqw4y3fu4zdambqfyydbl3cmfwgc3tdmuwwi2ltobwgc6nkinauimrqgayc4mbqvbrxk4tsmvxgg6ndinaujk3tmvxgizlsfvxgc3lfvbfg62doebcg6zi=",
"href": "http://epc-sandbox.elasticpath.net:8080/cortex/paymentinstructions/paymentmethods/orders/mobee/ge2ggylcgm3tcljwmq3toljumnrweljyme4dmllggjsgkolbgrrdszjqgy=/m5uwm5ddmvzhi2lgnfrwc5dffvrw63tgnftq=/instructions/qgvgmyllmuwwm2lfnrska=/q2tgc3lpovxhjjzsgaydalrqgcxgc3lpovxhillenfzxa3dbpgvegqkegiydambogaykoytbnrqw4y3fu4zdambqfyydbl3cmfwgc3tdmuwwi2ltobwgc6nkinauimrqgayc4mbqvbrxk4tsmvxgg6ndinaujk3tmvxgizlsfvxgc3lfvbfg62doebcg6zi="
},
"messages": [],
"links": [
{
"rel": "paymentmethod",
"type": "paymentmethods.order-payment-method",
"href": "http://epc-sandbox.elasticpath.net:8080/cortex/paymentmethods/orders/mobee/ge2ggylcgm3tcljwmq3toljumnrweljyme4dmllggjsgkolbgrrdszjqgy=/m5uwm5ddmvzhi2lgnfrwc5dffvrw63tgnftq="
}
],
"communication-instructions": {},
"payload": {
"amount": "2000.00",
"amount-display": "CAD2000.00",
"balance": "2000.00",
"balance-display": "CAD2000.00",
"currency": "CAD",
"sender-name": "John Doe"
}
}
The paymentinstrumentform resource for a payment method enabled by the gift certificate plugin allows shoppers to create a payment instrument for a gift certificate with a specified code. A limit amount can be set to define how much of the gift certificate should be used on the order.
Other Supported Payment Plugins
For details about more supported payment plugins, see Supported Accelerators.