Introduction
Tabby is a Buy Now Pay Later (BNPL) service that allows consumers to make purchases and pay for them later in installments. It operates as an intermediary between merchants and customers, providing flexible payment options for online and in-store purchases.
How Tabby Works?
- At Checkout: Customers select Tabby as a payment option during checkout on partnering merchant websites or physical stores.
- Approval: Tabby assesses the customer's eligibility for the payment plan in real time based on various factors.
- Payment Options
- Pay Later: Allows customers to pay for their purchases in installments over time (usually within weeks or months).
- Pay in Installments: Provides options for dividing the payment into equal parts without interest.
Step 1. Create A Project I have created a new project with Name Integration.
This is an MVC Core project.
My project Structure looks Like the one Below.
There is one controller PaymentController, 3 Model Classes For Handling API Response and Request
API_CLS.cs
This class structure is based on an API request and response. I'd require the specifics of the API, such as the request parameters, expected response structure, and data types involved. Without the API details, I can offer a generic example to demonstrate how a class might be designed based on an API request and response.
using System.ComponentModel;
namespace Inegration.Models
{
public class API_Class
{
}
public class BuyerHistory
{
public string registered_since { get; set; }
public int loyalty_level { get; set; }
public int wishlist_count { get; set; }
public bool is_social_networks_connected { get; set; }
public bool is_phone_number_verified { get; set; }
public bool is_email_verified { get; set; }
}
public class Buyer
{
public string phone { get; set; }
public string email { get; set; }
public string name { get; set; }
public string dob { get; set; }
public string id { get; set; }
}
public class Item
{
public string title { get; set; }
public string description { get; set; }
public int quantity { get; set; }
public decimal unit_price { get; set; }
public decimal discount_amount { get; set; }
public string reference_id { get; set; }
public string image_url { get; set; }
public string product_url { get; set; }
public string gender { get; set; }
public string category { get; set; }
public string color { get; set; }
public string product_material { get; set; }
public string size_type { get; set; }
public string size { get; set; }
public string brand { get; set; }
}
public class OrderHistory
{
public string purchased_at { get; set; }
public decimal amount { get; set; }
public string payment_method { get; set; }
public string Status { get; set; }
public Buyer buyer { get; set; }
public ShippingAddress shipping_address { get; set; }
public List<Item> items { get; set; }
}
public class ShippingAddress
{
public string city { get; set; }
public string address { get; set; }
public string zip { get; set; }
}
public class Attachment
{
public string body { get; set; }
public string content_type { get; set; }
}
public class Payment
{
public string browserTimeLabel { get; set; }
[DisplayName("Amount")]
public string amount { get; set; }
[DisplayName("Currency")]
public string currency { get; set; }
[DisplayName("Description")]
public string description { get; set; }
public Buyer buyer { get; set; }
public BuyerHistory buyer_history { get; set; }
public Order order { get; set; }
public List<OrderHistory> order_history { get; set; }
public ShippingAddress shipping_address { get; set; }
public Meta meta { get; set; }
public Attachment attachment { get; set; }
[DisplayName("Language")]
public string lang { get; set; }
public string merchant_code { get; set; }
public MerchantUrls merchant_urls { get; set; }
}
public class Order
{
public string tax_amount { get; set; }
public string shipping_amount { get; set; }
public string discount_amount { get; set; }
public DateTime updated_at { get; set; }
public string reference_id { get; set; }
public List<Item> items { get; set; }
}
public class Meta
{
public string order_id { get; set; }
public string customer { get; set; }
}
public class MerchantUrls
{
public string success { get; set; }
public string cancel { get; set; }
public string failure { get; set; }
}
}
CLS_Response.Cs
This is the Payment Return Json response Class.
namespace Inegration.Models
{
public class CLS_Response
{
}
public class InstallmentDetails
{
public string due_date { get; set; }
public object old_amount { get; set; }
public string amount { get; set; }
public string principal { get; set; }
public string service_fee { get; set; }
}
public class InstallmentProduct
{
public string downpayment { get; set; }
public string downpayment_percent { get; set; }
public object downpayment_increased_reason { get; set; }
public string amount_to_pay { get; set; }
public object old_downpayment_total { get; set; }
public string downpayment_total { get; set; }
public string order_amount { get; set; }
public string next_payment_date { get; set; }
public List<InstallmentDetails> installments { get; set; }
public bool pay_after_delivery { get; set; }
public string pay_per_installment { get; set; }
public string web_url { get; set; }
public string qr_code { get; set; }
public int id { get; set; }
public int installments_count { get; set; }
public string installment_period { get; set; }
public string service_fee { get; set; }
}
public class AvailableProducts
{
public List<InstallmentProduct> installments { get; set; }
}
public class Configuration
{
public string currency { get; set; }
public string app_type { get; set; }
public bool new_customer { get; set; }
public object available_limit { get; set; }
public object min_limit { get; set; }
public AvailableProducts available_products { get; set; }
public string country { get; set; }
public string expires_at { get; set; }
public bool is_bank_card_required { get; set; }
public object blocked_until { get; set; }
public bool hide_closing_icon { get; set; }
public object pos_provider { get; set; }
public bool is_tokenized { get; set; }
public string disclaimer { get; set; }
public string help { get; set; }
public bool is_ipqs_required { get; set; }
public Products products { get; set; }
}
public class Products
{
public Installments installments { get; set; }
}
public class Installments
{
public string type { get; set; }
public bool is_available { get; set; }
public object rejection_reason { get; set; }
}
public class Payment2
{
public string id { get; set; }
public DateTime created_at { get; set; }
public DateTime expires_at { get; set; }
public string status { get; set; }
public bool is_test { get; set; }
public Product product { get; set; }
public string amount { get; set; }
public string currency { get; set; }
public string description { get; set; }
public Buyer buyer { get; set; }
public ShippingAddress shipping_address { get; set; }
public Order order { get; set; }
public List<object> captures { get; set; }
public List<object> refunds { get; set; }
public BuyerHistory buyer_history { get; set; }
public List<object> order_history { get; set; }
public object meta { get; set; }
public bool cancelable { get; set; }
}
public class Product
{
public string type { get; set; }
public int installments_count { get; set; }
public string installment_period { get; set; }
}
public class Juicyscore
{
public string session_id { get; set; }
public string referrer { get; set; }
public string time_zone { get; set; }
public string useragent { get; set; }
}
public class Merchant
{
public string name { get; set; }
public string address { get; set; }
public string logo { get; set; }
}
public class RootObject
{
public string web_url { get; set; }
public string id { get; set; }
public object warnings { get; set; }
public Configuration configuration { get; set; }
public string api_url { get; set; }
public object token { get; set; }
public string flow { get; set; }
public Payment payment { get; set; }
public string status { get; set; }
public Customer customer { get; set; }
public Juicyscore juicyscore { get; set; }
public MerchantUrls merchant_urls { get; set; }
public object product_type { get; set; }
public string lang { get; set; }
public string locale { get; set; }
public object seon_session_id { get; set; }
public Merchant merchant { get; set; }
public string merchant_code { get; set; }
public bool terms_accepted { get; set; }
public object promo { get; set; }
public InstallmentPlan installment_plan { get; set; }
public bool is_ipqs_requested { get; set; }
}
public class Customer
{
public object id { get; set; }
public string phone { get; set; }
public string email { get; set; }
public object is_identity_auth_skipped { get; set; }
}
public class InstallmentPlan
{
public object installments { get; set; }
}
}
Payment View
Below Code Shows the Payment View.
@model Inegration.Models.Payment
<hr />
<label id="browserTimeLabel" style="font-size:large"></label>
<br />
<br />
<div class="row">
<div class="col-md-6">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="amount" class="control-label"></label>
<input asp-for="amount" class="form-control" />
<span asp-validation-for="amount" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="currency" class="control-label"></label>
<input asp-for="currency" class="form-control" [email protected] />
<span asp-validation-for="currency" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="description" class="control-label"></label>
<input asp-for="description" class="form-control" />
<span asp-validation-for="description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="lang" class="control-label"></label>
<input asp-for="lang" class="form-control" [email protected] />
<span asp-validation-for="lang" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="merchant_code" class="control-label"></label>
<input asp-for="merchant_code" class="form-control" [email protected] />
<span asp-validation-for="merchant_code" class="text-danger"></span>
</div>
<br />
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
@section Scripts {
<script>
// Function to update label with browser's current time
function updateBrowserTime() {
var label = document.getElementById('browserTimeLabel');
if (label !== null) {
// Get the current date and time in ISO 8601 format
var currentTime = new Date().toISOString();
// Display the time in the label
label.textContent = 'Browser Time: ' + currentTime;
}
}
// Call the function when the page loads
window.onload = updateBrowserTime;
</script>
}
The Dummy Payment Interface is shown below.
If certain parameters like description, language, and merchantCode can be handled or generated on the backend without user input, they can be set programmatically without requiring explicit input from the client side. You can set default or predefined values for these parameters on the server side before sending the request to the API.
PaymentController.cs
using Inegration.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Text;
using System.Text.Json.Serialization;
using System.Reflection;
namespace Inegration.Controllers
{
public class PaymentController : Controller
{
#region Constant Variables
public const string _Currency = "QAR";
public const string _MerchantCode = "QAT1";//Dummy
public const string _Language = "en";
public const string _Token = "pk_test_60cbb3f7";
public const string _SecretToken = "sk_test_";
#endregion
// GET: PaymentController
Uri baseAddress = new Uri("https://api.tabby.ai/api/v2/");
HttpClient _Client;
public PaymentController()
{
_Client = new HttpClient();
_Client.BaseAddress = baseAddress;
}
public ActionResult Index()
{
return View();
}
public ActionResult Details(int id)
{
return View();
}
// GET: PaymentController/Create
//Load Page
public ActionResult Create()
{
ViewBag.Language = _Language;
ViewBag.Merchantcode = _MerchantCode;
ViewBag.Currency = _Currency;
return View();
}
public string GetPaymentJson(Payment obj)
{
// This is used to assign the payments Object as per the document structure
//Note Buyer details are given as per the document
//When you are goin live insted of this change the details
Payment payment = new Payment
{
amount = obj.amount,
currency = _Currency,
description = "payment",
buyer = new Buyer
{
phone = "+97465000001", //Pass the details from user profile from the website
email = "[email protected]",
name = "test",// Pass User Full Name As Per ID
dob = "2019-08-24"//Pass this details from session variable
},
buyer_history = new BuyerHistory
{
registered_since = obj.browserTimeLabel,
loyalty_level = 0,
wishlist_count = 0,
is_social_networks_connected = true,
is_phone_number_verified = true,
is_email_verified = true
},
order = null,
order_history = null,
shipping_address = null,
meta = null,
attachment = null,
lang = _Language,
merchant_code = _MerchantCode,
merchant_urls = null
};
// Serialize the Payment object to JSON
string paymentJson = JsonConvert.SerializeObject(new { payment });
return paymentJson;
}
// POST: PaymentController/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Payment collection)
{
String Result_Url = null;
try
{
var data2= GetPaymentJson(collection);
// Set the content type header
_Client.DefaultRequestHeaders.Accept.Clear();
_Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string token = _Token;
_Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// Create the HttpContent for the request body
var jsonPayload = JsonConvert.SerializeObject(data2, Formatting.Indented);
var httpContent = new StringContent(data2, Encoding.UTF8, "application/json");
// Make the POST request
HttpResponseMessage response = _Client.PostAsync("https://api.tabby.ai/api/v2/checkout", httpContent).Result;
if (response.IsSuccessStatusCode)
{
string errorResponse = response.Content.ReadAsStringAsync().Result;
RootObject obj = JsonConvert.DeserializeObject<RootObject>(errorResponse);
Result_Url = obj.configuration.available_products.installments[0].web_url;
}
else
{
Console.WriteLine($"Request failed with status code: {response.StatusCode}. Response: {response.Content}");
return StatusCode((int)response.StatusCode, "Request failed. Check logs for details.");
}
return Redirect(Result_Url);
}
catch
{
return View();
}
}
}
}
To facilitate the redirection to the Tabby payment page after the user clicks the "Create" button and enters the paying amount, you can utilize server-side code to handle the redirection
The redirect Page will look like the one below.
To enhance the user experience for entering the OTP (One-Time Password) received on their phone after initiating the payment process, you can design an interactive interface that prompts the user to input the OTP and proceed with the transaction. After it redirects to a new page here, the payment is split up, and the next payment date will be displayed, here user can enter the card details and make payment.
Once the payment is Done, a payment success message will show.
Finally, it will be redirected to the postback Url window.
For the Api Document, check this link: API Document.