Technical Blog

3 Posts authored by: Darren Pendery

Fun with Camel

Posted by Darren Pendery Dec 23, 2011

When customizing Elastic Path the most common form of Java-code level customization is integration with an external fulfillment service.  This integration can take many forms, depending on your architecture:  SOAP or Restful Web services, JMS messages, flat files, RMI calls, etc.

Such integration is always tricky, and usually painful.  It doesn’t have to be.

The Elastic Path checkout architecture has become much more modularized, making it very easy to implement a new CheckoutAction where your fulfillment integration can start, but you still have to implement the code to make the call.

This is where Apache Camel can make your life much easier, by abstracting the plumbing of such a call from your Java code.

This post will show how you can use Apache Camel and Apache CXF to call an external SOAP-based fulfillment Web service using a few simple lines of code in your CheckoutAction.

First, a couple of assumptions:

  • You have a WSDL for the Web service
  • You have created an extension core project to extend the Elastic Path Core project

Now, create a new Maven project that will generate the Web service code and expose it to your application via a JAR file.  The following pom.xml file can be used as a starting point:

 

 

WS Client Project pom.xml

<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>com.mycompany.fulfillment.client</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>Test extension fulfillment client</name>
<properties>
<cxf-version>2.5.0</cxf-version>
</properties>
<build>
<finalName>orderfulfillment-client</finalName>
<plugins>

<!-- to compile with 1.5 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<!-- CXF wsdl2java generator, will plugin to the compile goal -->
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf-version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${basedir}/target/generated/src/main/java</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${basedir}/src/main/resources/wsdl/orderfulfillment.wsdl</wsdl>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

 

 

This pom assumes you have placed the Web service’s WSDL file in the src/main/resources/wsdl folder under your project folder.  It uses the Apache CXF plugin to generate the sources from the WSDL.

Executing “mvn install” will then create the JAR file, containing the classes and the WSDL file, and install it into your local Maven repository.

Add a dependency on this library in your core extension project.

Next, in your extension core project, add a couple other new dependencies:

 

Core Extension Dependencies
<!—Properties for the new dependencies. -->
<properties>
<cxf-version>2.5.0</cxf-version>
<camel-version>2.8.3</camel-version>
</properties>
<dependencies>

<!-- camel & cxf -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel-version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring</artifactId>
<version>${camel-version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>${cxf-version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>${cxf-version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-cxf</artifactId>
<version>${camel-version}</version>
</dependency>

<dependencies>

 

 

 

Now, implement a type converter class that will be used by Camel to convert the Elastic Path Order object to the object used by the Web service:

 

Type Converter

import org.apache.camel.Converter;
import org.apache.camel.Exchange;

 

@Converter
public class EpOrderToFulfillmentOrderConverter {

 

@Converter
public com.pendery.testextension.orderfulfillment.Order toEpOrder(final Order epOrder, final Exchange exchange) {

 

com.fulfillmentcompany.fulfillmentservice.orderfulfillment.Order fulfillmentOrder = new com.fulfillmentcompany.fulfillmentservice.orderfulfillment.Order();

 

// Copy the EP Order data into the fulfillmentOrder here.

 

return fulfillmentOrder;

}

 

}

 

 

 

Then add a file called TypeConverter (with no extension) to the src\main\resources\META-INF\services\org\apache\camel in your core extension project.  It should contain a single line, the fully qualified class name of your converter.  For example:

 

 

TypeConverter

com.mycompany.coreextension.camel.converter.EpOrderToFulfillmentOrderConverter

 

 

 

Next implement a Camel Route builder class that will route the EP Order to the Web service:

 

Route Builder

import org.apache.camel.builder.RouteBuilder;

 

public class OrderFulfillmentRouteImpl extends RouteBuilder {

 

@Override
public void configure() throws Exception {

 

final String toEndpoint = "cxf://http://externalservicehost/webservice/fulfillorder"
+ "?wsdlURL=wsdl/orderfulfillment.wsdl"
+ "&serviceName={http://orderfulfillment.fulfillmentservice.fulfillmentcompany.com/}FulfillOrderEndpointService"
+ "&portName={http://orderfulfillment.fulfillmentservice.fulfillmentcompany.com/}FulfillOrderSOAP"
+ "&serviceClass="com.fulfillmentcompany.fulfillmentservice.orderfulfillment.FulfillOrderEndpoint";

 

from ("direct:startorderfulfillment")
.convertBodyTo (com.fulfillmentcompany.fulfillmentservice.orderfulfillment.Order.class)
.to (toEndpoint);

}

 

}

 

 

This class defines a Camel endpoint called “direct:startorderfulfillment”.  It first converts the data sent to the endpoint, which should be an EP Order object, to the Order object expected by the fulfillment Web service.  Camel is smart enough to find the type converter you implemented earlier and uses that to do the conversion.  Finally it sends that converted object to the Web service.  It refers to the WSDL file from the class path, which is provided by the client project created earlier.

The Web service’s path, service name, port name and class name will be dependent on your situation.

 

Now implement your CheckoutAction to send the EP order to Camel:

 

Checkout Action

import org.apache.camel.CamelContext;

 

public class SendToFulfillmentCheckoutActionImpl implements FinalizeCheckoutAction {

 

public void execute(final FinalizeCheckoutActionContext context) throws EpSystemException {


Order epOrder = context.getOrder();

 

// Get Camel context bean.

CamelContext camel = (CamelContext) beanFactory.getBean("camel");

 

Object out = null;
OrderResponse response;

 

try {
// Send the fulfillment order to the Camel route.
response = (OrderResponse) camel.createProducerTemplate().requestBody("direct:startorderfulfillment", epOrder);
} catch (Exception e) {
throw new EpSystemException("Error sending order to fulfillment.", e);
}

 

// Check order response and any codes here.
}

}

 

 


This class sends the EP Order object to the Camel endpoint called “direct:startorderfulfillment”, which we defined in the route builder class we implemented earlier.

Finally, add the following Spring bean configuration in your core extension project’s plugin.xml file:

 

Spring Configuration (plugin.xml)

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">

...

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<package>com.mycompany.coreextension.camel.route</package>
</camelContext>

...

</beans>

 

 

Make sure to add http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd to the xsi:schemaLocation attribute.

The <package> element must specify the name of the package where your Camel route builder class is implemented.  Camel will automatically pick it up and configure it.

That’s it.  Compile and run your storefront and watch the orders pile up.

The really great thing about this design is that you can implement any endpoint you wish in the Camel route builder and never need to modify your checkout service.  Camel includes components for sending to JMS queues, RMI services, flat files, etc.
In your route, you can also include any number of endpoints, whether for logging/auditing purposes, messaging multiple services, etc.

You can also configure the routes in your Spring configuration file without having to implement a custom route builder class, but that’s a personal preference.

Enjoy the Camel ride.

0 Comments Permalink

Clustering Quartz Jobs

Posted by Darren Pendery Sep 19, 2011

Overview

The standard Elastic Path Quartz jobs are tied to the JVM and are not distributed.  If configured on multiple servers they will be executed on multiple servers simultaneously.  The challenge is that jobs which update entities should not be executed on multiple servers simultaneously due to risks of concurrent access issues.

This can be alleviated by tying them to a specific server when deploying to production.  However, this introduces a single point of failure and is a risk to the reliability and scalability of the system.

To resolve this issue use the persisted scheduler implementation available in the Quartz framework, which uses database tables to persist the job trigger schedules and controls which servers execute which jobs.

It is rather simple to implement this.

  1. Place the attached RowLockSemaphore and JobStoreTX classes into the core library of your development environment
    • These are necessary to resolve a known issue with the locking logic in Quartz 5.1.
  2. Place the attached AbstractProcessorJob class into the core library of your development environment
  3. Extend AbstractProcessorJob for each Quartz job to be distributed
  4. Place the Quartz SQL script appropriate for your RDBMS into your development environment
    • This can be found in the Quartz distribution
  5. Add a Spring module property that specifies the JDBC data source name (usually jdbc/epjndi)
  6. Configure the scheduler factory as well as trigger and job beans in your quartz.xml file

 

 

Warning:  You should rename the packages in the Java files to suit your project and to avoid conflicts if these files are ever included in the EP code base.

 

 

 

 

 

 

The following is an example of a persisted scheduler factory in a quartz.xml file:

 

 

 

<bean id="myPersistedSchedulerFactory"  class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

  <property name="applicationContextSchedulerContextKey"><value>applicationContext</value></property>

  <property name="quartzProperties">

  <props>

    <prop key="org.quartz.scheduler.instanceName">ClusteredScheduler-cmserver</prop>

    <prop key="org.quartz.scheduler.instanceId">AUTO</prop>

    <!-- ThreadPool -->

    <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>

    <prop key="org.quartz.threadPool.threadCount">10</prop>

    <prop key="org.quartz.threadPool.threadPriority">5</prop>

    <!-- Job store -->

    <prop key="org.quartz.jobStore.misfireThreshold">30000</prop>

    <prop key="org.quartz.jobStore.class">com.customer.ep.quartz.JobStoreTX</prop>

    <prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</prop>

    <prop key="org.quartz.jobStore.useProperties">true</prop>

    <prop key="org.quartz.jobStore.dataSource">myDataSourceName</prop>

    <prop key="org.quartz.jobStore.isClustered">true</prop>

    <prop key="org.quartz.jobStore.clusterCheckinInterval">20000</prop>

    <prop key="org.quartz.jobStore.selectWithLockSQL">UPDATE {0}LOCKS SET LOCK_NAME = ? WHERE LOCK_NAME = ?</prop>

    <!-- Configure Plugin -->

    <prop key="org.quartz.plugin.shutdownhook.class">org.quartz.plugins.management.ShutdownHookPlugin</prop>

    <prop key="org.quartz.plugin.shutdownhook.cleanShutdown">true</prop>

    <!--  Datasource -->

    <prop key="org.quartz.dataSource.mfldirectDS.jndiURL">${ep.quartz.datasource}</prop>

    <prop key="org.quartz.dataSource.mfldirectDS.validationQuery">select 0 from dual</prop>

  </props>

  </property>

  <property name="triggers">

  <list>

    <ref bean="customJobTrigger"/>

  </list>

  </property>

</bean>

 

 

Place this scheduler configuration in each application that will act as the container for the clustered jobs.

 

Warning:  Refer to the section below on multiple clustered schedulers when you need separate sets of clustered jobs.

 

 

 

Distributing a Job Bean

Distributed Quartz job beans are serialized and persisted in the Quartz database tables.  As a result, the Spring injection mechanism will not work for these beans.  Therefore, job beans must retrieve any beans they use from the bean factory directly.  The typical MethodInvokingJobDetailFactoryBean configuration will not work.

The attached AbstractProcessorJob Java class, which extends QuartzJobBean, provides a simple framework that allows extensions to easily get to the Elastic Path bean factory.

Extend this class to implement a custom Quartz job class and override the executeProcess method to implement the job logic.

 

 

Note:  Preferably the job logic itself should be implemented in a separate service bean and the custom job bean just calls the necessary method on that service.

 

The following code snippet shows an extension class that calls importJobProcessor.launchImportJob().

 

 

public class ImportProcessorJob extends AbstractProcessorJob {

    @Override

    protected void executeProcess(final ApplicationContext context) {

      try {

        ImportJobProcessor importJobProcessor;

        importJobProcessor = (ImportJobProcessor) context.getBean("importJobProcessor");

        importJobProcessor.launchImportJob();

      } catch (Exception e) {

        // Log the error and handle as appropriate.

      } finally {

        // Any appropriate finally logic.

      }

    }

}

 

Once the job class has been implemented configure the Quartz trigger for the job to use the new job class.

 

 

<bean id="processImportJobTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">

  <property name="jobDetail">

        <ref bean="processImportJob" />

  </property>

  <property name="startDelay" value="10000" />

  <property name="repeatInterval" value="5000" />

  <property name="group" value="MflClusteredScheduler-cmserver"/>

</bean>

<bean name="processImportJob" lazy-init="default" autowire="default" dependency-check="default">

  <property name="jobClass" value="com.myproject.ep.cmserver.quartz.ImportProcessorJob" />

</bean>

 

This is different than using the Quartz MethodInvokingJobDetailFactoryBean used for the OOTB configurations, which allows you to specify a service class and method.  As AbstractProcessorJob extends QuartzJobBean it is already a job bean class, and since you would retrieve beans explicitly from the bean factory, the job bean can then be serialized.

Distributing OOTB Quartz Jobs

The OOTB Quartz jobs in the CM Server are, by default, not distributed.  They should be clustered in a production environment to provide reliability.

To get around this, simply use the  AbstractProcessorJob described above to implement custom extensions for each CM Server job bean and configure them appropriately.

These include the following jobs in the CM Server quartz.xml:

  • topSeller
  • productRecommendation
  • demoProductRecommendation
  • releaseShipment
  • cleanupOrderLocks
  • processImportJob
  • importJobCleanup
  • staleImportJob
  • cleanupSessions

Multiple Clustered Schedulers

It may sometimes be necessary to setup different Quartz job containers for different sets of clustered jobs.  For example, the EP Connect application may contain a completely different set of jobs than the CM Server.  In these cases it is necessary to configure different persisted schedulers for the different sets.

However, there is a catch:  Quartz requires separate database tables for each persisted scheduler even if the scheduler names and triggers are different.  So, even if you specify different scheduler names and triggers, all triggers from all schedulers will be placed in the database tables and each scheduler instance will attempt to execute all of them.  For example, a job in the EP Connect scheduler whose class resides in the EP Connect application will result in a ClassNotFoundException when the CM Server scheduler is executed.

To get around this limitation you will need to create separate sets of Quartz tables in your database, one for each persisted scheduler.

Copy the Quartz SQL scripts to create separate script files for each scheduler.  Modify them to change the prefix on each table:  the default prefix is QRTZ_.  The following excerpt is from a scheduler for the CM Server application and specifies "qrtz_cm_" as the prefix:

 

 

CREATE TABLE qrtz_cm_job_details ...

 

A separate scheduler for the EP Connect application might then use "qrtz_connect_".

Add these scripts to your development and deployment processes.

Then, specify the table prefix in the corresponding scheduler factory configuration in quartz.xml:

<bean id="schedulerFactoryMfl"

  class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

...

<prop key="org.quartz.jobStore.tablePrefix">QRTZ_CM_</prop>

...

</bean>

 

 

 

The prefix is case-insensitive.

 

 

Operations Implications

 

Changes to the quartz schedules require the scheduler tables to be cleared.  Quartz may not always update the trigger schedules from the XML configuration when the scheduler starts up.

 

This typically impacts new deployments.

Include the attached reset SQL script to your operations manual and add steps to deployment manuals to execute it.

 

Note:  Be sure to modify the script to include the prefix if you have specified one. Also, it may be advisable to create separate scripts for each persisted scheduler when configuring multiple clustered schedulers.

 

 

 

 

0 Comments Permalink

Elastic Path offers several types of payment options: credit cards, gift certificates, PayPal, and Google Checkout. The specific implementation of a payment type is referred to as a payment gateway.  New payment types can be added, but a common requirement is to support a new credit card processor. This is the external service used to process your credit card transaction requests. Out of the box, Elastic Path includes support for CyberSource, Verisign, and Authorize.net, as well as a dummy gateway for use during development.

 

This post will show how to add a credit card payment gateway to your implementation.

 

The most difficult aspect is understanding the processor's requirements and communication protocol. Every processor has its own requirements, so you will need to fefer to the developer documentation for your credit card processor for more information on your processor.

 

There are several issues to consider when implementing a gateway for a credit card processor:

 

  • Pre-authorization
  • Settlements, referred to as Captures
  • Refunds
  • Voiding transactions
  • Supported credit cards

 

The Elastic Path framework includes a PaymentGateway interface and a CreditCardPaymentGateway interface, as well as corresponding implementations that cover offer support for all credit card transactions.

 

The CreditCardPaymentGateway also supports 3D Secure authentication, such as Verified-By-Visa and SecureCard from MasterCard with the following methods:

 

checkEnrollment

Used to determine whether or not a credit card account has been   enrolled in a 3D Secure Service.

validateAuthentication

Used to verify the card holder's authentication after they have been   redirected back to your site from the processor's verification site.

 

The Elastic Path storefront already makes use of these methods. Simply implement them to take advantage of the services.

 

Extend the AbstractCreditCardPaymentGatewayImpl and create your credit card payment gateway class as in the following code:

@Entity
1) @DiscriminatorValue("paymentGatewayMyProcessorNameHere")
public class MyProcessorNameHerePaymentGatewayImpl extends AbstractCreditCardPaymentGatewayImpl implements CreditCardPaymentGateway {
 
 
2)  private String type = "paymentGatewayMyProcessorNameHere";
 
@Basic
@Column(name = "TYPE", insertable = false)
public String getType() { return type; }
 
protected void setType(final String type) { this.type = type; }
 
 
3)  protected Set<String> getDefaultPropertyKeys() {
                      Set<String> defaultPropertyKeys = new TreeSet<String>();
                      defaultPropertyKeys.add("merchantId");
                      defaultPropertyKeys.add("username");
                      defaultPropertyKeys.add("password");
                      return defaultPropertyKeys;
      }
 
 
4)  public void preAuthorize(final OrderPayment payment, final Address billingAddress) {
 
 
5)                  String merchantId = getPropertiesMap().get("merchantId").getValue();
 
 
6)                  OrderShipment orderShipment = payment.getOrderShipment();
int number = 0;
for (OrderSku orderSku : orderShipment.getShipmentOrderSkus()) {
      addItemToRequest(request, number, orderSku);
      number++;
}
 
 
7)                  Order order = orderShipment.getOrder();
addOrderDetailsToRequest(order);
 
                      ...
 
      }
 
8)  public void capture(final OrderPayment payment) {
                      ...
      }
 
9)  public void sale(final OrderPayment payment, final Address billingAddress) {
                      ...
      }
 
10)  public void refund(final OrderPayment payment, final Address billingAddress) {
                      ...
      }
 
11)  public void reversePreAuthorization(final OrderPayment payment) {
                      ...
      }
 
12)  public void voidCaptureOrCredit(final OrderPayment payment) {
                      ...
      }
 
13)  public PayerAuthenticationEnrollmentResult checkEnrollment(
final ShoppingCart shoppingCart, final OrderPayment payment) {
                      ...
      }
 
14)  public boolean validateAuthentication(final OrderPayment payment, final String paRes) {
                      ...
      }
}

 

  1. The DiscriminatorValue must be set to the value specified for the gateway's type property.
  2. The type property must be set to a value unique among all payment gateways.
  3. getDefaultPropertyKeys must implemented to return a Set of property keys. The Set can be empty if there are no properties.
  4. The preAuthorize method must be implemented to make a pre-authorization transaction request to the payment processor.
  5. Properties can be accessed from the gateway's properties map.
  6. Access the shipment details through OrderPayment's orderShipments property.
  7. Access the order details through the order property of OrderPayment's orderShipment.
  8. The capture method must be implemented to make a settlement transaction request to the payment processor.
  9. The sale method must be implemented. Its purpose is to perform a combination of pre-authorization and capture at the same time. Some processors offer such a transaction. If not, then this method could make pre-authorization and capture transaction requests
    However, there is currently no logic using it in Elastic Path. So, it can be ignored
  10. The refund method must be implemented to make refund transaction requests to the processor.
  11. The reversePreAuthorization method must be implemented to make transaction requests to the processor that reverse the pre-authorization.
    Some credit card processors support this feature, but others do not. Refer to your processor's developer documentation for more information.
  12. The voidCaptureOrCredit method must be implemented to make transaction requests to the processor that void a previous capture or credit transaction.  Void transactions typically only work on transactions made the same day.
  13. The checkEnrollment method must be implemented to make requests to the processor to determine whether or not a credit card has been enrolled in the 3D Secure program.
  14. The validateAuthentication method must be implemented to make requests to the processor to verify a 3D Secure payment response after the customer has authenticated themselves on the processor's 3D Secure server.

 

Error Handling

Errors occasionally happen. With credit cards, they can frequently happen. Elastic Path implements several exceptions to account for the most common problems. These exceptions should be thrown from the preAuthorize or capture methods when appropriate.

 

CardDeclinedException

Thrown when the card number is declined.

CardExpiredException

Thrown when the card has expired.

CardErrorException

Thrown for any "generic" errors.

InsufficientFundException

Thrown when the card account does not have sufficient funds to cover   the request.

InvalidAddressException

Thrown when the processor determines the billing address is invalid.

AmountLimitExceededException

Thrown when the card has exceeded its limit.

InvalidCVV2Exception

Thrown when the CVV number supplied is invalid.

 

To add your own exceptions, extend one of the following exception classes, and throw it as appropriate:

 

PaymentProcessingException

Extended for general processing errors such as insufficient funds.

CardErrorException

Extended for card errors, such as declined.

 

Configuring the Payment Gateway Factory

In order to use your new credit card gateway, you'll need to make the payment gateway factory aware of it. The paymentGatewayFactory bean defined in the domainModel.xml file must include a new entry.

 

<bean id="paymentGatewayFactory"

      class="com.elasticpath.domain.payment.impl.PaymentGatewayFactoryImpl">

      <property name="paymentGateways">

                      <map>

                                      ...

                                                <entry key="paymentGatewayMyProcessorNameHere">

                                                                <value>

com...MyProcessorNameHerePaymentGatewayImpl

</value>

                                                </entry>

                                      ...

                      </map>

      </property>

</bean>

The entry's key must be set to the payment gateway's type property that was specified in the implementation class.

 

Using the Payment Gateway

To use your new credit card gateway implementation, you will first need to create a new payment gateway configuration in the CM Client, and then configure your store to use it.

 

In the CM Client:

  • Open the Configuration activity
  • Select Payment Gateways
  • Click Create Gateway
  • Type in your gateway name
  • Select your new gateway implementation the Type dropdown

 

To use the new payment gateway in your store:

  • Open the Configuration activity
  • Select Stores
  • Edit your store
  • Select the Payments tab
  • Select your new Payment Gateway in the Credit Card Payment Gateway dropdown
  • Choose the options for enabling the CCV value, saving credit card numbers, and the supported card types.

Caveats

Many processors will provide a testing infrastructure. Occasionally, this means a test merchant account, test card numbers, and possibly a different URL from what should be used in production.  Refer to your processor's development documentation to understand how to test it.  When moving to production, the first thing you will want to do is to test it. This means using a live credit card. There aren't many ways around this. However, you should have access to a console to manage your merchant account, and be able to cancel any transactions.

0 Comments 0 References Permalink