Purchase Path Extensions on
All Platforms

Purchase Path Extensions

Purchase Path Extensions is a Button SDK feature designed to allow Publishers to create completely custom experiences on top of Button In-App Checkout. Purchase Path Extensions build on the rich event framework including User Activity Events & Checkout Events and communicate with users consistently through Browser Cards -- a simple UX for displaying modular information during checkout.

In this guide, we'll go over:

What can I do with a Purchase Path Extension?

Purchase Path Extensions allow you to react to user action, execute custom code, and change visual elements during a checkout.

Current features include:

  • React to user navigation events
  • Get notified when the user begins and ends their checkout session
  • Update In-App Checkout visual elements including Title, Subtitle & Color
  • Get notified when a user has viewed a product or completed a checkout
  • Add Browser Cards to communicate with your users

Purchase Path Extension components

Purchase Path Extensions are powered by various Button SDK components

  • In-App Checkout - A Button SDK solution that allows users to complete a purchase in the publisher application.
  • Web Checkout - A version of In-App Checkout implemented using the embedded Button web browser.
  • Purchase Path Extension - A plugin that a publisher registers with the Button SDK to respond to user activity and customize the Web Browser interface.
  • Browser Interface - The public API for In-App Checkout that can be used by a developer to customize UI and UX from a Purchase Path Extension.
  • Browser Cards - A scrolling overlay for Web Checkout that allows publishers to communicate directly with the user and display modular content such as Predictable Rewards.

Checkout Extension Components

Sample Project

Get started quickly with a sample project showcasing Purchase Path Extensions with Browser Cards in Button In-App Checkout.

Getting Started

To begin, you will create a Purchase Path Extension class, implementing the Purchase Path Extension Interface.

class CustomPurchasePathExtension: NSObject, PurchasePathExtension {

    func browserDidInitialize(_ browser: BrowserInterface) {
        // You can now interact with and customize the browser interface and assume it's ready.
    }

    func browserWillNavigate(_ browser: BrowserInterface) {
        // This will be the first call back indicating the very beginning of a user's navigation within the browser.
    }

    func browser(_ browser: BrowserInterface, didNavigateTo page: BrowserPage) {
        // Respond to a user navigating to a page that doesn't contain a product or a purchase.
    }

    func browser(_ browser: BrowserInterface, didNavigateToProduct page: ProductPage) {
        // Respond to a user navigating to a product page.
    }

    func browser(_ browser: BrowserInterface, didNavigateToPurchase page: PurchasePage) {
        // Respond to a user having completed a purchase.
    }

    func browserDidClose() {
        // Respond to the browser closing. This does not mean a user completed a purchase or has logged out of your app.
    }

}
@implementation CustomPurchasePathExtension <BTNPurchasePathExtension>

- (void)browserDidInitialize:(id <BTNBrowserInterface>)browser {
    // You can now interfact with and customize the browser interface and assume it's ready.
}

- (void)browserWillNavigate:(id <BTNBrowserInterface>)browser {
    // This will be the first call back indicating the very beginning of a user's navigation within the browser.
}

- (void)browser:(id <BTNBrowserInterface>)browser didNavigateToPage:(id<BTNBrowserPage>)page {
    // Respond to a user navigating to a page that doesn't contain a product or a purchase.
}

- (void)browser:(id <BTNBrowserInterface>)browser didNavigateToProduct:(id<BTNProductPage>)page {
    // Respond to a user navigating to a product page.
}

- (void)browser:(id <BTNBrowserInterface>)browser didNavigateToPurchase:(id<BTNPurchasePage>)page {
    // Respond to a user having completed a purchase.
}

- (void)browserDidClose {
    // Respond to the browser closing. This does not mean a user completed a purchase or has logged out of your app.
}

@end
class CustomPurchasePathExtension : PurchasePathExtension {

    override fun onInitialized(BrowserInterface browser) {
        // You can now interfact with and customize the browser interface and assume it's ready.
    }

    override fun onStartNavigate(BrowserInterface browser) {
        // This will be the first call back indicating the very beginning of a user's navigation within the browser.
    }

    override fun onPageNavigate(BrowserInterface browser, BrowserPage page) {
        // Respond to a user navigating to a page that doesn't contain a product or a purchase.
    }

    override fun onProductNavigate(BrowserInterface browser, ProductPage page) {
        // Respond to a user navigating to a product page.
    }

    override fun onPurchaseNavigate(BrowserInterface browser, PurchasePage page) {
        // Respond to a user having completed a purchase.
    }

    override fun onClosed() {
        // Respond to the browser closing. This does not mean a user completed a purchase or has logged out of your app.
    }

}
class CustomPurchasePathExtension implements PurchasePathExtension {

    @Override
    public void onInitialized(BrowserInterface browser) {
        // You can now interfact with and customize the browser interface and assume it's ready.
    }

    @Override
    public void onStartNavigate(BrowserInterface browser) {
        // This will be the first call back indicating the very beginning of a user's navigation within the browser.
    }

    @Override
    public void onPageNavigate(BrowserInterface browser, BrowserPage page)  {
        // Respond to a user navigating to a page that doesn't contain a product or a purchase.
    }

    @Override
    public void onProductNavigate(BrowserInterface browser, ProductPage page) {
        // Respond to a user navigating to a product page.
    }

    @Override
    public void onPurchaseNavigate(BrowserInterface browser, PurchasePage page) {
        // Respond to a user having completed a purchase.
    }

    @Override
    public void onClosed() {
        // Respond to the browser closing. This does not mean a user completed a purchase or has logged out of your app.
    }
}

Once you have a Purchase Path Extension class of your own, you can set it on the Purchase Path Interface.

let purchasePathExtension = CustomPurchasePathExtension()
Button.purchasePath.extension = purchasePathExtension
CustomPurchasePathExtension *purchasePathExtension = [[CustomPurchasePathExtension alloc] init];
Button.purchasePath.extension = purchasePathExtension;
val purchasePathExtension = CustomPurchasePathExtension()
Button.purchasePath().extension = purchasePathExtension
CustomPurchasePathExtension purchasePathExtension = new CustomPurchasePathExtension();
Button.purchasePath().setExtension(purchasePathExtension)

You now have your first Purchase Path Extension! You're almost ready to improve your user's checkout experience.

Browser Cards

Browser Cards can be added via the browser interface which is passed as a parameter in the Purchase Path Extension callbacks. The browser interface has a cardList property where you can add, insert, replace, and remove your cards. Every Browser Card requires a "Call to Action"—a property of the card that defines an icon and text to be displayed on the bottom bar when a card is the top card in the list. Users can interact with the call to action to show and hide the top card. The top card can also be shown/hidden programmatically. When you add more than one card to the cardList, your users can access those cards by tapping on the "show all cards" button in the bottom bar.

Building a Text Card

A Text Card is a template card that displays title and body text. You can also set a key on your card to make it easy to lookup in the cardList. A key can be a string, or any object that can be compared.

let callToAction = BTNCardCallToAction(icon: UIImage(named: "ic_cash-back")!
                                      title: "Cash Back"
                                      color: .green)

let rewardCard = BTNTextCard(callToAction: callToAction,
                                    title: "2% Cash Back on this item",
                                     body: "Shop now to earn 2% Cash Back in this category of items")

let rewardCardKey = "reward card" as NSObjectProtocol
rewardCard.key = rewardCardKey
BTNCardCallToAction *callToAction = [BTNCardCallToAction alloc] initWithIcon:[UIImage imageNamed:@"ic_cash-back"]
                                                                       title:@"Cash Back"
                                                                  titleColor:UIColor.greenColor];

BTNTextCard *rewardCard = [BTNTextCard alloc] initWithCallToAction:callToAction]
                                                             title:@"%2 Cash Back on this item"
                                                              body:@"Shop now to earn 2% Cash Back in this category of items"];

rewardCard.key = @"reward card";
val callToAction = CallToAction(drawable.ic_cash_back, "Cash Back", Color.GREEN)
val rewardCard = TextCard.Builder(
        callToAction,
        "2% Cash Back on this item",
        "This category of items qualifies for 2% Cash Back when you shop now.")
        .build()
val rewardCardKey = "reward card"
rewardCard.key = rewardCardKey
CallToAction callToAction = new CallToAction(R.drawable.ic_cash_back, "Cash Back", Color.GREEN);
TextCard rewardCard = new TextCard.Builder(
        callToAction,
        "2% Cash Back on this item",
        "This category of items qualifies for 2% Cash Back when you shop now.")
        .build();
String rewardCardKey = "reward card";
rewardCard.setKey(rewardCardKey);

Now that you know how to build a card, let's start making your Purchase Path Extension do some cool stuff!

Changing Text and Colors in the Browser Interface

You can change the title text, subtitle text, as well as text color and bar colors in the browser interface. For most use cases, we recommend making these changes in your browser initialization callback. However, because you get access to the browser object in each call back, you are free to make edits where ever necessary.

func browserDidInitialize(_ browser: BrowserInterface) {
    browser.header.title.text = "Button"
    browser.header.subtitle.text = "www.usebutton.com"
    browser.header.title.color = UIColor.blue
    browser.header.subtitle.color = UIColor.orange
    browser.header.backgroundColor = UIColor.white
    browser.header.tintColor = UIColor.orange
    browser.footer.backgroundColor = UIColor.white
    browser.footer.tintColor = UIColor.orange
}
- (void)browserDidInitialize:(id <BTNBrowserInterface>)browser {
    browser.header.title.text = "Button"
    browser.header.subtitle.text = "www.usebutton.com"
    browser.header.title.color = UIColor.blue
    browser.header.subtitle.color = UIColor.orange
    browser.header.backgroundColor = UIColor.white
    browser.header.tintColor = UIColor.orange
    browser.footer.backgroundColor = UIColor.white
    browser.footer.tintColor = UIColor.orange
}
override fun onInitialized(BrowserInterface browser) {
    browser.header.title.text = "Button"
    browser.header.subtitle.text = "www.usebutton.com"
    browser.header.title.color = Color.BLUE
    browser.header.subtitle.color = Color.ORANGE
    browser.header.backgroundColor = Color.WHITE
    browser.header.tintColor = Color.ORANGE
    browser.footer.backgroundColor = Color.WHITE
    browser.footer.tintColor = Color.ORANGE
}
@Override
public void onInitialized(BrowserInterface browser) {
    browser.getHeader().getTitle().setText("Button");
    browser.getHeader().getSubtitle().setText("www.usebutton.com");
    browser.getHeader().getTitle().setColor(Color.BLUE);
    browser.getHeader().getSubtitle().setColor(Color.ORANGE);
    browser.getHeader().setBackgroundColor(Color.WHITE);
    browser.getHeader().setTintColor(Color.ORANGE);
    browser.getFooter().setBackgroundColor(Color.WHITE);
    browser.getFooter().setTintColor(Color.ORANGE);
}

Responding to User Activity

Purchase Path Extensions allow you to be notified and respond to a user's activity during Checkout. You can be notified of a user's navigation intentions for any interaction within the browser.

In the method shown below, you can expect a user has tapped a new link within the browser and prepare to clean up your UI. For instance, if the user was previously on a page with a commissionable item, and you had a card showing in the browser, you may want to hide that card. If necessary, you could also remove cards here.

func browserWillNavigate(_ browser: BrowserInterface) {
    // Hide a currently displaying card in preparation for this new navigation cycle
    browser.hideTopCard()
}
- (void)browserWillNavigate:(id <BTNBrowserInterface>)browser {
    // Hide a currently displaying card in preparation for this new navigation cycle
    [browser hideTopCard];
}
override fun onStartNavigate(BrowserInterface browser) {
    // Hide a currently displaying card in preparation for this new navigation cycle
    browser.hideTopCard()
}
@Override
public void onStartNavigate(BrowserInterface browser) {
    // Hide a currently displaying card in preparation for this new navigation cycle
    browser.hideTopCard();
}

When a page has finally loaded and it contains neither a product or commission, you can be notified by implementing the standard navigation call back for pages, shown below. In addition to knowing a product is not being viewed, you can access the url of the page via the page object in the callback.

func browser(_ browser: BrowserInterface didNavigateToPage page: BrowserPage) {
    if page.url.absoluteString.contains("some string") {
        // Show a card, change the header titles, etc.
    }
}
- (void)browser:(id <BTNBrowserInterface>)browser didNavigateToPage:(id<BTNBrowserPage>)page {
    if ([page.url.absoluteString containsString:@"some string"]) {
        // Show a card, change the header titles, etc.
    }
}
override fun onPageNavigate(BrowserInterface browser, BrowserPage page) {
    if (page.url.contains("some string")) {
        // Show a card, change the header titles, etc.
    }      
}
@Override
public void onPageNavigate(BrowserInterface browser, BrowserPage page) {
    if (page.getUrl().contains("some string")) {
        // Show a card, change the header titles, etc.
    }    
}

When a page is done loading and it contains a product you can be notified by implementing the product navigation callback, shown below.

With this method, you will know if the user has seen a commissionable or non-commissionable product. Here is where you want to communicate with your users and present the appropriate browser cards depending on the type of product seen and it's commissionability; you can get the product and commission objects from the page object.

func browser(_ browser: BrowserInterface, didNavigateToProduct page: ProductPage) {
    switch page.commission.commissionType {
    case .commissionable:
        browser.cardList.addCard(MY_COMMISSION_CARD)
        browser.showTopCard()
    case .nonCommissionable:
        browser.cardList.addCard(MY_NON_COMMISSION_CARD)
        browser.showTopCard()
    case .unknown:
        browser.cardList.addCard(MY_UNKNOWN_COMMISSION_CARD)
        // Maybe you don't want to show the card, and just leave the CTA visible indicating unknown commissionability
    default:
        ()
    }
}
- (void)browser:(id <BTNBrowserInterface>)browser didNavigateToProduct:(id<BTNProductPage>)page {
    switch (page.commission.commissionType) {
        case BTNCommissionTypeCommissionable:
            [browser.cardList addCard:MY_COMMISSION_CARD];
            [browser showTopCard];
            break;
        case BTNCommissionTypeNonCommissionable:
            [browser.cardList addCard:MY_NON_COMMISSION_CARD];
            [browser showTopCard];
            break;
        case BTNCommissionTypeUnknown:
            [browser.cardList addCard:MY_UNKNOWN_COMMISSION_CARD];
            // Maybe you don't want to show the card, and just leave the CTA visible indicating unknown commissionability
            break;
        default:
            break;
    }
}
override fun onProductNavigate(BrowserInterface browser, ProductPage page) {
    when (page.commission.commissionType) {
        COMMISSIONABLE -> {
            browser.cardList?.addCard(commissionCard)
            browser.showTopCard()
        }
        NON_COMMISSIONABLE -> {
            browser.cardList?.addCard(noCommissionCard)
            browser.showTopCard()
        }
        UNKNOWN -> {
            browser.cardList?.addCard(unknownCommissionCard)
            // Maybe you don't want to show the card, and just leave the CTA visible indicating unknown commissionability
        }
    }   
}

@Override
public void onProductNavigate(BrowserInterface browser, ProductPage page) {
    final CardList cardList = browser.getCardList();
    if (cardList == null) {
        return;
    }
    switch (page.getCommission().getCommissionType()) {
        case COMMISSIONABLE:
            cardList.addCard(commissionCard);
            browser.showTopCard();
            break;
        case NON_COMMISSIONABLE:
            cardList.addCard(noCommissionCard);
            browser.showTopCard();
            break;
        case UNKNOWN:
            cardList.addCard(unknownCommissionCard);
            // Maybe you don't want to show the card, and just leave the CTA visible indicating unknown commissionability
            break;
        default:
            break;
    }   
}

Communicating with Users

Upon exiting the checkout experience, you can communicate to users when they close the in-app checkout browser by implementing our browser close callback, shown below.

func browserDidClose() {
    // Send the user a message.
}

- (void)browserDidClose {
    // Send the user a message.
}
override fun onClosed() {
    // Send the user a message.
}
@Override
public void onClosed() {
    // Send the user a message.
}

You can communicate with a user who completed a purchase by implementing our purchase callback, shown below. For example, if you created a Browser Card for purchases, here you would add that card to the browser object's cardList property and then show it.

func browser(_ browser: BrowserInterface, didNavigateToPurchase page: PurchasePage) {
    browser.header.subtitle.text = "Purchase Complete"
    browser.cardList.addCard(MY_CHECKOUT_CARD);
    browser.showTopCard();
}
- (void)browser:(id <BTNBrowserInterface>)browser didNavigateToPurchase:(id<BTNPurchasePage>)page {
    browser.header.subtitle.text = @"Purchase Complete";
    [browser.cardList addCard:MY_CHECKOUT_CARD];
    [browser showTopCard];
}
override fun onPurchaseNavigate(BrowserInterface browser, PurchasePage page) {
    browser.header.subtitle.text = "Purchase Complete"
    browser.cardList?.addCard(checkoutCard)
    browser.showTopCard()
}
@Override
public void onPurchaseNavigate(BrowserInterface browser, PurchasePage page) {
    final CardList cardList = browser.getCardList();
    if (cardList == null) {
        return;
    }
    browser.getHeader().getSubtitle().setText("Purchase Complete");
    cardList.addCard(checkoutCard);
    browser.showTopCard();
}