What is Authorize.net SIM? Who should use Authorize.net SIM? What is the difference between Authorize.net AIM and SIM?

What is Authorize.net SIM?

Authorize.net, one of the most popular and respected merchant gateways in the world, offers multiple ways to utilize their gateways. SIM stands for Server Integration Method.

SIM uses scripting techniques to authenticate transactions with a unique transaction fingerprint.

The Authorize.Net Payment Gateway can handle all the steps in the secure transaction process – payment data collection, data submission and the response to the customer – while keeping Authorize.Net virtually transparent.

  • Payment gateway hosted payment form employs 128-bit SSL data encryption.
  • Digital fingerprints enhance security, providing multiple layers of authentication.
  • Customize the look and feel of the payment gateway hosted payment form and/or receipt page.
Who should use Authorize.net SIM?

SIM provides a customizable, secure hosted payment form to make integration easy for Web merchants that do not have an SSL certificate.

What is the difference between Authorize.net AIM and SIM?

AIM (Advanced Integration Method) and SIM differ in a few ways.

AIM can be used on your own website, SIM is processed on Authorize.net, ensuring a secure process (important for PCI compliance). AIM can be accessed via mobile, SIM can not. For AIM, a SSL is required. For SIM, it is not (because the processing is handled externally on Authorize.net). At a basic level, AIM offers more control, SIM offers more security. For more on these choices, see Authorize.net’s comparison. 

For more information on Authorize.net, check out their site at authorize.net

Subscriptions with Paypal IPN

subscr_eot is sent when a user’s last paid interval has expired. subscr_cancel is sent as soon as the use cancels the subscription – for example:

User signs up on day 1 for a subscription which is billed once a month.
subscr_signup is sent immediately, subscr_payment is sent as soon as payment goes through (usually immediately as well).

On day 13, the user cancels. subscr_cancel is immediately sent, although the user has technically paid through to day 30. Cancelling at this point is up to you.

On day 30, subscr_eot is sent – the user has cancelled, and this is the day which his last payment paid until.

Not much changes with trial subscriptions – if a user cancels before a trial subscription is up, subscr_cancel is sent immediately, and subscr_eot is sent at the end of the trial.

IPN response of subscr_payment type paypal

IPN POST Vars from Paypal:

mc_gross=0.01
protection_eligibility=Eligible
address_status=confirmed
payer_id=HUHDESDQEH76W
address_street=37347 Waterman Ave
payment_date=13:20:50 Oct 20
2011 PDT
payment_status=Completed
charset=windows-1252
address_zip=93550
first_name=Frank
mc_fee=0.01
address_country_code=US
address_name=Frank Haubrich
notify_version=3.4
subscr_id=I-0PV8RES942RA
custom=4
payer_status=verified
business=cgarrison@girlscoutsla.org
address_country=United States
address_city=Palmdale
verify_sign=AIyzbk41No2OS3BwZ0kGZdVXDK5HASzPFk2u1NAOJ1YMje3iAuFAlliJ
payer_email=franx@ebbtidegraphics.com
txn_id=3UW55514MF1126148
payment_type=instant
payer_business_name=Frank Haubrich
last_name=Haubrich
address_state=CA
receiver_email=cgarrison@girlscoutsla.org
payment_fee=0.01
receiver_id=VD757ZEQUC9AA
txn_type=subscr_payment
item_name=Membership Subscription: Annual
mc_currency=USD
residence_country=US
transaction_subject=
payment_gross=0.01
ipn_track_id=9FdlB-7GLN72aZeHZh4Z4Q,

First Data Global E4 Gateway API PHP Example ( Bank Of America )

I have a client with a First Data merchant account. She has also bought into the First Data Gateway – Global Gateway E4 Solution – as they call it.

The Global E4 Solution has both SOAP and REST API’s available. I hate SOAP. I always choose REST when available. With the REST API, the data must be in either XML or JSON format. I chose JSON.

Now that I know what I want to use, I needed to find an example of how to make it work with PHP on her website. I searched Google. I searched the First Data site. No luck. I only found examples for SOAP, which I didn’t want to use.

This meant I would have to figure it out on my own. Uggh. I hate it when that happens. But, that gives me a good opportunity for a blog post.

After some trial and error, this is what I found to work for me:

$url = 'https://api.demo.globalgatewaye4.firstdata.com/transaction/v11';

$data = array("gateway_id" => "AD1234-00", "password" => "password", "transaction_type" => "00", "amount" => "11", "cardholder_name" => "Test", "cc_number" => "411111111111111", "cc_expiry" => "0314");
$data_string= json_encode($data);

// Initializing curl
$ch = curl_init( $url );
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json; charset=UTF-8;','Accept: application/json' ));

// Getting results
$result = curl_exec($ch);

// Getting jSON result string
$data_string = json_decode($result);

if ($data_string) {
if ($data_string->bank_resp_code == '100') {
print('Approved!');
} else {
print($data_string->bank_message);
}
} else {
print($result);
}

Some things to keep in mind:

This script is for the Demo site. You will need to replace the URL with the production URL before it will work in production.
You will need to replace the gateway_id and the password with your own gateway_id and password found inside your First Data account
Hopefully, this will help you save some time if you need to use the First Data Global E4 Gateway API with PHP.

Silent Post URL ( Authorize.net Automatic Recurring Payment )

The Silent Post URL function is similar to the carbon copy (CC) function of an email message.

This feature allows you to specify a URL, or Web address, to which the payment gateway should copy the transaction response that is sent to the configured Relay Response URL. If you are not using Relay Response, the feature allows you to specify an additional URL to which transaction responses can be posted. If you are using Automated Recurring Billing (ARB), this feature also allows you to receive a transaction response for each payment submitted in a subscription. The Silent Post URL is the only way you can receive a name/value pair response for transactions in an ARB subscription.

Note: The Silent Post URL feature does not apply to transactions made using the Hosted Payment Form.

 

To add or edit a silent post URL:

Step 1: In the URL text field, enter the URL to which the payment gateway should copy the transaction relay response. This URL must start with either http:// or https://.

Step 2: Click Submit. A confirmation message indicates that the URL has been added.

IMPORTANT: When the Silent Post URL feature is enabled, responses for both ARB transactions and regular transactions will post to the specified URL. To determine which transaction responses are for ARB transactions, you can search the response for the x_subscription_id ( Subscription ID) and the x_subscription_paynum (Payment Number) fields. These fields are only returned in the response for individual payments from an ARB subscription.

Getting Started With The PayPal API

PayPal is the most popular platform for receiving online payments today. The ease of opening a PayPal account and receiving payments compared to opening a merchant account with a traditional payment gateway is probably the number one reason for its popularity, with a close second being the comprehensive API that PayPal provides for its payment services. In this post, I will break down some of the techniques and approaches to working with the PayPal API, in order to make integration and troubleshooting simpler and easier.

Disclaimer: PayPal’s API is among the worst I’ve ever had to deal with. Inconsistencies, sometimes poor or conflicting documentation, unpredictable failures and account changes, and major differences between the live and sandbox versions all conspire to make the PayPal API quite a pain in the arse to work with. Over the years, I’ve taken my lumps from working quite a bit with the PayPal API, and I’ve published the results of my hard-learned lessons as a commercial PHP PayPal API component on the source-code marketplace Binpress.

paypal-logo

The Different Payment Options

PayPal offers a variety of payment options, which might be confusing at first:

  • Express Checkout
    The premier PayPal service. Express Checkout allows you to receive payments without having a merchant account and without having to meet special requirements other than verifying your account (either via a bank account or a credit card). Previously, you could receive Express Checkout payments from PayPal users only, but PayPal has since added a credit-card option for non-PayPal users, making this service accessible to practically anyone with a major credit card. Note that the Express Checkout process occurs on PayPal’s platform and thus can never be fully integrated in your website’s experience.
  • Direct Payment
    The Direct Payment method allows you to receive credit-card payments directly through an API call. This enables you to host the payment process on your website in full, which might make for a more complete shopping experience for your customers. The Direct Payment method has several variations that enable you to authorize a payment and complete it at a later date: the appropriately named Authorization and Capture methods. These variations are a part of the Website Payments Pro 5 API, which is available only to US, Canadian and UK accounts.
  • Recurring Payments
    This allows you to set up a recurring transaction (i.e. a subscription payment).
  • Mass Payments
    This allows you to transfer money to multiple accounts at once.
  • Adaptive Payments
    Here is another API for sending funds to multiple recipients, with some differences from the Mass Payments API. (Did I mention that the PayPal API is confusing and a bit redundant?)

This list is not comprehensive, but it covers the main payment options (see the API documentation for more).

Making API Requests

PayPal supports two main formats over HTTP: NVP and SOAP. NVP is short for Name-Value Pair, and SOAP stands for Simple Object Access Protocol. I will cover the NVP approach, which I prefer to SOAP’s relatively verbose and complex syntax.

Each of the API methods has different parameters, but they all share some basic parameters, which are used to identify the API account and sign the transaction. These include:

  • USER
    Your PayPal API user name.
  • PWD
    Your PayPal API password.
  • VERSION
    The version number of the NVP API service, such as 74.0 6 (the most recent as of this writing).
  • SIGNATURE
    Your PayPal API signature string. This parameter is optional if you use a certificate to authenticate.

The last required parameter is METHOD, which declares which API method we are calling.

Requests are made over HTTPS. We’ll use cURL to build our basic request, and then encapsulate the process in a class:

class Paypal {
   /**
    * Last error message(s)
    * @var array
    */
   protected $_errors = array();

   /**
    * API Credentials
    * Use the correct credentials for the environment in use (Live / Sandbox)
    * @var array
    */
   protected $_credentials = array(
      'USER' => 'seller_1297608781_biz_api1.lionite.com',
      'PWD' => '1297608792',
      'SIGNATURE' => 'A3g66.FS3NAf4mkHn3BDQdpo6JD.ACcPc4wMrInvUEqO3Uapovity47p',
   );

   /**
    * API endpoint
    * Live - https://api-3t.paypal.com/nvp
    * Sandbox - https://api-3t.sandbox.paypal.com/nvp
    * @var string
    */
   protected $_endPoint = 'https://api-3t.sandbox.paypal.com/nvp';

   /**
    * API Version
    * @var string
    */
   protected $_version = '74.0';

   /**
    * Make API request
    *
    * @param string $method string API method to request
    * @param array $params Additional request parameters
    * @return array / boolean Response array / boolean false on failure
    */
   public function request($method,$params = array()) {
      $this -> _errors = array();
      if( empty($method) ) { //Check if API method is not empty
         $this -> _errors = array('API method is missing');
         return false;
      }

      //Our request parameters
      $requestParams = array(
         'METHOD' => $method,
         'VERSION' => $this -> _version
      ) + $this -> _credentials;

      //Building our NVP string
      $request = http_build_query($requestParams + $params);

      //cURL settings
      $curlOptions = array (
         CURLOPT_URL => $this -> _endPoint,
         CURLOPT_VERBOSE => 1,
         CURLOPT_SSL_VERIFYPEER => true,
         CURLOPT_SSL_VERIFYHOST => 2,
         CURLOPT_CAINFO => dirname(__FILE__) . '/cacert.pem', //CA cert file
         CURLOPT_RETURNTRANSFER => 1,
         CURLOPT_POST => 1,
         CURLOPT_POSTFIELDS => $request
      );

      $ch = curl_init();
      curl_setopt_array($ch,$curlOptions);

      //Sending our request - $response will hold the API response
      $response = curl_exec($ch);

      //Checking for cURL errors
      if (curl_errno($ch)) {
         $this -> _errors = curl_error($ch);
         curl_close($ch);
         return false;
         //Handle errors
      } else  {
         curl_close($ch);
         $responseArray = array();
         parse_str($response,$responseArray); // Break the NVP string to an array
         return $responseArray;
      }
   }
}

Note that I use a CA certificate file for SSL certificate validation. You can obtain the file from the cURL website or any trusted source. Update the path to the certificate file according to where you’ve placed it.

The response returned will be in NVP format as well, and I reformat it into an array before returning it. A parameter named ACK signifies the status of the request: Success or SuccessWithWarning when the request succeeds, and Error or Warning when the request fails.

A request could fail for many reasons, and there are different reasons for each API method, which are covered in detail in the manual. We’ll go over some further down in this article and look at ways to handle them. Keep in mind that the parameter values are case-sensitive, so code against them accordingly.

Express Checkout

One of the most popular APIs is the Express Checkout API, which enables you to receive payments without opening a Website Payments Pro account (which is available only to verified US accounts) or hosting the actual transaction yourself (which requires additional security).

The Express Checkout process works as follows:

  1. We request a checkout token from PayPal using the transaction details;
  2. If successful, we redirect the user to the PayPal endpoint using the received token;
  3. The user completes or cancels the payment on the PayPal platform and is redirected back to our website;
  4. We complete the payment either when the user is redirected back or via an Instant Payment Notification 7 (IPN).

scr_ukexpresscheckout-cut

1. Getting the Checkout Token: SetExpressCheckout

We initiate the Express Checkout process by passing the order details to the PayPal API, and we receive a token string that identifies it. This token would be used in the next step to redirect to PayPal.

Here are the required parameters:

  • METHOD
    This is the API method that we’re using (i.e. SetExpressCheckout).
  • RETURNURL
    The URL that the user will be redirected to after the payment process is completed.
  • CANCELURL
    The URL that the user will be redirected to after having cancelled the payment process.
  • PAYMENTREQUEST_0_AMT
    The transaction’s total amount. This must have two decimal places, with the decimal separator being a period (.). The optional thousands separator must be a comma (,).
  • PAYMENTREQUEST_0_ITEMAMT
    The total cost of the items in the order, excluding shipping, taxes and other costs. If there are no extra costs, then it should be the same value as PAYMENTREQUEST_0_AMT.

We can pass additional parameters to add more information about the order, some of which have default values:

  • PAYMENTREQUEST_0_CURRENCYCODE
    The payment’s currency, as a three-letter code. The default is USD.
  • PAYMENTREQUEST_0_SHIPPINGAMT
    The total shipping costs for this order.
  • PAYMENTREQUEST_0_TAXAMT
    The total tax amount for this order. This is required if per-item tax is specified (see below).
  • PAYMENTREQUEST_0_DESC
    The order’s description.

We can also add details about individual items in the order:

  • L_PAYMENTREQUEST_0_NAMEm
    The item’s name.
  • L_PAYMENTREQUEST_0_DESCm
    The item’s description.
  • L_PAYMENTREQUEST_0_AMTm
    The item’s cost.
  • L_PAYMENTREQUEST_0_QTYm
    The quantity of an item.

The variable index m identifies the item. (Use the same variable for all details of the same item.)

There are many other optional parameters, which can be found in the API documentation.

We’ll use the function that we wrote above to build the SetExpressCheckout request:

//Our request parameters
$requestParams = array(
   'RETURNURL' => 'http://www.yourdomain.com/payment/success',
   'CANCELURL' => 'http://www.yourdomain.com/payment/cancelled'
);

$orderParams = array(
   'PAYMENTREQUEST_0_AMT' => '500',
   'PAYMENTREQUEST_0_SHIPPINGAMT' => '4',
   'PAYMENTREQUEST_0_CURRENCYCODE' => 'GBP',
   'PAYMENTREQUEST_0_ITEMAMT' => '496'
);

$item = array(
   'L_PAYMENTREQUEST_0_NAME0' => 'iPhone',
   'L_PAYMENTREQUEST_0_DESC0' => 'White iPhone, 16GB',
   'L_PAYMENTREQUEST_0_AMT0' => '496',
   'L_PAYMENTREQUEST_0_QTY0' => '1'
);

$paypal = new Paypal();
$response = $paypal -> request('SetExpressCheckout',$requestParams + $orderParams + $item);

2. Redirecting to PayPal Using the Checkout Express Token

If the request is successful, we’ll receive a checkout token in the TOKEN parameter of the response.

if(is_array($response) && $response['ACK'] == 'Success') { //Request successful
      $token = $response['TOKEN'];
      header( 'Location: https://www.paypal.com/webscr?cmd=_express-checkout&token=' . urlencode($token) );
}

The user now goes through the purchase process on PayPal’s website. When they confirm or cancel it, they will return to one of the URLs that we’ve specified in the request.

3. Completing the Transaction

Assuming the user confirms the transaction, they will be redirected to our website by PayPal. At this point, we should use two relevant API methods: DoExpressCheckoutPayment will complete the transaction, but before that we might want to get additional information on the buyer using GetExpressCheckoutDetails.

PayPal will redirect the user back from the purchase with the checkout token, which we will use to call those methods. The token will be available in the URL query parameters via the token parameter. We will check for its existence in the confirmation URL and then send our API requests if we find it.

The GetExpressCheckoutDetails method requires only the checkout token. DoExpressCheckoutPayment requires a couple of additional parameters:

  • PAYMENTREQUEST_0_PAYMENTACTION
    This is the payment action. It should be set to Sale unless we’ve specified a different action in the SetExpressCheckout method (possible values include Authorization and Capture).
  • PAYERID
    This is the unique identification for the PayPal account. This, too, is returned in the URL query parameters (in the PayerID parameter) and can also be retrieved from the details returned by GetExpressCheckoutDetails.
if( isset($_GET['token']) && !empty($_GET['token']) ) { // Token parameter exists
   // Get checkout details, including buyer information.
   // We can save it for future reference or cross-check with the data we have
   $paypal = new Paypal();
   $checkoutDetails = $paypal -> request('GetExpressCheckoutDetails', array('TOKEN' => $_GET['token']));

   // Complete the checkout transaction
   $requestParams = array(
       'TOKEN' => $_GET['token'],
       'PAYMENTACTION' => 'Sale',
       'PAYERID' => $_GET['PayerID'],
       'PAYMENTREQUEST_0_AMT' => '500', // Same amount as in the original request
       'PAYMENTREQUEST_0_CURRENCYCODE' => 'GBP' // Same currency as the original request
   );

   $response = $paypal -> request('DoExpressCheckoutPayment',$requestParams);
   if( is_array($response) && $response['ACK'] == 'Success') { // Payment successful
       // We'll fetch the transaction ID for internal bookkeeping
       $transactionId = $response['PAYMENTINFO_0_TRANSACTIONID'];
   }
}

Direct Payment

The Direct Payment API allows you to receive payments directly on your website or application, giving you complete control over the checkout process. PayPal tends to push users to register and use a PayPal account, which is understandable, but this conflicts somewhat with our interest to make the payment process as simple and clear as possible for our customers. For this reason, full control over the checkout process is preferred and gives us more options to optimize sales and generate more sales.

scr_ukdirectpayment-cut

The process is a bit simpler than that of Express Checkout, because the entire interaction occurs on our website, and we need to perform just one API call to process a normal payment: DoDirectPayment.

A couple of more API requests are required if you want to perform a transaction that is billed at a later date (for example, when you ship the product or confirm availability). These would be the Authorization & Capture API methods, which I will not cover in this post, but be aware that this option exists.

DirectPayment Parameters

DirectPayment requires different parameters than Express Checkout, as to be expected. While the transaction details parameters are similar (with different key names, to make it more interesting), the method also requires credit-card and address information.

DirectPayment’s basic parameters:

  • METHOD
    This is DoDirectPayment.
  • IPADDRESS
    This is the IP address of the payer. In PHP, we can retrieve it using the superglobal $_SERVER['REMOTE_ADDR']. You’ll have to do a bit more work 8 to get the IP when dealing with set-ups that have a proxy between the PHP process and the outside network (such as nginx).
  • PAYMENTACTION
    This is the type of action that we want to perform. A value of Sale indicates an immediate transaction. A value of Authorization indicates that this transaction will not be performed immediately, but rather will be captured later using the Authorization & Capture API mentioned earlier.

Credit-card details:

  • CREDITCARDTYPE
    The credit-card type (Visa, MasterCard, etc.). See the API documentation for the full list.
  • ACCT
    The credit-card number. (Don’t you love these abbreviated key names?) This must conform to the particular format of the card’s type.
  • EXPDATE
    The expiration date, in MMYYYY format (i.e. a two-digit month and a four-digit year, as one string).
  • CVV2
    The “card verification value,” or security code, as it’s sometimes known.

Payer information and address parameters:

  • FIRSTNAME, LASTNAME
    The payer’s first name and last name, respectively (in separate fields). You can also provide an email address in an EMAIL parameter, but it’s not required.
  • CITY, STATE, COUNTRYCODE, ZIP
    The city, state, country code (as a two-letter code) and zip code parts of the address, all required.
  • STREET, STREET2
    Two lines for the address (only the first is required).

This address will be used in the address verification system (AVS). You’ll receive a specific error code if a transaction has failed due to an address verification failure.

The payment details parameters are the same as the ones for Express Checkout, but with slightly different names (AMT, ITEMAMT, CURRENCYCODE, SHIPPINGAMT, TAXAMT and DESC) and without the PAYMENTREQUEST_0_ prefix. Refer to the previous section or the API documentation for specific details on those.

Similarly, the item details parameters are similar to those of Express Checkout. These include L_NAMEm, L_DESCm, L_AMTm and L_QTYm, giving you granular control of item details in the order summary. The m integer variable is used to account for multiple items (replace with 0, 1 and so on for numbered items in the order). See the API documentation for a comprehensive list of item details.

Performing the Transaction

Sending the request using our function is very similar to GetExpressCheckoutToken. We pass all of the parameters into the request function as before, with the method set to DoDirectPayment.

$requestParams = array(
   'IPADDRESS' => $_SERVER['REMOTE_ADDR'],
   'PAYMENTACTION' => 'Sale'
);

$creditCardDetails = array(
   'CREDITCARDTYPE' => 'Visa',
   'ACCT' => '4929802607281663',
   'EXPDATE' => '062012',
   'CVV2' => '984'
);

$payerDetails = array(
   'FIRSTNAME' => 'John',
   'LASTNAME' => 'Doe',
   'COUNTRYCODE' => 'US',
   'STATE' => 'NY',
   'CITY' => 'New York',
   'STREET' => '14 Argyle Rd.',
   'ZIP' => '10010'
);

$orderParams = array(
   'AMT' => '500',
   'ITEMAMT' => '496',
   'SHIPPINGAMT' => '4',
   'CURRENCYCODE' => 'GBP'
);

$item = array(
   'L_NAME0' => 'iPhone',
   'L_DESC0' => 'White iPhone, 16GB',
   'L_AMT0' => '496',
   'L_QTY0' => '1'
);

$paypal = new Paypal();
$response = $paypal -> request('DoDirectPayment',
   $requestParams + $creditCardDetails + $payerDetails + $orderParams + $item
);

if( is_array($response) && $response['ACK'] == 'Success') { // Payment successful
   // We'll fetch the transaction ID for internal bookkeeping
   $transactionId = $response['TRANSACTIONID'];
}

There are plenty of parameters, but all relatively simple.

Error Handling

In a perfect world, this section would not exist. In reality, you will be referring to it quite a lot. PayPal can fail a transaction for a multitude of reasons, not all of which you can control.

The $response variable we returned from our paypalApiRequest() function could contain a different value than Success for the ACK parameter. That value could be:

  • Success
    Indicates a successful operation.
  • SuccessWithWarning
    Indicates a successful operation, and that messages were returned in the response that you should examine.
  • Failure
    Indicates a failed operation, and that the response contains one or more error messages explaining the failure.
  • FailureWithWarning
    Indicates a failed operation, and that messages were returned in the response that you should examine.

This gives us two success statuses and two failure statuses. The mock code above tests for the Success value only, but we could change it to check for SuccessWithWarning as well; and keep in mind that we need to find out what the warning is. A common scenario is that a Direct Payment charge will have been performed successfully, but the credit-card company responds that the transaction has failed, for whatever reason.

Errors from PayPal are returned in four parameters in the response:

  • L_ERRORCODE0
    A numeric error code, which can referenced against PayPal’s error code list 9 (there are quite a few).
  • L_SHORTMESSAGE0
    A short error message describing the problem.
  • L_LONGMESSAGE0
    A longer error message describing the problem.
  • L_SEVERITYCODE0
    The severity code. (I couldn’t find any useful documentation on this, and it doesn’t really matter, so let’s put it aside.)

The 0 part of these parameters is an incrementing integer for multiple error message (1, 2, etc.).

Here are some common errors you’ll run into:

  • 10002
    Authentication or authorization failed. This usually indicates invalid API credentials, or credentials that do not match the type of environment you are working in (such as a live or sandbox environment).
  • 81***
    Missing parameter. There are quite a few of these, all starting with 81. Each refers to a specific required parameter that is missing from the request.
  • 104**
    Invalid argument. This indicates that one of the supplied parameters has an invalid value. Each argument has specific errors, all starting with 104. A common one is 10413, which means that the total cost of the cart items does not match the order’s amount (i.e. the total amount parameter, AMT, does not equal the items’ total plus shipping, handling, taxes and other charges).

How Do We Handle These Errors in Practice?

PayPal error messages vary and could contain private information that you do not want your users to see (such as an invalid merchant configuration). That being the case, showing PayPal error messages directly to users is not advisable, even though some of them might be useful.

In most cases, I would do the following:

  1. Set up a white-list array of errors that can be shown safely (such as a missing credit-card number and expiration date);
  2. Check the response code against that array;
  3. If the error message is not white-listed, then display a generic message, such as “An error has occurred while processing your payment. Please try again in a few minutes, or contact us if this is a recurring issue.”

If an error falls outside of the white-listed array, I will also log it to a file on the server and send an email to the administrator, with the full details so that someone is up to speed on payment failures. In fact, logging PayPal requests and responses is good practice regardless of errors, so that you can monitor and troubleshoot payment failures (I provide this option in the commercial component that I mentioned at the beginning of this article).

Ready To Get Started With The PayPal API?

In this post, I’ve covered two of the most widely used API methods, as well as error handling with the PayPal API. This should be enough for you to get started using the most popular payment platform online.

The PayPal API has many more methods and processes, more than can be covered in any one post. Once you get up to speed on the basic methods, learning the others should be relatively straightforward (even if somewhat exhausting). I hope this guide has given you a good head start on using the API. If you have questions or comments, I would love to hear from you in the comments!

Disclaimer: PayPal’s API is among the worst I’ve ever had to deal with. Inconsistencies, sometimes poor or conflicting documentation, unpredictable failures and account changes, and major differences between the live and sandbox versions all conspire to make the PayPal API quite a pain in the arse to work with. Over the years, I’ve taken my lumps from working quite a bit with the PayPal API, and I’ve published the results of my hard-learned lessons as a commercial PHP PayPal API component on the source-code marketplace Binpress.

Library for using Authorize.net’s ARB (automated recurring billing)

<?php

/**
 * Library for using Authorize.net's ARB (automated recurring billing)
 *
 * @author      Brandon Cordell(brandon@leadandrock.com)
 * @link        http://projects.leadAndrock.com/cake_arb
 * @copyright   Copyright (c) 2010, Brandon Cordell
 * @version     1.0
 *
 *
 *
 * Usage:
 *
 * $arb = new AuthorizeARB(); // you can pass the login id and transaction key as parameters, or set them in the class
 *
 * // set a trial subscription
 * $arb->setTrial('1', '0.00');
 *
 * // set the payment schedule
 * $arb->setPaymentSchedule('1', 'months', 'now', '9999', '99.00');
 *
 * // set payment type/info
 * $arb->setPayment('creditcard', array($cc['number'], $cc['exp'], $cc['ccv']));
 *
 * // set billing info
 * $arb->billTo($billing_data['fname'], $billing_data['lname']);
 *
 * // get the response back from Authorize.Net
 * $response = $arb->createSubscription();
 *
 */

class AuthorizeARB extends object {

   
// the merchants API login ID (must be valid)
   
private $loginId = '';

   
// the merchants transaction key (must be valid)
   
private $transactionKey = '';

   
// active API url (testing mode by default)
   
private $activeUrl = 'https://apitest.authorize.net/xml/v1/request.api';

   
// authorize.net ARB production url
   
private $productionUrl = 'https://api.authorize.net/xml/v1/request.api';

   
// authorize.net ARB test url
   
private $testUrl = 'https://apitest.authorize.net/xml/v1/request.api';

   
// authorize.net ARB xml schema
   
private $xmlSchema = 'https://api.authorize.net/xml/v1/schema/AnetApiSchema.xsd';

   
// array to hold error codes and descriptions
   
private $errors = array(
       
'E00001' => array('message' => 'An unexpected system error occurred while processing this request.'),
       
'E00002' => array('message' => 'The only supported content-types are text/xml and application/xml.'),
       
'E00003' => array('message' => 'An error occurred while parsing the XML request.'),
       
'E00004' => array('message' => 'The name of the requested API method is invalid.')
   
);

   
// info about your trial period
   
private $trialInfo = array();

   
// info about your paymentSchedule
   
private $paymentScheduleInfo = array();

   
// info about your payment
   
private $paymentInfo = array();

   
// array to hold payment schedule information
   
private $paymentSchedule = null;

   
// array to hold the order info
   
private $orderInfo = array();

   
// array to hold the customer info
   
private $customerInfo = array();

   
// array to hold the billing info
   
private $billTo = array();
   
   
// we'll hold the subscription id throughout a couple methods
   
public  $subscriptionId = null;

   
// reference id, if you want to pass one to authorize.net
   
public  $referenceId = null;

   
/**
     * Authorize ARB construct
     *
     * For abstraction sake, you can pass in your
     * loginId and transactionKey on initialization
     *
     * @param string $loginId
     * @param string $transactionKey
     */

   
public function  __construct($loginId = null, $transactionKey = null) {

       
// FireCake::log($this->errors);

       
if($loginId && $transactionKey) {

            $this
->loginId = $loginId;
            $this
->transactionKey = $transactionKey;

       
} elseif ( ($loginId && !$transactionKey) || (!$loginId && $transactionKey) ) {

            trigger_error
('You must either pass both parameters, or no parameters', '512');
       
}
   
}

   
/**
     * Set your authorize.net ARB environment
     *
     * Accepts testing and production (testing is set by default)
     *
     * @param  string  $mode
     * @return bool
     */

   
public function setMode($mode) {

       
switch(strtolower($mode)) {

           
case 'testing':
                $this
->activeUrl = $this->testUrl;
               
break;

           
case 'production':
                $this
->activeUrl = $this->productionUrl;
               
break;

           
default:
                trigger_error
('Invalid mode passed, defaulting to testing mode', '512');
                $this
->activeUrl = $this->testUrl;
               
break;
       
}
   
}

   
/**
     * Create ARB subscription
     *
     * @param array $data   A full array of data to create the subscription
     * @param bool  $manual Must be true, in order to pass in a data array
     */

   
public function createSubscription() {
       
       
return $this->buildRequest('Create');
   
}

   
/**
     * Update ARB subscription:
     * To update an ARB subscription you need to pass in the subscriptionID (required by authorize.net)
     *
     * @param   mixed   $subscriptionId
     */

   
public function updateSubscription($subscriptionId) {

       
if(!$subscriptionId)
            trigger_error
('Parameter is required', E_ERROR);

        $this
->subscriptionId = $subscriptionId;
        $this
->buildRequest('Update');
   
}

   
/**
     * Cancel ARB subscription:
     * To cancel an ARB subscription you need to pass in the subscriptionID (required by authorize.net)
     *
     * @param   mixed   $subscriptionID
     * @param   mixed   $refId    Optional merchant assigned reference ID for this request
     */

   
public function cancelSubscription($subscriptionId) {

       
if(!$subscriptionId)
            trigger_error
('Parameter is required', '2');

        $this
->subscriptionId = $subscriptionId;
        $this
->buildRequest('Cancel');
   
}
   
   
/**
     * Set the trial occurrences and amounts in your request (Create only)
     *
     * @param int $occurences - Number of billing or payments in the trial period
     * @param int $amount     - The amount to be charged for each payment during the trial period
     */

   
public function setTrial($occurrences, $amount) {

        $this
->trialInfo[0] = $occurrences;
        $this
->trialInfo[1] = $amount;
   
}

   
/**
     * Set the payment schedule of your request (Create and Update Only)
     */

   
public function setPaymentSchedule($intervalLength, $intervalUnit, $startDate, $totalOccurrences, $amount) {

        $formattedDate
= date('Y-m-d', strtotime($startDate));

        $this
->paymentScheduleInfo[0] = $intervalLength;
        $this
->paymentScheduleInfo[1] = $intervalUnit;
        $this
->paymentScheduleInfo[2] = $formattedDate;
        $this
->paymentScheduleInfo[3] = $totalOccurrences;
        $this
->paymentScheduleInfo[4] = $amount;
   
}

   
/**
     * Set the payment information of your request (Create and Update Only)
         *
         * TODO: Add check/money order logic. It's only credit card right now
     */

   
public function setPayment($paymentType, $paymentInfo = array()) {

        $months
= array('01' => 'January', '02' => 'February', '03' => 'March',
                       
'04' => 'April',   '05' => 'May',      '06' => 'June',
                       
'07' => 'July',    '08' => 'August',   '09' => 'September',
                       
'10' => 'October', '11' => 'November', '12' => 'December');

       
switch(strtolower($paymentType)) {

           
case 'creditcard':
               
                $exp
= explode('/', $paymentInfo[1]);
                $exp_date
= date('Y-m', strtotime($months[$exp[0]].' 20'.$exp[1]));

                $this
->paymentInfo[0] = $paymentInfo[0];
                $this
->paymentInfo[1] = $exp_date;
                $this
->paymentInfo[2] = $paymentInfo[2];
               
break;

           
default:
               
break;
       
}
   
}

   
/**
     * Set up the billing information
     */

   
public function billTo($firstName, $lastName, $company = null, $address = null, $city = null, $state = null, $zip = null, $country = null) {

        $this
->billTo['firstName'] = $firstName;
        $this
->billTo['lastName']  = $lastName;

       
if($company) $this->billTo['company'] = $company;
       
if($address) $this->billTo['address'] = $address;
       
if($city)    $this->billTo['city']    = $city;
       
if($state)   $this->billTo['state']   = $state;
       
if($zip)     $this->billTo['zip']     = $zip;
       
if($country) $this->billTo['country'] = $country;
   
}

   
/**
     * Builds the request to be sent to $this->sendRequest()
     *
     * @param
     * @return
     */

   
public function buildRequest($requestType) {
       
        $request
= "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
                   "
<ARB{$requestType}SubscriptionRequest xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\">\n".
                   "    
<merchantAuthentication>\n" .
                   "        
<name>$this->loginId</name>\n" .
                   "        
<transactionKey>$this->transactionKey</transactionKey>\n" .
                   "    
</merchantAuthentication>\n";

        // check to see if we've set a reference Id
        if($this->referenceId)
            $request .= "    
<refId>$this->referenceId</refId>\n";

        // check to see if we've set a trial period
        if($this->trialInfo) {
            $trialOccurrences = "            
<trialOccurrences>{$this->trialInfo[0]}</trialOccurrences>\n";
            $trialAmount      = "        
<trialAmount>{$this->trialInfo[1]}</trialAmount>\n";
        } else {
            $trialOccurrences = $trialAmount = '';
        }

        if($this->billTo) {
            $firstName = $this->billTo['firstName'];
            $lastName  = $this->billTo['lastName'];
        } else {
            $firstName = $lastName = '';
        }

        switch($requestType) {

            case 'Create':
                $request .= "    
<subscription>\n".
                            "        
<paymentSchedule>\n".
                            "            
<interval>\n".
                            "                
<length>{$this->paymentScheduleInfo[0]}</length>\n".
                            "                
<unit>{$this->paymentScheduleInfo[1]}</unit>\n".
                            "            
</interval>\n".
                            "            
<startDate>{$this->paymentScheduleInfo[2]}</startDate>\n".
                            "            
<totalOccurrences>{$this->paymentScheduleInfo[3]}</totalOccurrences>\n".
                            $trialOccurrences.
                            "        
</paymentSchedule>\n".
                            "        
<amount>{$this->paymentScheduleInfo[4]}</amount>\n".
                            $trialAmount.
                            "        
<payment>\n".
                            "            
<creditCard>\n".
                            "                
<cardNumber>{$this->paymentInfo[0]}</cardNumber>\n".
                            "                
<expirationDate>{$this->paymentInfo[1]}</expirationDate>\n".
                            "            
</creditCard>\n".
                            "        
</payment>\n".
                            "        
<order>\n".
                            "            
<invoiceNumber>{$this->generateInvoiceNumber()}</invoiceNumber>\n".
                            "        
</order>\n".
                            "        
<billTo>\n".
                            "            
<firstName>$firstName</firstName>\n".
                            "            
<lastName>$lastName</lastName>\n".
                            "        
</billTo>\n".
                            "    
</subscription>\n";
                break;

            case 'Update':
                // $request .= "
<ARBUpdateSubscriptionRequest xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\">\n";
                break;

            case 'Cancel':
                $request .= "    
<subscriptionId>$this->subscriptionId</subscriptionId>\n";
                break;
        }

        $request .= "
</ARB{$requestType}SubscriptionRequest>";


       
        // debug($this->sendRequest($request));
        return $this->parseReturn($this->sendRequest($request));

    }

    private function sendRequest($content) {

        // set curl handle
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $this->activeUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: text/xml'));
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_VERBOSE, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);

        return curl_exec($ch);

        debug(curl_getinfo($ch));
    }

    /**
     * validate the xml against the authorize.net schema
     *
     * @param  mixed  $xmlToValidate
     * @return bool
     */
    private function validateSchema($xmlToValidate) {

        $xml = new DOMDocument();
        $xml->loadXML($xmlToValidate);

            if(!$xml->schemaValidateSource($this->xmlSchema))
            {
                return false;
            }

       
        return true;
    }

    //function to parse Authorize.net response
    private function parseReturn($content)
    {
        $refId = $this->substringBetween($content,'
<refId>','</refId>');
        $resultCode = $this->substringBetween($content,'
<resultCode>','</resultCode>');
        $code = $this->substringBetween($content,'
<code>','</code>');
        $text = $this->substringBetween($content,'
<text>','</text>');
        $subscriptionId = $this->substringBetween($content,'
<subscriptionId>','</subscriptionId>');

        return array('refID' => $refId,
                     'resultCode' => $resultCode,
                     'code' => $code,
                     'text' => $text,
                     'subscriptionId' => $subscriptionId);
    }

    //helper function for parsing response
    private function substringBetween($haystack,$start,$end)
    {
        if (strpos($haystack,$start) === false || strpos($haystack,$end) === false)
        {
            return false;
        }
        else
        {
            $start_position = strpos($haystack,$start)+strlen($start);
            $end_position = strpos($haystack,$end);
            return substr($haystack,$start_position,$end_position-$start_position);
        }
    }

    /**
     * Generate a invoice number for the transasction
     *
     * @return  string  $invoiceNumber
     */
    private function generateInvoiceNumber() {        
       
        return mt_rand('0', '1000000') + strtotime('now');
    }
}