Button Merchant Integration Guide on
Android

Button Merchant Integration Guide

This guide walks you through how to add Button to your Android 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 to your build.gradle file in the dependencies.

implementation 'com.usebutton.merchant:button-merchant:1+'

In your launch activity class file, import the Button Merchant Library at the top of your activity and start it within the onCreate() method. This should be called when your application class is created.

import com.usebutton.sdk.ButtonMerchant

class MyApplication : Application() {
  override fun onCreate() {
    super.onCreate()

    // Replace app-xxxxxxxxxxxxxxxx with your App ID from the Button Dashboard https://app.usebutton.com
    ButtonMerchant.configure(this, "app-xxxxxxxxxxxxxxxx")
  }
}
import com.usebutton.sdk.ButtonMerchant;

public class MyApplication extends Application {
  @Override
  public void onCreate() {
      super.onCreate();

      // Replace app-xxxxxxxxxxxxxxxx with your App ID from the Button Dashboard https://app.usebutton.com
      ButtonMerchant.configure(context, "app-xxxxxxxxxxxxxxxx");
  }
}

Note: The Button Merchant Library is disabled by default. No user activity is reported and no data is collected until configured with an application id. Initialization can be deferred by simply not calling this method until ready to do so.

In the Activity onCreate() method for whichever activity is started on first launch, handle any post-install deeplinks.

import com.usebutton.sdk.ButtonMerchant

class MainActivity : Activity() {
  override fun onCreate() {
    super.onCreate()

    ButtonMerchant.handlePostInstallIntent(this) { intent, t ->
            if (intent != null) {
                startActivity(intent)
            } else if (t != null) {
                Log.e(TAG, "Error checking post install intent", t)
            }
        }
    })

  }
}

import com.usebutton.sdk.ButtonMerchant;

public class MainActivity extends Activity {
  @Override
  public void onCreate() {
      super.onCreate();

      ButtonMerchant.handlePostInstallIntent(this, new PostInstallIntentListener() {
          @Override
          public void onComplete(@Nullable Intent intent, @Nullable Throwable t) {
              if (intent != null) {
                  startActivity(intent);
              } else if (t != null) {
                  Log.e(TAG, "Error checking post install intent", t);
              }
          }
      });
  }
}

Traffic from the Google Play 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.

Proguard Rules

If your app is using Proguard (minifyEnabled true in your build.gradle) make sure that the proguard rules below are in effect in your rules file. This file is usually located in yourapp/proguard-rules.pro, or its location is specified in build.gradle, look for proguardFiles in your default or variant configuration (note: this can be multiple files).

-keepattributes Exceptions,InnerClasses,EnclosingMethod
-keep class com.usebutton.** { *; }
-keepclassmembers class * implements android.os.Parcelable {
    static ** CREATOR;
}
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient { public *; }

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. This method should be called from the onCreate() method in your launch activity or whatever activity handles the deep link. For example, if you use Verified App to directly open the product activity, trackIncomingIntent(context, intent) should be called from ProductActivity.onCreate().

Incoming URLs are reported and attributed by calling the trackIncomingIntent method.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // Handle custom scheme deep links and App Links
    ButtonMerchant.trackIncomingIntent(this, intent)
}
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Handle custom scheme deep links and App Links
    ButtonMerchant.trackIncomingIntent(this, getIntent());

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

val order = Order.Builder("order-id-123")
        .setAmount(8999)
        .setCurrencyCode("USD")
        .build()

ButtonMerchant.trackOrder(context, order, object : UserActivityListener() {
    override fun onSuccess() {
        // Track order success
    }

    override fun onError(t: Throwable?) {
        // Track order error
    }
})
Order order = new Order.Builder("order-id-123")
        .setAmount(8999)
        .setCurrencyCode("USD")
        .build();

ButtonMerchant.trackOrder(context, order, new UserActivityListener() {
    @Override
    public void onSuccess() {
        // Track order success
    }

    @Override
    public void onError(@Nullable Throwable t) {
        // Track order error
    }
});

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.

val btnRef = ButtonMerchant.getAttributionToken(context)
// Obtain Attribution Token
final String btnRef = ButtonMerchant.getAttributionToken(context);

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:

val url = URL("https://api.yourcompany.com")
val urlConnection = url.openConnection() as HttpURLConnection
urlConnection.doOutput = true

val json = JSONObject()
json.put("btn_ref", btnRef)
json.put("price", 34.90)
json.put("location", "New York")

val out = BufferedOutputStream(urlConnection.getOutputStream())
writeStream(out, json)
URL url = new URL("https://api.yourcompany.com");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setDoOutput(true);

JSONObject json = new JSONObject();
json.put("btn_ref", btnRef);
json.put("price", 34.90);
json.put("location", "New York");

OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
writeStream(out, json);

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,
      description: 'cover',
      quantity: 1
    }
  ],
  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.handlePostInstallIntent()) - 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 App Links for the Button domain (to stop any browser redirecting) you need to add intent filters for the http and https scheme for the Button subdomain (yourdomain.bttn.io) you registered. If you already registered an intent filter for your domain, you can simply add another to the same activity declaration.

We support verified web associated App Links so make sure to add the autoVerify="true" attribute to your intent filter. Note: this support is part of Android Marshmallow (6.0), so you'll also need to build against this target Button Merchant Library level.

<activity name=".MainActivity">
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW"/>
                <data android:scheme="https"/>
                <data android:host="yourdomain.bttn.io"/>
                <data android:pathPattern=".*"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>
            </intent-filter>
...
</activity>

Button hosts the Digital Asset Links JSON file for your bttn.io subdomain. You can add your package name and sha256 certificate fingerprints to your app configuration in the Button dashboard.

Web Links

Use the following command to generate the fingerprint via the Java keytool:

keytool -list -printcert -jarfile  <APK_FILE>  | grep SHA256

Now, your link should open your app without ever opening your browser when the app is installed. Note: by supporting web associated app links you will prevent the Intent Chooser when opening the URL and your app will open automatically.


Test your Integration

To test your Button integration, download our Partner test app from the Play 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.