# Integration guide

This section explains the simplest approach to embed BNPL options to your (Market) platform. In this page, we adopt Skeleton model (Node.js) described in the previous section.

Let's integrate BNPL within one hour!

Here is the sample repository: https://github.com/credify-pte-ltd/market-sample

Sample UI:

sample ui

TIP

This is a sandbox environment for you to play around with our BNPL systems. If you need the production environment, please let us know (email: info@credify.one).

# 0. Preparation

If you have not set up a Market yet, please refer to Getting started - Set up.

Please make sure that you allow BNPL providers to access your scope schemas. In the sandbox environment, you can test BNPL with BNPL provider under Others section as the following screenshot.

access control

Also, please ensure you create API keys. To use BNPL, you need to grant claim_provider to the API keys.

api key

# 1. Set up the backend project

  • Node.js
  1. Clone Node.js Skeleton service and open market directory

Repository: https://github.com/credify-pte-ltd/serviceX-skeleton-nodejs

This repository has 2 projects in itself. In this guide, we just need market, so let's go to market directory and use this a project root.

$ git clone https://github.com/credify-pte-ltd/serviceX-skeleton-nodejs.git
$ cd serviceX-skeleton-nodejs
$ cd skeleton-model-app/market
$ yarn
  1. Set up .env
$ cp .env.sample .env

Here is an example of .env. More details are documented in the README.md of the Skeleton repository.

# database url
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/demo_bnpl
# `sandbox` or `production`
MODE=sandbox
# port to expose
PORT=8000
# organization ID you get on Dashboard
APP_ID=a0f9a736-ad97-d09b-abf7-d23ebca20bde
# your signing key you can see on Dashboard
APP_SIGNING_KEY=MC4BCZaWbPYDK2VwBBIEIOK3AJ2hzZ8s9YovNIa2dUmmEZ+PbpgeSfI+smahl0sJ
# your API key you can generate on Dashboard
APP_API_KEY=2pL9oQs10oI8LmN70Ip6OCDCkNmVZJ97JRnZRPy5NGeJgP00IkaBn21woWa0LcM
# profile keys that you provide. This should be same what you configure on Dashboard
APP_PROVIDING_BASIC_PROFILE=name,email,phone,address

Here is organization ID.

org id

Here is a signing key. You need to provide a password to see the value. In the .env, you need only the contents (e.g., MC4BCZaWbP...mahl0sJ).

signing key

You can find basic profile information here. It's either phone, name, email, and/or address. This means that you will provide BNPL providers with these bits of information through serviceX. Your users will not have to provide these pieces of information again since you share them.

standard-claims

  1. Set up database

This project has a sample database integrated, so you can follow the below if you want to test the entire flow. In reality, you may want to use your own database or API call instead of having a dedicated database in this server.

# MacOS - run PostgreSQL in this default settings
$ brew services restart postgresql

# Create database
$ yarn db:create
# Run migration
$ yarn db:setup
# Generate seed data (This is demo purpose)
$ yarn db:seed:all

Let's test if it's working well with the following command.

$ yarn start

Open another tab and run this command.

$ curl "http://localhost:8000/v1/demo-user"

If you see database connection errors, please check database/config/config.js.

# 2. Customize the backend project

  • Node.js

All you have to change is dataInteraction/index.js in the Skeleton project.

  1. Customize fetchUserClaimObject

What fetchUserClaimObject does is following:

  • Load a specified user
    • This may be retrieved either from a local DB or from your internal API.
    • In this documentation, the Skeleton service generates demo users by seed files. The User object is defined in database/models/user.js
  • Load commitments
    • This is a secret value attached to each scope to be used when data receiving services validate the transmitted data is correct.
    • The commitments should be generated during pushClaims process, namely inside upsertCommitments function of dataInteraction/index.js.
  • Form basic claim objects
    • JSON keys have been already implemented. You just need to map the correct values to each JSON key.
  • Form custom claim objects
    • The following code is just a sample. You need to update it to reflect your custom claims.

First, you have to check this configuration on Dashboard.

developer config

Then you can see the scope details here. This information will be needed for JSON keys when forming claims.

scope details

// dataInteraction/index.js

const fetchUserClaimObject = async (db, localId, credifyId, includingScopes, withCommitments) => {

  ////
  
  // NOTE: Please change this value according to what you have registered on Dashboard.
  const scopeName = "a0f9a736-ad97-d09b-abf7-d23ebca20bde:loyalty-point-data-1653892708";
  if (includingScopes.length === 0 || includingScopes.includes(scopeName)) {
    claims[scopeName] = {
      "a0f9a736-ad97-d09b-abf7-d23ebca20bde:amount-1653892708": user.loyaltyPoint,
      "a0f9a736-ad97-d09b-abf7-d23ebca20bde:tier-1653892708": user.tier,
      [`${scopeName}:commitment`]: commitments ? commitments[scopeName] : undefined,
    }
  }
  
  ////
}
  1. Map your user object to fetchVerificationInfo

The data structure is already defined. This function needs you to only map your user object to our pre-defined data structure.

  1. Complete updateUserId

We will send you Credify ID to your systems. You need to keep the Credify ID associated with your internal ID.

  1. Complete upsertCommitments

When you provide any data via Credify, you need to generate a certificate that says you have this data of this user. This certificate requires a commitment (a secret value). This commitment is used when data receiving parties validate the transmitted data is correct.

In this function, you are supposed to keep the provided commitments, so that you can retrieve the values whenever you need.

TIP

Once you are done so far, we recommend to deploy a server at this point because the frontend integration will need a remote API (offer filtering endpoint) instead of a localhost. That being said, the logic has been implemented already in the Skeleton service, so you can deploy it here. And then you can customize the following points thereafter.

If you test the server, using Heroku is the easiest. You can find how to deploy it to Heroku in Skeleton service's README.

  1. Customize authenticateInternalUser

Some API endpoints will be called by your frontend, so these endpoints should have your own authentication logic. This function is to authenticate your internal users.

  1. Customize authenticateInternalAPIClient

Some API endpoints will be called by your backend, so these endpoints should have your own authentication logic. This function is to authenticate your internal API clients.

  1. Register Webhook on Dashboard

Once you deploy the server, please register your webhook endpoints on Dashboard.

webhook

The default webhook endpoint of Skeleton service is

${your domain}/v1/webhook

Also, you can customize the behavior in handleWebhook.

  1. Complete getBNPLCallback

Once BNPL transaction is completed, the BNPL provider will redirect the user back to this callback. This should return your callback URL. And also, you can add your own logic at this point.

  1. Implement order management logic

Step #1~#7 are not BNPL specific. Step #8 and #9 are very specific to BNPL integration. Skeleton service provides order management API integration already through the following paths.

  • POST /orders
    • Create an order object
  • GET /orders/credify/:id
    • Get order status information
  • POST /orders/:id/cancel
    • Cancel a specified order
  • POST /orders/:id/disburse
    • Request disbursement

You may want to call the endpoints listed above from your internal systems. For example, when you create a new order with POST /orders, you will obtain order_id. This property is required to check payment status later, so you should save this information in your systems. If you want to extend the API endpoints of this Skeleton service, please check v1/index.js.

Disbursement requests should be made once BNPL providers confirm it's ready. This notification will be sent via webhook, so you may want to call disbursement API in the webhook handling process.

# 3. Integrate frontend SDK into your platform

Integration models - Frontend integration has links to our SDKs.

Before going through this section, please make sure you have approved an offer about BNPL.

bnpl

Besides, please update the organization information.

org api url

  1. Install SDK
  • Swift
  • React.js

If you use Swift Package Manager, please input https://github.com/credify-pte-ltd/credify-ios-sdk.

ios sdk

Once you successfully install the SDK, you have to instantiate a Credify object.

import UIKit
import Credify

let API_KEY = "aei9dQs3HqfbplkhSHIpaFCDCkNmVZJ973RnZRPy50GeJgPtAIWYiJD1woWamaOd"
let APP_NAME = "BNPL demo market"
let APP_ID = "a0f9a736-ad97-d09b-abf7-d23ebca20bde"

class SampleViewContoller: UIViewController {
    private let bnpl = serviceX.BNPL()
    private let passport = serviceX.Passport()
    
    private var user: CredifyUserModel!
    private var canUseBNPL: Bool = false
     
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let config = serviceXConfig(apiKey: API_KEY, env: .sandbox, appName: APP_NAME)
        serviceX.configure(config)
        
    }
}
  1. Set a user object

Some of our functions need to receive a user object (CredifyUserModel). To interact with our BNPL functionalities, we need your users to be logged in. Your internal user class needs to be mapped to CredifyUserModel.

  1. Check BNPL availability
  • Swift
  • React.js

Once you map your user to CredifyUserModel, you can call bnpl.getBNPLAvailability to see if this user can use BNPL options.

This function needs your backend API deployed already because our systems call into your Offers filtering API. Please make sure you deploy the service and update your API endpoints on Dashboard.

Update org info

In addition, please provide your API base URL too.

If you always get isAvailable: false, please let us know. We need to whitelist your organization in our system.

class SampleViewContoller: UIViewController {
    
    ///
    
    func getBNPLAvailability() {
        bnpl.getBNPLAvailability(user: self.user) { result in
            switch result {
            case .success((let isAvailable, let credifyId)):
                self.user.credifyId = credifyId
            
                if isAvailable {
                    self.canUseBNPL = true
                } else {
                    self.canUseBNPL = false
                }
            
            case .failure(let error):
                print(error)
            }
        }
    }
}
  1. Create Order ID

If a user uses BNPL options, you will have to create Order ID. In Customize the backend project, you have created POST /orders endpoint. You may want to call this endpoint via your internal backend systems. In this instruction, the frontend app calls into the Skeleton service directly for convenience.

  • Swift
  • React.js

In this example, the project has installed 2 new packages as following, but these are not required in the actual integration. It depends on your existing project.

import UIKIt
import Credify
import Alamofire
import NVActivityIndicatorView

///

let CREATE_ORDER_API_URL = "http://localhost:8000/v1/orders"
let ORDER_DATA: [String: Any] = [
    "reference_id": "testtest",
    "total_amount": [
        "value": "9000000",
        "currency": "VND"
    ],
    "order_lines": [
        [
            "name": "AirPods Pro",
            "reference_id": "airpods_pro",
            "image_url": "https://www.apple.com/v/airpods/shared/compare/a/images/compare/compare_airpods_pro__e9uzt0mzviem_large_2x.png",
            "product_url": "https://www.apple.com/vn/airpods-pro/",
            "quantity": 1,
            "measment_unit": "EACH", 
            "unit_price": [
                "value": "4000000",
                "currency": "VND"
            ],
            "subtotal": [
                "value": "4000000",
                "currency": "VND"
            ]
        ],
        [
            "name": "Apple Watch 3",
            "reference_id": "apple_watch_series_three",
            "image_url": "https://www.apple.com/v/apple-watch-series-3/v/images/overview/hero__e4ykmvto2gsy_large_2x.jpg",
            "product_url": "https://www.apple.com/vn/apple-watch-series-3/",
            "quantity": 2,
            "measment_unit": "EACH", 
            "unit_price": [
                "value": "2500000",
                "currency": "VND"
            ],
            "subtotal": [
                "value": "5000000",
                "currency": "VND"
            ]
        ]
    ]
]

class SampleViewContoller: UIViewController {
    
    private lazy var activityIndicatorView = {
        return NVActivityIndicatorView(frame: CGRect(origin: CGPoint(x: UIScreen.main.bounds.size.width * 0.5 - 40, y: UIScreen.main.bounds.size.height * 0.5 - 40), size: CGSize(width: 80, height: 80)), type: .ballRotateChase, color: .red)
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(activityIndicatorView)
        
        ///
    }
    
    ///
    
    func createOrder(completion: @escaping (OrderInfo) -> Void) {
        // Start loading view if necessary
        activityIndicatorView.startAnimating()

        AF.request(CREATE_ORDER_API_URL,
                   method: .post,
                   parameters: ORDER_DATA,
                   encoding: JSONEncoding.default)
            .responseJSON { (data) in
                DispatchQueue.main.async {
                    self.activityIndicatorView.stopAnimating()
                }
                switch data.result {
                case .success(let value):
                    guard let v = value as? [String: Any] else { return }
                
                    guard let id = v["id"] as? String else { return }
                    guard let amountObj = v["totalAmount"] as? [String: Any] else { return }
                    guard let amount = amountObj["value"] as? String else { return }
                
                    let orderInfo = OrderInfo(orderId: id, orderAmount: FiatCurrency(value: amount, currency: .vnd))
                    completion(orderInfo)
                case .failure(let err):
                    print(err)
                }
            }
    }
}
  1. Start Credify SDK

Now it's ready to kick off BNPL flow with our SDK. At this point, your backend (at least offer filtering endpoint) should be deployed to a server so our API client can call into it.

  • Swift
  • React.js
let PUSH_CLAIMS_API_URL = "http://localhost:8000/v1/push-claims"

class SampleViewContoller: UIViewController {
  
    ///
    
    @IBAction func checkout(_ sender: Any) {
        createOrder { orderInfo in
            self.startBNPL(orderInfo: orderInfo)
        }
    }
   
    func startBNPL(orderInfo: OrderInfo) {
        let task: ((String, ((Bool) -> Void)?) -> Void) = { credifyId, result in
            // Using Alamofire
            AF.request(PUSH_CLAIMS_API_URL,
                       method: .post,
                       parameters: ["id": self.user.id, "credify_id": credifyId],
                       encoding: JSONEncoding.default).responseJSON { data in
                switch data.result {
                case .success:
                    result?(true)
                case .failure:
                    result?(false)
                }
            }
        }
        
        bnpl.presentModally(
            from: self,
            userProfile: self.user,
            orderInfo: orderInfo,
            pushClaimTokensTask: task
        ) { [weak self] status, orderId, isPaymentCompleted in
            self?.dismiss(animated: false) {
                print("Status: \(status.rawValue), order id: \(orderId), payment completed: \(isPaymentCompleted)")
            }
        }
    }
}

Let's run the app and test it!

You will see this screen! Congratulations! 🎉

bnpl first screen

# 4. Handle status management and repayment

So far we see the flow of BNPL requests. Now that your users can authorize BNPL transactions. What's next to you is

  • handle payment status
  • request disbursement
  • instruct users to repay
  1. Handle payment status

You will receive status update via webhook. When order status becomes approved, you can proceed with the next step, such as delivery of products. This handling should be implemented in handleWebhook function in dataInteraction/index.js.

  1. Request disbursement

Once a transaction on your platform is done, you will need to request the BNPL provider to send money to your bank account. The Skeleton has basic implementation of this, but you have to handle your own logic related to this. The endpoint is /orders/:id/disburse. Here, you may have to attach the invoice data to request a disbursement. This requirement depends on BNPL providers.

For some reason if your user wants to withdraw a BNPL transaction, you can request orders/:id/cancel to revert the transaction.

  1. Instruct users to repay

We do not force users to install another app for BNPL. The users who enjoy BNPL can do everything inside your (Market) platform. We provide a function in SDK for your users to check necessary information.

  • Swift
  • React.js

The following function opens BNPL related UI for your users to manage BNPL usage.

class SampleViewContoller: UIViewController {

    ///
    
    func showBNPLDetail() {
        passport.showDetail(
            from: self,
            user: user,
            marketId: APP_ID,
            productTypes: [.consumerBNPL]
        ) {
            print("page dismissed")
        }
    }
}
Last Updated: 8/9/2022, 9:26:54 AM