Set Card PIN
This guides provides a detailed walkthrough on how to incorporate Basis Theory SDKs into your frontend for secure card PIN collection, and send them in accordance with regulations to the Issuer API through the Basis Theory Proxy.
Getting Started
To get started, you will need a Basis Theory account and a Tenant.
Provisioning Resources
In this section, we will explore the bare minimum resources necessary to enable your users to set PIN on their cards.
Management Application
To create all subsequent resources, you will need a Management Application.
Click here to create a Management Application or login to your Basis Theory account and create a new application with the following settings:
- Name - Resource Creator
- Application Type - Management
- Permissions:
application:create
,proxy: create
Public Application
You will need a Public Application to initialize Basis Theory Elements. This application represents your Frontend.
Using the Management Application key to authorize the request, call the Basis Theory API to create a new Public Application:
curl "https://api.basistheory.com/applications" \
-X "POST" \
-H "BT-API-KEY: <MANAGEMENT_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "Frontend",
"type": "public"
}'
Observe that at this stage, no permissions have been allocated yet. They will be granted later programmatically using sessions.
<MANAGEMENT_API_KEY>
with the Management API Key you created previously.key
from the created Public Application as it will be used later in this guide.Private Application
To authorize the Proxy call to be performed by your Frontend, you will need a Private Application. This new application represents your Backend.
Using the Management Application key to authorize the request, call Basis Theory API to create a new Private Application:
curl "https://api.basistheory.com/applications" \
-X "POST" \
-H "BT-API-KEY: <MANAGEMENT_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "Backend",
"type": "private",
"permissions": [ "token:use" ]
}'
<MANAGEMENT_API_KEY>
with the Management API Key you created previously.key
from the created Private Application as it will be used later in this guide.Pre-Configured Proxy
Now we will create a Pre-Configured Proxy that listens for incoming requests from the Frontend, injects credentials and forwards the PIN to the Issuer API. To achieve that, we will leverage a Request Transform code to manipulate the request as necessary:
- Lithic
- Marqeta
Lithic expects an encrypted PIN Block in the pin
field when updating a card. The code below receives the pin
in plaintext, encrypts the PIN Block using Lithic's Public Key, and injects the necessary credentials to call the endpoint. See documentation.
const { Buffer } = require("buffer");
const forge = require("node-forge");
const createKey = (LITHIC_PUB_KEY) => {
const pem = Buffer.from(LITHIC_PUB_KEY, "base64").toString();
return forge.pki.publicKeyFromPem(pem);
};
function randomInt(low, high) {
// Generate random integer between low and high, inclusive
return Math.floor(Math.random() * (high - low + 1) + low);
}
const encryptPinBlock = (pin, publicKey) => {
const nonce = randomInt(1e8, 1e12);
const pinBlock = {
nonce,
pin,
};
const ciphertext = publicKey.encrypt(JSON.stringify(pinBlock), "RSA-OAEP", {
md: forge.md.sha1.create(),
mgf1: {
md: forge.md.sha1.create(),
},
});
return forge.util.encode64(ciphertext);
};
module.exports = (req) => {
const {
args: {
headers,
body
},
configuration: {
LITHIC_PUB_KEY,
LITHIC_API_KEY
}
} = req;
const { pin } = body;
const publicKey = createKey(LITHIC_PUB_KEY);
const pinBlock = encryptPinBlock(pin, publicKey);
return {
body: {
pin: pinBlock,
},
headers: {
...headers,
Authorization: LITHIC_API_KEY,
},
};
};
Marqeta accepts the pin
in plaintext along with a control_token
that you can obtain going directly to them. The code below receives both in plaintext and injects the necessary credentials to call the endpoint. See documentation.
const { Buffer } = require("buffer");
module.exports = (req) => {
const {
args: {
headers,
body
},
configuration: {
MARQETA_APPLICATION_TOKEN,
MARQETA_ADMIN_ACCESS_TOKEN
}
} = req;
const credentials = `${MARQETA_APPLICATION_TOKEN}:${MARQETA_ADMIN_ACCESS_TOKEN}`;
const auth = new Buffer.from(credentials).toString('base64');
return {
body,
headers: {
...headers,
Authorization: `Basic ${auth}`,
},
};
};
Let's store the contents of the requestTransform.js
file into a variable:
request_transform_code=$(cat requestTransform.js)
And call the Basis Theory API to create the Proxy:
- Lithic
- Marqeta
curl "https://api.basistheory.com/proxies" \
-X "POST" \
-H "BT-API-KEY: <MANAGEMENT_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "PIN Proxy",
"destination_url": "https://sandbox.lithic.com/v1/cards",
"request_transform": {
"code": '"$(echo $request_transform_code | jq -Rsa .)"'
}
}'
curl "https://api.basistheory.com/proxies" \
-X "POST" \
-H "BT-API-KEY: <MANAGEMENT_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "PIN Proxy",
"destination_url": "https://sandbox-api.marqeta.com/v3/pins",
"request_transform": {
"code": '"$(echo $request_transform_code | jq -Rsa .)"'
}
}'
<MANAGEMENT_API_KEY>
with the Management API Key you created previously.key
from the created Proxy as it will be used later to invoke it.Done! These are all the resources needed. Let's see how to use them.
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 Text Element
Once installed and configured, add the Text Element to your application. This will enable your users to type in their desired card PIN in your form, while ensuring your systems never come in contact with the data.
- JavaScript
- React
- iOS
- Android
<div id="pin"></div>
import { BasisTheory } from '@basis-theory/basis-theory-js';
let bt;
let pinElement;
async function init() {
bt = await new BasisTheory().init('<PUBLIC_API_KEY>', { elements: true });
const wrapper = document.getElementById("pin");
wrapper.style.border = "1px solid black";
pinElement = bt.createElement("text", {
targetId: "pinElement",
mask: [/\d/u, /\d/u, /\d/u, /\d/u],
style: {
base: {
fontSize: "25px",
fontFamily: "conceal",
textAlign: "center"
}
}
});
await pinElement.mount(wrapper);
}
init();
import React, { useRef } from "react";
import { TextElement, useBasisTheory } from "@basis-theory/basis-theory-react";
export default function App() {
const { bt } = useBasisTheory("<PUBLIC_API_KEY>", {
elements: true
});
const pinRef = useRef(null);
return (
<div className="App">
<div style={{ border: "1px solid black" }}>
<TextElement
id="pin"
mask={[/\d/u, /\d/u, /\d/u, /\d/u]}
bt={bt}
style={{
base: {
fontSize: "25px",
fontFamily: "conceal",
textAlign: "center"
}
}}
ref={pinRef}
/>
</div>
</div>
);
}
import Foundation
import UIKit
import BasisTheoryElements
import Combine
class ViewController: UIViewController {
@IBOutlet weak var pinTextField: TextElementUITextField!
override func viewDidLoad() {
super.viewDidLoad()
let regexDigit = try! NSRegularExpression(pattern: "\\d")
let pinMask = [regexDigit, regexDigit, regexDigit, regexDigit] as [Any]
pinTextField.setConfig(options: TextElementOptions(mask: pinMask))
}
}
<?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.TextElement
android:id="@+id/pin"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
import com.basistheory.android.view.TextElement
import com.basistheory.android.service.BasisTheoryElements
class MainActivity : AppCompatActivity() {
private lateinit var pinElement: TextElement
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pinElement = findViewById(R.id.pin)
pinElement.mask = ElementMask("####")
}
}
Setting the PIN
With the TextElement enabling you to securely capture the PIN in your user-facing application(s), it is time to send it to the Issuer API using the Proxy. We will add a button to trigger a function to get 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. To create a session, add the following code:
- JavaScript
- React
- iOS
- Android
<div id="pin"></div>
<button onclick="submit();">Submit</button>
import { BasisTheory } from '@basis-theory/basis-theory-js';
let bt;
let pinElement;
async function init () { ... }
async function submit () {
try {
const session = await bt.sessions.create();
} catch (error) {
console.error(error);
}
}
init();
import React, { useRef } from "react";
import { TextElement, useBasisTheory } from "@basis-theory/basis-theory-react";
export default function App() {
const { bt } = useBasisTheory("<PUBLIC_API_KEY>", {
elements: true
});
const pinRef = useRef(null);
const submit = async () => {
try {
const session = await bt.sessions.create();
} catch (error) {
console.error(error);
}
}
return (
<div>
...
<button onClick={submit}>Submit</button>
</div>
);
}
Add a new UIButton
to your Main.storyboard
and create a new Action for it called submit
.
import Foundation
import UIKit
import BasisTheoryElements
import Combine
class ViewController: UIViewController {
private var sessionApiKey: String = ""
@IBOutlet weak var pinTextField: TextElementUITextField!
@IBAction func submit(_ sender: Any) {
BasisTheoryElements.createSession(apiKey: "<PUBLIC_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/submit_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:backgroundTint="#00A4BA"
android:text="Submit" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
class MainActivity : AppCompatActivity() {
private val bt = BasisTheoryElements.builder()
.apiKey("<PUBLIC_API_KEY>")
.build()
private lateinit var pinElement: TextElement
private lateinit var button: Button;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pinElement = findViewById(R.id.pin)
pinElement.mask = ElementMask("####")
button = findViewById(R.id.submit_button)
button.setOnClickListener {
submit()
}
}
private fun submit() {
val session = bt.createSession()
}
}
<PUBLIC_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 backend resources, such as the Proxy. In our case, we want to allow it to invoke the Proxy, which requires token:use
permission.
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,
permissions: ['token:use']
});
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,
Permissions = new List<String> { "token:use" }
});
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.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"),
permissions = ["token:use"]
))
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))
authorizeSessionRequest.SetPermissions([]string{"token:use"})
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 submit
function call this new endpoint passing the session nonce
.
- JavaScript
- React
- iOS
- Android
import { BasisTheory } from '@basis-theory/basis-theory-js';
let bt;
let pinElement;
async function init () { ... }
async function submit () {
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 } from "react";
import { TextElement, useBasisTheory } from "@basis-theory/basis-theory-react";
export default function App() {
const { bt } = useBasisTheory("<PUBLIC_API_KEY>", {
elements: true
});
const pinRef = useRef(null);
const submit = 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 (
<div>
...
<button onClick={submit}>Submit</button>
</div>
);
}
import Foundation
import UIKit
import BasisTheoryElements
import Combine
class ViewController: UIViewController {
private var sessionKey: String = ""
@IBOutlet weak var pinTextField: TextElementUITextField!
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 submit(_ sender: Any) {
BasisTheoryElements.createSession(apiKey: "<PUBLIC_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 val bt = BasisTheoryElements.builder()
.apiKey("<PUBLIC_API_KEY>")
.build()
private lateinit var pinElement: TextElement
private lateinit var button: Button;
override fun onCreate(savedInstanceState: Bundle?) { ... }
private fun submit() {
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
}
}
}
Invoking the Proxy
After properly authorizing the user session, you can use it to invoke the Pre-Configured Proxy by passing the Text Element value for the pin
attribute in the request body.
- Lithic
- Marqeta
Additionally, we pass the Lithic's card id in the request path
to properly call the Update Card endpoint.
- JavaScript
- React
- iOS
- Android
import { BasisTheory } from '@basis-theory/basis-theory-js';
let bt;
let pinElement;
async function init () { ... }
async function submit () {
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 }),
});
await bt.proxy.patch({
path: "ef7d65c-9023-4da3-b113-3b8583fd7951",
headers: {
"BT-PROXY-KEY": "proxy_key_1234567890"
},
body: {
pin: pinElement,
},
apiKey: session.sessionKey
});
} catch (error) {
console.error(error);
}
}
init();
import React, { useRef } from "react";
import { TextElement, useBasisTheory } from "@basis-theory/basis-theory-react";
export default function App() {
const { bt } = useBasisTheory("<PUBLIC_API_KEY>", {
elements: true
});
const pinRef = useRef(null);
const submit = 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 }),
});
await bt.proxy.patch({
path: "ef7d65c-9023-4da3-b113-3b8583fd7951",
headers: {
"BT-PROXY-KEY": "proxy_key_1234567890"
},
body: {
pin: pinRef.current,
},
apiKey: session.sessionKey
});
} catch (error) {
console.error(error);
}
}
return (
<div>
...
<button onClick={submit}>Submit</button>
</div>
);
}
import Foundation
import UIKit
import BasisTheoryElements
import Combine
class ViewController: UIViewController {
private var sessionKey: String = ""
@IBOutlet weak var pinTextField: TextElementUITextField!
func authorizeSession(nonce: String, completion: @escaping ([String: Any]?, Error?) -> Void) { ... }
@IBAction func submit(_ sender: Any) {
BasisTheoryElements.createSession(apiKey: "<PUBLIC_API_KEY>") { data, error in
self.sessionKey = data!.sessionKey!
let nonce = data!.nonce!
self.authorizeSession(nonce: nonce) { result, error in
let proxyHttpRequest = ProxyHttpRequest(
method: .patch,
path: String("/ef7d65c-9023-4da3-b113-3b8583fd7951"),
body: [
"pin": pinTextField
])
BasisTheoryElements.proxy(apiKey: sessionKey, proxyKey: "proxy_key_1234567890", proxyHttpRequest: proxyHttpRequest)
{ response, data, error in
}
}
}
}
override func viewDidLoad() { ... }
}
import com.basistheory.android.view.TextElement
import com.basistheory.android.service.BasisTheoryElements
import com.basistheory.android.service.ProxyRequest
import java.net.HttpURLConnection
import java.net.URL
class MainActivity : AppCompatActivity() {
private val bt = BasisTheoryElements.builder()
.apiKey("<PUBLIC_API_KEY>")
.build()
private lateinit var pinElement: TextElement
private lateinit var button: Button;
override fun onCreate(savedInstanceState: Bundle?) { ... }
private fun submit() {
val session = bt.createSession()
authorizeSession(session.nonce)
val proxyRequest: ProxyRequest = ProxyRequest().apply {
path = "/ef7d65c-9023-4da3-b113-3b8583fd7951"
headers = mapOf(
"BT-PROXY-KEY" to "proxy_key_1234567890",
"Content-Type" to "application/json"
)
body = object {
val pin = pinElement
}
}
bt.proxy.patch(proxyRequest, session.sessionKey)
}
private fun authorizeSession(nonce: String) { ... }
}
Additionally, we pass Marqeta's control_token
for the PIN in the request body
. You can create one by calling their /pins/controltoken
endpoint before making the proxy call.
- JavaScript
- React
- iOS
- Android
import { BasisTheory } from '@basis-theory/basis-theory-js';
let bt;
let pinElement;
async function init () { ... }
async function submit () {
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 }),
});
await bt.proxy.put({
headers: {
"BT-PROXY-KEY": "proxy_key_1234567890"
},
body: {
control_token,
pin: pinElement,
},
apiKey: session.sessionKey
});
} catch (error) {
console.error(error);
}
}
init();
import React, { useRef } from "react";
import { TextElement, useBasisTheory } from "@basis-theory/basis-theory-react";
export default function App() {
const { bt } = useBasisTheory("<PUBLIC_API_KEY>", {
elements: true
});
const pinRef = useRef(null);
const submit = 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 }),
});
await bt.proxy.put({
headers: {
"BT-PROXY-KEY": "proxy_key_1234567890"
},
body: {
control_token,
pin: pinRef.current,
},
apiKey: session.sessionKey
});
} catch (error) {
console.error(error);
}
}
return (
<div>
...
<button onClick={submit}>Submit</button>
</div>
);
}
import Foundation
import UIKit
import BasisTheoryElements
import Combine
class ViewController: UIViewController {
private var sessionKey: String = ""
@IBOutlet weak var pinTextField: TextElementUITextField!
func authorizeSession(nonce: String, completion: @escaping ([String: Any]?, Error?) -> Void) { ... }
@IBAction func submit(_ sender: Any) {
BasisTheoryElements.createSession(apiKey: "<PUBLIC_API_KEY>") { data, error in
self.sessionKey = data!.sessionKey!
let nonce = data!.nonce!
self.authorizeSession(nonce: nonce) { result, error in
let proxyHttpRequest = ProxyHttpRequest(
method: .put,
body: [
"control_token": control_token,
"pin": pinTextField
])
BasisTheoryElements.proxy(apiKey: sessionKey, proxyKey: "proxy_key_1234567890", proxyHttpRequest: proxyHttpRequest)
{ response, data, error in
}
}
}
}
override func viewDidLoad() { ... }
}
import com.basistheory.android.view.TextElement
import com.basistheory.android.service.BasisTheoryElements
import com.basistheory.android.service.ProxyRequest
import java.net.HttpURLConnection
import java.net.URL
class MainActivity : AppCompatActivity() {
private val bt = BasisTheoryElements.builder()
.apiKey("<PUBLIC_API_KEY>")
.build()
private lateinit var pinElement: TextElement
private lateinit var button: Button;
override fun onCreate(savedInstanceState: Bundle?) { ... }
private fun submit() {
val session = bt.createSession()
authorizeSession(session.nonce)
val proxyRequest: ProxyRequest = ProxyRequest().apply {
headers = mapOf(
"BT-PROXY-KEY" to "proxy_key_1234567890",
"Content-Type" to "application/json"
)
body = object {
val control_token = control_token
val pin = pinElement
}
}
bt.proxy.put(proxyRequest, session.sessionKey)
}
private fun authorizeSession(nonce: String) { ... }
}
proxy_key_1234567890
with the Proxy Key you created previously.And that's it. Invoking the proxy is the last step to securely set the card PIN against the issuer API.
Conclusion
This comprehensive guide equips developers with the essential tools to seamlessly integrate Basis Theory's frontend SDKs for secure card PIN capture. By diligently following the outlined steps, developers ensure both robust security and adherence to PCI compliance, while unlocking the possibilities around Elements customization.
Our emphasis on security, flexibility and customization not only addresses inherent challenges in card PIN handling but also paves the way for innovative solutions in collaboration with card issuers. With code samples spanning various frontend and backend technologies, developers gain the knowledge needed to navigate diverse development environments effectively. By adopting these practices, developers bolster their ability to create secure, adaptable and customized solutions that resonate within the dynamic landscape of modern payment systems.