The Tokenization Component provides a secure, embeddable form for collecting debit card information. It is an alternative to the Cards API. Like the Cards API, the Tokenization Component may be used to process card data for use when creating a card account for a user. The Component is designed for use in mobile apps (WebView) and web apps (iframe), and supports robust validation and cross-platform messaging. This guide explains how to integrate the Tokenization Component in a native application using a WebView.
The DailyPay Debit Card Tokenization Component is a secure, embeddable web form designed for seamless integration into mobile and web applications. It is intended to be loaded in a WebView (React Native, iOS, Android) or iframe, and communicates with the host application using the postMessage API. The component handles all user input, validation, and securely transmits card data to a DailyPay backend service, which performs the actual tokenization with the payment gateway. Only the resulting token and non-sensitive metadata are returned to the host. No sensitive card data is ever exposed to or stored by the host application.
The Tokenization Component is hosted at two URLs:
Production: https://companion.workloads.production.dailypay.com/v1/widgets/debitcard/tokenizer
UAT: https://companion.workloads.uat.dailypay.com/v1/widgets/debitcard/tokenizer
The following parameters are required whenever the Component is used:
Parameter | Notes |
---|---|
client_id |
The client ID used in authorization, supplied by DailyPay |
implementation_id |
The ID of this particular application, supplied by DailyPay |
For example: https://companion.../tokenizer?client_id=123&implementation_id=3213
The widget communicates with your app using the postMessage API:
Successful Tokenization
{
"type": "DAILYPAY_CARD_TOKENIZE_RESPONSE",
"response": {
"first_name": "...", // User's first name
"last_name": "...", // User's last name
"address_line_one": "...", // First line of address
"address_line_two": "...", // Second line of address (optional)
"address_city": "...", // City
"address_state": "...", // State (2-letter abbreviation)
"address_zip_code": "...", // ZIP code
"address_country": "USA", // Country (always set to "USA")
"expiration_month": "...", // Card expiration month (MM format)
"expiration_year": "...", // Card expiration year (YY or YYYY format)
"issuer": "...", // First 6 digits of the card number (BIN)
"token": "..." // Tokenized card data from payment gateway
},
"success": true
}
Failed Tokenization
{
"type": "DAILYPAY_CARD_TOKENIZE_RESPONSE",
"success": false
// Error details
}
response
propertyThe response
object returned with DAILYPAY_CARD_TOKENIZE_RESPONSE
may be used
directly in the POST request to the
accounts endpoint as the
value for the details
field when the fields "account_type": "CARD", "subtype": "DEBIT"
are used. This action will create a transfer account for the user based on
the card details submitted to the webview.
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
let url: URL
let handlerName = "iosListener"
var onTokenReceived: ((String) -> Void)?
var onFullResponseReceived: ((DailypayCardTokenizeResponse) -> Void)?
class Coordinator: NSObject, WKScriptMessageHandler {
var onIssuerReceived: ((String) -> Void)?
var onTokenReceived: ((String) -> Void)?
var onFullResponseReceived: ((DailypayCardTokenizeResponse) -> Void)?
init(onTokenReceived: ((String) -> Void)?, onFullResponseReceived: ((DailypayCardTokenizeResponse) -> Void)?) {
self.onTokenReceived = onTokenReceived
self.onFullResponseReceived = onFullResponseReceived
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("Received postMessage: \(message.body)")
var data: Data? = nil
if let dict = message.body as? [String: Any] {
data = try? JSONSerialization.data(withJSONObject: dict)
} else if let str = message.body as? String {
data = str.data(using: .utf8)
}
if let data, let msg = try? JSONDecoder().decode(DailypayCardTokenizeMessage.self, from: data),
msg.type == "DAILYPAY_CARD_TOKENIZE_RESPONSE" {
onIssuerReceived?(msg.response.issuer)
onTokenReceived?(msg.response.token)
onFullResponseReceived?(msg.response)
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(onTokenReceived: onTokenReceived, onFullResponseReceived: onFullResponseReceived)
}
func makeUIView(context: Context) -> WKWebView {
let contentController = WKUserContentController()
contentController.add(context.coordinator, name: handlerName)
let config = WKWebViewConfiguration()
config.userContentController = contentController
// Forward window.postMessage to iOS
let js = """
window.postMessage = function(data) {
window.webkit.messageHandlers.\(handlerName).postMessage(data);
};
"""
let userScript = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false)
contentController.addUserScript(userScript)
let webView = WKWebView(frame: .zero, configuration: config)
let request = URLRequest(url: url)
webView.load(request)
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
// No-op
}
}
struct DailypayCardTokenizeResponse: Decodable {
let firstName: String
let lastName: String
let addressLineOne: String
let addressLineTwo: String
let addressCity: String
let addressState: String
let addressZipCode: String
let expirationMonth: String
let expirationYear: String
let addressCountry: String
let issuer: String
let token: String
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case addressLineOne = "address_line_one"
case addressLineTwo = "address_line_two"
case addressCity = "address_city"
case addressState = "address_state"
case addressZipCode = "address_zip_code"
case expirationMonth = "expiration_month"
case expirationYear = "expiration_year"
case addressCountry = "address_country"
case issuer
case token
}
}
struct DailypayCardTokenizeMessage: Decodable {
let type: String
let response: DailypayCardTokenizeResponse
let success: Bool
}
struct ContentView: View {
// Replace client_id and implementation_id with values supplied by DailyPay
// that are unique to you and your application
@State private var urlString: String = "https://companion.workloads.production.dailypay.com/v1/widgets/debitcard/tokenizer?client_id=123&implementation_id=3213"
@State private var showAlert = false
@State private var issuer: String? = nil
@State private var showTokenAlert = false
@State private var token: String? = nil
func handleCardTokenizeResponse(_ response: DailypayCardTokenizeResponse) {
// Add card to DailyPay using POST /accounts
// See https://developer.dailypay.com/tag/Accounts#operation/createAccount
print("Ready to send to the DailyPay REST API: \(response)")
}
var body: some View {
VStack {
TextField("Enter URL", text: $urlString)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
if let url = URL(string: urlString) {
WebView(
url: url,
onTokenReceived: { token in
self.token = token
self.showTokenAlert = true
},
onFullResponseReceived: { response in
handleCardTokenizeResponse(response)
}
)
.edgesIgnoringSafeArea(.all)
} else {
Text("Invalid URL")
}
}
.alert(isPresented: $showTokenAlert) {
Alert(
title: Text("Token Received"),
message: Text(token ?? "No token"),
dismissButton: .default(Text("OK"))
)
}
}
}
#Preview {
ContentView()
}
package com.example.webviewrunner
import android.os.Bundle
import android.webkit.JavascriptInterface
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
webView = findViewById(R.id.webView)
// Configure WebView settings
val webSettings: WebSettings = webView.settings
webSettings.javaScriptEnabled = true // Enable JavaScript if your page uses it
webSettings.setSupportZoom(true) // Enable zoom controls
webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW // Allow mixed content (HTTP/HTTPS)
webSettings.domStorageEnabled = true // Enable DOM storage
// Set a WebViewClient to handle page navigation within the WebView itself
webView.webViewClient = object : WebViewClient() {
// This method is deprecated in API 24 and later, but still useful for older devices
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
url?.let {
view?.loadUrl(it)
}
return true // Return true to indicate that the app handled the URL
}
// For API 24 and later
override fun shouldOverrideUrlLoading(view: WebView?, request: android.webkit.WebResourceRequest?): Boolean {
request?.url?.let { uri ->
view?.loadUrl(uri.toString())
}
return true // Return true to indicate that the app handled the URL
}
override fun onReceivedError(view: WebView?, request: android.webkit.WebResourceRequest?, error: android.webkit.WebResourceError?) {
super.onReceivedError(view, request, error)
android.util.Log.e("WebView", "Error: ${error?.description}")
}
override fun onReceivedHttpError(view: WebView?, request: android.webkit.WebResourceRequest?, errorResponse: android.webkit.WebResourceResponse?) {
super.onReceivedHttpError(view, request, errorResponse)
android.util.Log.e("WebView", "HTTP error: ${errorResponse?.statusCode}")
}
}
// Add JavaScript interface for postMessage
webView.addJavascriptInterface(object {
@JavascriptInterface
fun postMessage(message: String) {
// Interpret response and send to DailyPay using POST /accounts
// See https://developer.dailypay.com/tag/Accounts#operation/createAccount
// For demo - alert the response payload here.
runOnUiThread {
AlertDialog.Builder(this@MainActivity)
.setTitle("Message from WebView")
.setMessage(message)
.setPositiveButton("OK", null)
.show()
}
}
}, "AndroidInterface")
// Replace client_id and implementation_id with values supplied by DailyPay
// that are unique to you and your application
webView.loadUrl("https://companion.workloads.production.dailypay.com/v1/widgets/debitcard/tokenizer?client_id=123&implementation_id=3213")
}
// Handle back button presses to navigate within the WebView's history
override fun onBackPressed() {
if (webView.canGoBack()) {
webView.goBack()
} else {
super.onBackPressed()
}
}
}
Install dependencies:
npm install react-native-webview
or yarn add react-native-webview
import React, { useRef, useState } from "react";
import { View } from "react-native";
import { WebView } from "react-native-webview";
const TokenizerComponent = () => {
const webViewRef = useRef(null);
const [tokenData, setTokenData] = useState(null);
const handleWebViewMessage = (event) => {
try {
const messageData = JSON.parse(event.nativeEvent.data);
if (messageData.type === "DAILYPAY_CARD_TOKENIZE_RESPONSE") {
if (messageData.success && messageData.response) {
setTokenData(messageData.response);
}
}
} catch (error) {
console.error("Failed to parse message:", error);
}
};
return (
<WebView
ref={webViewRef}
source={{
// Replace client_id and implementation_id with values supplied by DailyPay
// that are unique to you and your application
uri: "https://companion.workloads.production.dailypay.com/v1/widgets/debitcard/tokenizer?client_id=123&implementation_id=3213",
}}
onMessage={handleWebViewMessage}
javaScriptEnabled={true}
domStorageEnabled={true}
/>
);
};