Two Tap API

Two Tap allows programmers to place orders on ecommerce sites using code. There are two ways of using it: via the HTML5 interface, or via the API.


HTML5 Interface

The HTML5 interface is a wrapper around our API. It's the easiest way to integrate Two Tap in your applications.

To use it just point a web browser, a UIWebView (iOS), or a WebView (Android) to https://checkout.twotap.com with a list of products, your public api token, a unique token for the transaction, and a confirm url.

At the end of the checkout flow the interface will call the confirm_url with the unique_token and a purchase_id to ask for the final order confirmation. More information about this below.

The HTML5 interface is stateless, which means you can't add products to an existing cart. For multi-products / multi-retailers we recommend you store the list of product urls locally (localstorage, NSUserDefaults), and open the interface once the users presses your checkout button. You don't have to do any extra UI work, since you would have a 'checkout' button (that contains a count of products in the cart) anyways. When the shoppers presses that button pass us that array of product urls.

The interface acccepts both GET requests with query arguments and POST requests with body arguments. We recommend you use GET requests for quick debugging, and POST requests in production. POST requests ensure you won't hit the maximum number of characters allowed in an URL in browsers.

Parameters via GET


#  https://checkout.twotap.com?
#  public_token=your_public_token&
#  unique_token=an_unique_token&
#  confirm_url=your_sms_confirm_url&
#  products=product_1_url,product_2_url&
#  affiliate_links=product_1_affiliate_url,product_2_affiliate_url
            

We recommend you encode each individual url to avoid any issues (js: encodeURIComponent(), ruby: CGI.escape(), python: urllib.parse.quote()).
public_token The public token from your Two Tap publisher interface.
unique_token An unique token generated by you that is sent back with the confirm url call.
confirm_url An URL on your end where the interface will ask you to confirm the purchase. Only ports 80 (http) and 443 (https) are allowed for outbound connections. Using GET the interface will default to the SMS flow.
products A list of products urls separated by commas. We encourage you to encode each product url beforehand.
affiliate_links (Optional) A list of affiliate links for the above product urls.
test_mode (Optional) fake_confirm. A way to test the interface without making actual purchases.
custom_css_url (Optional) An url to your custom css file. Has to be served over https.
show_close_button (Optional) true or false. If true a close button is displayed in the top right corner of the interface. On tap it triggers a postMessage event called 'close_pressed'.
standard_top_banner_url
standard_top_banner_message
success_top_banner_url
success_top_banner_message

(Optional) Add these params if you want a banner at the top of the interface. This is useful in case you want a custom back button or need to display a message.

At the end of the checkout process the interface will set the banner with the values of success_*.

Parameters via POST

public_token The public token from your Two Tap publisher interface.
unique_token An unique token generated by you that is sent back with the confirm url call.
confirm

A hash that tells the checkout interface which API flow to use. Only ports 80 (http) and 443 (https) are allowed for outbound connections. We recommend the SMS flow for ease of use.

{ method: 'sms', sms_confirm_url: 'http(s)://your_endpoint', sms_finished_url: 'http(s)://your_endpoint (optional)' }.

{ method: 'http', http_confirm_url: 'http(s)://your_endpoint', http_finished_url: 'http(s)://your_endpoint' }.

For more information see the SMS flow or API flow.

Transform this object into JSON before sending it.

products

An array of hashes like:

[ { 'url': 'the product url', 'affiliate_link': '(Optional) an affiliate link', 'input_fields': '(Optional) a hash of product values, ex: { 'quantity': 2 }.' } ].

Transform this object into JSON before sending it. Max 15 products in one session.

input_fields

(Optional) If you want to prefill information about the user/purchase you can send the values. Send a hash like:

{ 'shipping_state': 'California', 'coupons': { site_id: 'coupon_value' } }.

Transform this object into JSON before sending it.


If you'd like to send PCI DSS sensitive data (like payment information) please see the wallet meta feature.


See a list of possible keys and values.

test_mode (Optional) fake_confirm. A way to test the interface without making actual purchases.
custom_css_url (Optional) An url to your custom css file. Has to be served over https.
close_button

(Optional) Add hash like below if you want a close button on the top right side of the interface. You don't need to specify an action. By default it will send a postMessage that you can catch and handle on both desktop and iOS.

{ show: 'true', action: (optional)'redirect', url: (optional)'http://where-to-redirect_to' }

retry_url (Optional) In case of failures (like payment issues) shoppers receive an SMS with a link to retry the purchase. By default this restarts the checkout in Safari, however you can tweak the retry_url to send them back to your app. See the 'Restarting purchases' section below.
top_banner

(Optional) Add hash like below if you want a banner at the top of the interface:

{ 'standard_message': 'message', 'standard_url': 'url', 'success_message': 'message', 'success_url': 'url' }.

This is useful in case you want a custom back button or need to display a message.

At the end of the checkout process the interface will set the banner with the values of success_*.

hide_intro (Optional) true or false. By default an intro screen is displayed once to all new users. You can disable it.
intro_messages

(Optional) The intro screen displays a title, three benefits, and a dismiss button. Add hash like below if you want to tweak the messages:

{ 'title': 'title', 'first': 'first', 'second': 'second', 'third': 'third', 'button': 'button' }.

Testing the HTML5 interface

The only thing you have to test is the confirm callback. Before finalizing the order we ping your server ensure that it's actually you placing the order, and not someone else using your public token.

Callbacks are also a convenient way of storing important information about the purchase on your end.

Depending on what confirm mode you are using (the default mode is SMS), see the SMS API or HTTP API confirm flows, with example code on how to implement the callbacks.

We provide an easy way to test out the confirm callbacks once you've implemented them. Use following snippet:


curl https://core.twotap.com/callback_test.sh | bash -s http://YOUR_LOCAL_MACHINE/PATH_TO_CALLBACK
            

Make sure the snippet above returns OK and that your callback endpoints are internet accessible before placing any orders.

Once everything is set up try placing an order with test_mode=fake_confirm.

In 'fake_confirm' test mode the HTML5 interface will start the purchase as usual on the retailer site, except at the final 'Purchase Confirm' step it will return a fake OK and not place the order.

It's important to note that some stores validate payment information when the order is actually placed, consequently the sms_finished_url will be called directly with payment errors.

Web Integration Example

We provide a very simple skeleton example for a desktop website integrating the HTML5 interface in this github repository.

Helpful notes

Two Tap fires certain postMessage events during the purchase flow. The object is a hash that looks like: { action: Action, various_args }. On Internet Explorer 9 or earlier the object is a JSON encoded string.

Action Returns Description
cart_contents_changed cart_contents, cart_event This event fires anytime a cart is changed or when a purchase has been completed successfully.
cart_contents looks like { siteId: { productMD5: { title, image, price, url, original_url, required_fields, required_field_values, fields_input } } }
cart_event can be 'cart_loaded', 'product_removed', 'product_attributes_updated', or 'cart_finalized'.
cart_finalized This event fires anytime a purchase has completed successfully.
close_pressed This event fires if the user presses the close button (see the show_close_button parameter).

iOS Integration Example

We provide a very simple skeleton example for a iOS app integrating the HTML5 interface in this github repository.

Helpful notes

We provide a couple of helper functions that you can use in your iOS workflow.

You can use the stringByEvaluatingJavaScriptFromString: function to run them.


NSString *result = [self.webView stringByEvaluatingJavaScriptFromString:@"currentCartJSON()"];
            
Function Description
currentCartJSON() Returns the current updated cart in JSON form. On a finalized purchase the result will be empty.
The hash format is { siteId: { productMD5: { title, image, price } } }
postMessagesJSON() The checkout interface triggers postMessages alerts. Since it's impossible to push these updates on mobile devices postMessagesJSON() can be polled periodically to retrieve what's being sent.

Meta

There are cases where you'd be interested in hiding some data from the consumer.

The most classic example here is having private coupons that you would not like leaked out.

Another use case is when you are interested in prefilling payment data inside the HTML5 interface, but you'd rather keep a
[Your Server] <-> Two Tap communication channel for PCI DSS reasons.


Meta allows you to specify an array of hashes like:

{
  "original": "this is the actual real value, like RETAILERCOUPON",
  "meta": "this is virtual value that you can show to the shopper or prefill in the HTML5 interface, like IAMNOTAREALCOUPON",
   "expires_in": "in how many seconds should we expire this information"
}.


Once you have these entries set up, send the 'meta' value with the 'input_fields' variable (if using the HTML5 interface) or with the 'fields_input' variable (if using the API manually via /purchase).

Internally Two Tap will replace the meta values with the original ones, and afterwards place the order on the retailer website.



response = RestClient.post "https://api.twotap.com/v1.0/wallet/meta?public_token=PUBLIC_TOKEN", { 
  "meta_fields": [
    {
      "original": "original value",
      "meta": "visible value",
      "expires_in": "seconds until the entry should expire"
    }
  ]
}

# Completed Status Response
{ message: "done" }
                  

#!/bin/bash

PUBLIC_TOKEN=PUBLIC_TOKEN

curl https://api.twotap.com/v1.0/wallet/meta?public_token=$PUBLIC_TOKEN --header 'Content-Type: application/json' --data-binary '{
  "meta_fields": [
    {
      "original": "original value",
      "meta": "visible value",
      "expires_in": "seconds until the entry should expire"
    }
  ]
}'
                  

#!/usr/bin/python

import requests
import json

payload = { 
  "meta_fields": [
    {
      "original": "original value",
      "meta": "visible value",
      "expires_in": "seconds until the entry should expire"
    }
  ]
}

r = requests.post('https://api.twotap.com/v1.0/wallet/meta?public_token=PUBLIC_TOKEN',
  data=json.dumps(payload), headers={'Content-Type': 'application/json'})
print(r.json())
                  

$.post('https://api.twotap.com/v1.0/wallet/meta?public_token=PUBLIC_TOKEN', { 
  "meta_fields": [
    {
      "original": "original value",
      "meta": "visible value",
      "expires_in": "seconds until the entry should expire"
    } 
  ]
}, function(data, status) {
    console.log(data);
});
                  

var request = require("request");
request({
  url: 'https://api.twotap.com/v1.0/wallet/meta?public_token=PUBLIC_TOKEN'
    json: { 
      "meta_fields": [
        {
          "original": "original value",
          "meta": "visible value",
          "expires_in": "seconds until the entry should expire"
        }
      ]
    },
    method: "POST"
}, function (err, reponse, body) {
    console.log(body); 
});
                  

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$url = "https://api.twotap.com/v1.0/wallet/meta?public_token=PUBLIC_TOKEN";
$payload = array( 
  "meta_fields": [
    {
      "original": "original value",
      "meta": "visible value",
      "expires_in": "seconds until the entry should expire"
    }
  ]
);
$response = Requests::post($url, array(), $payload);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
  "bytes"
)

func main() {
    url := "https://api.twotap.com/v1.0/wallet/meta?public_token=PUBLIC_TOKEN"
    var jsonStr = []byte(`{ 
      "meta_fields": [
        {
          "original": "original value",
          "meta": "visible value",
          "expires_in": "seconds until the entry should expire"
        }
      ]
    }`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;
using System.Text;
using Newtonsoft.Json;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/wallet/meta?public_token=PUBLIC_TOKEN");
      dynamic payload = new ExpandoObject ();
      payload.meta_fields = new ExpandoObject();
      payload.meta_fields.original = "original value";
      payload.meta_fields.meta = "visible value";
      payload.meta_fields.expires_in = "seconds until the entry should expire";
      string json = JsonConvert.SerializeObject(payload);
      var result = client.PostAsync ("", new StringContent(json, Encoding.UTF8, "application/json")).Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  


API Interface

You can use the API to create your own personalized checkout experience.

The interaction is divided into three separate tasks which have to be executed in order: '/cart', '/purchase', '/purchase/confirm'.

As we are growing to support hundreds of stores, even though we check the integrations constantly, edge cases and failures are bound to happen.

If you want to integrate the Two Tap API directly you should consider using the HTTP integration flow or SMS integration flow. Implementing these flows allows us to intervene in case of issues in the background and resolve them quickly without the shopper noticing anything.


Product Availability

To ask for product information use the /cart endpoint.


SMS Checkout flow

The SMS flow is an easy way to integrate Two Tap. The API will handle the shopper communication via SMS, and all you have to do is confirm the purchase.

The diagram below describes how you should interact with the API to create an SMS flow.

SMS confirm flow

To start the HTTP SMS flow send a confirm option like below with the /purchase request.

This step is not necessary if you are using the HTML5 interface.


#!/usr/bin/env ruby

fields_input = __INPUT__
products = __PRODUCTS__

confirm = {}
confirm['method'] = 'sms'
confirm['phone'] = 'the phone number of the shopper'
confirm['sms_confirm_url'] = 'An endpoint on your end where you will handle confirmations.'
confirm['sms_finished_url'] = 'An endpoint on your end where we will send the result of the purchase.'
confirm['retry_url'] = 'your_url_type://purchase/%%PURCHASE_ID%%/retry'

response = RestClient.post "https://api.twotap.com/v1.0/purchase?public_token=PUBLIC_TOKEN", { 
  cart_id: cart_id, 
  fields_input: fields_input, 
  products: products,
  confirm: confirm 
}
                  

#!/bin/sh

PUBLIC_TOKEN=PUBLIC_TOKEN
FIELDS_INPUT=FIELDS_INPUT
PRODUCTS=PRODUCTS

curl "https://api.twotap.com/v1.0/purchase?public_token=$PUBLIC_TOKEN" --header "Content-Type: application/json" --data-binary '{
  "cart_id": "CART_ID",
  "fields_input": "$FIELDS_INPUT",
  "products": "$PRODUCTS",
  "confirm": {
    "method": "sms",
    "phone": "the phone number of the shopper",
    "sms_confirm_url": "An endpoint on your end where you will handle confirmations.",
    "sms_finished_url": "An endpoint on your end where we will send the result of the purchase.",
    "retry_url": "your_url_type://purchase/PURCHASE_ID/retry"
  }
}'
                  

#!/usr/bin/python

import requests
import json

public_token = 'PUBLIC_TOKEN'
fields_input = 'FIELDS_INPUT'
products = 'PRODUCTS'

payload = { 
  "cart_id": "CART_ID", 
  "fields_input": fields_input, 
  "products": products,
  "confirm": {
    'method': 'sms',
    'phone': 'the phone number of the shopper',
    'sms_confirm_url': 'An endpoint on your end where you will handle confirmations.',
    'sms_finished_url': 'An endpoint on your end where we will send the result of the purchase.',
    'retry_url': 'your_url_type://purchase/PURCHASE_ID/retry'
  }
}

r = requests.post('https://api.twotap.com/v1.0/purchase?public_token=' + public_token,
  data=json.dumps(payload), headers={'Content-Type': 'application/json'})

print(r.json())
                  

var request = require("request");
var public_token = 'PUBLIC_TOKEN';
var fields_input = 'FIELDS_INPUT';
var products = 'PRODUCTS';
request({
  url: 'https://api.twotap.com/v1.0/purchase?public_token=' + public_token,
  json: { 
  "cart_id": "CART_ID", 
  "fields_input": fields_input, 
  "products": products,
  "confirm": {
    'method': 'sms',
    'phone': 'the phone number of the shopper',
    'sms_confirm_url': 'An endpoint on your end where you will handle confirmations.',
    'sms_finished_url': 'An endpoint on your end where we will send the result of the purchase.',
    'retry_url': 'your_url_type://purchase/PURCHASE_ID/retry'
  }
},
  method: "POST"
}, function (err, reponse, body) {
  console.log(body); 
});
                  

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$public_token = 'PUBLIC_TOKEN';
$fields_input = 'FIELDS_INPUT';
$products = 'PRODUCTS';
$url = 'https://api.twotap.com/v1.0/purchase?public_token='.$public_token;
$payload = array(
  "cart_id" => "CART_ID", 
  "fields_input" => $fields_input, 
  "products" => $products,
  "confirm" => array(
    'method' => 'sms',
    'phone' => 'the phone number of the shopper',
    'sms_confirm_url' => 'An endpoint on your end where you will handle confirmations.',
    'sms_finished_url' => 'An endpoint on your end where we will send the result of the purchase.',
    'retry_url' => 'your_url_type://purchase/PURCHASE_ID/retry'
    )
);
$response = Requests::post($url, array(), $payload);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
  "bytes"
)

func main() {
    public_token := "PUBLIC_TOKEN"
    fields_input := "FIELDS_INPUT"
    products := "PRODUCTS"
    url := "https://api.twotap.com/v1.0/purchase?public_token=" + public_token
    var jsonStr = []byte(`{
    "cart_id": "CART_ID",
    "fields_input": "$FIELDS_INPUT",
    "products": "$PRODUCTS",
    "confirm": {
      "method": "sms",
      "phone": "the phone number of the shopper",
      "sms_confirm_url": "An endpoint on your end where you will handle confirmations.",
      "sms_finished_url": "An endpoint on your end where we will send the result of the purchase.",
      "retry_url": "your_url_type://purchase/PURCHASE_ID/retry"
    }
}`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    req.Header.Add("Content-Type", "application/json")
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;
using System.Text;
using Newtonsoft.Json;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      var public_token = "PUBLIC_TOKEN";
      var cart_id = "CART_ID";
      var fields_input = "FIELDS_INPUT";
      var products = "PRODUCTS";
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/purchase?public_token=" + public_token);
      dynamic payload = new ExpandoObject ();
      payload.cart_id = cart_id;
      payload.fields_input = fields_input;
      payload.products = products;
      payload.confirm = new ExpandoObject ();
      payload.confirm.method = "sms";
      payload.confirm.phone = "the phone number of the shopper";
      payload.confirm.sms_confirm_url = "An endpoint on your end where you will handle confirmations.";
      payload.confirm.sms_finished_url = "An endpoint on your end where we will send the result of the purchase.";
      payload.confirm.retry_url = "your_url_type://purchase/PURCHASE_ID/retry";
      string json = JsonConvert.SerializeObject(payload);
      var result = client.PostAsync ("", new StringContent(json, Encoding.UTF8, "application/json")).Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  

SMS confirm URL (confirm callback)

Right at the end the HTML5 interface and API will ask you to perform the last '/purchase/confirm' API call.

This is done for security reasons and also as a way for you to retrieve/store purchase information.

At this step you should check that the unique_token and list of products match the ones from your initial call.

You have to return the JSON response of the '/purchase/confirm' call.


// example POST API callback:
// POST http://an_url/callback
//
// encoded JSON body arguments:
// purchase_id
// unique_token - a unique token if sent by the HTML5 interface
// sites { 
//   [site_id] {
//     products: { productMD5: { title, price, image, url } }
//     prices: { sales_tax, shipping_price, coupon_value, gift_card_value, final_price }
//     status: 'done or failed'
//     status_messages: [ 'An array of messages in case of failures' ]
//   }
// }

// example nodejs implementation:
exports.confirmURLCallback = function(req, res) {
  var privateToken = 'YOUR_PRIVATE_TWOTAP_TOKEN';
  var testMode = req.body.test_mode;

  var callPath = '/v1.0/purchase/confirm?private_token=' + privateToken; 

  // Pass whatever testMode is being used to the API call.
  if (testMode && testMode.length > 0) {
    callPath += "&test_mode=" + testMode;
  }

  var purchaseId  = req.body.purchase_id;
  var uniqueToken = req.body.unique_token;
  var sites       = req.body.sites; 

  // ...Check validity of request...
  // Check that you have created the unique token.
  // And that the product urls were part of the 
  // original request.

  // Call the Two Tap api to confirm (note https).
  rest.post('https://api.twotap.com' + callPath, {
    data: { purchase_id: purchaseId },

  }).on('complete', function(data, response) {
    // This is important! 
    // This is where you are feeding back the confirmation to the API.
    res.json(data);
    return;
  });  
};
                

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\ClassLoader\ClassLoader;

$loader = new ClassLoader();
$loader->addPrefix('Requests', '/path/to/Requests/library');
$loader->register();

use Requests;

class DefaultController extends Controller
{
    /**
     * @Route("/api/callback")
     */
    public function indexAction()
    {
      $request = Request::createFromGlobals();
      if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
        $data = json_decode($request->getContent(), true);
        $request->request->replace(is_array($data) ? $data : array());
      }

      $privateToken = 'YOUR_PRIVATE_TWOTAP_TOKEN';
      $testMode = $request->request->get('test_mode');
      $callPath = '/v1.0/purchase/confirm?private_token=' . $privateToken;
      if (null !== $testMode && strlen($testMode) > 0)
        $callPath .= "&test_mode=" . $testMode;
      
      $purchaseId = $request->request->get('purchase_id');
      $uniqueToken = $request->request->get('unique_token');
      $sites = $request->request->get('sites');

      // ...Check validity of request...
      // Check that you have created the unique token.
      // And that the product urls were part of the 
      // original request.

      // Call the Two Tap api to confirm (note https).
      $response = Requests::post('https://api.twotap.com' . $callPath, array(), array('purchase_id' => $purchaseId));
      // This is important! 
      // This is where you are feeding back the confirmation to the API.
      return new Response($response->body);
    }
}

                  

require 'rubygems'
require 'rest_client'

class SmsController < ApplicationController
  def index
    private_token = 'YOUR_PRIVATE_TWOTAP_TOKEN'
    test_mode = params[:test_mode]
    call_path = '/v1.0/purchase/confirm?private_token=' + private_token

    if test_mode && test_mode.size > 0
      call_path += '&test_mode=' + test_mode
    end

    purchase_id = params[:purchase_id]
    unique_token = params[:unique_token]
    sites = params[:sites]

    ## ...Check validity of request...
    ## Check that you have created the unique token.
    ## And that the product urls were part of the
    ## original request.

    ## Call the Two Tap api to confirm (note https).
    response = RestClient.post 'https://api.twotap.com' + call_path, { purchase_id: purchase_id }

    render :json => response.body
  end
end
                  

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
import json, pprint, requests

@csrf_exempt
def index(request):
  json_data = json.loads(request.body);

  privateToken = 'YOUR_PRIVATE_TWOTAP_TOKEN'
  callPath = '/v1.0/purchase/confirm?private_token=' + privateToken
  # Pass whatever testMode is being used to the API call.
  if 'test_mode' in json_data:
    callPath += '&test_mode=' + json_data['test_mode']

  purchaseId = json_data['purchase_id']
  uniqueToken = json_data['unique_token']
  sites = json_data['sites']
  
  # ...Check validity of request...
  # Check that you have created the unique token.
  # And that the product urls were part of the 
  # original request.

  # Call the Two Tap api to confirm (note https).
  payload = {'purchase_id': purchaseId}
  response = requests.post('https://api.twotap.com' + callPath,
    data=json.dumps(payload), headers={'Content-Type': 'application/json'})
  return HttpResponse(response.text, content_type='application/json')
                

SMS finished URL

This endpoint is called after we've finished the purchase process.

Alternatively you can hit our /purchase/status endpoint to fetch information like order_ids, and status of all stores.


// example POST API callback:
// POST http://an_url/callback
//
// encoded JSON body arguments:
// purchase_id
// sites { 
//   [site_id] {
//     order_id: 'order id'
//     status: 'done or failed'
//     status_messages: [ 'An array of messages in case of failures' ]
//   }
// }

// Example nodejs implementation:
exports.confirmURLCallback = function(req, res) {
  var purchaseId = req.body.purchase_id;
  // ...
  res.json({});
}
                  

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;


class SmsFinishedController extends Controller
{
    /**
     * @Route("/api/smsfinished")
     */
    public function indexAction()
    {
      $request = Request::createFromGlobals();
      if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
        $data = json_decode($request->getContent(), true);
        $request->request->replace(is_array($data) ? $data : array());
      }

      $purchaseId = $request->request->get('purchase_id');
      // ...
      return new Response('{}');
    }
}
                  

require 'rubygems'
require 'rest_client'

class HttpController < ApplicationController
  def index
    purchaseId = params[:purchase_id]
    # ...
    render :json => '{}'
  end
end
                  

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
import json

@csrf_exempt
def index(request):
  json_data = json.loads(request.body);

  purchaseId = json_data['purchase_id']
  # ...
  return HttpResponse('{}')
                  

Retrying SMS purchases

Using the SMS flow if a purchase doesn't go through, which in most cases means payment failure, shoppers receive an SMS with a link to restart the order.

By default the message looks like this: "You can restart your purchase anytime: http://ttap.co/XXXXXX". This opens in Safari, keeping all your branding and options, allowing the shopper to re-enter the information.

Often times on iOS you don't want to open the links in Safari, and you want to redirect to your app. Tweaking the 'retry_url' allows you to do this.


retry_url: 'your_url_type://purchase/%%PURCHASE_ID%%/retry'
            

When they get back to your app in the 'retry' flow you are receiving a purchase_id. All you have to do is to open an UIWebView pointing it to https://checkout.twotap.com/retry?purchase_id=%%PURCHASE_ID%%.


HTTP Checkout flow

The HTTP flow allows you more flexibility and control with shopper communication. You can use push notifications or other ways of prompting her to confirm purchases.

The diagram below describes how you should interact with the API to create an HTTP flow.

HTTP confirm flow

To start the HTTP API flow send a confirm option like below with the /purchase request.

This step is not necessary if you are using the HTML5 interface.


#!/usr/bin/env ruby

fields_input = __INPUT__
products = __PRODUCTS__

confirm = {}
confirm['method'] = 'http'
confirm['http_confirm_url']  = 'An endpoint on your end where you will handle confirmations.'
confirm['http_finished_url'] = 'An endpoint on your end where we will send the result of the purchase.'

response = RestClient.post "https://api.twotap.com/v1.0/purchase?public_token=PUBLIC_TOKEN", { 
  cart_id: cart_id, 
  fields_input: fields_input, 
  products: products, 
  confirm: confirm 
}
                  

#!/bin/sh

PUBLIC_TOKEN=PUBLIC_TOKEN
FIELDS_INPUT=FIELDS_INPUT
PRODUCTS=PRODUCTS

curl "https://api.twotap.com/v1.0/purchase?public_token=$PUBLIC_TOKEN" --header "Content-Type: application/json" --data-binary '{
  "cart_id": "CART_ID",
  "fields_input": "$FIELDS_INPUT",
  "products": "$PRODUCTS",
  "confirm": {
    "method": "http",
    "http_confirm_url": "An endpoint on your end where you will handle confirmations.",
    "http_finished_url": "An endpoint on your end where we will send the result of the purchase."
  }
}'
                  

#!/usr/bin/python

import requests
import json

public_token = 'PUBLIC_TOKEN'
fields_input = 'FIELDS_INPUT'
products = 'PRODUCTS'

payload = { 
  "cart_id": "CART_ID", 
  "fields_input": fields_input, 
  "products": products,
  "confirm": {
    'method': 'http',
    'http_confirm_url': 'An endpoint on your end where you will handle confirmations.',
    'http_finished_url': 'An endpoint on your end where we will send the result of the purchase.'
  }
}
r = requests.post('https://api.twotap.com/v1.0/purchase?public_token=' + public_token,
  data=json.dumps(payload), headers={'Content-Type': 'application/json'})

print(r.json())
                  

var request = require("request");
var public_token = 'PUBLIC_TOKEN';
var fields_input = 'FIELDS_INPUT';
var products = 'PRODUCTS';
request({
  url: 'https://api.twotap.com/v1.0/purchase?public_token=' + public_token,
  json: { 
  "cart_id": "CART_ID", 
  "fields_input": fields_input, 
  "products": products,
  "confirm": {
    'method': 'http',
    'http_confirm_url': 'An endpoint on your end where you will handle confirmations.',
    'http_finished_url': 'An endpoint on your end where we will send the result of the purchase.'
  }
},
  method: "POST"
}, function (err, reponse, body) {
  console.log(body); 
});
                  

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$public_token = 'PUBLIC_TOKEN';
$fields_input = 'FIELDS_INPUT';
$products = 'PRODUCTS';
$url = 'https://api.twotap.com/v1.0/purchase?public_token='.$public_token;
$payload = array(
  "cart_id" => "CART_ID", 
  "fields_input" => $fields_input, 
  "products" => $products,
  "confirm" => array(
    'method' => 'http',
    'http_confirm_url' => 'An endpoint on your end where you will handle confirmations.',
    'http_finished_url' => 'An endpoint on your end where we will send the result of the purchase.'
    )
);
$response = Requests::post($url, array(), $payload);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
  "bytes"
)

func main() {
    public_token := "PUBLIC_TOKEN"
    fields_input := "FIELDS_INPUT"
    products := "PRODUCTS"
    url := "https://api.twotap.com/v1.0/purchase?public_token=" + public_token
    var jsonStr = []byte(`{
    "cart_id": "CART_ID",
    "fields_input": "$FIELDS_INPUT",
    "products": "$PRODUCTS",
    "confirm": {
      "method": "http",
      "http_confirm_url": "An endpoint on your end where you will handle confirmations.",
      "http_finished_url": "An endpoint on your end where we will send the result of the purchase."
    }
}`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    req.Header.Add("Content-Type", "application/json")
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;
using System.Text;
using Newtonsoft.Json;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      var public_token = "PUBLIC_TOKEN";
      var cart_id = "CART_ID";
      var fields_input = "FIELDS_INPUT";
      var products = "PRODUCTS";
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/purchase?public_token=" + public_token);
      dynamic payload = new ExpandoObject ();
      payload.cart_id = cart_id;
      payload.fields_input = fields_input;
      payload.products = products;
      payload.confirm = new ExpandoObject ();
      payload.confirm.method = "http";
      payload.confirm.http_confirm_url = "An endpoint on your end where you will handle confirmations.";
      payload.confirm.http_finished_url = "An endpoint on your end where we will send the result of the purchase.";
      string json = JsonConvert.SerializeObject(payload);
      var result = client.PostAsync ("", new StringContent(json, Encoding.UTF8, "application/json")).Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  

HTTP confirm URL (confirm callback)

The HTTP confirm url is called with the product information, purchase prices, but most importantly two variables 'confirm_with_user (true or false)', and 'confirm_message'.

If our estimates were lower than the final purchase prices or if there were coupons/gift cards with the order confirm_with_user will be true and we'll ask you to confirm the new prices with the shopper. The confirmation message is in the confirm_message variable.

When you are ready hit our /purchase/confirm endpoint which will finalize the proccess.

Please note that 2 minutes after this call the session will expire.


// example POST API callback:
// POST http://an_url/callback
//
// encoded JSON body arguments:
// purchase_id
// unique_token - If using HTML5 interface
// sites { 
//   [site_id] {
//     products: { productMD5: { title, price, image, url } }
//     prices: { sales_tax, shipping_price, coupon_value, gift_card_value, final_price }
//     status: 'done or failed'
//     status_messages: [ 'An array of messages in case of failures' ]
//   }
// }
// confirm_with_user - true or false
// confirm_message - A string representing a message to ask the user 
// test_mode - empty, or fake_confirm

// Example nodejs implementation:
exports.confirmURLCallback = function(req, res) {
  var purchaseId      = req.body.purchase_id;
  var sites           = req.body.sites;
  var confirmWithUser = req.body.confirm_with_user;
  var confirmMessage  = req.body.confirm_message;

  if (confirmWithUser) {
    // Confirm with shopper, use confirmMessage.
  } else {
    confirm();
  }  

  res.json({});
}

// Call this when you are ready to confirm the purchase:
function confirm() {
  var privateToken = 'YOUR_PRIVATE_TWOTAP_TOKEN';
  var testMode = req.body.test_mode;

  var callPath = '/v1.0/purchase/confirm?private_token=' + privateToken; 
  // Pass whatever testMode is being used to the API call.
  if (testMode && testMode.length > 0) {
    callPath += "&test_mode=" + testMode;
  }

  // Call the Two Tap api to confirm (note https).
  rest.post('https://api.twotap.com' + callPath, {
    data: { purchase_id: purchaseId },
  }).on('complete', function(data, response) {
  });

  res.json({});
}

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\ClassLoader\ClassLoader;

$loader = new ClassLoader();
$loader->addPrefix('Requests', '/path/to/Requests/library');
$loader->register();

use Requests;

class DefaultController extends Controller
{
    /**
     * @Route("/api/callback")
     */
    public function indexAction()
    {
      $request = Request::createFromGlobals();
      if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
        $data = json_decode($request->getContent(), true);
        $request->request->replace(is_array($data) ? $data : array());
      }

      $purchaseId = $request->request->get('purchase_id');
      $sites = $request->request->get('sites');
      $confirmWithUser = $request->request->get('confirm_with_user');
      $confirmMessage = $request->request->get('confirm_message');

      if (null !== $confirmMessage) {
        // Confirm with shopper, use confirmMessage.
      }
      else {
        $privateToken = 'YOUR_PRIVATE_TWOTAP_TOKEN';
        $testMode = $request->request->get('test_mode');

        $callPath = '/v1.0/purchase/confirm?private_token=' . $privateToken;
        // Pass whatever testMode is being used to the API call.
        if (null !== $testMode && strlen($testMode) > 0)
          $callPath .= "&test_mode=" . $testMode;

        // Call the Two Tap api to confirm (note https).
        $response = Requests::post('https://api.twotap.com' . $callPath, array(), array('purchase_id' => $purchaseId));
        return new Response('{}');
      }
      return new Response('{}');
    }
}

                  

require 'rubygems'
require 'rest_client'

class HttpController < ApplicationController
  def index
    purchase_id = params[:purchase_id]
    sites = params[:sites]
    confirm_with_user = params[:confirm_with_user]
    confirm_message = params[:confirm_message]

    if confirm_with_user 
      ## Confirm with shopper, use confirm_message.
      render :nothing => true

    else
      private_token = 'YOUR_PRIVATE_TWOTAP_TOKEN'
      test_mode = params[:test_mode]
      call_path = '/v1.0/purchase/confirm?private_token=' + private_token

      if test_mode && test_mode.size > 0
        callPath += "&test_mode=" + test_mode
      end

      response = RestClient.post 'https://api.twotap.com' + call_path, { purchase_id: purchase_id }
      render :json => response.body
    end
  end
end
                  

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
import json, pprint, requests

@csrf_exempt
def index(request):
  json_data = json.loads(request.body);

  purchaseId = json_data['purchase_id']
  sites = json_data['sites']
  confirmWithUser = json_data['confirm_with_user']
  confirmMessage = json_data['confirm_message']

  if confirmWithUser:
    # Confirm with shopper, use confirmMessage.
  else:
    privateToken = 'YOUR_PRIVATE_TWOTAP_TOKEN'
    testMode = json_data['test_mode']

    callPath = '/v1.0/purchase/confirm?private_token=' + privateToken
    # Pass whatever testMode is being used to the API call.
    if 'test_mode' in json_data:
      callPath += '&test_mode=' + json_data['test_mode']

    # Call the Two Tap api to confirm (note https).
    payload = {'purchase_id': purchaseId}
    response = requests.post('https://api.twotap.com' + callPath,
      data=json.dumps(payload), headers={'Content-Type': 'application/json'})
    return HttpResponse(response.text, content_type='application/json')
  return HttpResponse('{}')
                  

HTTP finished URL

This endpoint is called after we've finished the purchase process. We'll send you a final_message which contains a confirmation you could give to the user.

Alternatively you can hit our /purchase/status endpoint to fetch information like order_ids, and status of all stores.


// example POST API callback:
// POST http://an_url/callback
//
// encoded JSON body arguments:
// purchase_id
// sites { 
//   [site_id] {
//     order_id: 'order id'
//     status: 'done or failed'
//     status_messages: [ 'An array of messages in case of failures' ]
//   }
// }
// final_message - A string representing a confirmation message to the user

// Example nodejs implementation:
exports.confirmURLCallback = function(req, res) {
  var purchaseId = req.body.purchase_id;
  var finalMessage = req.body.final_message;
  
  // Send final_message to the user.

  res.json({});
}
                  

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;


class SmsFinishedController extends Controller
{
    /**
     * @Route("/api/smsfinished")
     */
    public function indexAction()
    {
      $request = Request::createFromGlobals();
      if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
        $data = json_decode($request->getContent(), true);
        $request->request->replace(is_array($data) ? $data : array());
      }

      $purchaseId = $request->request->get('purchase_id');
      $finalMessage = $request->request->get('final_message');
      
      // Send final_message to the user.

      return new Response('{}');
    }
}
                  

require 'rubygems'
require 'rest_client'

class HttpController < ApplicationController
  def index
    purchaseId = params[:purchase_id]
    finalMessage = params[:final_message]
    
    # Send final_message to the user.

    render :json => '{}'
  end
end
                  

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
import json

@csrf_exempt
def index(request):
  json_data = json.loads(request.body);

  purchaseId = json_data['purchase_id']
  finalMessage = json_data['final_message']
  
  # Send final_message to the user.

  return HttpResponse('{}')
                  

Testing

The only thing you have to test is the confirm callback. Before finalizing the order we ping your server ensure that it's actually you placing the order, and not someone else using your public token.

Callbacks are also a convenient way of storing important information about the purchase on your end.

Depending on what confirm mode you are using (the default mode is SMS), see the SMS API or HTTP API confirm flows, with example code on how to implement the callbacks.

We provide an easy way to test out the confirm callbacks once you've implemented them. Use following snippet:


curl https://core.twotap.com/callback_test.sh | bash -s http://YOUR_LOCAL_MACHINE/PATH_TO_CALLBACK
            

Make sure the snippet above returns OK and that your callback endpoints are internet accessible before placing any orders.

Once everything is set up try placing an order with test_mode=fake_confirm.

In 'fake_confirm' test mode the API will start the purchase as usual on the retailer site, except at the final 'Purchase Confirm' step it will return a fake OK and not place the order.

It's important to note that some stores validate payment information when the order is actually placed, consequently the sms_finished_url will be called directly with payment errors.

Examples

See complete checkout flows in different programming languages in this github repository.


API methods

Cart

The first request you have to make is to '/cart'. Send '/cart' a list of products and it will return product information (title, price, image) and the required fields that the user has to fill in to finalize the purchase.

The response will include the required fields for one or two possible checkout flows: authenticated (authCheckout, if the user has an account on that website), and as guest (noauthCheckout).

You can not add products to an existing cart. If you'd like to add more products feel free to start a new '/cart' call.

This request is processed in the background. You either have to call '/cart/status' which will retrieve the relevant information once the job is finished, or set 'finished_url' which will trigger a callback to your servers.

Initial request


#!/usr/bin/env ruby

require 'rubygems'
require 'rest_client'

products = [ 'http://fab.com/sale/4850/product/11263/', 'http://fab.com/sale/4800/product/43565/' ]

response = RestClient.post "https://api.twotap.com/v1.0/cart?public_token=PUBLIC_TOKEN", { products: products }

puts response.body
                  

#!/bin/bash

curl https://api.twotap.com/v1.0/cart?public_token=$PUBLIC_TOKEN --header 'Content-Type: application/json' --data-binary '{
  "products": ["http://fab.com/sale/4850/product/11263/", "http://fab.com/sale/4800/product/43565/"]
}'
                  

#!/usr/bin/python

import requests
import json

PUBLIC_TOKEN = 'YOUR PUBLIC TOKEN'

payload = {'products': [ 'http://fab.com/sale/4850/product/11263/', 'http://fab.com/sale/4800/product/43565/' ]}

response = requests.post('https://api.twotap.com/v1.0/cart?public_token=' + PUBLIC_TOKEN,
        data=json.dumps(payload), headers={'Content-Type': 'application/json'})

print(response.json())
                  

$.post('https://api.twotap.com/v1.0/cart?public_token=PUBLIC_TOKEN', { 
    "products": [ "http://fab.com/sale/4850/product/11263/", "http://fab.com/sale/4800/product/43565/" ]
  }, function(data, status) {
    console.log(data);
});
                  

var request = require("request");
var public_token = 'PUBLIC_TOKEN';
request({
  url: 'https://api.twotap.com/v1.0/cart?public_token=' + public_token,
  json: { "products": [ "http://fab.com/sale/4850/product/11263/", "http://fab.com/sale/4800/product/43565/" ]},
  method: "POST"
}, function (err, reponse, body) {
  console.log(body); 
});
                  

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$public_token = 'PUBLIC_TOKEN';
$url = 'https://api.twotap.com/v1.0/cart?public_token='.$public_token;
$products = array('products' => 'http://fab.com/sale/4850/product/11263/', 'http://fab.com/sale/4800/product/43565/');
$response = Requests::post($url, array(), $products);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
  "bytes"
)

func main() {
    public_token := "PUBLIC_TOKEN"
    url := "https://api.twotap.com/v1.0/cart?public_token=" + public_token
    var jsonStr = []byte(`{"products":"http://fab.com/sale/4850/product/11263/,http://fab.com/sale/4800/product/43565/"}`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;
using System.Text;
using Newtonsoft.Json;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      var public_token = "PUBLIC_TOKEN";
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/cart?public_token=" + public_token);
      dynamic payload = new ExpandoObject ();
      payload.products = new String[] { "http://fab.com/sale/4850/product/11263/", "http://fab.com/sale/4800/product/43565/" };
      string json = JsonConvert.SerializeObject(payload);
      var result = client.PostAsync ("", new StringContent(json, Encoding.UTF8, "application/json")).Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  

Request

POST https://api.twotap.com/v1.0/cart?public_token=PUBLIC_TOKEN

Parameters

products A list of product URLs. Max 15 in one request, the API will silently drop anything above that.
finished_url (Optional) An endpoint where Two Tap can POST the product information once the information is retrieved.
test_mode (Optional) fake_confirm. A way to test the API the interface without making actual purchases.

Response


{
  "cart_id": "50f414b9e6a4869bf6000009",
  "message": "still_processing",
  "description": "Still processing."
}                    
            

Response description

cart_id An ID to use when checking the cart status.
message The status, which will be 'still_processing'.
description A more human friendly description of the status.

Cart status

Call '/cart/status' to see the status of an '/cart' request. The response is different if response['message'] is 'still_processing', 'failed', or 'done'.

Status request


#!/usr/bin/env ruby

require 'rubygems'
require 'rest_client'

cart_id = ARGV[0]

response = RestClient.get "https://api.twotap.com/v1.0/cart/status?public_token=PUBLIC_TOKEN&cart_id=#{cart_id}"

puts response.body
                  

#!/bin/bash

PUBLIC_TOKEN=PUBLIC_TOKEN
CART_ID=CART_ID

curl https://api.twotap.com/v1.0/cart/status?public_token=$PUBLIC_TOKEN\&cart_id=$CART_ID
                  

#!/usr/bin/python

import requests

public_token = 'PUBLIC_TOKEN'
cart_id = 'CART_ID'
r = requests.get('https://api.twotap.com/v1.0/cart/status?public_token=' + public_token + '&cart_id=' + cart_id)
print(r.json())
                  

$.get("https://api.twotap.com/v1.0/cart/status?public_token=PUBLIC_TOKEN&cart_id=CART_ID", function(data, status) {
  console.log(data);
})
                  

var request = require("request");
var public_token = 'PUBLIC_TOKEN';
var cart_id = 'CART_ID';
request({
  url: 'https://api.twotap.com/v1.0/cart/status?public_token=' + public_token + '&cart_id=' + cart_id,
  method: "GET"
}, function (err, reponse, body) {
  console.log(body); 
});
                  

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$public_token = 'PUBLIC_TOKEN';
$cart_id = 'CART_ID';
$url = 'https://api.twotap.com/v1.0/cart/status?public_token='.$public_token.'&cart_id='.$cart_id;
$response = Requests::get($url);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
)

func main() {
    public_token := "PUBLIC_TOKEN"
    cart_id := "CART_ID"
    url := "https://api.twotap.com/v1.0/cart/status?public_token=" + public_token + "&cart_id=" + cart_id
    req, err := http.NewRequest("GET", url, nil)
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      var public_token = "PUBLIC_TOKEN";
      var cart_id = "CART_ID";
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/cart/status?public_token=" + public_token + "&cart_id=" + cart_id);
      var result = client.GetAsync ("").Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  

Request

GET https://api.twotap.com/v1.0/cart/status?public_token=PUBLIC_TOKEN

Parameters

cart_id The cart id that is sent by '/cart'.
test_mode (Optional) fake_confirm. A way to test the API the interface without making actual purchases.

Still processing response


{
  "sites": {
    "50f414b9e6a4869bf6000002": {
      "cart": {
        "8aa20fab1787c6f9eef77a0bd8a70ae3": {
          "last_message": "Fetching title.",
          "url": "http://fab.com/sale/4850/product/11263/",
          "status": "done"
        },
        "aa5cb27ddd44c3cb639d11419d9574db": {
          "last_message": "Fetching required fields.",
          "url": "http://fab.com/sale/4800/product/43565/",
          "status": "still_processing"
        }
      },
      "info": {
        "name": "Fab",
        "url": "fab.com"
      }
    }
  },
  "cart_id": "50f414b9e6a4869bf6000009",
  "message": "still_processing",
  "description": "Still processing."
} 
          

Completed response


{
  "sites": {
    "50f414b9e6a4869bf6000002": {
      "info": {
        "name": "Fab",
        "url": "fab.com"
      },
      "coupon_support": true,
      "gift_card_support": true,
      "shipping_options": {
        "cheapest": "Usually 7-10 days",
        "fastest": "Usually 1-3 days",
      },
      "required_fields": {
        "authCheckout": {
          "country": {
            "input_options": [],
            "input_type": "text",
            "input_name": "INPUT"
          }
        },
        "noauthCheckout": {
          "address": {
            "field_type": "shipping",
            "input_values": [],
            "input_type": "text",
            "input_name": "INPUT"
          },
          "last_name": {
            "field_type": "shipping",
            "input_values": [],
            "input_type": "text",
            "input_name": "INPUT"
          },
          "first_name": {
            "field_type": "shipping",
            "input_values": [],
            "input_type": "text",
            "input_name": "INPUT"
          },
           "shipping_country": {
            "field_type": "shipping",
            "input_values": [
              {
                "value": "",
                "text": "Please Select"
              },
              {
                "value": "AG",
                "text": "Antigua And Barbuda"
              },
              {
                "value": "AR",
                "text": "Argentina"
              },
            ],
            "input_type": "select-one",
            "input_name": "SELECT"
          }
        },
        "login": {
          "password": {
            "input_options": [],
            "input_type": "password",
            "input_name": "INPUT"
          },
          "email": {
            "input_options": [],
            "input_type": "text",
            "input_name": "INPUT"
          }
        }
      },
      "add_to_cart": {
        "367bcd6f21fc7568b135c387d3fa5d57": {
          "required_fields": {
            "quantity": {
              "input_type": "text",
              "input_name": "INPUT"
            },
            "Size": {
              "input_type": "select-one",
              "input_name": "SELECT"
            },
            "Color": {
              "input_type": "select-one",
              "input_name": "SELECT"
            }
          },
          "required_field_values": {
            "Color": [
              {
                "value": "",
                "text": "First, choose color",
                "dep": {
                  "Size": [
                    {
                      "value": "",
                      "text": "Then, choose size",
                      "dep": {}
                    }
                  ]
                }
              },
              {
                "value": "13233550|1055745",
                "text": "Red",
                "dep": {
                  "Size": [
                    {
                      "value": "",
                      "text": "Then, choose size",
                      "dep": {}
                    },
                    {
                      "value": "13233550|13442751",
                      "text": "28",
                      "dep": {}
                    },
                    {
                      "value": "13233550|13442753",
                      "text": "30",
                      "dep": {}
                    },
                  ]
                }
              },
              {
                "value": "13233550|1055747",
                "text": "Black",
                "dep": {
                  "Size": [
                    {
                      "value": "",
                      "text": "Then, choose size",
                      "dep": {}
                    },
                    {
                      "value": "13233550|13442771",
                      "text": "28",
                      "dep": {}
                    },
                  ]
                }
              },
              {
                "value": "13233550|1068432",
                "text": "New Blue",
                "dep": {
                  "Size": [
                    {
                      "value": "",
                      "text": "Then, choose size",
                      "dep": {}
                    },
                    {
                      "value": "13233550|13442762",
                      "text": "29",
                      "dep": {}
                    },
                    {
                      "value": "13233550|13442763",
                      "text": "30",
                      "dep": {}
                    }
                  ]
                }
              }
            ]
          },          
          "url": "http://fab.com/sale/4850/product/11263",
          "image": "//d15bx2axpp9a1x.cloudfront.net/product/92931-360x360-1355670380-primary.png",
          "alt_images": [ 
            "//d15bx2axpp9a1x.cloudfront.net/product/92931-360x360-1355670381-primary.png",
            "//d15bx2axpp9a1x.cloudfront.net/product/92931-360x360-1355670382-primary.png"
          ],
          "price": "41€",
          "title": "Heart Pendant Red Small",
          "description": "A sophisticated laptop case for the fashionable commuter. This form-fitting, durable neoprene sleeve keeps your computer completely safe with a fully-lined faux-fur interior, a reinforced protective interior border, and padded zipper collar. Its slim, simple design makes it easy to use as a standalone case or to slip inside of a bag.",
          "returns": "You may return any item not marked ‘Final Sale’ within 30 days of delivery for free.",
          "status": "done"
        }
      },
      "failed_to_cart": {},
        "8aa20fab1787c6f9eef77a0bd8a70ae3": {
          "field_values": {},
          "url": "http://fab.com/sale/4800/product/43565/"
        },
      }
    }
  },
  "unknown_urls": [
    "http://www.google.com"
  ],
  "cart_id": "511fa680692d5d9e06000001",
  "message": "has_failures",
  "description": "Encountered some errors while parsing the urls."
}                        
            

Response description

message The status, which can be 'still_processing', 'has_failures', or 'done'.
description A more human friendly description of the status.
unknown_urls An array with URLs that Two Tap does not support yet.
sites A hash that contains information grouped by a siteID.
sites[id][info] A hash that contains the 'name' and 'url' of the site.
sites[id][coupon_support] true or false. If we have coupon support for a certain site.
sites[id][gift_card_support] true or false. If we have gift card support for a certain site.
sites[id][shipping_options] Can be empty, or a hash that contains available shipping options. { "cheapest": "description", "fastest": "description" }.
sites[id][required_fields]

A hash that contains the required fields grouped by three possible flows: 'login', 'authCheckout', and 'noauthCheckout'.

For non-authenticated checkouts send the 'noauthCheckout' fields. For authenticated checkouts send the 'authCheckout' and 'login' fields.

sites[id][required_fields][flow] A hash that contains the required fields grouped by the field name.
sites[id][required_fields][flow][field] A hash that contains input_name, input_type, and input_options (in case of SELECT).
sites[id][add_to_cart] A hash that contains product information grouped by it's URL MD5.
sites[id][add_to_cart][productMD5][last_message] The latest status message sent by the request processor. This is used during the 'still_processing' phase.
sites[id][add_to_cart][productMD5][required_fields] A hash that contains the product required fields grouped by their field name.
sites[id][add_to_cart][productMD5][required_field_values] A hash that contains the possible product required field values as a dependency tree.
sites[id][add_to_cart][productMD5][url] URL of the product.
sites[id][add_to_cart][productMD5][image] The product's image URL.
sites[id][add_to_cart][productMD5][alt_images] The product's secondary images as an array.
sites[id][add_to_cart][productMD5][price] The product's price.
sites[id][add_to_cart][productMD5][title] The product's title.
sites[id][add_to_cart][productMD5][description] The product's description.
sites[id][add_to_cart][productMD5][returns] The product's return policy.
sites[id][add_to_cart][productMD5][status] The processing status for this product. Can be 'done' or 'still_processing'.
sites[id][failed_to_cart] Similar to sites[id][add_to_cart] however it lists which products have failed processing for some reason.

Cart estimates

This endpoint will return an estimated sales tax, shipping price, and final price for a certain cart.

The more information you can give it the more accurate it becomes.

Usually you should send the shopper selected product options. Some product attribute combinations have different prices so in order to have a valid estimates the attributes are recommended.

You can also send the shoppers shipping city, state, and zip code for more accurate sales tax calculations.

Request


#!/usr/bin/env ruby

require 'rubygems'
require 'rest_client'

cart_id = ARGV[0]
fields_input = { 
  [site_id]: { 
    addToCart: { 
      [product_md5]: { 
        size: 'M'
      }
    },
    noauthCheckout: {
      shipping_zip: '94303',
      shipping_city: 'City',
      shipping_state: 'State'
    },
    shipping_option: 'cheapest'
  } 
}

products = [ 'http://fab.com/sale/4850/product/11263/', 'http://fab.com/sale/4800/product/43565/' ]

response = RestClient.post "https://api.twotap.com/v1.0/cart/estimates?public_token=PUBLIC_TOKEN", { 
  cart_id: cart_id, 
  fields_input: fields_input, 
  products: products 
}

puts response.body
            

#!/bin/sh

PUBLIC_TOKEN=PUBLIC_TOKEN

curl "https://api.twotap.com/v1.0/cart/estimates?public_token=$PUBLIC_TOKEN" --header "Content-Type: application/json" --data-binary '{
  "cart_id": "CART_ID",
  "fields_input": {
    "SITE_ID: {
      "addToCart": {
        "PRODUCT_MD5": {
          "size": "M" 
        }
      },
      "noauthCheckout": {
        "shipping_zip": "94303",
        "shipping_city": "City",
        "shipping_state": "State"
      },
      "shipping_option": "cheapest"
    }
  }
}'
                  

#!/usr/bin/python

import requests
import json

public_token = 'PUBLIC_TOKEN'
payload = {
  "cart_id": "CART_ID",
  "fields_input": {
    "SITE_ID": {
      "addToCart": {
        "PRODUCT_MD5": {
          "size": "M" 
        }
      },
      "noauthCheckout": {
        "shipping_zip": "94303",
        "shipping_city": "City",
        "shipping_state": "State"
      },
      "shipping_option": "cheapest"
    }
  }
};
r = requests.post('https://api.twotap.com/v1.0/cart/estimates?public_token=' + public_token,
  data=json.dumps(payload), headers={'Content-Type': 'application/json'})
print(r.json())
                  

$.post('https://api.twotap.com/v1.0/cart/estimates?public_token=PUBLIC_TOKEN', {
  "cart_id": "CART_ID",
  "fields_input": {
    "SITE_ID": {
      "addToCart": {
        "PRODUCT_MD5": {
          "size": "M" 
        },
        "noauthCheckout": {
          "shipping_zip": "94303",
          "shipping_city": "City",
          "shipping_state": "State"
        },
        "shipping_option": "cheapest"
      }
    }
  }
}, function(data, status) {
    console.log(data);
});
                  

var request = require("request");
var public_token = 'PUBLIC_TOKEN';
request({
  url: 'https://api.twotap.com/v1.0/cart/estimates?public_token=' + public_token,
  json: {
  "cart_id": "CART_ID",
  "fields_input": {
    "SITE_ID": {
      "addToCart": {
        "PRODUCT_MD5": {
          "size": "M" 
        }
      },
      "noauthCheckout": {
        "shipping_zip": "94303",
        "shipping_city": "City",
        "shipping_state": "State"
      },
      "shipping_option": "cheapest"
    }
  }
},
  method: "POST"
}, function (err, reponse, body) {
  console.log(body); 
});
                  

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$public_token = 'PUBLIC_TOKEN';
$url = 'https://api.twotap.com/v1.0/cart/estimates?public_token='.$public_token;
$payload = array(
  "cart_id" => "CART_ID",
  "fields_input" => array(
    "SITE_ID" => array(
      "addToCart" => array(
        "PRODUCT_MD5" => array(
          "size" => "M"
        )
      ),
      "noauthCheckout" => array(
        "shipping_zip" => "94303",
        "shipping_city" => "City",
        "shipping_state" => "State"
      ),
      "shipping_option" => "cheapest"
    )
  )
);
$response = Requests::post($url, array(), $payload);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
  "bytes"
)

func main() {
    public_token := "PUBLIC_TOKEN"
    url := "https://api.twotap.com/v1.0/cart/estimates?public_token=" + public_token
    var jsonStr = []byte(`{
      "cart_id": "CART_ID",
      "fields_input": {
        "SITE_ID": {
          "addToCart": {
            "PRODUCT_MD5": {
              "size": "M" 
            }
          },
          "noauthCheckout": {
            "shipping_zip": "94303",
            "shipping_city": "City",
            "shipping_state": "State"
          },
          "shipping_option": "cheapest"
        }
      }
    }`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    req.Header.Add("Content-Type", "application/json")
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;
using System.Text;
using Newtonsoft.Json;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      var public_token = "PUBLIC_TOKEN";
      var cart_id = "CART_ID";
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/cart/estimates?public_token=" + public_token);
      dynamic payload = new ExpandoObject ();
      payload.cart_id = cart_id;
      payload.fields_input = new ExpandoObject ();
      payload.fields_input.SITE_ID = new ExpandoObject ();
      payload.fields_input.SITE_ID.addToCart = new ExpandoObject ();
      payload.fields_input.SITE_ID.addToCart.PRODUCT_MD5 = new ExpandoObject ();
      payload.fields_input.SITE_ID.addToCart.PRODUCT_MD5.size = "M";
      payload.fields_input.SITE_ID.noauthCheckout = new ExpandoObject ();
      payload.fields_input.SITE_ID.noauthCheckout.shipping_zip = "94303";
      payload.fields_input.SITE_ID.noauthCheckout.shipping_city = "City";
      payload.fields_input.SITE_ID.noauthCheckout.shipping_state = "State";
      payload.fields_input.shipping_option = "cheapest";
      string json = JsonConvert.SerializeObject(payload);
      var result = client.PostAsync ("", new StringContent(json, Encoding.UTF8, "application/json")).Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  

Request

POST https://api.twotap.com/v1.0/cart/estimates?public_token=PUBLIC_TOKEN

Parameters

cart_id A cart id that has finished running.
fields A hash that looks like:
{
  "[site_id]":
    {
      "noauthCheckout": { required checkout fields },
      "addToCart": {
        "[productMD5_1]": "required product fields",
        "[productMD5_2]": "required product fields",
      "shipping_option": { "cheapest or fastest" } (optional),
    }
}
products (Optional) An array of product URLs that have accepted by the user. This is useful in case a user removes some items between /cart and /purchase.

If specified fields_input should contain only the required data for those products.

Response


{
  "message":"done",
  "estimates":{
    "51bf1d1055a0f9e0d0000003":{
      "prices":{
        "shipping_price":"$5.00",
        "sales_tax":"$2.54",
        "final_price":"$31.54"
      }
    }
  }
}
            

Response description

message The status, which will be 'done'.
estimates A hash that looks like { [site_id]: { prices: { shipping_price, sales_tax, final_price } } }

Purchase

Purchase will retrieve the shipping price, sales tax, and final price of the order. It will start the checkout process but not finish it to ensure the data sent back is valid.

If in the '/cart' request the required_fields entry contains both 'authCheckout' and 'noauthCheckout' it means that two checkout flows are supported: logged in and not logged in. You have to pick one of the two flows. If you send both the API will proceed with the not logged in checkout flow.

This request needs to be POSTed with the field information that was required in '/cart', and uses only the valid products from that call.

To keep things simple if you're building your own native checkout always ask for the following fields, and send them all with each site_id. This way you don't need to look into each site's required fields.


  [ 
    'email',
    'shipping_title',
    'shipping_first_name',
    'shipping_last_name',
    'shipping_address',
    'shipping_city',
    'shipping_state',
    'shipping_country',
    'shipping_zip',
    'shipping_telephone',
    'billing_title',
    'billing_first_name',
    'billing_last_name',
    'billing_address',
    'billing_city',
    'billing_state',
    'billing_country',
    'billing_zip',
    'billing_telephone',
    'card_type',
    'card_number',
    'card_name',
    'expiry_date_year',
    'expiry_date_month',
    'cvv'
  ]

This request is processed in the background which means you either have to call '/purchase/status' or use the SMS API flow or HTTP API flow to receive callbacks.

Once this request finishes you will have 2 minutes available to call '/purchase/confirm' to confirm it.

On authenticated checkout flows (authCheckout) Two Tap currently supports only using the shipping and payment information the user has saved on that particular site.

If you are using the API directly we highly recommend you use an address validator like lob.com or easypost.com in your app/service. It's free.

Initial request


#!/usr/bin/env ruby

require 'rubygems'
require 'rest_client'

cart_id = ARGV[0]

fields_input = { 
  [site_id]: {
    "noauthCheckout": { "email" => "shopper@gmail.com", "shipping_telephone" => "6503941234", "shipping_zip" => "94303", "shipping_state" => "California", "shipping_city" => "Palo Alto", "shipping_address" => "555 Palo Alto Avenue", ".." => ".." },
    "addToCart": {
      [product_md5]: { "quantity": 1 }
    }
  }
}

affiliate_links = {
  [site_id]: "http://affiliate_link"  
}

products = [ 'http://fab.com/sale/4850/product/11263' ]

response = RestClient.post "https://api.twotap.com/v1.0/purchase?public_token=PUBLIC_TOKEN", { 
  cart_id: cart_id, 
  fields_input: fields_input, 
  affiliate_links: affiliate_links, 
  products: products 
}

puts response.body
            

#!/bin/sh

PUBLIC_TOKEN=PUBLIC_TOKEN

curl "https://api.twotap.com/v1.0/purchase?public_token=$PUBLIC_TOKEN" --header "Content-Type: application/json" --data-binary '
{
  "cart_id": "CART_ID",
  "fields_input": {
    "SITE_ID": {
      "noauthCheckout": { "email": "shopper@gmail.com", "shipping_telephone": "6503941234", "shipping_zip": "94303", "shipping_state": "California", "shipping_city": "Palo Alto", "shipping_address": "555 Palo Alto Avenue", "..": ".." },
      "addToCart": {
        "[product_md5]": { "quantity": 1 }
      }
    }
  },
  "affiliate_links": {
   "SITE_ID": "http://affiliate_link"  
  },
  "products": "http://fab.com/sale/4850/product/11263"
}'
                  

#!/usr/bin/python

import requests
import json

public_token = 'PUBLIC_TOKEN'
payload = {
  "cart_id": "CART_ID",
  "fields_input": {
    "SITE_ID": {
      "noauthCheckout": { "email": "shopper@gmail.com", "shipping_telephone": "6503941234", "shipping_zip": "94303", "shipping_state": "California", "shipping_city": "Palo Alto", "shipping_address": "555 Palo Alto Avenue", "..": ".." },
      "addToCart": {
        "[product_md5]": { "quantity": 1 }
      }
    }
  },
  "affiliate_links": {
   "SITE_ID": "http://affiliate_link"  
  },
  "products": "http://fab.com/sale/4850/product/11263"
};
r = requests.post('https://api.twotap.com/v1.0/purchase?public_token=' + public_token,
  data=json.dumps(payload), headers={'Content-Type': 'application/json'})
print(r.json())
                  

$.post('https://api.twotap.com/v1.0/purchase?public_token=PUBLIC_TOKEN', {
  "cart_id": "CART_ID",
  "fields_input": {
    "SITE_ID": {
      "noauthCheckout": { "email": "shopper@gmail.com", "shipping_telephone": "6503941234", "shipping_zip": "94303", "shipping_state": "California", "shipping_city": "Palo Alto", "shipping_address": "555 Palo Alto Avenue", "..": ".." },
      "addToCart": {
        "[product_md5]": { "quantity": 1 }
      }
    }
  },
  "affiliate_links": {
   "SITE_ID": "http://affiliate_link"  
  },
  "products": "http://fab.com/sale/4850/product/11263"
}, function(data, status) {
    console.log(data);
});
                  

var request = require("request");
var public_token = 'PUBLIC_TOKEN';
request({
  url: 'https://api.twotap.com/v1.0/purchase?public_token=' + public_token,
  json: {
  "cart_id": "CART_ID",
  "fields_input": {
    "SITE_ID": {
      "noauthCheckout": { "email": "shopper@gmail.com", "shipping_telephone": "6503941234", "shipping_zip": "94303", "shipping_state": "California", "shipping_city": "Palo Alto", "shipping_address": "555 Palo Alto Avenue", "..": ".." },
      "addToCart": {
        "[product_md5]": { "quantity": 1 }
      }
    }
  },
  "affiliate_links": {
  "SITE_ID": "http://affiliate_link"  
  },
  "products": "http://fab.com/sale/4850/product/11263"
},
  method: "POST"
}, function (err, reponse, body) {
  console.log(body); 
});
                  

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$public_token = 'PUBLIC_TOKEN';
$url = 'https://api.twotap.com/v1.0/purchase?public_token='.$public_token;
$payload = array(
  "cart_id" => "CART_ID",
  "fields_input" => array(
    "SITE_ID" => array(
      "noauthCheckout": { "email": "shopper@gmail.com", "shipping_telephone": "6503941234", "shipping_zip": "94303", "shipping_state": "California", "shipping_city": "Palo Alto", "shipping_address": "555 Palo Alto Avenue", "..": ".." },
      "addToCart" => array(
        "[product_md5]" => array( "quantity" => 1 )
      )
    )
  ),
  "affiliate_links" => array(
   "SITE_ID" => "http://affiliate_link"  
  ),
  "products" => "http://fab.com/sale/4850/product/11263"
);
$response = Requests::post($url, array(), $payload);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
  "bytes"
)

func main() {
    public_token := "PUBLIC_TOKEN"
    url := "https://api.twotap.com/v1.0/purchase?public_token=" + public_token
    var jsonStr = []byte(`{
  "cart_id": "CART_ID",
  "fields_input": {
    "SITE_ID": {
      "noauthCheckout": { "email": "shopper@gmail.com", "shipping_telephone": "6503941234", "shipping_zip": "94303", "shipping_state": "California", "shipping_city": "Palo Alto", "shipping_address": "555 Palo Alto Avenue", "..": ".." },
      "addToCart": {
        "[product_md5]": { "quantity": 1 }
      }
    }
  },
  "affiliate_links": {
  "SITE_ID": "http://affiliate_link"  
  },
  "products": "http://fab.com/sale/4850/product/11263"
}`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    req.Header.Add("Content-Type", "application/json")
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;
using System.Text;
using Newtonsoft.Json;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      var public_token = "PUBLIC_TOKEN";
      var cart_id = "CART_ID";
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/purchase?public_token=" + public_token);
      dynamic payload = new ExpandoObject ();
      payload.cart_id = cart_id;
      payload.fields_input = new ExpandoObject ();
      payload.fields_input.SITE_ID = new ExpandoObject ();
      payload.fields_input.SITE_ID.noauthCheckout = new ExpandoObject ();
      payload.fields_input.SITE_ID.noauthCheckout.email = "shopper@gmail.com";
      payload.fields_input.SITE_ID.noauthCheckout.shipping_telephone = "6503941234";
      payload.fields_input.SITE_ID.noauthCheckout.shipping_zip = "94303";
      payload.fields_input.SITE_ID.noauthCheckout.shipping_city = "Palo Alto";
      payload.fields_input.SITE_ID.noauthCheckout.shipping_state = "California";
      payload.fields_input.SITE_ID.noauthCheckout.shipping_address = "555 Palo Alto Avenue";
      payload.fields_input.SITE_ID.addToCart = new ExpandoObject ();
      payload.fields_input.SITE_ID.addToCart.PRODUCT_MD5 = new ExpandoObject ();
      payload.fields_input.SITE_ID.addToCart.PRODUCT_MD5.quantity = 1;
      payload.affiliate_links = new ExpandoObject ();
      payload.affiliate_links.SITE_ID = "http://affiliate_link";
      payload.products = new String[] { "http://fab.com/sale/4850/product/11263" };
      string json = JsonConvert.SerializeObject(payload);
      var result = client.PostAsync ("", new StringContent(json, Encoding.UTF8, "application/json")).Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  

Request

POST https://api.twotap.com/v1.0/purchase?public_token=PUBLIC_TOKEN

Parameters

cart_id The cart id that is sent by '/cart'.
fields_input For non-authenticated checkout a hash that has the form:
{
  "[site_id]":
    {
      "noauthCheckout": { required checkout fields },
      "addToCart": {
        "[productMD5_1]": "required product fields",
        "[productMD5_2]": "required product fields",
      },
      "coupon": "coupon value" } (optional),
      "gift_card": { "number": "number", "pin": "pin") } (optional),
      "shipping_option": { "cheapest or fastest" } (optional),
    },
  "[site_id_2]": { .. }
}
For authenticated checkout a hash that has the form:
{
  "[site_id]":
    {
      "authCheckout": { required checkout fields },
      "login": { required login fields },
      "addToCart": {
        "[productMD5_1]": "required product fields",
        "[productMD5_2]": "required product fields",
      }
      "coupon": "coupon value" } (optional),
      "gift_card": { "number": "number", "pin": "pin") } (optional),
      "shipping_option": { "cheapest or fastest" } (optional),
    }
  "[site_id_2]": { .. }
}
affiliate_links (Optional) A hash that has the form:
{ "[site_id]": "affiliate_link" }
or if you need per product affiliate links:
{ "[site_id]": { "[productMD5]": "affiliate_link" } }
Please note that per product affiliate links will increase the checkout time.
confirm

(Optional but highly recommended) Instead of polling /purchase/status Two Tap can send callbacks as it's processing the purchase. See the SMS API flow or HTTP API flow.

Example: { method: 'http', http_confirm_url: 'https://your_confirm_endpoint', http_finished_url: 'https://your_finished_endpoint' }

products (Optional) An array of product URLs that have accepted by the user. This is useful in case a user removes some items between /cart and /purchase.

If specified fields_input should contain only the required data for those products.
test_mode (Optional) fake_confirm. A way to test the API the interface without making actual purchases.

Response


{
  "purchase_id": "50f414b9e6a4869bf6000010",
  "message": "still_processing",
  "description": "Still processing."
}              
            

Purchase status

Call '/purchase/status' to see the status of a '/purchase' request. The response is different if response['message'] is 'still_processing', 'failed', or 'done'.

Status Request


#!/usr/bin/env ruby

require 'rubygems'
require 'rest_client'

purchase_id = ARGV[0]

response = RestClient.get "https://api.twotap.com/v1.0/purchase/status?public_token=PUBLIC_TOKEN&purchase_id=#{purchase_id}"

puts response.body
                  

#!/bin/bash

PUBLIC_TOKEN=PUBLIC_TOKEN
PURCHASE_ID=PURCHASE_ID

curl https://api.twotap.com/v1.0/purchase/status?public_token=$PUBLIC_TOKEN\&purchase_id=$PURCHASE_ID
                  

#!/usr/bin/python

import requests

public_token = 'PUBLIC_TOKEN'
purchase_id = 'PURCHASE_ID'
r = requests.get('https://api.twotap.com/v1.0/purchase/status?public_token=' + public_token + '&purchase_id=' + purchase_id)
print(r.json())
                  

$.get("https://api.twotap.com/v1.0/purchase/status?public_token=PUBLIC_TOKEN&purchase_id=PURCHASE_ID", function(data, status) {
  console.log(data);
})
                  

var request = require("request");
var public_token = 'PUBLIC_TOKEN';
var purchase_id = 'PURCHASE_ID';
request({
  url: 'https://api.twotap.com/v1.0/purchase/status?public_token=' + public_token + '&purchase_id=' + purchase_id,
  method: "GET"
}, function (err, reponse, body) {
  console.log(body); 
});
                  

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$public_token = 'PUBLIC_TOKEN';
$purchase_id = 'PURCHASE_ID';
$url = 'https://api.twotap.com/v1.0/purchase/status?public_token='.$public_token.'&purchase_id='.$purchase_id;
$response = Requests::get($url);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
)

func main() {
    public_token := "PUBLIC_TOKEN"
    purchase_id := "PURCHASE_ID"
    url := "https://api.twotap.com/v1.0/purchase/status?public_token=" + public_token + "&purchase_id=" + purchase_id
    req, err := http.NewRequest("GET", url, nil)
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      var public_token = "PUBLIC_TOKEN";
      var purchase_id = "PURCHASE_ID";
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/purchase/status?public_token=" + public_token + "&purchase_id=" + purchase_id);
      var result = client.GetAsync ("").Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  

Request

GET https://api.twotap.com/v1.0/purchase/status?public_token=PUBLIC_TOKEN

Parameters

purchase_id The purchase id that is sent by '/purchase'.
test_mode (Optional) fake_confirm. A way to test the API the interface without making actual purchases.

Still processing response


{
  "purchase_id": "50f414b9e6a4869bf6000010",
  "message": "still_processing",
  "description": "Still processing.",
  "sites": {
    "50f414b9e6a4869bf6000002": {
      "info": {
        "url": "fab.com",
        "name": "Fab"
      },
      "last_message": "Logging in.",
      "status": "still_processing"
    }
  }
}
          

Completed response


{
  "sites": {
    "50f414b9e6a4869bf6000002": {
      "prices": {
        "sales_tax": "$4.11",   
        "coupon_value": "$4.00",
        "gift_card_value": "$5.00",
        "final_price": "$51.10",
        "shipping_price": "$7.00"
      },
      "details": {
        "active_payment_method": "The active payment method (only for authenticated checkouts).",
        "active_shipping_address": "The active shipping address (only for authenticated checkouts).",
        "shipping_estimate": "The estimated delivery date",
      },
      "info": {
        "url": "fab.com",
        "name": "Fab"
      },
      "status": "done",
      "status_messages": [
        "An array of errors.",
        "Present only if status is failed."
      ]
    }
  },
  "pending_confirm": true,
  "purchase_id": "511294b0a73e4a9059000001",
  "message": "done"
}                      
            

Response description

purchase_id The purchase's id.
message The status, which can be 'still_processing', 'has_failures', or 'done'.
description A more human friendly description of the status.
pending_confirm Boolean, true or false. Whether /purchase/confirm was called.
sites A hash that contains information grouped by a siteID.
sites[id][prices] A hash that contains always contains the final_price, and may also contain a shipping_price, the sales_tax, a gift_card_value (if sent), a coupon_value (if sent).
sites[id][details] Contains information about the checkout, like the shipping_estimate. In the case of authenticated checkouts it can also include the active_payment_method and the active_shipping_address.
sites[id][order_id] After 'confirm' finished this could contain an order id.
sites[id][info] A hash that contains the 'name' and 'url' of the site.
sites[id][status] The status, which can be 'still_processing', 'has_failures', or 'done'.
sites[id][status_messages] An array of errors that is present if status is 'failed'.
sites[id]last_message] The latest status message sent by the request processor. This is used during the 'still_processing' phase.

Purchase Confirm

Purchase confirm finalizes the order. Only perform this request server side as it uses your private token.

This request is available for 2 minutes after '/purchase' finishes.

Initial request


#!/usr/bin/env ruby

require 'rubygems'
require 'rest_client'

purchase_id = ARGV[0]

response = RestClient.post "https://api.twotap.com/v1.0/purchase/confirm?private_token=PRIVATE_TOKEN", { purchase_id: purchase_id }

puts response.body
            

#!/bin/bash

PRIVATE_TOKEN=PRIVATE_TOKEN
PURCHASE_ID=PURCHASE_ID

curl https://api.twotap.com/v1.0/purchase/confirm?private_token=$PRIVATE_TOKEN --header 'Content-Type: application/json' --data-binary '{"purchase_id" : "$PURCHASE_ID"}'
                  

#!/usr/bin/python

import requests
import json

private_token = 'PRIVATE_TOKEN'
payload = {"purchase_id": "PURCHASE_ID"}
r = requests.post('https://api.twotap.com/v1.0/purchase/confirm?private_token=' + private_token,
  data=json.dumps(payload), headers={'Content-Type': 'application/json'})
print(r.json())
                  

$.post('https://api.twotap.com/v1.0/purchase/confirm?private_token=PRIVATE_TOKEN', {
  "purchase_id": "PURCHASE_ID"
}, function(data, status) {
    console.log(data);
});
                  

var request = require("request");
var private_token = 'PRIVATE_TOKEN';
request({
  url: 'https://api.twotap.com/v1.0/purchase/confirm?private_token=' + private_token,
  json: {"purchase_id": "PURCHASE_ID"},
  method: "POST"
}, function (err, reponse, body) {
  console.log(body); 
});
                  

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$private_token = 'PRIVATE_TOKEN';
$url = 'https://api.twotap.com/v1.0/purchase/confirm?private_token='.$private_token;
$payload = array("purchase_id" => "PURCHASE_ID");
$response = Requests::post($url, array(), $payload);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
  "bytes"
)

func main() {
    private_token := "PRIVATE_TOKEN"
    url := "https://api.twotap.com/v1.0/purchase/confirm?private_token=" + private_token
    var jsonStr = []byte(`{"purchase_id": "PURCHASE_ID"}`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;
using System.Text;
using Newtonsoft.Json;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      var private_token = "PRIVATE_TOKEN";
      var purchase_id = "PURCHASE_ID";
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/purchase/confirm?private_token=" + private_token);
      dynamic payload = new ExpandoObject ();
      payload.purchase_id = purchase_id;
      string json = JsonConvert.SerializeObject(payload);
      var result = client.PostAsync ("", new StringContent(json, Encoding.UTF8, "application/json")).Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  

Response


{
  "purchase_id": "50f414b9e6a4869bf6000010",
  "message": "still_processing",
  "description": "Still processing.",
}
            

Request

POST https://api.twotap.com/v1.0/purchase/confirm?private_token=PRIVATE_TOKEN

Parameters

purchase_id The purchase id that is sent by '/purchase'.
test_mode (Optional) fake_confirm. A way to test the API the interface without making actual purchases.

Validating input fields

Let's say you are capturing data from the user. Two Tap requires it to be in a certain format or it will reject it. This API method allows you to validate it before sending it with the purchase.

Request


#!/usr/bin/env ruby

require 'rubygems'
require 'rest_client'

# A hash like { field_key: field_value }
flat_fields_input = { shipping_first_name: 'name', shipping_address: 'address' }
response = RestClient.post "https://api.twotap.com/v1.0/fields_input_validate", { flat_fields_input: flat_fields_input }

puts response.body
            

#!/bin/bash

curl https://api.twotap.com/v1.0/fields_input_validate --header 'Content-Type: application/json' --data-binary '{"shipping_first_name": "name", "shipping_address": "address"}'
                  

#!/usr/bin/python

import requests
import json

payload = {"shipping_first_name": "name", "shipping_address": "address"}
r = requests.post('https://api.twotap.com/v1.0/fields_input_validate',
  data=json.dumps(payload), headers={'Content-Type': 'application/json'})
print(r.json())
                  

$.post('https://api.twotap.com/v1.0/fields_input_validate', {
  "shipping_first_name": "name", "shipping_address": "address"
}, function(data, status) {
    console.log(data);
});
                  

var request = require("request");
request({
  url: 'https://api.twotap.com/v1.0/fields_input_validate',
  json: {"shipping_first_name": "name", "shipping_address": "address"},
  method: "POST"
}, function (err, reponse, body) {
  console.log(body); 
});
                  

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$url = 'https://api.twotap.com/v1.0/fields_input_validate';
$payload = array("shipping_first_name" => "name", "shipping_address" => "address");
$response = Requests::post($url, array(), $payload);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
  "bytes"
)

func main() {
    url := "https://api.twotap.com/v1.0/fields_input_validate"
    var jsonStr = []byte(`{"shipping_first_name": "name", "shipping_address": "address"}`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;
using System.Text;
using Newtonsoft.Json;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/fields_input_validate");
      dynamic flat_fields_input = new ExpandoObject ();
      flat_fields_input.shipping_first_name = "name";
      flat_fields_input.shipping_address = "address";
      string json = JsonConvert.SerializeObject(flat_fields_input);
      var result = client.PostAsync ("", new StringContent(json, Encoding.UTF8, "application/json")).Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  

Response


{
  "message": "done or bad_required_fields",
  "description": "A string that lists the issues."
}
            

Quicky

Quicky is Two Tap's version of the Amazon Dash Replenishment Service. When you hit this endpoint the shopper will receive an SMS with a link to purchase certain products.

You still have to implement the SMS Confirm URL in your backend.

Initial request


#!/usr/bin/env ruby

require 'rubygems'
require 'rest_client'


response = RestClient.get "https://api.twotap.com/v1.0/quicky?public_token=PUBLIC_TOKEN&sms_confirm_url=SMS_CONFIRM_URL&phone=PHONE&message=MESSAGE&products=PRODUCTS"

puts response.body
            

Response


{
  "message": "done",
  "description": "Message sent to PHONE.",
  "contents": "We heard you'd like to buy some products. Tap here to start: http://ttap.co/ID."
}
            

Request

GET/POST https://api.twotap.com/v1.0/quicky?public_token=PUBLIC_TOKEN

Parameters

products A list of products urls separated by commas. We encourage you to encode each product url beforehand.
sms_confirm_url Quicky defaults to the SMS flow, which requires the implemenation of a confirm url. See information about setting it up here.
phone The shopper's phone number. US only for now.
message (Optional) The message the shopper would receive. eg: "We heard you'd like to buy some products. Tap here to start: %%URL%%."

Supported sites

Query this url to get a list of our currently supported sites.

Request


#!/usr/bin/env ruby

require 'rubygems'
require 'rest_client'

purchase_id = ARGV[0]

response = RestClient.get "https://api.twotap.com/v1.0/supported_sites"

puts response.body
            

#!/bin/bash

curl https://api.twotap.com/v1.0/supported_sites
                  

#!/usr/bin/python

import requests

r = requests.get('https://api.twotap.com/v1.0/supported_sites');
print(r.json())
                  

$.get("https://api.twotap.com/v1.0/supported_sites", function(data, status) {
  console.log(data);
})
                  

var request = require("request");
request({
  url: 'https://api.twotap.com/v1.0/supported_sites',
  method: "GET"
}, function (err, reponse, body) {
  console.log(body); 
});
                  


require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$url = 'https://api.twotap.com/v1.0/supported_sites';
$response = Requests::get($url);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
)

func main() {
    url := "https://api.twotap.com/v1.0/supported_sites"
    req, err := http.NewRequest("GET", url, nil)
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/supported_sites");
      var result = client.GetAsync ("").Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  

Response


[
  {
    "id": "52d7a85ece04fabdcd000005",
    "name": "Shoebuy",
    "url": "shoebuy.com",
    "logo": "https://core.twotap.com/system/recipes/logos/5374/e544/ce04/faea/d600/000f/small/Shoebuy_2.png?1400808613861"
  }
  [..]
]
            



The Wallet

The wallet allows users and publishers to store data with Two Tap and use them for purchases. The retrieving and storing of data is integrated in the '/cart' and '/purchase' requests if 'user_token' is sent as a parameter.


Storing data (/purchase)

Storing of data is done automatically during '/purchase' if a 'user_token' is supplied.


Retrieving data (/cart)

When posting a request to '/cart' if a 'user_token' is present the API will respond with the stored entries for the required fields.

The stored fields are grouped in three possible categories: 'shipping', 'payment', and 'login'. The user can store multiple shipping addresses, payment methods, and login credentials which are grouped together.

The 'shipping' and 'payment' groups are identified by a random id. 'login' groups are identified by the site_id they belong to.

Wallet Retrieval



# Similar to a regular '/cart' request however an 'user_token' is specified.
response = RestClient.post "https://api.twotap.com/v1.0/cart?public_token=PUBLIC_TOKEN", { 
  products: products, 
  user_token: USER_TOKEN 
}

# Completed Status Response
{
  "sites": {
    # ...
  },
  "stored_field_values": {
    "shipping": {
      "o6nfusor": {
        "data": {
          "email": {
            "value": "anemail@gmail.com"
          },
          "shipping_last_name": {
            "value": "Doe"
          },
          "shipping_first_name": {
            "value": "Joe"
          }
        },
        "name": "John, Doe, anemail@gmail.com"
      }
    },
    "login": {
      "50f414b9e6a4869bf6000002": {
        "data": {
          "email": {
            "value": "anuser@gmail.com"
          }
        },
        "name": "anuser@gmail.com"
      }
    }
  },
  # ...
}
            

#!/bin/bash

PUBLIC_TOKEN=PUBLIC_TOKEN

curl https://api.twotap.com/v1.0/cart?public_token=$PUBLIC_TOKEN --header 'Content-Type: application/json' --data-binary '{
  "products": PRODUCTS,
  "user_token": USER_TOKEN
}'
            

#!/usr/bin/python

import requests
import json

payload = {
  "products": "PRODUCTS",
  "user_token": "USER_TOKEN"
}

r = requests.post('https://api.twotap.com/v1.0/cart?public_token=PUBLIC_TOKEN',
  data=json.dumps(payload), headers={'Content-Type': 'application/json'})

print(r.json())
            

$.post('https://api.twotap.com/v1.0/cart?public_token=PUBLIC_TOKEN', {
  "products": PRODUCTS, "user_token": USER_TOKEN
}, function(data, status) {
    console.log(data);
});
            

var request = require("request");
request({
    url: 'https://api.twotap.com/v1.0/cart?public_token=PUBLIC_TOKEN'
    json: {"products": PRODUCTS, "user_token": USER_TOKEN},
    method: "POST"
}, function (err, reponse, body) {
    console.log(body); 
});
            

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$url = "https://api.twotap.com/v1.0/cart?public_token=PUBLIC_TOKEN";
$payload = array("products" => PRODUCTS, "user_token" => USER_TOKEN);
$response = Requests::post($url, array(), $payload);
echo $response->body;
            

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
  "bytes"
)

func main() {
    url := "https://api.twotap.com/v1.0/cart?public_token=PUBLIC_TOKEN"
    var jsonStr = []byte(`{"products": PRODUCTS, "user_token": USER_TOKEN}`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
            

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;
using System.Text;
using Newtonsoft.Json;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/cart?public_token=PUBLIC_TOKEN");
      dynamic payload = new ExpandoObject ();
      payload.products = "PRODUCTS";
      payload.user_token = "USER_TOKEN";
      string json = JsonConvert.SerializeObject(payload);
      var result = client.PostAsync ("", new StringContent(json, Encoding.UTF8, "application/json")).Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
            

Using data (/purchase)

Each 'shipping', 'payment', and 'login' groups have an id associated with it. These fields are used during '/purchase'.

Instead of adding the key=value pairs to the required field values just use 'shipping_group_id', 'payment_group_id', or 'login_group_id' with the associated value.

Wallet Usage


# During a /purchase request.
required_fields = { 
  "516dece5e6a4862049000001": {
    "noauthCheckout": { "shipping_group_id": "xvv4pldi",  "payment_group_id": "frx2d9d", "an_extra_required_field": "has this value" }, 
    "addToCart": {
      "6e69ada944a4c35bb320615b827ad60a": { "quantity": 1, "Size": "13233550|13442751", "Color": "13233550|1055745" }
    }
  }
}
            

Updating data

To update an existing entry in the wallet you need to send at least a field_type and a list of fields.

If you'd like to update an existing group also send field_group_id. Otherwise this endpoint will create a new group with your information.


response = RestClient.post "https://api.twotap.com/v1.0/wallet/store?public_token=PUBLIC_TOKEN", { 
  field_type: "shipping",
  field_group_id: "xvv4pldi (optional)",
  fields: { 
    shipping_first_name: "My new first name",
    shipping_last_name: "My new last name"
  }
}

# Completed Status Response
{ 
  message: "done",
  stored_field_values:
    "xvv4pldi": {
      "data": {
        "email": {
          "value": "anemail@gmail.com"
        },
        "shipping_last_name": {
          "value": "My new last name"
        },
        "shipping_first_name": {
          "value": "My new first name"
        }
      },
      "name": "My new first name, My new last name, anemail@gmail.com"
    }
}
                  

#!/bin/bash

PUBLIC_TOKEN=PUBLIC_TOKEN

curl https://api.twotap.com/v1.0/wallet/store?public_token=PUBLIC_TOKEN --header 'Content-Type: application/json' --data-binary '{ 
  "field_type": "shipping",
  "field_group_id": "xvv4pldi (optional)",
  "fields": { 
    "shipping_first_name": "My new first name",
    "shipping_last_name": "My new last name"
  }
}'
                  

#!/usr/bin/python

import requests
import json

payload = { 
  "field_type": "shipping",
  "field_group_id": "xvv4pldi (optional)",
  "fields": { 
    "shipping_first_name": "My new first name",
    "shipping_last_name": "My new last name"
  }
}

r = requests.post('https://api.twotap.com/v1.0/wallet/store?public_token=PUBLIC_TOKEN',
  data=json.dumps(payload), headers={'Content-Type': 'application/json'})

print(r.json())
                  

$.post('https://api.twotap.com/v1.0/wallet/store?public_token=PUBLIC_TOKEN', {
  "field_type": "shipping",
  "field_group_id": "xvv4pldi (optional)",
  "fields": { 
    "shipping_first_name": "My new first name",
    "shipping_last_name": "My new last name"
  }
}, function(data, status) {
    console.log(data);
});
                  

var request = require("request");
request({
    url: 'https://api.twotap.com/v1.0/wallet/store?public_token=PUBLIC_TOKEN'
    json: { 
    "field_type": "shipping",
    "field_group_id": "xvv4pldi (optional)",
    "fields": { 
      "shipping_first_name": "My new first name",
      "shipping_last_name": "My new last name"
    }
  },
    method: "POST"
}, function (err, reponse, body) {
    console.log(body); 
});
                  

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$url = "https://api.twotap.com/v1.0/wallet/store?public_token=PUBLIC_TOKEN";
$payload = array( 
  "field_type" => "shipping",
  "field_group_id" => "xvv4pldi (optional)",
  "fields" => array( 
    "shipping_first_name" => "My new first name",
    "shipping_last_name" => "My new last name"
  )
);
$response = Requests::post($url, array(), $payload);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
  "bytes"
)

func main() {
    url := "https://api.twotap.com/v1.0/wallet/store?public_token=PUBLIC_TOKEN"
    var jsonStr = []byte(`{ 
      "field_type": "shipping",
      "field_group_id:" "xvv4pldi (optional)",
      "fields": { 
        "shipping_first_name": "My new first name",
        "shipping_last_name": "My new last name"
      }
    }`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;
using System.Text;
using Newtonsoft.Json;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/wallet/store?public_token=PUBLIC_TOKEN");
      dynamic payload = new ExpandoObject ();
      payload.field_type = "shipping";
      payload.field_group_id = "xvv4pldi (optional)";
      payload.fields = new ExpandoObject ();
      payload.fields.shipping_first_name = "My new first name";
      payload.shipping_last_name = "My new last name";
      string json = JsonConvert.SerializeObject(payload);
      var result = client.PostAsync ("", new StringContent(json, Encoding.UTF8, "application/json")).Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  

Deleting data

To delete an entry from the wallet all you have to do is send a field_type and a field_group_id.


response = RestClient.post "https://api.twotap.com/v1.0/wallet/delete?public_token=PUBLIC_TOKEN", { 
  field_type: "shipping",
  field_group_id: "xvv4pldi"
}

# Completed Status Response
{ message: "done", group_id: "xvv4pldi" }
                  

#!/bin/bash

PUBLIC_TOKEN=PUBLIC_TOKEN

curl https://api.twotap.com/v1.0/wallet/delete?public_token=$PUBLIC_TOKEN --header 'Content-Type: application/json' --data-binary '{
  "field_type": "shipping",
  "field_group_id": "xvv4pldi"
}'
                  

#!/usr/bin/python

import requests
import json

payload = { 
  "field_type": "shipping",
  "field_group_id": "xvv4pldi"
}

r = requests.post('https://api.twotap.com/v1.0/wallet/delete?public_token=PUBLIC_TOKEN',
  data=json.dumps(payload), headers={'Content-Type': 'application/json'})
print(r.json())
                  

$.post('https://api.twotap.com/v1.0/wallet/delete?public_token=PUBLIC_TOKEN', {
  "field_type": "shipping",
  "field_group_id": "xvv4pldi"
}, function(data, status) {
    console.log(data);
});
                  

var request = require("request");
request({
  url: 'https://api.twotap.com/v1.0/wallet/delete?public_token=PUBLIC_TOKEN'
    json: { 
      "field_type": "shipping",
      "field_group_id": "xvv4pldi"
    },
    method: "POST"
}, function (err, reponse, body) {
    console.log(body); 
});
                  

require_once 'Requests/library/Requests.php';

Requests::register_autoloader();
$url = "https://api.twotap.com/v1.0/wallet/delete?public_token=PUBLIC_TOKEN";
$payload = array( 
  "field_type" => "shipping",
  "field_group_id" => "xvv4pldi"
);
$response = Requests::post($url, array(), $payload);
echo $response->body;
                  

package main

import (
  "fmt"
  "net/http"
  "io/ioutil"
  "bytes"
)

func main() {
    url := "https://api.twotap.com/v1.0/wallet/delete?public_token=PUBLIC_TOKEN"
    var jsonStr = []byte(`{ 
      "field_type": "shipping",
      "field_group_id": "xvv4pldi"
    }`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}
                  

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Dynamic;
using System.Text;
using Newtonsoft.Json;

public class EmptyClass
{
  public static void Main ()
  {
    using (var client = new HttpClient ()) {
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.BaseAddress = new Uri ("https://api.twotap.com/v1.0/wallet/delete?public_token=PUBLIC_TOKEN");
      dynamic payload = new ExpandoObject ();
      payload.field_type = "shipping";
      payload.field_group_id = "xvv4pldi";
      string json = JsonConvert.SerializeObject(payload);
      var result = client.PostAsync ("", new StringContent(json, Encoding.UTF8, "application/json")).Result;
      String resultContent = result.Content.ReadAsStringAsync ().Result;
      Console.WriteLine (resultContent);
    }
  }
}
                  

Possible keys and values

Two Tap works with a lot of merchants and as such we require a lot of input data. We've standardized some of them to ensure that the wallet is consistent on all purchases.

Possible keys

shipping
shipping_titleshipping_first_nameshipping_last_nameshipping_nameshipping_address
shipping_cityshipping_stateshipping_countryshipping_zipshipping_telephone
emailemail_confirmationpasswordpassword_confirmation
login
emailusernamepassword
payment
card_typecard_numbercard_nameexpiry_date_yearexpiry_date_month
cvvbilling_titlebilling_first_namebilling_last_namebilling_name
billing_addressbilling_citybilling_statebilling_countrybilling_zip
billing_telephone

Possible values

The fields listed here require data in the format below. If you don't respect this you will break user's wallets.

card_type
VisaMastercardAmerican ExpressDiscover
month_of_birth
0102030405
0607080910
1112
expiry_date_month
0102030405
0607080910
1112
expiry_date_year
20132014201520162017
20182019202020212022
2023
year_of_birth
19301931193219331934
19351936193719381939
19401941194219431944
19451946194719481949
19501951195219531954
19551956195719581959
19601961196219631964
19651966196719681969
19701971197219731974
19751976197719781979
19801981198219831984
19851986198719881989
19901991199219931994
19951996199719981999
20002001200220032004
20052006200720082009
20102011201220132014
shipping_title, billing_title
Mr.Mrs.Ms.
shipping_state, billing_state
AlabamaAlaskaAlbertaAmerican SamoaArizona
ArkansasArmed Forces AmericasArmed Forces EuropeArmed Forces PacificBritish Columbia
CaliforniaColoradoConnecticutDelawareDistrict of Columbia
District of ColumbiaFederated States of MicronesiaFloridaGeorgiaGuam
HawaiiIdahoIllinoisIndianaIowa
KansasKentuckyLouisianaMaineManitoba
Marshall IslandsMarylandMassachusettsMichiganMinnesota
MississippiMissouriMontanaNebraskaNevada
New BrunswickNew HampshireNew JerseyNew MexicoNew York
Newfoundland and LabradorNorth CarolinaNorth DakotaNorthern Mariana IslandsNorthwest Territories
Nova ScotiaNunavutOhioOklahomaOntario
OregonPalauPennsylvaniaPrince Edward IslandPuerto Rico
QuebecRhode IslandSaskatchewanSouth CarolinaSouth Dakota
TennesseeTexasU.S. Virgin IslandsUtahVermont
VirginiaWashingtonWest VirginiaWisconsinWyoming
Yukon
shipping_country, billing_country
AfghanistanAland IslandsAlbaniaAlgeriaAmerican Samoa
AndorraAngolaAnguillaAntarcticaAntigua and Barbuda
ArgentinaArmeniaArubaAustraliaAustria
AzerbaijanBahamasBahrainBangladeshBarbados
BelarusBelgiumBelizeBeninBermuda
BhutanBoliviaBosnia and HerzegowinaBotswanaBouvet Island
BrazilBritish Indian Ocean TerritoryBrunei DarussalamBulgariaBurkina Faso
BurundiCambodiaCameroonCanadaCape Verde
Cayman IslandsCentral African RepublicChadChileChina
Christmas IslandCocos (Keeling) IslandsColombiaComorosCongo
Congo, The Democratic Republic Of TheCook IslandsCosta RicaCote D'IvoireCroatia
CubaCyprusCzech RepublicDenmarkDjibouti
DominicaDominican RepublicEast TimorEcuadorEgypt
El SalvadorEquatorial GuineaEritreaEstoniaEthiopia
Falkland Islands (Malvinas)Faroe IslandsFijiFinlandFrance
French GuianaFrench Southern TerritoriesGabonGambiaGeorgia
GermanyGhanaGibraltarGreeceGreenland
GrenadaGuadeloupeGuamGuatemalaGuinea
Guinea-BissauGuyanaHaitiHeard And Mc Donald IslandsHonduras
Hong KongHungaryIcelandIndiaIndonesia
IranIraqIrelandIsle of ManIsrael
ItalyJamaicaJapanJordanKazakhstan
KenyaKiribatiKuwaitKyrgyzstanLao People's Democratic Republic
LatviaLebanonLesothoLiberiaLibyan Arab Jamahiriya
LiechtensteinLithuaniaLuxembourgMacauMacedonia, The Former Yugoslav Republic
MadagascarMalawiMalaysiaMaldivesMali
MaltaMarshall IslandsMartiniqueMauritaniaMauritius
MayotteMexicoMicronesia (Federated States Of)MoldovaMonaco
MongoliaMontenegroMontserratMoroccoMozambique
MyanmarNamibiaNauruNepalNetherlands
Netherlands AntillesNew CaledoniaNew ZealandNicaraguaNiger
NigeriaNiueNorfolk IslandNorth KoreaNorthern Mariana Islands
NorwayOmanPakistanPalauPalestenian Territories
PanamaPapua New GuineaParaguayPeruPhilippines
PitcairnPolandPortugalPuerto RicoQatar
ReunionRomaniaRussian FederationRwandaSaint Kitts And Nevis
Saint LuciaSaint Vincent And The GrenadinesSamoaSan MarinoSao Tome And Principe
Saudi ArabiaScotlandSenegalSerbiaSeychelles
Sierra LeoneSingaporeSlovakiaSloveniaSolomon Islands
SomaliaSouth AfricaSouth Georgia And The South Sandwich IslSouth KoreaSpain
Spain - Canary IslandsSri LankaSt BarthelemySt. HelenaSt. Pierre And Miquelon
SudanSurinameSvalbard And Jan Mayen IslandsSwazilandSweden
SwitzerlandSyrian Arab RepublicTahitiTaiwan, Province of ChinaTajikistan
Tanzania, United Republic OfThailandTogoTokelauTonga
Trinidad And TobagoTunisiaTurkeyTurkmenistanTurks And Caicos Islands
TuvaluUgandaUkraineUnited Arab EmiratesUnited Kingdom
United Kingdom - GuernseyUnited Kingdom - JerseyUnited States Minor Outlying IslandsUnited States of AmericaUruguay
UzbekistanVanuatuVatican City State (Holy See)VenezuelaVietnam
Virgin Islands (British)Virgin Islands (U.S)Wallis And Futuna IslandsWestern SaharaYemen
ZambiaZimbabwe