Offsite Payments

Payment types such as PayPal require the customer to go to a gateway-supplied page to fill in details and complete the payment process. These payment methods and gateways are called “offsite” since they require sending the customer off your site in order to complete the payment process.

While we’ve done our best to keep our Offsite API consistent with our traditional Direct API, there are important differences and necessary complexity that must be handled.

Payment form

After creating a gateway that supports offsite purchases you need to collect payment information using the transparent redirect form, just like regular credit card transactions. The difference is, you specify a payment_method_type field. In this case we’re going to use the test gateway’s fake offsite sprel payment type (in production use values such as: paypal):

<form accept-charset="UTF-8" action="https://core.spreedly.com/v1/payment_methods" method="POST">
  <input name="redirect_url" type="hidden" value="http://example.com/transparent_redirect_complete" />
  <input name="environment_key" type="hidden" value="C7cRfNJGODKh4Iu5Ox3PToKjniY" />
  <input name="utf8" type="hidden" value="✓" />

  <label for="email">Email</label>
  <input id="email" name="email" type="text"/>

  <fieldset>
    <button type="submit" name="payment_method_type" value="sprel">Pay via Sprel</button>
  </fieldset>

  <fieldset>
    <label for="credit_card_first_name">First name</label>
    <input id="credit_card_first_name" name="credit_card[first_name]" type="text" />

    <label for="credit_card_last_name">Last name</label>
    <input id="credit_card_last_name" name="credit_card[last_name]" type="text" />

    <label for="credit_card_number">Card Number</label>
    <input autocomplete="off" id="credit_card_number" name="credit_card[number]" type="text" />

    <label for="credit_card_verification_value">Security Code</label>
    <input autocomplete="off" id="credit_card_verification_value" name="credit_card[verification_value]" type="text" />

    <label for="credit_card_month">Expires on</label>
    <input id="credit_card_month" name="credit_card[month]" type="text" />
    <input id="credit_card_year" name="credit_card[year]" type="text" />

    <button type='submit'>Pay via Credit Card</button>
  </fieldset>
</form>

You’ll get a redirect that contains the payment method token:

http://example.com/transparent_redirect_complete?token=XRxaRbWNvufMKqdzOr8cPqs4LWJ

Note that we’re still allowing credit cards right from the same form. You can support them right alongside traditional credit card processing. Also, you can still attach data fields to the offsite payment method, so you can collect any extra information you want to here and it will be attached as usual to the payment method.

Initiate a purchase

Once you have an offsite payment method in hand, the next step is to run a purchase (or authorization):

curl https://core.spreedly.com/v1/gateways/LlkjmEk0xNkcWrNixXa1fvNoTP4/purchase.xml \
  -u 'Ll6fAtoVSTyVMlJEmtpoJV8S:RKOCG5D8D3fZxDSg504D0IxU2XD4Io5VXmyzdCtTivHFTTSy' \
  -H 'Content-Type: application/xml' \
  -d '<transaction>
    <amount>100</amount>
    <currency_code>USD</currency_code>
    <payment_method_token>QReVWckhVm0ZME2IyKMKHoDPG44</payment_method_token>
    <redirect_url>http://example.com/handle_redirect</redirect_url>
    <callback_url>http://example.com/handle_callback</callback_url>
  </transaction>'

Note the required redirect_url and callback_url fields. These will be ignored if you’re running a credit card transaction, but in the case of an offsite transaction they will be used to give you results after the customer completes their transaction. More on those below.

Note that attempting a 3D Secure transaction when performing an offsite transaction is not supported. Please be sure to specify false for attempt_3dsecure, otherwise the transaction will fail.

The response includes the resulting transaction:

<transaction>
  <on_test_gateway type="boolean">false</on_test_gateway>
  <created_at type="dateTime">2015-01-08T20:57:50Z</created_at>
  <updated_at type="dateTime">2015-01-08T20:57:50Z</updated_at>
  <succeeded type="boolean">false</succeeded>
  <state>pending</state>
  <token>Y3XPb5f1TFNuy8r3AtGI8UB11PQ</token>
  <transaction_type>OffsitePurchase</transaction_type>
  <order_id nil="true"/>
  <ip nil="true"/>
  <description nil="true"/>
  <email nil="true"/>
  <merchant_name_descriptor nil="true"/>
  <merchant_location_descriptor nil="true"/>
  <gateway_specific_fields nil="true"/>
  <gateway_specific_response_fields nil="true"/>
  <gateway_transaction_id nil="true"/>
  <amount type="integer">100</amount>
  <currency_code>USD</currency_code>
  <reference nil="true"/>
  <message key="messages.transaction_pending">Pending</message>
  <gateway_token>8XJtbE1p4NTZ6fFqwwn0GrkjEmW</gateway_token>
  <api_urls>
    <callback_conversations>http://core.spreedly.dev/v1/callbacks/XjCHPJad2pDbIKKq2fOJezXmxiM/conversations.xml</callback_conversations>
  </api_urls>
  <payment_method>
    <token>W1lyon1vGzv2MIXAuDmqG7zOwau</token>
    <created_at type="dateTime">2015-01-08T15:57:50-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T15:57:50-05:00</updated_at>
    <email>john@example.com</email>
    <data nil="true"/>
    <storage_state>cached</storage_state>
    <test type="boolean">true</test>
    <payment_method_type>sprel</payment_method_type>
    <errors>
    </errors>
  </payment_method>
  <callback_url>http://127.0.0.1:3044</callback_url>
  <redirect_url>http://example.com/handle_redirect</redirect_url>
  <checkout_url>http://core.spreedly.dev/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/Y3XPb5f1TFNuy8r3AtGI8UB11PQ</checkout_url>
  <checkout_form>
    <![CDATA[
<form action="" method="POST">
  <div>
    <input name="PaReq" value="" type="hidden"/>
    <input name="MD" value="" type="hidden"/>
    <input name="TermUrl" value="http://core.spreedly.dev/transaction/Y3XPb5f1TFNuy8r3AtGI8UB11PQ/redirect" type="hidden"/>
    <input name="Complete" value="Authorize Transaction" type="submit"/>
  </div>
</form>
]]>
  </checkout_form>
  <setup_response>
    <success type="boolean">true</success>
    <message nil="true"/>
    <error_code nil="true"/>
    <checkout_url>http://core.spreedly.dev/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/Y3XPb5f1TFNuy8r3AtGI8UB11PQ</checkout_url>
    <created_at type="dateTime">2015-01-08T20:57:50Z</created_at>
    <updated_at type="dateTime">2015-01-08T20:57:50Z</updated_at>
  </setup_response>
</transaction>

This is where the process diverges from traditional payments. Note the state field of the transaction: it is set to pending. We’ve also received back a status code of 202, which indicates that this transaction isn’t complete - rather it needs further processing in order to actually collect payment.

To do that further processing, you simply need to redirect the customer’s browser to the checkout_url returned in the transaction.

Redirecting to this url will land the customer on the offsite page they will use to authorize payment. In this case we’re using Spreedly’s built-in fake payment method Sprel, so the browser lands on a page that looks like this:

Sprel Checkout Page

Note that in the case of a real offsite gateway, this will not be a page on spreedly.com, but rather will be on paypal.com, etc…

Recurring charges

Instead of doing a purchase right away, some offsite gateways support the ability to do an offsite authorization. Once this authorization occurs, the payment method can then be used to regularly charge your customer without having to redirect your customer to the gateway’s site for future purchases.

Creating an offsite authorization is very similar to creating a purchase, in that you need to be using an offsite gateway and collecting offsite payment methods types such as paypal. Once you have an offsite payment method token in hand, the next step is to run an authorize:

curl https://core.spreedly.com/v1/gateways/LlkjmEk0xNkcWrNixXa1fvNoTP4/authorize.xml \
  -u 'Ll6fAtoVSTyVMlJEmtpoJV8S:RKOCG5D8D3fZxDSg504D0IxU2XD4Io5VXmyzdCtTivHFTTSy' \
  -H 'Content-Type: application/xml' \
  -d '<transaction>
    <amount>1400</amount>
    <currency_code>USD</currency_code>
    <payment_method_token>KMlLeKt5WYQMvDkoW0DXvvsv4pK</payment_method_token>
    <redirect_url>http://example.com/handle_redirect</redirect_url>
    <callback_url>http://example.com/handle_callback</callback_url>
    <retain_on_success>true</retain_on_success>
  </transaction>' \

Notice that we passed a retain_on_success parameter to that authorize call. This will retain the payment method if the authorize is successful. If you don’t pass this parameter, you’ll need to retain the payment method in a separate API call before attempting to do a purchase.

Spreedly will return a response that includes the transaction:

<transaction>
  <on_test_gateway type="boolean">false</on_test_gateway>
  <created_at type="dateTime">2015-01-08T20:55:01Z</created_at>
  <updated_at type="dateTime">2015-01-08T20:55:01Z</updated_at>
  <succeeded type="boolean">false</succeeded>
  <state>pending</state>
  <token>I4P5HMxwOED2Vrh9d2BwwhjOX85</token>
  <transaction_type>OffsiteAuthorization</transaction_type>
  <order_id nil="true"/>
  <ip nil="true"/>
  <description nil="true"/>
  <email nil="true"/>
  <merchant_name_descriptor nil="true"/>
  <merchant_location_descriptor nil="true"/>
  <gateway_specific_fields nil="true"/>
  <gateway_specific_response_fields nil="true"/>
  <gateway_transaction_id nil="true"/>
  <amount type="integer">1400</amount>
  <currency_code>USD</currency_code>
  <reference nil="true"/>
  <message key="messages.transaction_pending">Pending</message>
  <gateway_token>8XJtbE1p4NTZ6fFqwwn0GrkjEmW</gateway_token>
  <api_urls>
    <callback_conversations>http://core.spreedly.dev/v1/callbacks/XjCHPJad2pDbIKKq2fOJezXmxiM/conversations.xml</callback_conversations>
  </api_urls>
  <payment_method>
    <token>Dfu6FU22BqUmyJtwx4MRG9A2igU</token>
    <created_at type="dateTime">2015-01-08T15:55:01-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T15:55:01-05:00</updated_at>
    <email nil="true"/>
    <data>
      <email>gandalf@wizards.com</email>
    </data>
    <storage_state>cached</storage_state>
    <test type="boolean">true</test>
    <payment_method_type>sprel</payment_method_type>
    <errors>
    </errors>
  </payment_method>
  <callback_url>http://127.0.0.1:3044</callback_url>
  <redirect_url>http://example.com/handle_redirect</redirect_url>
  <checkout_url>http://core.spreedly.dev/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/I4P5HMxwOED2Vrh9d2BwwhjOX85</checkout_url>
  <checkout_form>
    <![CDATA[
<form action="" method="POST">
  <div>
    <input name="PaReq" value="" type="hidden"/>
    <input name="MD" value="" type="hidden"/>
    <input name="TermUrl" value="http://core.spreedly.dev/transaction/I4P5HMxwOED2Vrh9d2BwwhjOX85/redirect" type="hidden"/>
    <input name="Complete" value="Authorize Transaction" type="submit"/>
  </div>
</form>
]]>
  </checkout_form>
  <setup_response>
    <success type="boolean">true</success>
    <message nil="true"/>
    <error_code nil="true"/>
    <checkout_url>http://core.spreedly.dev/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/I4P5HMxwOED2Vrh9d2BwwhjOX85</checkout_url>
    <created_at type="dateTime">2015-01-08T20:55:01Z</created_at>
    <updated_at type="dateTime">2015-01-08T20:55:01Z</updated_at>
  </setup_response>
</transaction>

This is where the process diverges from traditional authorization. Note the state field of the transaction: it is set to pending. We’ve also received back a status code of 202, which indicates that this transaction isn’t complete - rather it needs further processing in order to actually collect payment.

To do that further processing, you simply need to redirect the customer’s browser to the checkout_url returned in the transaction. Doing so will land the customer on the offsite page they will use to authorize payment.

In this case we’re using Spreedly’s built-in fake payment method Sprel, so the browser lands on a page that looks like this:

Sprel Checkout Authorization Page

Once the offsite authorization is complete, you can now run purchases against your payment method as a standard purchase call:

curl https://core.spreedly.com/v1/gateways/LlkjmEk0xNkcWrNixXa1fvNoTP4/purchase.xml \
  -u 'Ll6fAtoVSTyVMlJEmtpoJV8S:RKOCG5D8D3fZxDSg504D0IxU2XD4Io5VXmyzdCtTivHFTTSy' \
  -H 'Content-Type: application/xml' \
  -d '<transaction>
    <payment_method_token>4IfpQGwQAZsWAQwbKB2Ui6gTzD0</payment_method_token>
    <amount>100</amount>
    <currency_code>USD</currency_code>
    <callback_url>http://example.com/handle_callback</callback_url>
  </transaction>'

The callback_url is required since the purchase may be delayed for echecks, etc…

<transaction>
  <on_test_gateway type="boolean">true</on_test_gateway>
  <created_at type="dateTime">2015-01-08T21:00:02Z</created_at>
  <updated_at type="dateTime">2015-01-08T21:00:02Z</updated_at>
  <succeeded type="boolean">true</succeeded>
  <state>succeeded</state>
  <token>NECf2j21qgIIlUFA469LE3uG1E5</token>
  <transaction_type>PurchaseViaPreauthorization</transaction_type>
  <order_id nil="true"/>
  <ip nil="true"/>
  <description nil="true"/>
  <email nil="true"/>
  <merchant_name_descriptor nil="true"/>
  <merchant_location_descriptor nil="true"/>
  <gateway_specific_fields nil="true"/>
  <gateway_specific_response_fields nil="true"/>
  <gateway_transaction_id>62</gateway_transaction_id>
  <amount type="integer">100</amount>
  <currency_code>USD</currency_code>
  <message key="messages.transaction_succeeded">Succeeded!</message>
  <gateway_token>8XJtbE1p4NTZ6fFqwwn0GrkjEmW</gateway_token>
  <response>
    <success type="boolean">true</success>
    <message>Successful purchase</message>
    <avs_code nil="true"/>
    <avs_message nil="true"/>
    <cvv_code nil="true"/>
    <cvv_message nil="true"/>
    <pending type="boolean">false</pending>
    <result_unknown type="boolean">false</result_unknown>
    <error_code></error_code>
    <error_detail nil="true"/>
    <cancelled type="boolean">false</cancelled>
    <fraud_review nil="true"/>
    <created_at type="dateTime">2015-01-08T21:00:02Z</created_at>
    <updated_at type="dateTime">2015-01-08T21:00:02Z</updated_at>
  </response>
  <api_urls>
    <callback_conversations>http://core.spreedly.dev/v1/callbacks/XjCHPJad2pDbIKKq2fOJezXmxiM/conversations.xml</callback_conversations>
  </api_urls>
  <payment_method>
    <token>C1qaC91X4zCkfF5GDbsTyzJJCXT</token>
    <created_at type="dateTime">2015-01-08T15:59:59-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T15:59:59-05:00</updated_at>
    <email nil="true"/>
    <data>
      <email>gandalf@wizards.com</email>
    </data>
    <storage_state>retained</storage_state>
    <test type="boolean">true</test>
    <payment_method_type>sprel</payment_method_type>
    <errors>
    </errors>
  </payment_method>
  <callback_url>http://127.0.0.1:3044</callback_url>
</transaction>

Finalizing the transaction

Once the customer completes the offsite flow, their browser will be sent back to the redirect_url that you specified when creating the transaction:

http://example.com/handle_redirect?transaction_token=Q8cXyELAy9mzAoV1TGgyArcDW97

Note the extra field tacked on to the redirect_url; the transaction_token allows you to distinguish which transaction this redirect is in regards to. You can then lookup details on the transaction and see exactly what happened:

curl https://core.spreedly.com/v1/transactions/Q8cXyELAy9mzAoV1TGgyArcDW97.xml \
  -u 'Ll6fAtoVSTyVMlJEmtpoJV8S:RKOCG5D8D3fZxDSg504D0IxU2XD4Io5VXmyzdCtTivHFTTSy'

The succeeded attribute is now true and we consider the transaction complete. From here you would redirect your customer to a “Thank You” page of some kind, and get on with processing their order now that it’s paid for.

<transaction>
  <on_test_gateway type="boolean">false</on_test_gateway>
  <created_at type="dateTime">2015-01-08T16:03:38-05:00</created_at>
  <updated_at type="dateTime">2015-01-08T16:03:42-05:00</updated_at>
  <succeeded type="boolean">true</succeeded>
  <state>succeeded</state>
  <token>PMI6UMjiYDM3AUWp6nrH2Zjtt60</token>
  <transaction_type>OffsitePurchase</transaction_type>
  <order_id nil="true"/>
  <ip nil="true"/>
  <description nil="true"/>
  <email nil="true"/>
  <merchant_name_descriptor nil="true"/>
  <merchant_location_descriptor nil="true"/>
  <gateway_specific_fields nil="true"/>
  <gateway_specific_response_fields nil="true"/>
  <gateway_transaction_id nil="true"/>
  <amount type="integer">100</amount>
  <currency_code>USD</currency_code>
  <reference>TheReferenceId</reference>
  <message key="messages.transaction_succeeded">Succeeded!</message>
  <gateway_token>8XJtbE1p4NTZ6fFqwwn0GrkjEmW</gateway_token>
  <response>
    <success type="boolean">true</success>
    <message>Succeeded</message>
    <avs_code nil="true"/>
    <avs_message nil="true"/>
    <cvv_code nil="true"/>
    <cvv_message nil="true"/>
    <pending type="boolean">false</pending>
    <result_unknown type="boolean">false</result_unknown>
    <error_code nil="true"/>
    <error_detail nil="true"/>
    <cancelled nil="true"/>
    <fraud_review type="boolean">false</fraud_review>
    <created_at type="dateTime">2015-01-08T16:03:38-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:03:42-05:00</updated_at>
  </response>
  <api_urls>
    <callback_conversations>http://core.spreedly.dev/v1/callbacks/XjCHPJad2pDbIKKq2fOJezXmxiM/conversations.xml</callback_conversations>
  </api_urls>
  <payment_method>
    <token>NUtqul2Zhp8v5eSzS8DvDduV1EK</token>
    <created_at type="dateTime">2015-01-08T16:03:38-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:03:38-05:00</updated_at>
    <email>john@example.com</email>
    <data nil="true"/>
    <storage_state>used</storage_state>
    <test type="boolean">true</test>
    <payment_method_type>sprel</payment_method_type>
    <errors>
    </errors>
  </payment_method>
  <callback_url>http://127.0.0.1:3044</callback_url>
  <redirect_url>http://example.com/handle_redirect</redirect_url>
  <checkout_url>http://core.spreedly.dev/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/PMI6UMjiYDM3AUWp6nrH2Zjtt60</checkout_url>
  <checkout_form>
    <![CDATA[
<form action="" method="POST">
  <div>
    <input name="PaReq" value="" type="hidden"/>
    <input name="MD" value="" type="hidden"/>
    <input name="TermUrl" value="http://core.spreedly.dev/transaction/PMI6UMjiYDM3AUWp6nrH2Zjtt60/redirect" type="hidden"/>
    <input name="Complete" value="Authorize Transaction" type="submit"/>
  </div>
</form>
]]>
  </checkout_form>
  <setup_response>
    <success type="boolean">true</success>
    <message nil="true"/>
    <error_code nil="true"/>
    <checkout_url>http://core.spreedly.dev/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/PMI6UMjiYDM3AUWp6nrH2Zjtt60</checkout_url>
    <created_at type="dateTime">2015-01-08T16:03:38-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:03:42-05:00</updated_at>
  </setup_response>
  <redirect_response>
    <success type="boolean">true</success>
    <message>Succeeded</message>
    <avs_code nil="true"/>
    <avs_message nil="true"/>
    <cvv_code nil="true"/>
    <cvv_message nil="true"/>
    <pending type="boolean">false</pending>
    <result_unknown type="boolean">false</result_unknown>
    <error_code nil="true"/>
    <error_detail nil="true"/>
    <cancelled nil="true"/>
    <fraud_review type="boolean">false</fraud_review>
    <created_at type="dateTime">2015-01-08T16:03:38-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:03:42-05:00</updated_at>
  </redirect_response>
</transaction>

Payment states

For some gateways and payment methods, the transaction may not be succeeded at this stage. In particular, some gateways won’t be succeeded because funds have not actually reached your account yet. The state attribute of the transaction will be processing to indicate that the transaction was accepted, but it might take a few days for the funds to actually reach your account.

For an offsite Paypal purchase, most of the time the transaction will be succeeded when you get to this point in the process. For some customers though, it’ll be in the processing state because their Paypal account lacks a connection to a credit card and it’s only linked to a checking account. In this case the purchase is really an eCheck in Paypal parlance. These can take a few days for the funds to actually get to you.

It’s up to you at this point how you’d like to respond if the transaction is in the processing state. For some businesses, you may want to simply grant the customer access to whatever service they’re buying, knowing that a significant percentage of them will be successful and that you’ll eventually receive the funds. If you’re shipping a very expensive product, you may want to wait until the funds actually hit your account before shipping.

So how will you know when the state of the transaction actually moves from processing? The answer lies in the following section on Callbacks:

Callbacks

In a best case scenario, customers browsers will always come back to your site after a successful payment, but in the real world, there are various reasons you might never receive the redirect back to your site. In those cases it’s important to still hear back about what happened with the transaction, and the callback_url (specified when you created the purchase) is your means to do so.

The callback url will receive a POST back of all transactions that have changed since the last callback. In most cases, you’ll receive the redirect and a callback, and the order of the two is not guaranteed. Generally you’ll just want to pay attention to the first one, and ignore the second one that comes in since you’ve already handled the transaction. Note you may receive more than one callback per initiated offsite transaction, subsequent callbacks can be safely ignored if the transaction has already been handled.

In some cases though such as a Paypal eCheck, the transaction initially moved to a state of processing and stayed there for a few days before the funds were actually transferred. You’ll eventually receive a callback indicating that the transaction has moved to a different state. Here are the possible states for a transaction:

  • succeeded: The transaction has succeeded and funds have been received.
  • processing: The transaction has been accepted. Funds have not yet been received.
  • pending: The transaction needs further processing which typically involves redirecting the customer to a redirect_url to allow them to specify a payment method.
  • gateway_processing_failed: The transaction failed because the gateway declined the charge for some reason.
  • gateway_processing_result_unknown: We had difficulty communicating with the service and we’re unsure what the result of the operation was. (timeouts, connection errors, etc).
  • failed: The transaction failed. This could be caused by a number of things such as the payment method not being valid, the payment method being redacted, etc.
  • gateway_setup_failed: The transaction failed because the attempt to setup the transaction on the offsite gateway failed.

The callback POST looks like this:

<transactions>
  <transaction>
    <on_test_gateway type="boolean">false</on_test_gateway>
    <created_at type="dateTime">2015-01-08T15:55:48-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T15:55:49-05:00</updated_at>
    <succeeded type="boolean">true</succeeded>
    <state>succeeded</state>
    <token>M0n7624DWjWkALwwAqmsS1Ye7zK</token>
    <transaction_type>OffsitePurchase</transaction_type>
    <order_id nil="true"/>
    <ip nil="true"/>
    <description nil="true"/>
    <email nil="true"/>
    <merchant_name_descriptor nil="true"/>
    <merchant_location_descriptor nil="true"/>
    <gateway_specific_fields nil="true"/>
    <gateway_specific_response_fields nil="true"/>
    <gateway_transaction_id nil="true"/>
    <amount type="integer">100</amount>
    <currency_code>USD</currency_code>
    <reference>TheReferenceId</reference>
    <message key="messages.transaction_succeeded">Succeeded!</message>
    <gateway_token>8XJtbE1p4NTZ6fFqwwn0GrkjEmW</gateway_token>
    <response>
      <success type="boolean">true</success>
      <message>Succeeded</message>
      <avs_code nil="true"/>
      <avs_message nil="true"/>
      <cvv_code nil="true"/>
      <cvv_message nil="true"/>
      <pending type="boolean">false</pending>
      <result_unknown type="boolean">false</result_unknown>
      <error_code nil="true"/>
      <error_detail nil="true"/>
      <cancelled nil="true"/>
      <fraud_review type="boolean">false</fraud_review>
      <created_at type="dateTime">2015-01-08T15:55:48-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:55:49-05:00</updated_at>
    </response>
    <api_urls>
      <callback_conversations>http://core.spreedly.dev/v1/callbacks/XjCHPJad2pDbIKKq2fOJezXmxiM/conversations.xml</callback_conversations>
    </api_urls>
    <signed>
      <signature>f5b701255eedbe1da6562b535bfbcd3b35fd944c</signature>
      <fields>amount created_at currency_code ip on_test_gateway order_id state succeeded token transaction_type updated_at</fields>
      <algorithm>sha1</algorithm>
    </signed>
    <payment_method>
      <token>Jv0mXKkKmzVa6f2b7phO8Mxku0N</token>
      <created_at type="dateTime">2015-01-08T15:55:48-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:55:48-05:00</updated_at>
      <email>john@example.com</email>
      <data nil="true"/>
      <storage_state>used</storage_state>
      <test type="boolean">true</test>
      <payment_method_type>sprel</payment_method_type>
      <errors>
      </errors>
    </payment_method>
    <callback_url>http://127.0.0.1:3044</callback_url>
    <redirect_url>http://example.com/handle_redirect</redirect_url>
    <checkout_url>http://core.spreedly.dev/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/M0n7624DWjWkALwwAqmsS1Ye7zK</checkout_url>
    <checkout_form>
      <![CDATA[
<form action="" method="POST">
  <div>
    <input name="PaReq" value="" type="hidden"/>
    <input name="MD" value="" type="hidden"/>
    <input name="TermUrl" value="http://core.spreedly.dev/transaction/M0n7624DWjWkALwwAqmsS1Ye7zK/redirect" type="hidden"/>
    <input name="Complete" value="Authorize Transaction" type="submit"/>
  </div>
</form>
]]>
    </checkout_form>
    <setup_response>
      <success type="boolean">true</success>
      <message nil="true"/>
      <error_code nil="true"/>
      <checkout_url>http://core.spreedly.dev/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/M0n7624DWjWkALwwAqmsS1Ye7zK</checkout_url>
      <created_at type="dateTime">2015-01-08T15:55:48-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:55:49-05:00</updated_at>
    </setup_response>
    <redirect_response>
      <success type="boolean">true</success>
      <message>Succeeded</message>
      <avs_code nil="true"/>
      <avs_message nil="true"/>
      <cvv_code nil="true"/>
      <cvv_message nil="true"/>
      <pending type="boolean">false</pending>
      <result_unknown type="boolean">false</result_unknown>
      <error_code nil="true"/>
      <error_detail nil="true"/>
      <cancelled nil="true"/>
      <fraud_review type="boolean">false</fraud_review>
      <created_at type="dateTime">2015-01-08T15:55:48-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:55:49-05:00</updated_at>
    </redirect_response>
  </transaction>
  <transaction>
    <on_test_gateway type="boolean">false</on_test_gateway>
    <created_at type="dateTime">2015-01-08T15:56:20-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T15:56:20-05:00</updated_at>
    <succeeded type="boolean">true</succeeded>
    <state>succeeded</state>
    <token>Q8cXyELAy9mzAoV1TGgyArcDW97</token>
    <transaction_type>OffsitePurchase</transaction_type>
    <order_id nil="true"/>
    <ip nil="true"/>
    <description nil="true"/>
    <email nil="true"/>
    <merchant_name_descriptor nil="true"/>
    <merchant_location_descriptor nil="true"/>
    <gateway_specific_fields nil="true"/>
    <gateway_specific_response_fields nil="true"/>
    <gateway_transaction_id nil="true"/>
    <amount type="integer">100</amount>
    <currency_code>USD</currency_code>
    <reference>TheReferenceId</reference>
    <message key="messages.transaction_succeeded">Succeeded!</message>
    <gateway_token>8XJtbE1p4NTZ6fFqwwn0GrkjEmW</gateway_token>
    <response>
      <success type="boolean">true</success>
      <message>Succeeded</message>
      <avs_code nil="true"/>
      <avs_message nil="true"/>
      <cvv_code nil="true"/>
      <cvv_message nil="true"/>
      <pending type="boolean">false</pending>
      <result_unknown type="boolean">false</result_unknown>
      <error_code nil="true"/>
      <error_detail nil="true"/>
      <cancelled nil="true"/>
      <fraud_review type="boolean">false</fraud_review>
      <created_at type="dateTime">2015-01-08T15:56:20-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:56:20-05:00</updated_at>
    </response>
    <api_urls>
      <callback_conversations>http://core.spreedly.dev/v1/callbacks/XjCHPJad2pDbIKKq2fOJezXmxiM/conversations.xml</callback_conversations>
    </api_urls>
    <signed>
      <signature>d5e19b07f3e867d4afdc0552c6f2b2d018e2d718</signature>
      <fields>amount created_at currency_code ip on_test_gateway order_id state succeeded token transaction_type updated_at</fields>
      <algorithm>sha1</algorithm>
    </signed>
    <payment_method>
      <token>RPenzCeiKLKM0cF9lzIVV2shnXG</token>
      <created_at type="dateTime">2015-01-08T15:56:19-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:56:20-05:00</updated_at>
      <email>john@example.com</email>
      <data nil="true"/>
      <storage_state>used</storage_state>
      <test type="boolean">true</test>
      <payment_method_type>sprel</payment_method_type>
      <errors>
      </errors>
    </payment_method>
    <callback_url>http://127.0.0.1:3044</callback_url>
    <redirect_url>http://example.com/handle_redirect</redirect_url>
    <checkout_url>http://core.spreedly.dev/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/Q8cXyELAy9mzAoV1TGgyArcDW97</checkout_url>
    <checkout_form>
      <![CDATA[
<form action="" method="POST">
  <div>
    <input name="PaReq" value="" type="hidden"/>
    <input name="MD" value="" type="hidden"/>
    <input name="TermUrl" value="http://core.spreedly.dev/transaction/Q8cXyELAy9mzAoV1TGgyArcDW97/redirect" type="hidden"/>
    <input name="Complete" value="Authorize Transaction" type="submit"/>
  </div>
</form>
]]>
    </checkout_form>
    <setup_response>
      <success type="boolean">true</success>
      <message nil="true"/>
      <error_code nil="true"/>
      <checkout_url>http://core.spreedly.dev/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/Q8cXyELAy9mzAoV1TGgyArcDW97</checkout_url>
      <created_at type="dateTime">2015-01-08T15:56:20-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:56:20-05:00</updated_at>
    </setup_response>
    <redirect_response>
      <success type="boolean">true</success>
      <message>Succeeded</message>
      <avs_code nil="true"/>
      <avs_message nil="true"/>
      <cvv_code nil="true"/>
      <cvv_message nil="true"/>
      <pending type="boolean">false</pending>
      <result_unknown type="boolean">false</result_unknown>
      <error_code nil="true"/>
      <error_detail nil="true"/>
      <cancelled nil="true"/>
      <fraud_review type="boolean">false</fraud_review>
      <created_at type="dateTime">2015-01-08T15:56:20-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:56:20-05:00</updated_at>
    </redirect_response>
  </transaction>
</transactions>

Note that the callback response is signed; this allows you to process the results of the callback without having to round trip back out to Spreedly (though you certainly can round trip if you’d like - see below). Since an attacker could call your callback url with a valid looking transaction, the signature allows you to verify that the information is really coming from Spreedly. Full details on validating the signature is in our signing reference.

We recognize that some customers may not be interested in going through the trouble of writing code to validate the signature of the callback response. In this case, you could simply grab the tokens of the transactions you receive in the callback and then make an authenticated API call to retrieve the details each transaction. Because that call is authenticated and you’re making the request, there’s no need to verify where the information is coming from.

Callback Response

You should respond with a 200 OK response within 5 seconds. If Spreedly does not receive back a 200 response within this time, it will retry the callback again at least 4 times at ever-increasing intervals.

If you need to do potentially time-consuming operations when a callback is received, we recommend doing them asynchronously to avoid being timed out.

Callback Debugging

Spreedly not calling your callback_url? In the transaction XML, you’ll notice an api_urls element that has a conversations url - if you call that url via the API, you’ll be able to see a history of all of our attempts to contact your callback url, similar to the transcript we save of conversations with the gateway.

Testing

When using a test gateway, transactions are not immediately settled. A transaction will move to a state of ‘pending’ to indicate that a redirect to an offsite authorization page is required. That offsite page may cause the transaction to settle by moving to a succeeded, failed, or gateway_processing_failed state. Or, it could end up being a delayed transaction in the processing state to indicate that the transaction has been accepted but funds have not yet been received (like a Paypal eCheck). For a gateway like Paypal, you’ll eventually be notified via a callback when the state of the transaction changes.

For the Test Gateway though, we provide an API call you can use to settle all transactions which are currently in the processing state. When the transactions are settled, you’ll then be notified via callback of the transaction changes. This allows you to test the full life cycle of an offsite delayed transaction.

This is what the API call looks like:


curl https://core.spreedly.com/v1/gateways/LlkjmEk0xNkcWrNixXa1fvNoTP4/settle.xml \
  -u 'C7cRfNJGODKh4Iu5Ox3PToKjniY:4UIuWybmdythfNGPqAqyQnYha6s451ri0fYAo4p3drZUi7q2Jf4b7HKg8etDtoKJ' \
  -H 'Content-Type: application/xml' \
  -d '<settlement>
        <state>gateway_processing_failed</state>
      </settlement>'

You’ll receive a 200(OK) HTTP response code on success. You may specify one of the following terminal states: gateway_processing_failed or succeeded.

Error handling

Of course, things don’t go exactly right all the time. And with offsite transactions, there are more places that things can break, since there are more interactions with the gateway.

curl https://core.spreedly.com/v1/transactions/ACj6uhcYKcEW2Bb02rbC62JN2Og.xml \
  -u 'Ll6fAtoVSTyVMlJEmtpoJV8S:RKOCG5D8D3fZxDSg504D0IxU2XD4Io5VXmyzdCtTivHFTTSy'

The state element will change to reflect failures when processing:

<transaction>
  <on_test_gateway type="boolean">false</on_test_gateway>
  <created_at type="dateTime">2015-01-08T16:03:56-05:00</created_at>
  <updated_at type="dateTime">2015-01-08T16:04:02-05:00</updated_at>
  <succeeded type="boolean">false</succeeded>
  <state>gateway_processing_failed</state>
  <token>8nPmoMc9HHl024jgZEjeoyE4rVP</token>
  <transaction_type>OffsitePurchase</transaction_type>
  <order_id nil="true"/>
  <ip nil="true"/>
  <description nil="true"/>
  <email nil="true"/>
  <merchant_name_descriptor nil="true"/>
  <merchant_location_descriptor nil="true"/>
  <gateway_specific_fields nil="true"/>
  <gateway_specific_response_fields nil="true"/>
  <gateway_transaction_id nil="true"/>
  <amount type="integer">100</amount>
  <currency_code>USD</currency_code>
  <reference>TheReferenceId</reference>
  <message>A failure response was selected.</message>
  <gateway_token>8XJtbE1p4NTZ6fFqwwn0GrkjEmW</gateway_token>
  <response>
    <success type="boolean">false</success>
    <message>A failure response was selected.</message>
    <avs_code nil="true"/>
    <avs_message nil="true"/>
    <cvv_code nil="true"/>
    <cvv_message nil="true"/>
    <pending type="boolean">false</pending>
    <result_unknown type="boolean">false</result_unknown>
    <error_code nil="true"/>
    <error_detail nil="true"/>
    <cancelled nil="true"/>
    <fraud_review type="boolean">false</fraud_review>
    <created_at type="dateTime">2015-01-08T16:03:56-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:04:02-05:00</updated_at>
  </response>
  <api_urls>
    <callback_conversations>http://core.spreedly.dev/v1/callbacks/XjCHPJad2pDbIKKq2fOJezXmxiM/conversations.xml</callback_conversations>
  </api_urls>
  <payment_method>
    <token>OxsImbp9Io71ze8p9jcs1VUTpIX</token>
    <created_at type="dateTime">2015-01-08T16:03:56-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:03:56-05:00</updated_at>
    <email>john@example.com</email>
    <data nil="true"/>
    <storage_state>cached</storage_state>
    <test type="boolean">true</test>
    <payment_method_type>sprel</payment_method_type>
    <errors>
    </errors>
  </payment_method>
  <callback_url>http://127.0.0.1:3044</callback_url>
  <redirect_url>http://example.com/handle_redirect</redirect_url>
  <checkout_url>http://core.spreedly.dev/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/8nPmoMc9HHl024jgZEjeoyE4rVP</checkout_url>
  <checkout_form>
    <![CDATA[
<form action="" method="POST">
  <div>
    <input name="PaReq" value="" type="hidden"/>
    <input name="MD" value="" type="hidden"/>
    <input name="TermUrl" value="http://core.spreedly.dev/transaction/8nPmoMc9HHl024jgZEjeoyE4rVP/redirect" type="hidden"/>
    <input name="Complete" value="Authorize Transaction" type="submit"/>
  </div>
</form>
]]>
  </checkout_form>
  <setup_response>
    <success type="boolean">true</success>
    <message nil="true"/>
    <error_code nil="true"/>
    <checkout_url>http://core.spreedly.dev/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/8nPmoMc9HHl024jgZEjeoyE4rVP</checkout_url>
    <created_at type="dateTime">2015-01-08T16:03:56-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:04:02-05:00</updated_at>
  </setup_response>
  <redirect_response>
    <success type="boolean">false</success>
    <message>A failure response was selected.</message>
    <avs_code nil="true"/>
    <avs_message nil="true"/>
    <cvv_code nil="true"/>
    <cvv_message nil="true"/>
    <pending type="boolean">false</pending>
    <result_unknown type="boolean">false</result_unknown>
    <error_code nil="true"/>
    <error_detail nil="true"/>
    <cancelled nil="true"/>
    <fraud_review type="boolean">false</fraud_review>
    <created_at type="dateTime">2015-01-08T16:03:56-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:04:02-05:00</updated_at>
  </redirect_response>
</transaction>

In general you can just depend on the message element when something goes wrong - we do our best to provide a useful message there that you can display rather than having to dig around in the transaction details. That said, up to three responses are returned with the transaction: setup_response, redirect_response, and callback_response. These may have additional error details that will aid in debugging any issues.

The transaction’s state can also be an indication as to what has failed. For example, when initiating the purchase, we setup a transaction on the offsite gateway. If that setup process fails, the state of the transaction will be gateway_setup_failed. (You can simulate this with the Test gateway by specifying an amount of 44 cents).

Like always, you can use the transcript call to get a verbatim copy of Spreedly’s actual conversation with the gateway.