Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ node_modules

# Dist files
dist

# APM test config files
examples/apm/config.json
21 changes: 21 additions & 0 deletions examples/apm/config-example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"project_id": "test-proj_IPW8DgC6yoI2YEUGmmkHERjbuMiGKBdx",
"gateways": [
{
"value": "gway_conf_vupnl08h7hy1jxf3cz5z4dc2lw5py2ve",
"name": "ProcessOut"
},
{
"value": "gway_conf_fh67c2llxupjhcqxxbr4cvlfosncdbr7:authorization",
"name": "Forage Authorization"
},
{
"value": "gway_conf_fh67c2llxupjhcqxxbr4cvlfosncdbr7:tokenization",
"name": "Forage Tokenization"
},
{
"value": "gway_conf_zuo5iyryn390he7fx3rd50twwvhpcvob.adyenblik",
"name": "Adyen Blik"
}
]
}
49 changes: 39 additions & 10 deletions examples/apm/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
<body>
<div>
<form id="initialise-apm">
<input type="text" id="project-id" name="project_id" placeholder="Project ID" />
<select name="gateway" id="apm-gateway">
<option value="gway_conf_vupnl08h7hy1jxf3cz5z4dc2lw5py2ve">ProcessOut</option>
<option value="gway_conf_fh67c2llxupjhcqxxbr4cvlfosncdbr7:authorization">Forage Authorization</option>
<option value="gway_conf_fh67c2llxupjhcqxxbr4cvlfosncdbr7:tokenization">Forage Tokenization</option>
<option value="gway_conf_zuo5iyryn390he7fx3rd50twwvhpcvob.adyenblik">Adyen Blik</option>
<option value="">Select Gateway</option>
</select>
<input type="text" id="invoice-id" name="invoice_id" placeholder="Invoice ID" />
<input type="text" id="customer-id" name="customer_id" placeholder="Customer ID" style="display: none" />
Expand All @@ -23,10 +21,31 @@
<div id="apm-container"></div>
<script src="../../dist/processout.js" crossorigin="anonymous"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const projectId = 'test-proj_IPW8DgC6yoI2YEUGmmkHERjbuMiGKBdx';
function populateGatewaySelect(gateways) {
const select = document.getElementById('apm-gateway');
gateways
.sort((a, b) => a.name.localeCompare(b.name))
.forEach(gateway => {
const option = document.createElement('option');
option.value = gateway.value;
option.textContent = gateway.name;
select.appendChild(option);
});
}

function initConfigs(file) {
return fetch(file)
.then(response => response.json())
.then(data => {
document.getElementById('project-id').value = data.project_id;
populateGatewaySelect(data.gateways);
})
}

function initProcessOut() {
const projectId = document.getElementById('project-id').value;
const gatewayEl = document.querySelector('#apm-gateway');

// Build gateway dictionary from select options
const gatewayDict = {};

Expand All @@ -49,7 +68,7 @@
gatewayEl.addEventListener('change', (e) => {
const value = e.target.value;
const gateway = gatewayDict[value];

switch (gateway) {
case 'Forage Authorization':
flow = 'authorization';
Expand Down Expand Up @@ -141,11 +160,11 @@
if (apm) {
apm.on('*', ({ type, ...rest }) => {
console.log(Date.now(), `apm event - ${type}`, rest)
if (type === 'success' || type === 'failure' || type === 'payment-cancelled') {
if (type === 'success' || type === 'failure' || type === 'payment-cancelled') {
apm.cleanUp()
}
})

try {
apm.initialise()
} catch (e) {
Expand All @@ -154,6 +173,16 @@
}
}
})
}


document.addEventListener("DOMContentLoaded", function () {
initConfigs('config.json')
.catch(error => {
console.error('Error loading gateways:', error);
return initConfigs('config-example.json');
})
.then(it => initProcessOut());
})
</script>
</body>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "processout.js",
"version": "1.6.2",
"version": "1.6.3",
"description": "ProcessOut.js is a JavaScript library for ProcessOut's payment processing API.",
"scripts": {
"build:processout": "tsc -p src/processout && uglifyjs --compress --keep-fnames --ie8 dist/processout.js -o dist/processout.js",
Expand Down
66 changes: 33 additions & 33 deletions src/apm/elements/phone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module ProcessOut {
name: string,
}>
oninput?: FormFieldUpdate,
onblur?: (key: string, value: { dialing_code: string, value: string }) => void,
onblur?: (key: string, value: { dialing_code: string, number: string }) => void,
value?: { dialing_code: string, value: string },
}

Expand Down Expand Up @@ -46,7 +46,7 @@ module ProcessOut {

const parseCleanNumber = (currentValue: string, dialingCode: string, iso: string) => {
const phoneUtil = (window as any).libphonenumber.PhoneNumberUtil.getInstance();

try {
// Try to parse as international number first
const parsedNumber = phoneUtil.parseAndKeepRawInput(currentValue, iso);
Expand All @@ -61,16 +61,16 @@ module ProcessOut {
// Use StateManager for internal state management
const { state, setState } = useComponentState({
dialing_code: value && value.dialing_code || dialing_codes[0] && dialing_codes[0].value || '',
value: value && value.value || '',
number: value && value.value || '',
iso: ''
});

// Load libphonenumber and handle all state initialization in callback
ContextImpl.context.page.loadScript('libphonenumber', 'https://cdnjs.cloudflare.com/ajax/libs/google-libphonenumber/3.2.42/libphonenumber.min.js', () => {
let dialingCode = state.dialing_code || dialing_codes[0].value;
let phoneNumber = state.value || '';
let phoneNumber = state.number || '';
let iso = state.iso;

// Set ISO code using libphonenumber
if (!iso) {
const phoneUtil = (window as any).libphonenumber && (window as any).libphonenumber.PhoneNumberUtil && (window as any).libphonenumber.PhoneNumberUtil.getInstance();
Expand All @@ -88,7 +88,7 @@ module ProcessOut {
iso = dialing_codes.find(item => item.value === state.dialing_code) && dialing_codes.find(item => item.value === state.dialing_code).region_code || '';
}
}

// Update the UI if elements are available
if (phoneRef) {
phoneRef.value = getFullNumber(dialingCode, phoneNumber);
Expand All @@ -100,15 +100,15 @@ module ProcessOut {
if (dialingCodesRef) {
dialingCodesRef.value = iso;
}

// Trigger callback to update form state if there's a value
if (value) {
oninput && oninput(name, state, true);
}

setState({
dialing_code: dialingCode,
value: phoneNumber,
number: phoneNumber,
iso: iso
});
});
Expand All @@ -130,47 +130,47 @@ module ProcessOut {
const cursorPosition = input.selectionStart;

let dialingCode = state.dialing_code;
let phoneNumber = state.value;
let phoneNumber = state.number;
let iso = state.iso;

// Helper function to update state and UI when country is detected
const updateDetectedCountry = (detectedCountry, nationalNumber: string) => {
dialingCode = detectedCountry.dialingCode.value;
phoneNumber = nationalNumber;
iso = detectedCountry.region;

// Update the input with formatted value
const formattedValue = getFullNumber(dialingCode, phoneNumber);
input.value = formattedValue;

// Update flag image
const flagImg = input.parentElement.querySelector('img');
if (flagImg) {
flagImg.src = `https://flagcdn.com/w80/${iso.toLowerCase()}.jpg`;
flagImg.alt = `Selected ${detectedCountry.dialingCode.name} dialing code`;
}

// Update select value
if (dialingCodesRef) {
dialingCodesRef.value = iso;
}

if (label) {
updateFilledState(input);
}

// Trigger callback
oninput && oninput(name, {
dialing_code: dialingCode,
value: phoneNumber,
number: phoneNumber,
});

// Set cursor at end
input.setSelectionRange(formattedValue.length, formattedValue.length);

setState({
dialing_code: dialingCode,
value: phoneNumber,
number: phoneNumber,
iso: iso
});
};
Expand All @@ -187,12 +187,12 @@ module ProcessOut {
const parsedNumber = phoneUtil.parseAndKeepRawInput(valueWithoutCurrentPrefix, '');
const countryCode = parsedNumber.getCountryCode();
const nationalNumber = parsedNumber.getNationalNumber().toString();

// Find matching dialing code in our list
const matchingDialingCode = dialing_codes.find(code =>
const matchingDialingCode = dialing_codes.find(code =>
code.value === `+${countryCode}`
);

if (matchingDialingCode) {
const detectedCountry = {
dialingCode: matchingDialingCode,
Expand All @@ -219,7 +219,7 @@ module ProcessOut {

// Use libphonenumber to properly parse the number
const cleanNumber = parseCleanNumber(currentValue, dialingCode, iso);

const formattedValue = getFullNumber(dialingCode, cleanNumber);

let newCursorPosition = numberStartIndex;
Expand Down Expand Up @@ -247,22 +247,22 @@ module ProcessOut {
dialingCodesRef.showPicker()
setState({
dialing_code: dialingCode,
value: phoneNumber,
number: phoneNumber,
iso: iso
});
return
}

if (state.value !== cleanNumber) {
if (state.number !== cleanNumber) {
phoneNumber = cleanNumber;
oninput && oninput(name, {
dialing_code: dialingCode,
value: phoneNumber,
number: phoneNumber,
});
}
setState({
dialing_code: dialingCode,
value: phoneNumber,
number: phoneNumber,
iso: iso
});
input.setSelectionRange(newCursorPosition, newCursorPosition);
Expand Down Expand Up @@ -299,19 +299,19 @@ module ProcessOut {

const handleSelectChange = e => {
const currentValue = (e.target as HTMLSelectElement).value;
const cleanNumber = parseCleanNumber(getFullNumber(state.dialing_code, state.value), state.dialing_code, state.iso);
const cleanNumber = parseCleanNumber(getFullNumber(state.dialing_code, state.number), state.dialing_code, state.iso);

const newDialingCode = dialing_codes.find(item => item.region_code === currentValue).value;

setState({
dialing_code: newDialingCode,
iso: currentValue,
value: cleanNumber
number: cleanNumber
});

phoneRef.value = getFullNumber(newDialingCode, cleanNumber);
phoneRef.focus();
oninput && oninput(name, { dialing_code: newDialingCode, value: cleanNumber });
oninput && oninput(name, { dialing_code: newDialingCode, number: cleanNumber });

(e.target as HTMLSelectElement).parentElement.querySelector('img').src = `https://flagcdn.com/w80/${currentValue.toLowerCase()}.jpg`;
}
Expand Down Expand Up @@ -349,7 +349,7 @@ module ProcessOut {
const handleMouseDown = () => {
focusMethod = 'mouse';
}

if (!state.dialing_code) {
return null
}
Expand Down Expand Up @@ -382,7 +382,7 @@ module ProcessOut {
input({
type: "tel",
autocomplete: "tel",
value: getFullNumber(state.dialing_code, state.value),
value: getFullNumber(state.dialing_code, state.number),
inputMode: "tel",
name: `${name}.value`,
disabled,
Expand Down
10 changes: 5 additions & 5 deletions src/apm/views/utils/form.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module ProcessOut {
interface PhoneState {
dialing_code: string
value: string
number: string
}
export interface FormState {
touched: Record<string, boolean>
Expand Down Expand Up @@ -61,7 +61,7 @@ module ProcessOut {
}

let errors = { ...prevState.form.errors }

if (prevState.form.touched[key]) {
delete errors[key]
errors[key] = validateField(prevState, key, value)
Expand Down Expand Up @@ -169,7 +169,7 @@ module ProcessOut {
} else {
otpType = "text";
}

input = OTP({
name: field.key,
label: field.label,
Expand All @@ -192,7 +192,7 @@ module ProcessOut {
onblur: onBlur(setState),
errored: !!error,
disabled: state.loading,
value: value as PhoneState,
number: value as PhoneState,
});
break;
}
Expand Down Expand Up @@ -264,7 +264,7 @@ module ProcessOut {
className: 'group-boolean'
}
}

// Don't group other field types for now
return null
}
Expand Down
Loading