Display Cards
Displaying cards to your application users is a feature that often comes with its own set of problems. Being able to implement a custom and refined user experience usually means either having to deal with the compliance burden associated with handling the sensitive cardholder data directly, or having to compromise aesthetics and developer experience trying to conform with outworn solutions.
In this guide we will cover the necessary steps to integrate your frontend with Basis Theory SDKs to display tokenized cardholder data to your users, while keeping security control in your backend. The visual Elements are focused in the card data points: primary account number (PAN), expiration date and verification code, allowing you own the card visuals, which is out of scope for this guide.
Unlock a seamless experience with Basis Theory by ensuring you have card tokens readily available before diving into the integration steps. The guides below can walk you through how to start protecting cardholder data with the platform:
- Issue Cards - create brand-new cards using an Issuer API;
- Collect Cards - capture cards in the frontend;
- Receive Cards via API / Webhooks - receive cards in API requests;
- Import from a Database - migrate to Basis Theory.
Getting Started
To get started, you will need to create a Basis Theory Account and a TEST Tenant.
Provisioning Resources
In this section, we will explore the bare minimum resources necessary to display cards to your users.
Public Application
You will need a Public Application to initialize Basis Theory Elements. This application represents your Frontend.
Click here to create it. Notice how no permissions are assigned at this point. They will be programmatically granted later using sessions.
Private Application
Next, you will need a Private Application using our PCI-compliant template Read PCI Tokens
. This application represents your Backend.
Click here to create it with the following Access Controls:
- Permissions:
token:read
- Containers:
/pci/
- Transform:
mask
Configuring Basis Theory Elements
Basis Theory Elements are available for the following technologies. Click below for detailed instructions on how to install and configure them.
Adding Card Elements
Once installed and configured, add the Card Elements to your application. This will enable your users to type in their card data in your form, while ensuring your systems never come in contact with the data.
- JavaScript
- React
- iOS
- Android
- React Native
<div id="cardNumber"></div>
<div style="display: flex;">
<div id="cardExpirationDate" style="width: 100%;"></div>
<div id="cardVerificationCode" style="width: 100%;"></div>
</div>
import { BasisTheory } from "@basis-theory/basis-theory-js";
let bt;
let cardNumberElement;
let cardExpirationDateElement;
let cardVerificationCodeElement;
async function init() {
bt = await new BasisTheory().init("<API_KEY>", { elements: true });
// Creates Elements instances
cardNumberElement = bt.createElement("cardNumber", {
targetId: "myCardNumber", // (custom) used for tracking validation errors
readOnly: true,
});
cardExpirationDateElement = bt.createElement("cardExpirationDate", {
targetId: "myCardExpiration",
readOnly: true,
});
cardVerificationCodeElement = bt.createElement("cardVerificationCode", {
targetId: "myCardVerification",
readOnly: true,
});
// Mounts Elements in the DOM in parallel
await Promise.all([cardNumberElement.mount("#cardNumber"), cardExpirationDateElement.mount("#cardExpirationDate"), cardVerificationCodeElement.mount("#cardVerificationCode")]);
}
init();
import React, { useRef, useState } from "react";
import {
BasisTheoryProvider,
CardNumberElement,
CardExpirationDateElement,
CardVerificationCodeElement,
useBasisTheory,
} from "@basis-theory/basis-theory-react";
export default function App() {
const { bt } = useBasisTheory("<API_KEY>", { elements: true });
// Refs to get access to the Elements instance
const cardNumberRef = useRef(null);
const cardExpirationRef = useRef(null);
const cardVerificationRef = useRef(null);
return (
<BasisTheoryProvider bt={bt}>
<CardNumberElement id="myCardNumber" readOnly ref={cardNumberRef} />
<div style={{ display: "flex" }}>
<div style={{ width: "100%" }}>
<CardExpirationDateElement id="myCardExpiration" readOnly ref={cardExpirationRef} />
</div>
<div style={{ width: "100%" }}>
<CardVerificationCodeElement id="myCardVerification" readOnly ref={cardVerificationRef} />
</div>
</div>
</BasisTheoryProvider>
);
}
import Foundation
import UIKit
import BasisTheoryElements
import Combine
class ViewController: UIViewController {
@IBOutlet weak var cardNumberTextField: CardNumberUITextField!
@IBOutlet weak var expirationDateTextField: CardExpirationDateUITextField!
@IBOutlet weak var cvcTextField: CardVerificationCodeUITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Setting Elements as read-only
cardNumberTextField.isUserInteractionEnabled = false
expirationDateTextField.isUserInteractionEnabled = false
cvcTextField.isUserInteractionEnabled = false
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.basistheory.android.view.CardNumberElement
android:id="@+id/card_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false" />
<com.basistheory.android.view.CardExpirationDateElement
android:id="@+id/expiration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false" />
<com.basistheory.android.view.CardVerificationCodeElement
android:id="@+id/cvc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
import com.basistheory.android.view.TextElement
import com.basistheory.android.service.BasisTheoryElements
class MainActivity : AppCompatActivity() {
private lateinit var cardNumberElement: CardNumberElement
private lateinit var cardExpirationDateElement: CardExpirationDateElement
private lateinit var cardVerificationCodeElement: CardVerificationCodeElement
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
cardNumberElement = findViewById(R.id.card_number)
cardExpirationDateElement = findViewById(R.id.expiration_date)
cardVerificationCodeElement = findViewById(R.id.cvc)
}
}
import React, { useRef } from "react";
import { SafeAreaView, ScrollView, StatusBar, StyleSheet, View } from "react-native";
import type { BTRef } from "@basis-theory/basis-theory-react-native";
import { CardNumberElement, CardExpirationDateElement, CardVerificationCodeElement } from "@basis-theory/basis-theory-react-native";
const App = (): JSX.Element => {
const cardNumberRef = useRef<BTRef>(null);
const cardExpirationDateRef = useRef<BTRef>(null);
const cardVerificationCodeRef = useRef<BTRef>(null);
return (
<SafeAreaView>
<StatusBar />
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={styles.viewContainer}>
<CardNumberElement btRef={cardNumberRef} editable={false} placeholder="Card Number" style={styles.elements} />
<CardExpirationDateElement btRef={cardExpirationDateRef} editable={false} placeholder="Card Expiration Date" style={styles.elements} />
<CardVerificationCodeElement btRef={cardVerificationCodeRef} editable={false} placeholder="CVC" style={styles.elements} />
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
cardNumber: {
backgroundColor: "#eeeeee",
borderColor: "blue",
borderWidth: 2,
color: "purple",
height: 40,
margin: 12,
padding: 10,
},
viewContainer: {
display: "flex",
flexDirection: "column",
marginTop: 32,
},
});
export default App;
<API_KEY>
with the Public API Key you created previously.Displaying Card Data
Now that we have Elements ready, it is time to fetch the card data and display it. We will add a button to trigger a function that gets the job done.
Creating a Session
The first step of securely fetching the card data is creating a Session to grant temporary elevated access to our Public Application. Add the following code to create a session:
- JavaScript
- React
- iOS
- Android
- React Native
<div id="cardNumber"></div>
<div style="display: flex;">
<div id="cardExpirationDate" style="width: 100%;"></div>
<div id="cardVerificationCode" style="width: 100%;"></div>
</div>
<button onclick="display();">Display</button>
import { BasisTheory } from '@basis-theory/basis-theory-js';
let bt;
let cardNumberElement;
let cardExpirationDateElement;
let cardVerificationCodeElement;
async function init () { ... }
async function display () {
try {
const session = await bt.sessions.create();
} catch (error) {
console.error(error);
}
}
init();
import React, { useRef, useState } from "react";
import { BasisTheoryProvider, CardNumberElement, CardExpirationDateElement, CardVerificationCodeElement, useBasisTheory } from "@basis-theory/basis-theory-react";
export default function App() {
const { bt } = useBasisTheory("<API_KEY>", { elements: true });
// Refs to get access to the Elements instance
const cardNumberRef = useRef(null);
const cardExpirationRef = useRef(null);
const cardVerificationRef = useRef(null);
const display = async () => {
try {
const session = await bt.sessions.create();
} catch (error) {
console.error(error);
}
};
return (
<BasisTheoryProvider bt={bt}>
... // highlight-next-line
<button onClick={display}>Display</button>
</BasisTheoryProvider>
);
}
Add a new UIButton
to your Main.storyboard
and create a new Action for it called display
.
import Foundation
import UIKit
import BasisTheoryElements
import Combine
class ViewController: UIViewController {
private var sessionApiKey: String = ""
@IBOutlet weak var cardNumberTextField: CardNumberUITextField!
@IBOutlet weak var expirationDateTextField: CardExpirationDateUITextField!
@IBOutlet weak var cvcTextField: CardVerificationCodeUITextField!
@IBAction func display(_ sender: Any) {
BasisTheoryElements.createSession(apiKey: "<API_KEY>") { data, error in
self.sessionKey = data!.sessionKey!
let nonce = data!.nonce!
}
}
override func viewDidLoad() { ... }
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
...
<Button
android:id="@+id/display_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:backgroundTint="#00A4BA"
android:text="Display" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
class MainActivity : AppCompatActivity() {
private lateinit var cardNumberElement: CardNumberElement
private lateinit var cardExpirationDateElement: CardExpirationDateElement
private lateinit var cardVerificationCodeElement: CardVerificationCodeElement
private lateinit var button: Button;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
cardNumberElement = findViewById(R.id.card_number)
cardExpirationDateElement = findViewById(R.id.expiration_date)
cardVerificationCodeElement = findViewById(R.id.cvc)
button = findViewById(R.id.display_button)
button.setOnClickListener {
display()
}
}
private fun display() {
val bt = BasisTheoryElements.builder()
.apiKey("<API_KEY>")
.build()
val session = bt.createSession()
}
}
import React, { useRef } from "react";
import { Button, SafeAreaView, ScrollView, StatusBar, StyleSheet, View } from "react-native";
import type { BTRef } from "@basis-theory/basis-theory-react-native";
import { CardNumberElement, CardExpirationDateElement, CardVerificationCodeElement, useBasisTheory } from "@basis-theory/basis-theory-react-native";
const App = (): JSX.Element => {
const cardNumberRef = useRef<BTRef>(null);
const cardExpirationDateRef = useRef<BTRef>(null);
const cardVerificationCodeRef = useRef<BTRef>(null);
const { bt } = useBasisTheory('<PUBLIC_API_KEY>');
const display = async () => {
try {
const session = await bt?.sessions.create();
} catch (error) {
console.error(JSON.stringify(error));
}
};
return (
<SafeAreaView>
<StatusBar />
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={styles.viewContainer}>
<CardNumberElement btRef={cardNumberRef} editable={false} placeholder="Card Number" style={styles.elements} />
<CardExpirationDateElement btRef={cardExpirationDateRef} editable={false} placeholder="Card Expiration Date" style={styles.elements} />
<CardVerificationCodeElement btRef={cardVerificationCodeRef} editable={false} placeholder="CVC" style={styles.elements} />
</View>
<Button onPress={display} title="Display" />
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
cardNumber: {
backgroundColor: "#eeeeee",
borderColor: "blue",
borderWidth: 2,
color: "purple",
height: 40,
margin: 12,
padding: 10,
},
viewContainer: {
display: "flex",
flexDirection: "column",
marginTop: 32,
},
});
export default App;
<API_KEY>
with the Public API Key you created previously.Authorizing a Session
Sessions must be authorized by a Private Application to perform any actions against tokens. In our case, we want to allow it to temporarily read
the card token to display it for the user.
For example, given you have a previously stored card token with the following identifier:
{
"id": "d2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4",
"type": "card",
"tenant_id": "15f48eb5-8b52-4cdd-a396-608f7cf001d0",
"data": {
"number": "XXXXXXXXXXXX4242",
"expiration_month": 12,
"expiration_year": 2025
},
"created_by": "4a6ae2a6-79f8-4640-968f-88db913743df",
"created_at": "2023-04-17T12:54:44.8320458+00:00",
"mask": {
"number": "{{ data.number | reveal_last: 4 }}",
"expiration_month": "{{ data.expiration_month }}",
"expiration_year": "{{ data.expiration_year }}"
},
"search_indexes": [],
"containers": ["/pci/high/"]
}
We will add a new /authorize
endpoint to our backend that receives the session nonce
and authorizes it with the necessary permissions.
- Node
- .NET
- Python
- Go
In this example, we are using Basis Theory SDK and Express framework for Node.js.
const express = require("express");
const cors = require("cors");
const { BasisTheory } = require("@basis-theory/basis-theory-js");
const app = express();
const port = 4242;
app.use(cors());
app.use(express.json());
app.post("/authorize", async (request, response) => {
const bt = await new BasisTheory().init("<PRIVATE_API_KEY>");
const { nonce } = request.body;
// authorizing a session returns an empty 200 response
await bt.sessions.authorize({
nonce: nonce,
rules: [
{
description: "Display Token",
priority: 1,
conditions: [
{
attribute: "id",
operator: "equals",
value: "d2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4",
},
],
permissions: ["token:read"],
transform: "reveal",
},
],
});
response.status(204).send();
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
In this example, we are using Basis Theory SDK and ASP.NET Core Framework.
using System.Collections.Generic;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using BasisTheory.net.Sessions;
using BasisTheory.net.Applications.Entities;
namespace server.Controllers
{
public class Program
{
public static void Main(string[] args)
{
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://0.0.0.0:4242")
.UseWebRoot("public")
.UseStartup<Startup>()
.Build()
.Run();
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddNewtonsoftJson();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseRouting();
app.UseStaticFiles();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
}
[Route("authorize")]
[ApiController]
public class CheckoutApiController : Controller
{
var client = new SessionClient("<PRIVATE_API_KEY>");
[HttpPost]
public ActionResult AuthorizeSession([FromBody] string nonce)
{
await client.AuthorizeAsync(new AuthorizeSessionRequest {
Nonce = nonce,
Rules = new List<AccessRule> {
new AccessRule {
Description = "Display Token",
Priority = 1,
Conditions = new List<Condition> {
new Condition {
Attribute = "id",
Operator = "equals",
Value = "d2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4"
}
},
Permissions = new List<string> { "token:read" },
Transform = "reveal"
},
}
});
return new StatusCodeResult(204);
}
}
}
In this example, we are using Basis Theory SDK and Flask Framework.
import os
from flask import Flask, request
import basistheory
from basistheory.api import sessions_api
from basistheory.model.access_rule import AccessRule
from basistheory.model.condition import Condition
from basistheory.model.authorize_session_request import AuthorizeSessionRequest
app = Flask(__name__)
@app.route('/authorize', methods=['POST'])
def authorize_session():
body = request.get_json()
with basistheory.ApiClient(configuration=basistheory.Configuration(api_key="<PRIVATE_API_KEY>")) as api_client:
session_client = sessions_api.SessionsApi(api_client)
session_client.authorize(authorize_session_request=AuthorizeSessionRequest(
nonce = body.get("nonce"),
rules = [AccessRule(
description = "Display Tokens",
priority = 1,
conditions = [Condition(
attribute = "id",
operator = "equals",
value = "d2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4",
)],
permissions = [ "token:read" ],
transform = "reveal"
)]
))
return '', 204
if __name__ == '__main__':
app.run(port=4242)
In this example, we are using Basis Theory SDK and Go HTTP package.
package main
import (
"log"
"net/http"
"context"
"encoding/json"
"github.com/Basis-Theory/basistheory-go/v3"
)
func main() {
http.HandleFunc("/authorize", authorizeSession)
addr := "localhost:4242"
log.Printf("Listening on %s", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}
func authorizeSession(rw http.ResponseWriter, r *http.Request) {
var payload map[string]interface{}
json.NewDecoder(r.Body).Decode(&payload)
configuration := basistheory.NewConfiguration()
apiClient := basistheory.NewAPIClient(configuration)
contextWithAPIKey := context.WithValue(context.Background(), basistheory.ContextAPIKeys, map[string]basistheory.APIKey{
"ApiKey": {Key: "<PRIVATE_API_KEY>"},
})
authorizeSessionRequest := *basistheory.NewAuthorizeSessionRequest(payload["nonce"].(string))
accessRule := *basistheory.NewAccessRule()
accessRule.SetDescription("Display Token")
accessRule.SetPriority(1)
accessRule.SetPermissions([]string{"token:read"})
accessRule.SetTransform("reveal")
condition := *basistheory.NewCondition()
condition.SetAttribute("id")
condition.SetOperator("equals")
condition.SetValue("d2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4")
accessRule.SetConditions([]basistheory.Condition{condition})
authorizeSessionRequest.SetRules([]basistheory.AccessRule{accessRule})
authorizedSession, httpResponse, err := apiClient.SessionsApi.Authorize(contextWithAPIKey).AuthorizeSessionRequest(authorizeSessionRequest).Execute()
w.WriteHeader(http.StatusNoContent)
}
<PRIVATE_API_KEY>
with the Private API Key you created previously.Now let's have our frontend display
function call this new endpoint passing the session nonce
.
- JavaScript
- React
- iOS
- Android
- React Native
import { BasisTheory } from '@basis-theory/basis-theory-js';
let bt;
let cardNumberElement;
let cardExpirationDateElement;
let cardVerificationCodeElement;
async function init () { ... }
async function display () {
try {
const session = await bt.sessions.create();
await fetch('http://localhost:4242/authorize', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ nonce: session.nonce }),
});
} catch (error) {
console.error(error);
}
}
init();
import React, { useRef, useState } from "react";
import { BasisTheoryProvider, CardNumberElement, CardExpirationDateElement, CardVerificationCodeElement, useBasisTheory } from "@basis-theory/basis-theory-react";
export default function App() {
const { bt } = useBasisTheory("<API_KEY>", { elements: true });
// Refs to get access to the Elements instance
const cardNumberRef = useRef(null);
const cardExpirationRef = useRef(null);
const cardVerificationRef = useRef(null);
const display = async () => {
try {
const session = await bt.sessions.create();
await fetch("http://localhost:4242/authorize", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ nonce: session.nonce }),
});
} catch (error) {
console.error(error);
}
};
return (
<BasisTheoryProvider bt={bt}>
... // highlight-next-line
<button onClick={display}>Display</button>
</BasisTheoryProvider>
);
}
import Foundation
import UIKit
import BasisTheoryElements
import Combine
class ViewController: UIViewController {
private var sessionKey: String = ""
@IBOutlet weak var cardNumberTextField: CardNumberUITextField!
@IBOutlet weak var expirationDateTextField: CardExpirationDateUITextField!
@IBOutlet weak var cvcTextField: CardVerificationCodeUITextField!
func authorizeSession(nonce: String, completion: @escaping ([String: Any]?, Error?) -> Void) {
let parameters = ["nonce": nonce]
let url = URL(string: "http://localhost:4242/authorize")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
completion(nil, error)
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: request, completionHandler: { data, response, error in
guard error == nil else {
completion(nil, error)
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String: Any]
completion(json, nil)
} catch let error {
completion(nil, error)
}
})
task.resume()
}
@IBAction func display(_ sender: Any) {
BasisTheoryElements.createSession(apiKey: "<API_KEY>") { data, error in
self.sessionKey = data!.sessionKey!
let nonce = data!.nonce!
self.authorizeSession(nonce: nonce) { result, error in
}
}
}
override func viewDidLoad() { ... }
}
import com.basistheory.android.view.TextElement
import com.basistheory.android.service.BasisTheoryElements
import java.net.HttpURLConnection
import java.net.URL
class MainActivity : AppCompatActivity() {
private lateinit var cardNumberElement: CardNumberElement
private lateinit var cardExpirationDateElement: CardExpirationDateElement
private lateinit var cardVerificationCodeElement: CardVerificationCodeElement
private lateinit var button: Button;
override fun onCreate(savedInstanceState: Bundle?) { ... }
private fun display() {
val bt = BasisTheoryElements.builder()
.apiKey("<API_KEY>")
.build()
val session = bt.createSession()
authorizeSession(session.nonce)
}
private fun authorizeSession(nonce: String) {
val url = URL("http://localhost:4242/authorize")
val con = url.openConnection() as HttpURLConnection
con.requestMethod = "POST"
con.doOutput = true
con.setRequestProperty("Content-Type", "application/json")
con.setRequestProperty("Accept", "application/json");
val body = String.format("{\"nonce\": \"%s\"}", nonce)
con.outputStream.use { os ->
val input: ByteArray = body.toByteArray(Charsets.UTF_8)
os.write(input, 0, input.size)
}
if (con.responseCode == 200) {
return
}
}
}
import React, { useRef } from "react";
import { Button, SafeAreaView, ScrollView, StatusBar, StyleSheet, View } from "react-native";
import type { BTRef } from "@basis-theory/basis-theory-react-native";
import { CardNumberElement, CardExpirationDateElement, CardVerificationCodeElement, useBasisTheory } from "@basis-theory/basis-theory-react-native";
const App = (): JSX.Element => {
const cardNumberRef = useRef<BTRef>(null);
const cardExpirationDateRef = useRef<BTRef>(null);
const cardVerificationCodeRef = useRef<BTRef>(null);
const { bt } = useBasisTheory('<PUBLIC_API_KEY>');
const display = async () => {
try {
const session = await bt?.sessions.create();
// authorize the session
await fetch("https://localhost:4242/authorize", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nonce: session.nonce,
}),
});
} catch (error) {
console.error(error);
}
};
return (
<SafeAreaView>
<StatusBar />
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={styles.viewContainer}>
<CardNumberElement btRef={cardNumberRef} editable={false} placeholder="Card Number" style={styles.elements} />
<CardExpirationDateElement btRef={cardExpirationDateRef} editable={false} placeholder="Card Expiration Date" style={styles.elements} />
<CardVerificationCodeElement btRef={cardVerificationCodeRef} editable={false} placeholder="CVC" style={styles.elements} />
</View>
<Button onPress={display} title="Display" />
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
cardNumber: {
backgroundColor: "#eeeeee",
borderColor: "blue",
borderWidth: 2,
color: "purple",
height: 40,
margin: 12,
padding: 10,
},
viewContainer: {
display: "flex",
flexDirection: "column",
marginTop: 32,
},
});
export default App;
Retrieving the Card Token
With the frontend session properly authorized, it is time to retrieve the token using the session's key and display the card data to the user.
- JavaScript
- React
- iOS
- Android
- React Native
import { BasisTheory } from '@basis-theory/basis-theory-js';
let bt;
let cardNumberElement;
let cardExpirationDateElement;
let cardVerificationCodeElement;
async function init () { ... }
async function display () {
try {
const session = await bt.sessions.create();
await fetch('http://localhost:4242/authorize', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ nonce: session.nonce }),
});
const token = await bt.tokens.retrieve('d2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4', {
apiKey: session.sessionKey
});
cardNumberElement.setValue(token.data.number);
cardExpirationDateElement.setValue({
year: token.data.expiration_year,
month: token.data.expiration_month,
});
cardVerificationCodeElement.setValue(token.data.cvc);
} catch (error) {
console.error(error);
}
}
init();
import React, { useRef, useState } from "react";
import { BasisTheoryProvider, CardNumberElement, CardExpirationDateElement, CardVerificationCodeElement, useBasisTheory } from "@basis-theory/basis-theory-react";
export default function App() {
const { bt } = useBasisTheory("<API_KEY>", { elements: true });
// Refs to get access to the Elements instance
const cardNumberRef = useRef(null);
const cardExpirationRef = useRef(null);
const cardVerificationRef = useRef(null);
const display = async () => {
try {
const session = await bt.sessions.create();
await fetch("http://localhost:4242/authorize", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ nonce: session.nonce }),
});
const token = await bt.tokens.retrieve("d2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4", {
apiKey: session.sessionKey,
});
cardNumberRef.current.setValue(token.data.number);
cardExpirationRef.current.setValue({
year: token.data.expiration_year,
month: token.data.expiration_month,
});
cardVerificationRef.current.setValue(token.data.cvc);
} catch (error) {
console.error(error);
}
};
return (
<BasisTheoryProvider bt={bt}>
...
<button onClick={display}>Display</button>
</BasisTheoryProvider>
);
}
import Foundation
import UIKit
import BasisTheoryElements
import Combine
class ViewController: UIViewController {
private var sessionApiKey: String = ""
@IBOutlet weak var cardNumberTextField: CardNumberUITextField!
@IBOutlet weak var expirationDateTextField: CardExpirationDateUITextField!
@IBOutlet weak var cvcTextField: CardVerificationCodeUITextField!
func authorizeSession(nonce: String, completion: @escaping ([String: Any]?, Error?) -> Void) { ... }
@IBAction func display(_ sender: Any) {
BasisTheoryElements.createSession(apiKey: "<API_KEY>") { data, error in
self.sessionKey = data!.sessionKey!
let nonce = data!.nonce!
self.authorizeSession(nonce: nonce) { result, error in
BasisTheoryElements.getTokenById(id: 'd2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4', apiKey: self.sessionKey) { data, error in
DispatchQueue.main.async {
self.cardNumberTextField.setValue(elementValueReference: data!.data!.number!.elementValueReference)
self.expirationDateTextField.setValue(
month: data!.data!.expiration_month!.elementValueReference,
year: data!.data!.expiration_year!.elementValueReference
)
self.cvcTextField.setValue(data!.data!.cvc!.elementValueReference)
}
}
}
}
}
override func viewDidLoad() { ... }
}
class MainActivity : AppCompatActivity() {
private lateinit var cardNumberElement: CardNumberElement
private lateinit var cardExpirationDateElement: CardExpirationDateElement
private lateinit var cardVerificationCodeElement: CardVerificationCodeElement
private lateinit var button: Button;
override fun onCreate(savedInstanceState: Bundle?) { ... }
private fun display() {
val bt = BasisTheoryElements.builder()
.apiKey("<API_KEY>")
.build()
val session = bt.createSession()
authorizeSession(session.nonce)
val token = bt.getToken("d2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4", session.sessionKey)
cardNumberElement.setValueRef(token.data.getElementValueReference("number"))
cardExpirationDateElement.setValueRef(
token.data.getElementValueReference("expiration_month"),
token.data.getElementValueReference("expiration_year"),
)
cardVerificationCodeElement.setValueRef(token.data.getElementValueReference("cvc"))
}
private fun authorizeSession(nonce: String) { ... }
}
import React, { useRef } from "react";
import { Button, SafeAreaView, ScrollView, StatusBar, StyleSheet, View } from "react-native";
import type { BTRef } from "@basis-theory/basis-theory-react-native";
import { CardNumberElement, CardExpirationDateElement, CardVerificationCodeElement, useBasisTheory } from "@basis-theory/basis-theory-react-native";
interface CardToken {
expiration_year: InputBTRef;
expiration_month: InputBTRef;
number: InputBTRef;
cvc?: InputBTRef;
}
const App = (): JSX.Element => {
const cardNumberRef = useRef<BTRef>(null);
const cardExpirationDateRef = useRef<BTRef>(null);
const cardVerificationCodeRef = useRef<BTRef>(null);
const { bt } = useBasisTheory("<PUBLIC_API_KEY>");
const display = async () => {
try {
const session = await bt?.sessions.create();
// authorize the session
await fetch("http://localhost:4242/authorize", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
nonce: session.nonce,
tokenId,
}),
});
const token = await bt?.tokens.getById(tokenId, session?.sessionKey);
/**
* Sometimes, we can't infer the token type; therefore,
* we must resort to type casting in order to specify
* its interface for TypeScript to work correctly.
*/
cardNumberRef.current?.setValue((token.data as CardToken).number);
cardExpirationDateRef.current?.setValue({
year: (token.data as CardToken).expiration_year,
month: (token.data as CardToken).expiration_month,
});
cardVerificationCodeRef.current?.setValue((token.data as CardToken).cvc);
} catch (error) {
console.error(error);
}
};
return (
<SafeAreaView>
<StatusBar />
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={styles.viewContainer}>
<CardNumberElement btRef={cardNumberRef} editable={false} placeholder="Card Number" style={styles.elements} />
<CardExpirationDateElement btRef={cardExpirationDateRef} editable={false} placeholder="Card Expiration Date" style={styles.elements} />
<CardVerificationCodeElement btRef={cardVerificationCodeRef} editable={false} placeholder="CVC" style={styles.elements} />
</View>
<Button onPress={display} title="Display" />
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
cardNumber: {
backgroundColor: "#eeeeee",
borderColor: "blue",
borderWidth: 2,
color: "purple",
height: 40,
margin: 12,
padding: 10,
},
viewContainer: {
display: "flex",
flexDirection: "column",
marginTop: 32,
},
});
export default App;
In the steps above, we had retrieved the card token data within the secure Elements context. This means we can set Elements value, and therefore display it, but without interacting with the data directly.
Key Considerations
Design
Basis Theory Elements' unopinionated nature allows for creative freedom, enabling your design team to craft a visually appealing interface that aligns with the brand's identity and resonates with the target audience. Applying good design principles within a familiar developer experience ensures that your business is not restricted to antiquated ways of solving the compliance problem.
CVC Retention
Card tokens retain verification code for 1 hour. This limitation is enforced by default to comply with PCI-DSS guidelines. However, depending on your business case, extending this period or implementing technical workarounds may be possible upon analysis. Please, reach out to our support team for assistance anytime.
Security
Data security and compliance are major concerns when dealing with cardholder data. By using the Application Templates provided in the Provisioning Resources section, you can rest assured that your applications won't directly access sensitive data at any given moment.
To display the cardholder to the end user, the temporary frontend session is briefly granted escalated privilege of reading unmasked tokenized data, while the secure Elements context is used for making the HTTPS request and manipulating the data.
Conclusion
The best practices prescribed in this guide ensure that your applications stay compliant with PCI-DSS standards while displaying cards to your users. With the access controls provisioned in the steps above, we are authorizing the frontend to retrieve existing tokenized data from the secure vault and display it using minimal UI components.
While the example workflow is triggered by user interaction through a button click, your team can design different user experiences that better fit your app. You may chose to do all the steps when the view loads; or perhaps create a session earlier in the workflow, and only authorize if/when displaying the data is necessary.