Button Merchant Integration Guide on

Button Merchant Integration Guide

This guide walks you through how to add Button to your iOS application. In this guide, we'll:

  1. Add the Button Merchant Library
  2. Attribute Users from Incoming Deeplinks
  3. Track Orders in App
  4. Pass the Button Attribution Token to your Checkout API (Server)
  5. Report Orders to Button's Order API
  6. Other Configurations
  7. Test your Integration

Add the Button Merchant Library

Add the Button Merchant Library through CocoaPods.

pod 'ButtonMerchant', '~> 1'

Carthage

You can include the Button Merchant library from GitHub using Carthage — we recommend using a major version match with fuzzy-matching for feature releases.

github "button/button-merchant-ios" ~> 1.0

In your AppDelegate file, configure the Button Merchant Library and handle any post-install deeplinks within the didFinishLaunchingWithOptions function.

import ButtonMerchant

func application(_ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    // Replace app-xxxxxxxxxxxxxxxx with your App ID from the Button Dashboard https://app.usebutton.com
    ButtonMerchant.configure(applicationId: "<#app-xxxxxxxxxxxxxxxx#>")

    ButtonMerchant.handlePostInstallURL { url, error in

        guard let url = url else {
            // Route user to the url
        }
    }

    return true
}
// Coming soon

Traffic from the App Store can result in users opening your app without any deeplink path or parameters. Button's Deferred Deeplink product closes this gap by recovering the link the user was originally following based on device fingerprinting.

After a fresh install, the user may both have an attributed source, and a destination deep link which needs to be retrieved from the Button API.

This is achieved using a single call in the Merchant library — the Merchant library will ensure that this network call is only performed when it is possible to have a response (e.g. after first open) so you can safely call it as much as is needed.

The Merchant library will automatically store the attribution from the received URL, so there’s no need to pass it back to trackIncomingURL, but you will need to route the user to the url that is returned, if present.

Attribute Users from Incoming Deeplinks

Whenever a user deep links into your app, the incoming URL should be passed to the Merchant library so that the library can extract and store the token.

Incoming URLs are reported, and attributed by calling the trackIncomingURL method.

// Handle custom scheme urls
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey: Any] = [:]) -> Bool {

    ButtonMerchant.trackIncomingURL(url)

    return true
}

// Handle Universal Links
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {

    if let url = userActivity.webpageURL {
        ButtonMerchant.trackIncomingURL(url)
    }

    return true
}
// Coming soon

Track Orders in App

The first order reporting signal is from the client, which includes only the order ID and amount — as well as the attribution token, which is automatically added by the Button Merchant Library.

Note: this signal does not replace server-side Order Reporting

let order = Order(id: "order_id", amount: 5000, currencyCode: "USD") // amount is in cents (i.e. $50 is 5000)
ButtonMerchant.trackOrder(order, nil)
// Coming soon

This signal powers our Instant Rewards feature for Publishers to notify their customers as quickly as possible that their order has been correctly tracked. This signal will not generate liabilities in Button's systems (see Order Reporting for more details)


Pass the Button Attribution Token to your Checkout API (Server)

When an order has been placed in your app, and just before the request to your own Checkout API (server) is made, fetch the Button Attribution token via the Button Merchant Library.

// Obtain Attribution Token
if let btn_ref = ButtonMerchant.attributionToken {
    // referred token is present
}
// Coming soon

Then execute a call to your Checkout API just like you currently do, adding the Button Attribution Token retrieved to the request payload along with the order details:

// Set parameters for POST, including Attribution Token
let parameters: Parameters = [
    "btn_ref": btn_ref, // Attribution Token from previous section
    "price": 50.00,
    "location": "New York"
]

// Perform POST request to your Checkout API (server)
Alamofire.request("https://api.yourapi.com", method: .post, parameters: parameters, encoding: JSONEncoding(options: []))
// Coming soon

Report Orders to Button's Order API

Create a New Order

Once an "order request" is passed to your Checkout API, your normal order processing workflow should occur (e.g. order validation, calls to payment microservice, fulfillment microservice, etc).

Once the workflow is successfully complete, and just before your Checkout API returns an HTTP '200 - Success' response code back to your mobile client, the Checkout API should execute an asynchronous HTTP POST request to the Button Order API’s "create order" end point.

This is a secure server to server call, and is therefore the source of truth that Button uses to validate orders reported via our client side Merchant Library in app are valid and authorized. While those signals may be used as an immediate purchase notification, orders reported via our Order API will be the source of truth for commissioning.

You can find your API key here in the Dashboard.

The Order API accepts the following parameters when creating, updating, or deleting an order:

  • total: Total value of the order. Includes any discounts, if applicable. Must exclude VAT, all other taxes, surcharges and any additional costs such as delivery. This value represents the commissionable total. Must be an integer >= 0. Example: 1234 for $12.34.
  • currency: Three-letter ISO currency code as specified in ISO 4217.
  • order_id: The Order ID unique to this order. Treated as an ASCII string. 255 character maximum.
  • purchase_date (required as of API Version 2018-02-13) ISO-8601 string representing the time the purchase was made by the user.
  • btn_ref (optional): The Button source token. A URL safe string up to 255 characters. This field is optional to create an order, but is required for attribution. When testing your integration, you should send dummy source tokens to Button, in the following format: ^fakesrctok-[a-f0-9]{16}$ (e.g. fakesrctok-abcdef0123456789). Note: even though this is marked as optional, you must provide a valid Button source token when reporting orders driven by a Button Publisher.
  • advertising_id (optional): Deprecated.
  • customer (optional):
    • id: The ID for the transacting customer in your system.
    • email_sha256 (optional): The SHA-256 hash of the transacting customer's lowercase email, as a 64-character hex string. Note: The value of the e-mail address must be converted to lowercase before computing the hash. The hash itself may use uppercase or lowercase hex characters. Internally, Button always normalizes the hash to lowercase.
    • advertising_id (optional): The customer's IDFA / AAID.
  • customer_order_id: (optional): The customer-facing order ID. Treated as a UTF-8 string. 255 character maximum. Note: Use this field to report the order identifier as communicated to the customer.
  • line_items (optional): An array of the line item details that comprise the order. Note: while optional for integration with Button, line items are required to partner with many Button Publishers.
    • identifier: The unique identifier for this line item, within the scope of this order. This must be unique across all line-items within the order. We suggest using the SKU or UPC of the product.
    • total: The total price of all items bought in a particular line item (e.g. if 3 bananas were purchased for $3.00 each, total would be 900). If provided, amount will be ignored and set to round (total / quantity). Must be net of any discounts or taxes and represent the value a Merchant expects to commission on. Must be an integer > 0.
    • amount: Unit price for each item represented in the line item. Must include discounts, if applied. This is the value on which commission is calculated. Should exclude VAT, all other taxes, surcharges and any additional costs such as delivery.
    • quantity (optional): The number of unique units represented by this line item. Must be an integer > 1. Defaults to 1.
    • sku (optional): The Stock Keeping Unit of the line item. A Merchant-specific identifier for the line item. String with a maximum length of 255.
    • upc (optional): The Universal Product Code of the line item. Also referred to as UPC-A and GTIN-12. UPC-E (8-digit) codes should be converted to 12-digit codes. String with 12 numeric digits.
    • category (optional): The category of the line item. An ordered list of strings, starting with the topmost (or most general) category. The total number of subcategories is capped at 7. Example: ["Electronics", "Computers", "Laptops", "Accessories", "Bags"].
    • description (optional): Text describing the line item.
    • attributes (optional): A key/value store for strings to specify additional information about a line item. Examples of attributes are: top level / meta category identifier with a category; secondary category identifier with a subcategory; primary manufacturer of the product with a brand; the UPC (universal product code) of an item with a upc. Note: even though this is marked as optional, these are required for retail Merchants. See this section for more information and examples.
  • status: A read-only field giving the order's current status. See Order Status

The Order API has advanced request parameters. These should only be provided in the payload if specifically told to do so by Button. Providing them otherwise may cause errors when reporting.

  • finalization_date (optional): When (in an ISO-8601 date) the order should move to the finalized state. If no value is supplied the default finalization date for your organization will be used. A date more than a year from now will be capped at a year from the current time. Note: Only use this field if you have variable order finalization dates (e.g. Hotel stays, which typically are finalized at the end of a reservation and may vary from order to order.).
curl https://api.usebutton.com/v1/order \
  -X POST \
  -u your_api_key: \
  -H "Content-Type: application/json" \
  -d '{
    "total": 5000,
    "currency": "USD",
    "order_id": "1989",
    "purchase_date": "2017-07-01T00:00:00Z",
    "finalization_date": "2017-08-02T19:26:08Z",
    "btn_ref": "srctok-XXX",
    "line_items": [
      {
        "identifier": "sku-123",
        "total": 2000,
        "amount": 2000
        "quantity": 1,
        "description": "pillow"
      },
      {
        "identifier": "sku-456",
        "total": 3000,
        "amount": 3000,
        "quantity": 1,
        "description": "sheet"
      }
    ],
    "customer": {
      "id": "mycustomer-1234",
      "email_sha256": "'`echo -n "user@example.com" | openssl dgst -sha256`'"
    },
    "customer_order_id": "abcdef-123456"
  }'
var crypto = require('crypto');
var buttonClient = require('@button/button-client-node')('sk-XXX');

buttonClient.orders.create({
  total: 5000,
  currency: 'USD',
  order_id: '1989',
  purchase_date: '2017-07-01T00:00:00Z',
  finalization_date: '2017-08-02T19:26:08Z',
  btn_ref: 'srctok-XXX', // This is the Attribution Token POSTed from your app
  line_items: [
    {
      identifier: 'sku-123',
      total: 2000,
      amount: 2000,
      quantity: 1,
      description: 'pillow'
    },
    {
      identifier: 'sku-456',
      total: 3000,
      amount: 3000,
      quantity: 1,
      description: 'sheet'
    }
  ],
  customer: {
    id: 'mycustomer-1234',
    email_sha256: crypto.createHash('sha256').update('user@example.com'.toLowerCase().trim()).digest('hex')
  },
  customer_order_id: 'abcdef-123456'
}, function(err, res) {
  console.log(res);
});
require 'digest'
require 'button'

buttonClient = Button::Client.new('sk-XXX')

response = buttonClient.orders.create({
  total: 5000,
  currency: 'USD',
  order_id: '1989',
  purchase_date: '2017-07-01T00:00:00Z',
  finalization_date: '2017-08-02T19:26:08Z',
  btn_ref: 'srctok-XXX', # This is the Attribution Token POSTed from your app
  line_items: [
    {
      identifier: 'sku-123',
      total: 2000,
      amount: 2000,
      quantity: 1,
      description: 'pillow'
    },
    {
      identifier: 'sku-456',
      total: 3000,
      amount: 3000,
      quantity: 1,
      description: 'sheet'
    }
  ],
  customer: {
    id: 'mycustomer-1234',
    email_sha256: Digest::SHA256.hexdigest('user@example.com'.downcase.strip)
  },
  customer_order_id: 'abcdef-123456'
})

puts response
# => Button::Response(button_order_id: btnorder-XXX, total: 5000, ... )
import hashlib
from pybutton import Client

buttonClient = Client('sk-XXX')

response = buttonClient.orders.create({
    'total': 5000,
    'currency': 'USD',
    'order_id': '1989',
    'purchase_date': '2017-07-01T00:00:00Z',
    'finalization_date': '2017-08-02T19:26:08Z',
    'btn_ref': 'srctok-XXX', # This is the Attribution Token POSTed from your app
    line_items: [
      {
        'identifier': 'sku-123',
        'total': 2000,
        'amount': 2000,
        'quantity': 1,
        'description': 'pillow'
      },
      {
        'identifier': 'sku-456',
        'total': 3000,
        'amount': 3000,
        'quantity': 1,
        'description': 'sheet'
      }
    ],
    'customer': {
        'id': 'mycustomer-1234',
        'email_sha256': hashlib.sha256("user@example.com".lower().strip()).hexdigest()
    },
    'customer_order_id': 'abcdef-123456'

})

print(response)
# <class pybutton.Response total: 5000, currency: 'USD', ...>

  • Button provides client libraries for Ruby, Python, and Node.js to more easily structure requests to our APIs.
  • The format in which the Button Attribution Token is passed to your Checkout API depends on the contract between your mobile clients and your Checkout API. We recommend using a key of btn_ref to pass this value for consistency.
  • When testing your integration and sending dummy source tokens to Button, send in the format of ^fakesrctok-[a-f0-9]{16}$ (e.g. "btn_ref": "fakesrctok-abcdef0123456789").
  • Orders reported to Button will initially enter a pending state that is persisted until a "finalization date" (finalization_date), set by default as a static number of days after Button receives the order.
  • This "finalization window" is configured by Button to reflect your cancellation/return policy.
  • At any point before the "finalization date" (finalization_date) you can adjust or cancel the order.
  • Orders will not generate commissions in Button’s systems until the "finalization date" (finalization_date) has passed without cancellation.
  • The "finalization_date" (finalization_date) can be specified when reporting a new order to the Button Order API’s "create order endpoint" (see "Advanced Request Parameters"). This will override the default date generated based on the static window configured.

Update an Existing Order

When an existing order has been updated in your OMS, an "Order Adjustment Consumer" will trigger an "order update" workflow.

As part of this workflow, an HTTP POST request should be made (with the order ID first reported to Button) to the Button Order API’s "update order" end point, providing the new state of the order:

curl https://api.usebutton.com/v1/order/<merchant-order-id> \
  -X POST \
  -u your_api_key: \
  -H "Content-Type: application/json" \
  -d '{
    "total": 8000,
    "currency": "USD",
    "order_id": "1989",
    "purchase_date": "2017-07-01T00:00:00Z",
    "finalization_date": "2017-08-02T19:26:08Z",
    "line_items": [
      {
        "identifier": "sku-789",
        "total": 5000,
        "amount": 5000
        "quantity": 1,
        "description": "mattress"
      },
      {
        "identifier": "sku-012",
        "total": 3000,
        "amount": 3000,
        "quantity": 1,
        "description": "cover"
      }
    ],
    "customer": {
      "id": "mycustomer-1234",
      "email_sha256": "'`echo -n "user@example.com" | openssl dgst -sha256`'"
    },
    "customer_order_id": "abcdef-123456"
  }'
var crypto = require('crypto');
var buttonClient = require('@button/button-client-node')('sk-XXX');

buttonClient.orders.update('<merchant-order-id>', {
  total: 8000,
  currency: 'USD',
  order_id: '1989',
  purchase_date: '2017-07-01T00:00:00Z',
  finalization_date: '2017-08-02T19:26:08Z',
  line_items: [
    {
      identifier: 'sku-789',
      total: 5000,
      amount: 5000,
      quantity: 1,
      description: 'mattress'
    },
    {
      identifier: 'sku-012',
      total: 3000,
      amount: 3000,
      quantity: 1,
      description: 'cover'
    }
  ],
  customer: {
    id: 'mycustomer-1234',
    email_sha256: crypto.createHash('sha256').update('user@example.com'.toLowerCase().trim()).digest('hex')
  },
  customer_order_id: 'abcdef-123456'
}, function(err, res) {
  console.log(res);
});
require 'digest'
require 'button'

buttonClient = Button::Client.new('sk-XXX')

response = buttonClient.orders.update('<merchant-order-id>', {
  total: 8000,
  currency: 'USD',
  order_id: '1989',
  purchase_date: '2017-07-01T00:00:00Z',
  finalization_date: '2017-08-02T19:26:08Z',
  line_items: [
    {
      identifier: 'sku-789',
      total: 5000,
      amount: 5000,
      quantity: 1,
      description: 'mattress'
    },
    {
      identifier: 'sku-012',
      total: 3000,
      amount: 3000,
      quantity: 1,
      description: 'cover'
    }
  ],
  customer: {
    id: 'mycustomer-1234',
    email_sha256: Digest::SHA256.hexdigest('user@example.com'.downcase.strip)
  },
  customer_order_id: 'abcdef-123456'
})

puts response
# => Button::Response(button_order_id: btnorder-XXX, total: 5000, ... )
import hashlib
from pybutton import Client

buttonClient = Client('sk-XXX')

response = buttonClient.orders.update('<merchant-order-id>', {
    'total': 8000,
    'currency': 'USD',
    'order_id': '1989',
    'purchase_date': '2017-07-01T00:00:00Z',
    'finalization_date': '2017-08-02T19:26:08Z',
    line_items: [
      {
        'identifier': 'sku-789',
        'total': 5000,
        'amount': 5000,
        'quantity': 1,
        'description': 'mattress'
      },
      {
        'identifier': 'sku-012',
        'total': 3000,
        'amount': 3000,
        'quantity': 1,
        'description': 'cover'
      }
    ],
    'customer': {
        'id': 'mycustomer-1234',
        'email_sha256': hashlib.sha256("user@example.com".lower().strip()).hexdigest()
    },
    'customer_order_id': 'abcdef-123456'
})

print(response)
# <class pybutton.Response total: 5000, currency: 'USD', ...>

  • For the full API documentation, see here.
  • Updated orders will be sent to Button’s Order API as per the above, but commissions won’t be generated against it until the "finalization date" (finalization_date).
  • There is no need to include the Button Attribution token (btn_ref) as we already have it associated with the unique order identifier reported to Button.
  • Partial returns are considered "updates" in Button’s system. Cancellations are when an entire order, and all line-items within the order, have been cancelled/returned.
  • Sending order adjustments are vital to ensuring that you aren’t improperly billed for orders, or line-items, that are no longer valid.

Delete an Existing Order

When an existing order has been cancelled (see notes below for what constitutes a cancellation) in your OMS, an "Order Cancellation Consumer" will trigger an "order delete" workflow.

As part of this workflow, an HTTP DELETE request should be made (with the order ID first reported to Button ) to the Button Order API’s "delete order" end point, indicating a full cancellation of the order

curl https://api.usebutton.com/v1/order/<merchant-order-id> \
   -X DELETE \
   -u your_api_key:
var buttonClient = require('@button/button-client-node')('sk-XXX');

buttonClient.orders.del('<merchant-order-id>', function(err, res) {
    console.log(res);
});
require 'button'

buttonClient = Button::Client.new('sk-XXX')

response = buttonClient.orders.delete('<merchant-order-id>')

puts response
# => Button::Response()
from pybutton import Client

buttonClient = Client('sk-XXX')

response = buttonClient.orders.delete('<merchant-order-id>')

print(response)
# <class pybutton.Response >

  • For the full API documentation, see here.
  • Updated orders will be sent to Button’s Order API as per the above, but commissions won’t be generated against it until the "finalization date" (finalization_date).
  • Orders can only be deleted prior to the "finalization date" (finalization_date).
  • Partial returns are considered "updates" in Button’s system. Cancellations are when an entire order, and all line-items within the order, have been cancelled/returned.
  • Sending order cancellations and adjustments are vital to ensuring that you aren’t improperly billed for orders, or line-items, that are no longer valid.

Other Configurations

The following subsections - along with the section completed above implementing post-install deeplinking (ButtonMerchant.handlePostInstallURL) - will enable you for Button's mobile web Publishers.

Configure Button Links

In the Button Dashboard, navigate to "Merchant" > "Apps" & click on the App you want to configure a Button Links domain for. Once on the App details page, click on the "Add a Button Links Domain" button in the "Button Links Domains" section. Simply fill out the details in the popup modal to complete the configuration.

Configure Universal Links

To support Universal Links for the Button domain (to stop any Safari redirecting) add the Button subdomain (yourdomain.bttn.io) to the Associated Domains Capability. Switch Associated Domains on if it's not already and add your domain to the list of domains registered in the format applinks:yourapp.bttn.io.

Web Links


Test your Integration

To test your Button integration, download our Partner test app from the App Store. This app allows you to mimic a Publisher so you may test the Button flow. Login with your Button Dashboard credentials and you will see any Buttons you have associated with your account. After completing a purchase through the Button flow, you can verify it went through Button here in the Dashboard.

Note: If you do not see any Buttons after logging into the Partner test app, reach out to your Button point of contact and they will populate this for you.