Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 80 additions & 3 deletions examples/card-form/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,19 @@
data-processout-placeholder="CVC"
></div>
</div>
<button type="submit" class="submit-button">Pay</button>
<div className="form-messages">
<!-- Combo Card Types Section -->
<div id="combo-card-types" style="display: none; margin-top: 20px;">
<h3>Combo Card Types</h3>
<div id="combo-card-radio-group" style="margin-top: 10px;">
<!-- Radio buttons will be dynamically added here -->
</div>
</div>
<div class="form-input-group">
<button type="submit" class="submit-button">
Pay
</button>
</div>
<div class="form-messages">
<div id="errors"></div>
<div id="success"></div>
</div>
Expand All @@ -36,7 +47,7 @@
<script>
document.addEventListener("DOMContentLoaded", function () {
// You need to replace these values with your own
const projectId = "test-proj_qEi1u5BwoYcZb6mOMKDWIm4mpqKCq6bN"
const projectId = "proj_2ra13ORom2dWhiL6DUgMTdmJAwnOW6Ym"
const invoiceId = "iv_5pKzE0HKcubbPLklZ7iAHvN2LThuzx8j"

const client = new ProcessOut.ProcessOut(projectId)
Expand All @@ -53,6 +64,67 @@
},
},
function (form) {
// Test with a dummy card number string
const dummyCardNumber = "4242424242424242";
console.log("Testing getIINDetails with dummy number:", dummyCardNumber);
client.getIINDetails(dummyCardNumber, function (response) {
console.log("IIN Details Response with dummy number:", response)

// Handle combo card types if available
if (response.card_information && response.card_information.combo_card_types) {
console.log("displayingggg")
displayComboCardTypes(response.card_information.combo_card_types);
}
}, function (err) {
console.log("IIN Details Error with dummy number:", err)
})

// For testing: Always display dummy combo card types
const dummyComboTypes = ["prepaid", "debit"];
displayComboCardTypes(dummyComboTypes);

// Function to display combo card types
function displayComboCardTypes(comboTypes) {
const comboSection = document.getElementById("combo-card-types");
const comboRadioGroup = document.getElementById("combo-card-radio-group");

// Clear existing radio buttons
comboRadioGroup.innerHTML = "";

// Add radio buttons for each combo card type
comboTypes.forEach(function(type) {
const radioDiv = document.createElement("div");
radioDiv.style.marginBottom = "10px";

const radio = document.createElement("input");
radio.type = "radio";
radio.name = "combo_card_type"; // Group all radio buttons
radio.value = type;
radio.id = `combo-card-${type}`;

const label = document.createElement("label");
label.htmlFor = `combo-card-${type}`;
label.textContent = type.charAt(0).toUpperCase() + type.slice(1); // Capitalize first letter
label.style.marginLeft = "8px";
label.style.cursor = "pointer";

radioDiv.appendChild(radio);
radioDiv.appendChild(label);
comboRadioGroup.appendChild(radioDiv);
});

// Show the section
comboSection.style.display = "block";

console.log("Combo card types displayed:", comboTypes);
}

// Function to get the selected combo card type
function getSelectedComboCardType() {
const selectedRadio = document.querySelector('input[name="combo_card_type"]:checked');
return selectedRadio ? selectedRadio.value : null;
}

form.getNumberField().on("focus", function (e) {
document.getElementById("errors").innerHTML = ""
})
Expand All @@ -68,11 +140,16 @@
form.addEventListener("submit", function (e) {
e.preventDefault()

// Get the selected combo card type
const selectedComboType = getSelectedComboCardType();
console.log("Selected combo card type:", selectedComboType);

client.tokenize(
form,
{
name: document.getElementById("card-form-name").value,
zip: "10018",
preferred_card_type: selectedComboType // Pass the selected combo card type
},
function (token) {
console.log("Tokenization Success", token)
Expand Down
14 changes: 14 additions & 0 deletions src/processout/cardfield.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ module ProcessOut {

window.addEventListener("message", function (event) {
var data = Message.parseEvent(event);
console.log("CardField tokenize response - data:", data);
if (data.messageID != id)
return;
if (data.action != "tokenize")
Expand Down Expand Up @@ -596,5 +597,18 @@ module ProcessOut {
return Translator.translateMessage("cardfield.title")
}
}

/**
* getIINDetails retrieves the IIN details using the IINDetails class
* @param {Callback} success
* @param {Callback} error
* @return {Promise<IINDetailsResponse>} Promise that resolves to the IIN details response
*/
public getIINDetails(success: (data: IINDetailsResponse) => void,
error: (err: Exception) => void): Promise<IINDetailsResponse> {
// Create an instance of IINDetails and delegate to it
const iinDetails = new IINDetails(this.instance);
return iinDetails.getIINDetails("", success, error);
}
}
}
18 changes: 18 additions & 0 deletions src/processout/cardform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,5 +355,23 @@ module ProcessOut {

this.cvc.refreshCVC(cardUID, success, error);
}

/**
* getIINDetails retrieves the IIN details using the number field
* and calls the success callback with the IIN details when successful.
* If an error arises, the error callback is called
* @param {Callback} success
* @param {Callback} error
* @return {Promise<IINDetailsResponse>} Promise that resolves to the IIN details response
*/
public getIINDetails(success: (data: IINDetailsResponse) => void,
error: (err: Exception) => void): Promise<IINDetailsResponse> {

if (!this.number)
error(new Exception("processout-js.wrong-type-for-action",
"getIINDetails was called but the form has no number field initialized."));

return this.number.getIINDetails(success, error);
}
}
}
134 changes: 134 additions & 0 deletions src/processout/iindetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/// <reference path="../references.ts" />

module ProcessOut {
/**
* CardInformation interface represents the card information returned by the IIN details API
*/
export interface CardInformation {
bank_name: string;
brand: string;
category: string;
co_scheme: string | null;
combo_card_types: string | null;
country: string;
scheme: string;
type: string;
}

/**
* IINDetailsResponse interface represents the complete response from the IIN details API
*/
export interface IINDetailsResponse {
card_information: CardInformation;
success: boolean;
}

/**
* IINDetails class for handling IIN (Issuer Identification Number) details
* and API calls to retrieve card information based on IIN numbers.
*/
export class IINDetails {
/**
* ProcessOut instance
* @var {ProcessOut}
*/
protected instance: ProcessOut;

/**
* IINDetails constructor.
* @param {ProcessOut} instance
*/
public constructor(instance: ProcessOut) {
this.instance = instance;
}

/**
* getIINDetails retrieves the IIN (Issuer Identification Number) details
* for a given card number string. The IIN is truncated to 6 digits for lookup.
* @param {string} cardNumber
* @param {callback} success
* @param {callback} error
* @return {Promise<IINDetailsResponse>} Promise that resolves to the IIN details response
*/
public getIINDetails(
cardNumber: string,
success?: (data: IINDetailsResponse) => void,
error?: (err: Exception) => void
): Promise<IINDetailsResponse> {
return new Promise((resolve, reject) => {
// Add a timeout to detect if the Promise hangs
const timeoutId = setTimeout(() => {
const timeoutException = new Exception("processout-js.iin-timeout",
"IIN details request timed out");
reject(timeoutException);
if (error) error(timeoutException);
}, 10000); // 10 second timeout

// Validate the IIN number first
if (!this.validateIIN(cardNumber)) {
clearTimeout(timeoutId);
const exception = new Exception("processout-js.iin-invalid-length",
"IIN number must be more than 6 digits.");
reject(exception);
if (error) error(exception);
return;
}

// Truncate the IIN number to appropriate length
const truncatedIIN = this.truncateIIN(cardNumber);
if (!truncatedIIN) {
clearTimeout(timeoutId);
const exception = new Exception("processout-js.iin-invalid-length",
"Failed to truncate IIN number.");
reject(exception);
if (error) error(exception);
return;
}

// Make API request to get IIN details using ProcessOut apiRequest with basicAuth disabled
// Use the same URL pattern as tokenize method: "iins/{iin}" instead of "/iins/{iin}"
const iinPath = `iins/${truncatedIIN}`;
this.instance.apiRequest("get", iinPath, {},
function(data: IINDetailsResponse, req: XMLHttpRequest, e: Event): void {
clearTimeout(timeoutId);
resolve(data);
if (success) success(data);
},
function(req: XMLHttpRequest, e: Event, errorCode: any): void {
clearTimeout(timeoutId);
const exception = new Exception("processout-js.iin-details-error",
"Failed to retrieve IIN details.");
reject(exception);
if (error) error(exception);
},
undefined, // retry parameter
{
isLegacy: false,
basicAuth: false
} // disable basic auth for this endpoint
);
});
}

/**
* validateIIN validates that the IIN number has sufficient length for processing
* @param {string} iinNumber
* @return {boolean} true if valid, false otherwise
*/
public validateIIN(iinNumber: string): boolean {
return iinNumber.length >= 6;
}

/**
* truncateIIN truncates the IIN number to the appropriate length for API lookup
* @param {string} iinNumber
* @return {string | null} truncated IIN number or null if invalid
*/
public truncateIIN(iinNumber: string): string | null {
if (iinNumber.length >= 6) {
return iinNumber.substring(0, 6);
}
return null;
}
}
}
59 changes: 58 additions & 1 deletion src/processout/processout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface Window {
interface apiRequestOptions {
isLegacy?: boolean
clientSecret?: string
basicAuth?: boolean
}

/**
Expand Down Expand Up @@ -302,6 +303,7 @@ module ProcessOut {

method = method.toLowerCase()

const basicAuth = options && options.basicAuth !== undefined ? options.basicAuth : true
const isLegacy = options && options.isLegacy !== undefined ? options.isLegacy : true
const clientSecret =
options && options.clientSecret !== undefined ? options.clientSecret : null
Expand All @@ -319,7 +321,7 @@ module ProcessOut {

if (SCRIPT_VERSION) customHeaders["ProcessOut-JS-Version"] = SCRIPT_VERSION

if (this.projectID) headers["Authorization"] = `Basic ${btoa(this.projectID + ":")}`
if (this.projectID && basicAuth) headers["Authorization"] = `Basic ${btoa(this.projectID + ":")}`

// We need the data to at least be an empty object
if (!data) data = {}
Expand Down Expand Up @@ -550,6 +552,60 @@ module ProcessOut {
)
}

/**
* getIINDetails retrieves the IIN details
* for a given card form or card number. The IIN is truncated to 6 digits
* for lookup. If a CardForm is provided, attempts to extract the card number
* from the form's number field.
* @param {CardForm | string} cardFormOrNumber
* @param {callback} success
* @param {callback} error
* @return {Promise<IINDetailsResponse>} Promise that resolves to the IIN details response containing card_information and success
*/
public getIINDetails(
cardFormOrNumber: CardForm | string,
success: (data: IINDetailsResponse) => void,
error: (err: Exception) => void,
): Promise<IINDetailsResponse> {
if (cardFormOrNumber instanceof CardForm)
return this.getIINDetailsForm(<CardForm>cardFormOrNumber, success, error)

return this.getIINDetailsString(<string>cardFormOrNumber, success, error)
}

/**
* getIINDetailsForm retrieves the IIN details using the given form to
* fetch the card number value
* @param {CardForm} form
* @param {callback} success
* @param {callback} error
* @return {Promise<IINDetailsResponse>} Promise that resolves to the IIN details response
*/
protected getIINDetailsForm(
form: CardForm,
success: (data: IINDetailsResponse) => void,
error: (err: Exception) => void,
): Promise<IINDetailsResponse> {
return form.getIINDetails(success, error);
}

/**
* getIINDetailsString retrieves the IIN details for the given card number string
* @param {string} cardNumber
* @param {callback} success
* @param {callback} error
* @return {Promise<IINDetailsResponse>} Promise that resolves to the IIN details response
*/
protected getIINDetailsString(
cardNumber: string,
success: (data: IINDetailsResponse) => void,
error: (err: Exception) => void,
): Promise<IINDetailsResponse> {
// Create an instance of IINDetails and delegate to it
const iinDetails = new IINDetails(this);
return iinDetails.getIINDetails(cardNumber, success, error);
}

/**
* tokenizePaymentToken takes the payment token payload
* and encodes in base64. Then it creates a ProcessOut
Expand Down Expand Up @@ -696,6 +752,7 @@ module ProcessOut {
success: (token: string, card: Card) => void,
error: (err: Exception) => void,
): void {
console.log("tokenizeForm response - form:", form, "data:", data);
form.validate(
function () {
form.tokenize(data, success, error)
Expand Down
Loading
Loading