# [Draft - Engineering] AXO SDK Integration Guide This document provides guidance for integrating Connect using the Braintree SDK. **Note: The reference javascript code below uses [async/await syntax](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Promises#async_and_await) instead of promises, [object destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), and [TypeScript Types](https://www.typescriptlang.org/docs/) for the [reference types](#Reference-Types). ### To Discuss with Product - [x] Which address type do we want to support? Vault/BT do not match - [x] New integration pattern for merchant that does not own email address - does that change the authenticationState? - [ ] Styling options - There is a bit of a mismatch between BT HCF and the requirements ---- Latest version of the guide: https://hackmd.io/5_FDdKwwSKGdXDdSHZ-ldw?view ---- ## Integration Guide (v0.5) ### Server-side Client-Token Generation To instantiate Braintree client SDK and the Connect components, a client-token will need to be generated via a server-side call and then passed into the SDK. See instructions for generating a client token on your server and passing that to the SDK in our [Braintree Docs - Set Up Your Server](https://developer.paypal.com/braintree/docs/start/hello-server#generate-a-client-token). ### Add `*.paypal.com` to your Content Security Policy [Content Security Policy](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) is a feature of web browsers that mitigates cross-site scripting and other attacks. By limiting the origins of resources that may be loaded on your page, you can maintain tighter control over any potentially malicious code. While browser support is [relatively limited](http://caniuse.com/#feat=contentsecuritypolicy), we recommend considering the implementation of a CSP when available. In addition to [Braintree's directives](https://developer.paypal.com/braintree/docs/reference/client-reference/javascript/v2/best-practices#using-braintree.js-with-a-content-security-policy), you will need to add the following directive to your policy: ||Sandbox | Production |--|--|--| |`frame-src`|`*.paypal.com`|`*.paypal.com`| ### Initialize Connect Connect is initialized via a call to `braintree.connect.create` and passed a configuration object. You need to pass the device data to when initializing Connect to prevent fraudulent access to Connect profiles. To collect this data, you will instantiate the Braintree client and the Braintree data collector, passing the results of those methods into the configuration object that is passed into `braintree.connect.create`. **HTML** ```htmlmixed= <script src="https://js.braintreegateway.com/web/<version>/js/client.min.js"></script> <script src="https://js.braintreegateway.com/web/<version>/js/data-collector.min.js"></script> <script src="https://js.braintreegateway.com/web/<version>/js/connect.min.js"></script> ``` **JavaScript** ```javascript= const clientInstance = await braintree.client.create({ authorization: "<YOUR CLIENT TOKEN>" }) const dataCollectorInstance = await braintree.dataCollector.create({ client: clientInstance }); const deviceData = dataCollectorInstance.deviceData; const connect = await braintree.connect.create({ client: clientInstance, deviceData: deviceData, }); const identity = connect.identity; ``` If you are adding Connect to your existing Braintree integration, you may be already initializing `braintree.client` and `braintree.dataCollector`; you can reuse the same instance of those objects. The `connect` namespace contains a map of Connect features names to their corresponding UI Components and functions. See [reference types](#Connect-namespace). > **Open issues** > 1. Does Big C use `BT client token` or `tokenization key`? > 2. What version of the web SDK is Big C using today? > 3. Is Big C using BT Hosted Card Fields? ### Identity: Look up email & trigger authentication flow #### Look up profile based on email To initiate the Connect checkout flow, collect the email address from the customer and call `identity.lookupCustomerByEmail(email)` to identify whether the email is associalted with a Connect profile (or belongs to a PayPal member). **JavaScript** ```javascript const { customerId } = await identity.lookupCustomerByEmail(event.target.value); if (customerId) { // Email is associated with a Connect profile or a PayPal member // Trigger authentication flow here } else { // No profile found for this email address } ``` #### Trigger authentication flow for the previously identified customer To trigger the authentication, you need to use the `customerId` that is returned from the `lookupCustomerByEmail(email)` call. To retrieve the full profile data for the customer, set `fetchFullProfileData` option to `true`. If set to `false`, the profile object will only contain identifier to fetch the profile attributes at a later point. Triggering the authentication will present the customer with a screen to authenticate themselves. You can customize the styling of authentication screen to match the style on your site. **JavaScript** ```javascript const options = { styles: { colorTextPrimary: "#333333", colorBackgroundPrimary: "#e6f7ff" }, fetchFullProfileData: false; }; const { authenticationState, profileData } = await identity.triggerAuthenticationFlow(customerId, options); if (authenticationState === 'succeeded') { // To fetch the profile information from your server, send the // following attributes to your server and call the Vault API: const connectCustomerAuthAssertionToken = profileData.connectCustomerAuthAssertionToken; const connectCustomerId = profileData.connectCustomerId; } else { // authentication failed or canceled by the customer } ``` The `triggerAuthenticationFlow()` method returns [`AuthenticatedCustomerResult`](#AuthenticatedCustomerResult). You can use the `authenticationState` in the response to determine if the customer has successfully authenticated themselves. If the customer fails or declines to authenticate, they can continue to checkout as a guest user. > **Open issues** > 1. What styling options does Big C require? ### Fetching profile data using REST API After the customer authenticates themselves, you can fetch their profile information on your server. #### Retrieve Connect Profile Details | **API Endpoint** | GET v3/vault/customers/connect_customer_id | | -------- | -------- | | **Header**| <YOUR CLIENT TOKEN> + connect_customer_auth_assertion_token | | **URL Payload** | connect_customer_id | | **Response Payload:** |Customer ID, Name, Phone Number, Email ID, Shipping Address (ID, Company, Address_line_1, Address_line_2,admin_area_2,admin_area_1, postal_code,country_code), payment_source (ID=token, card((Last 4, expiry, name, billing address(Address_line_1, Address_line_2,admin_area_2,admin_area_1, postal_code,country_code)) | | **Critiera**| | > **Open issues** > 1. Review Company name, phone number for shipping/billing address. > 2. Hateos Links to be confirmed. **Sample Request Payload:** **<YOUR CLIENT TOKEN>** - is one minted as part of the initialization of the BT Web SDK. **customer_auth_assertion_token** - is returned within the BT Web SDK upon successful customer authentication. **connect_customer_id** - is returned within the BT Web SDK upon successful customer authentication. ```bash > curl -v -k -X GET 'https://api.sandbox.paypal.com/v3/vault/customers/c_124233534523' \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <YOUR CLIENT TOKEN>" \ -H "Authorization: PayPal-Auth-Assertion : <customer_auth_assertion_token>" \ ``` <br> **Response Payload** ```bash { "id": "c_124233534523", "name": { "given_name": "Ryan", "surname": "Remembered" }, "email": "ryan@connect.com", "phones": [ { "national_number": "4086751340", "country_code": "1" } ], "payment_tokens": [ { "id": "4C9917EAVB2781612", "payment_source": { "card": { "last_digits": "1111", "brand": "VISA", "expiry": "2027-02", "name": "Ryan Remembered", "billing_address": { "address_line_1": "2211 N First Street", "address_line_2": "Building 17", "admin_area_1": "CA", "admin_area_2": "San Jose", "country_code": "US", "postal_code": "95131" } } } }, { "id": "5C991763VB2781612", "payment_source": { "card": { "last_digits": "4566", "brand": "VISA", "expiry": "2027-02", "name": "Ryan Remembered", "billing_address": { "address_line_1": "2211 N First Street", "address_line_2": "Building 17", "admin_area_1": "CA", "admin_area_2": "San Jose", "country_code": "US", "postal_code": "95131" } } } } ], "addresses": [ { "id": "9asdaf12313i", "name": { "given_name": "Ryan", "surname": "Remembered" }, "postal_code": "95131", "address_line_1": "2211 N First Street", "address_line_2": "Building 17", "admin_area_1": "CA", "admin_area_2": "San Jose", "country_code": "US" }, { "id": "4asdaf123222", "name": { "given_name": "Ryan", "surname": "Address 2" }, "postal_code": "95131", "address_line_1": "2211 N First Street", "address_line_2": "Building 15", "admin_area_1": "CA", "admin_area_2": "San Jose", "country_code": "US" } ] } ``` ### Displaying card fields As part of your checkout flow, you will need to display the card fields for guest customers as well as customers with a Connect profile so that they can enter a new card and complete the transaction. The `ConnectCardComponent` is a ready-made payment UI that offers a quick and easy way to securely accept payments. Customers that don't have a Connect profile will be automatically shown an option to optionally create a Connect profile when the complete the transaction. You can customize the styling of component to match the style on your site. **JavaScript** ```javascript= const options = { styles: { colorTextPrimary: "#333333", colorBackgroundPrimary: "#e6f7ff" }, fields: { cardholderName: { prefill: "<insert prefill value>" }, phone: { prefill: "<insert prefill value>" } } } const connectCardComponent = ConnectCardComponent().render('#payment-container'); submitButton.addEventListener('click', ()=> { const { nonce } = await connectCardComponent.tokenize({ shipping: {}, billing: {...} }); // Send nonce & previously captured device data to server to complete checkout }) ``` See [reference types](#ConnectCardComponent). > **Open issues** > 1. What styling options does Big C require? > 2. If Big C is currently using hosted card fields, what styles are you specifying? --- ## Reference Types ### Connect's `create` configuration ```typescript interface ConnectOptions { authorization: string, client: braintree.client, deviceData: string } ``` ```typescript type create = (options: ConnectOptions) => Connect; ``` ### Connect namespace This is returned as the result of calling `connect.create({...})` ```typescript interface Connect { identity: { lookupCustomerByEmail: (email: string) => LookupCustomerResult, triggerAuthenticationFlow: (identifiedUserId: string, options: AuthenticationFlowOptions) => AuthenticatedCustomerResult }, ConnectCardComponent: (options: ConnectCardComponentOptions) => ConnectCardComponent, } ``` ### LookupCustomerResult The LookupCustomerResult is returned from methods such as `identity.lookupCustomerByEmail(email);` ```typescript interface LookupCustomerResult { customerId: string; } ``` ### Style Options ```typescript interface StyleOptions{ ... } ``` ### Authentication Flow Options This are the options passed to `identity.triggerAuthenticationFlow(customerId, options)` ```typescript interface AuthenticationFlowOptions { styles: StylesOptions; fetchFullProfileData: boolean; // default: true } ``` ### AuthenticatedCustomerResult The authenticated user is returned from methods such as `identity.triggerAuthenticationFlow(customerId, options);` ```typescript= interface AuthenticatedCustomerResult { authenticationState: 'succeeded'|'failed'|'canceled'; profileData: profileData; } interface profileData { connectCustomerAuthAssertionToken: string; // auth-assertion connectCustomerId: string; // customer ID addresses: Address[]; cards: PaymentToken[]; } interface Address { firstName: string; lastName: string; company: string; streetAddress: string; extendedAddress: string; locality: string; // City region: string; // State postalCode: string; countryCodeNumeric: number; countryCodeAlpha2: string; countryCodeAlpha3: string; } interface CardPaymentSource { type: 'card'; brand: string; expiry: string; // "YYYY-MM" lastDigits: string; // "1111" name: string; isPreferred: boolean; billingAddress: Address; } interface PaymentSource { card: CardPaymentSource; }; interface PaymentToken { id: string; // This is the nonce / token paymentSource: PaymentSource; } ``` ### ConnectCardComponent The ConnectCardComponent uses the Hosted Card Fields, the resulting interface is a subset of the [Card Fields interface](#tokenize-examples). The instance of a ConnectCardComponent can be created using: ```javascript const connectCardComponent = connect.ConnectCardComponent({...}).render('#card-container') ``` The resulting Payment Component instance will be used to tokenize the payment method: ```javascript const {nonce} = connectCardComponent.tokenize({ shipping: {...}, billing: {...} }); createOrder(nonce); ``` #### Configuring fields The different fields can be configured during instantiation of the ConnectCardComponent following the pattern: ```javacript connect.ConnectCardComponent({ styles: {}, fields: { number: { placeholder: 'Number', }, phoneNumber: { prefill: '555-555-5555' } } }).render(elem) ``` ##### Available Fields | Name | Type | Attributes | Description | | -- | -- | -- | -- | | number | field | <optional> | A field for card number. | | expirationDate | field | <optional> | A field for expiration date in MM/YYYY or MM/YY format. This should not be used with the expirationMonth and expirationYear properties. | | expirationMonth | field | <optional> | A field for expiration month in MM format. This should be used with the expirationYear property. | | expirationYear | field | <optional> | A field for expiration year in YYYY or YY format. This should be used with the expirationMonth property. | | cvv | field | <optional> | A field for 3 or 4 digit card verification code (like CVV or CID). If you wish to create a CVV-only payment method nonce to verify a card already stored in your Vault, omit all other fields to only collect CVV. | | postalCode | field | <optional>| A field for postal or region code. | | cardholderName | field | <optional> | A field for the cardholder name on the customer's credit card. | | phoneNumber | field | <optional> | A field for the cardholder name on the customer's credit card. | #### ConnectCardComponent reference types ```typescript type ConnectCardComponent = (options: ConnectCardComponentOptions) => ConnectCardComponent; type CardBrands = visa | mastercard | american-express | diners-club | discover | jcb | union-pay | maestro | elo | mir | hiper| hipercard; interface Field { placeholder: string; prefill: string; supportedCardBrands: CardBrands; } interface ConnectCardComponentFields { ['fields']: Field; } interface ConnectCardComponentOptions { styles: StyleConfiguration; fields: ConnectCardComponentFields; } interface TokenizeDetails { bin: string; cardType: string; expirationMoth: string; expirationYear: string; cardholderName: string; lastFour: string; lastTwo: string; } interface TokenizeResult { nonce: string; details: TokenizeDetails; description: string; type: 'CreditCard'; } interface TokenizeOptions { shippingAddress: Address; billingAddress: Address; } interface Card { type: string; niceType: string; code: { name: 'CVV' | 'CID' | 'CVC'; size: number; // typically 3 or 4 } } interface StateObject { cards: Card[]; fields: Object; //See HCF } interface ConnectCardComponent { tokenize: async (options: TokenizeOptions) => TokenizeResult; on: (eventName: string, callback: (state: StateObject) => void) } ``` --- ## FAQ ### What browser versions will be supported? We support the same set of browsers that are [supported by PayPal Checkout today](https://developer.paypal.com/docs/archive/checkout/reference/faq/#link-whichbrowsersdoespaypalcheckoutsupport) * Edge version 12 and later * Chrome version 30 and later * Firefox version 30 and later * Safari version 7 and later * Opera version 23 and later --- ## Archived Discussion ### Accelerated Namespace Various options below were considered, but ultimately we aligned to using `Option 2`. We find namespacing to be valuable, and generally wanted to align to the idea that a collection of "utilities" are associated with a single concept (like `identity`). That being said, we see a future where a single component might not exist on a single noun (for example, right now there is a Payment component but no other utility functions). We chose to keep the components at the root level as each one maps to a "drop-in" in the braintree world. ```javascript= //Option 1: namespaced core functions & UI components { auth: { Auth: (options: AuthOptions) => Component lookupUser: (email: string) => UnAuthenticatedUser, authenticate: (email: string, options: componentOptions) => AuthenticatedUser }, payment: { Payment: (options: PaymentOptions) => Component } } //Option 2: Components at the root, namespaced core functionality { AuthComponent: (options: AuthOptions) => Component, PaymentComponent: (options: PaymentOptions) => Component, identity: { lookupUser: (email: string) => UnAuthenticatedUser, authenticate: (email: string) => AuthenticatedUser, verifyAuthenticatedUser: (buyerAccessToken) => AuthenticatedUser, }, } // Option 3: Static methods on components - requires components { Auth: (options: AuthOptions) => Component | { lookupUser: (email: string) => UnAuthenticatedUser, authenticate: (email: string) => AuthenticatedUser, }, Payment: (options: PaymentOptions) => Component, } // Option 4: Everything at the root { AuthComponent: (options: AuthOptions) => Component, PaymentComponent: (options: PaymentOptions) => Component, lookupUser: (email: string) => LookupUserResult, authenticate: (email: string) => AuthenticatedUser } //Option 5: render functions for components { auth: { render: (options: AuthOptions) => Component lookupUser: (email: string) => UnAuthenticatedUser, authenticate: (email: string, options: componentOptions) => AuthenticatedUser }, payment: { render: (options: PaymentOptions) => Component } } ``` ### Lookup & Authentication The decision was made to provide two separate SDK methods for lookup vs triggering the authentication flow. We toyed with the idea of having a single method that would do both at the same time as a convenience for a merchant: ```javascript const buyerProfile = await auth.authenticate(email); ``` ### Modify a billing address associated with a credit card ```javascript const modifiedNonce = await payment.updatePaymentAddress(nonce, address) const NewNonce = await payment.addAddress(nonce, address) ``` ### Other ideas * Opportunity to return a JWT from the buyer profile, this allows for a serialized value that can be persisted on merchant server. Would likely require decoding in the browser for displaying the values * Could also be returning plain js object with tools / convenience methods for enabling serializing/encoding as a JWT ```javascript axo = braintree.accelerated.create({}); const state = axo.getState(); localstorage.set('axo-state', state); fetch('my-server.com', { data: state, }) // Next time: braintree.accelerated.create({ state: localstorage.get('axo-state'), }) ``` ```css= --accelerated-color-text-primary: black; --accelerated-color-danger: orange; .braintree-accelerated- ``` ### Merchant-owned returning buyer render Assumption: Merchant is rendering their own connect-buyer UI ```javascript= let paymentComponent; if(userProfileData) { // Ryan: // render list cards // using userProfileData renderList(userProfileData) } else { // Gary: // render a guest experience paymentComponent = payment.PaymentCardFields({ onPaymentSelected: async () => {} }).render('#guest-payment-container'); } function renderReturningBuyer(userProfileData) { userProfileData.wallet.forEach((walletItem) => { renderWallet(walletItem); }); //If buyer chooses to change, just change state back to guest experience } submitButton.addEventListener('click', ()=> { // need to know if they're a guest or not const { nonce } = await paymentComponent.tokenize({ shipping: {}, billing: {}, }); // Send data to server to complete checkout }) ``` ### Combined PayWall (Outside of 0.5 scope) The Paywall component will handle both the guest experience and the returning buyer experience automatically. If a buyer is unknown, they will be presented with the card form. If they are a known buyer they will be presented with the returning buyer FI selection screen. The same method can be used to get the tokenized card in both the guest and the remembered buyer experience `paywallComponent.tokenize()`. ```javascript= const paywallComponent = PaywallComponent({ onPaymentSelected: async () => { ... // This can be used for performing validation / enabling a purchase button } }).render('payment-container'); submitButton.addEventListener('click', ()=> { const { nonce } = await paywallComponent.tokenize({ shipping: {}, billing: {}, }); // Send data to server to complete checkout }) ``` ### Paywall Component with optional returning buyer experience ```javascript const paywallComponent = PaywallComponent({ showVaultedCards: true, styles: {} }).render('payment-container'); ``` #### Trigger the user authentication with email address TODO: Check if the Identity API can expose this capability ```javascript const { authenticationState, userProfileData } = await identity.triggerAuthenticationFlowByEmail(emailAddress); if (authenticationState === 'succeeded') { // userProfileData contains all of the data needed to render a returning buyer experience } else { // render the guest experience } ``` ```typescript= interface PPCPAddress { firstName: string; lastName: string; addressLine1: string; addressLine2: string; adminArea1: string; // State adminArea2: string; // City postalCode: string; countryCode: string; } ``` ### Patterns for customizing the look #### CSS-based styling * The SDK sets an ID/class name to the DOM element; the merchant can define style it in their CSS. * Merchant will be able to show/hide the element, change the position and dimensions of the element etc. * This pattern gives the merchant a lot of control to style the component (or hide it) and the SDK can not restrict it. * When should this pattern be used? * We want to provide the merchant the ability to control all styling attributes of the elements (including hiding elements). * BT Drop-in and hosted card fields uses this pattern. #### Configuration-based styling (via Javascript) * The component accepts named styling options provided (e.g.: `border-color`) * Merchant cannot customize other attributes. For example, if `background-color` is not provided, the merchant will not be able to customize it). * This pattern controls what the merchant can style * When should this pattern be used? * Merchant should only be able to change specific styling attributes in the component. * Hosted fields can only use this pattern (due to technical limitations) ### Styling your experience Note: We are waiting for finalized requirements, we prefer to start with a small list of exposed options and then add on in the future as we learn more. The Accelerated Checkout experience can be styled to fit your theme by configuring various different individual settings that will be applied to the style of the experience that is displayed. This can be configured one of two ways. #### [WIP] Styling using the style option for accelerated ```javascript= const styles = { 'toggleColorPrimary': 'blue', 'toggleColorSecondary': 'grey' } const accelerated = await braintree.accelerated.create({ authorization: "<YOUR CLIENT TOKEN>", styles, }); ``` #### [WIP] supported style properties | Style Name | description | default | | -- | -- | -- | | `toggleColorPrimary` | The primary color of the toggle | x | | `toggleColorSecondary` | Secondary color of the toggle | x | | `fontFamily` | The font family used throughout the UI | x | | `fontSizeHeading` | The font size of the heading | x | | `fontSizeBody` | The font size of the rest of the content | x | | `phoneBorderRadius` | The border radius used for the phone input | x | | `phoneBorderColor` | The border color of the phone input | x | | `phoneBorderColorFocus` | The border color of the phone input when focused | x | | `colorBackgroundPrimary` | The color used for the background of the UI. Match this with your sites background | x | | `colorTextPrimary` | The default text color for all the text | x | | `colorDanger` | A color used to indicate errors or destructive actions in the UI. | x | | `brandColor` | A color used to display the Connect Brand | 'light' | 'dark' | ## Components Components are denoted with `PascalCase` variable names and have a consistent return type. Once instantiated a component returns an object with an async `render` method. The async function is resolved when the UI component is rendered into the supplied container or emits an error if there were any issues. ```javascript= try { await accelerated.Component({ ...componentOptions }).render("#container"); } catch (error) { // handle error as appropriate. below just logs it console.error(error); } ``` ## Customizing the look We need to provide merchants the ability to customize the "look-and-feel" of the UI elements that we render so that the elements fit seemlessly on the merchant's site. Based on the level of customizability required, one of the following patterns will be provided for each component. ### AXO UI components | Component | Customizability required | Recommended pattern | | -- | -- | -- | | Authentication interstitials | Low | Config-based | | Card component elements | Low | Config-based | | Card component hosted input fields | High | Config-based | ### Braintree UI components Below is a list of Braintree UI components and the pattern supported by them | Component | Customizability required | Recommended pattern | | -- | -- | -- | | BT Drop-in elements | High | CSS-based | | BT HCF/Drop-in hosted input fields | High | Config-based | **Why does Braintree Drop-in use the CSS-based customization pattern?** Braintree drop-in has not been designed to have an opinionated UI. It provides the merchant complete control on the UI. **Why does BT HCF use the config-based customization pattern?** Since the Hosted Card Fields are rendered in an iFrame, CSS-based configuration is not technically feasible.