Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
e43405b
Add empty page.
Vladyslav-Kuksiuk Apr 17, 2026
2ee4955
Add total section.
Vladyslav-Kuksiuk Apr 17, 2026
d5dd929
Fix layout.
Vladyslav-Kuksiuk Apr 17, 2026
3a0759d
Add checkout fields.
Vladyslav-Kuksiuk Apr 17, 2026
d5c28a5
Improve fields.
Vladyslav-Kuksiuk Apr 17, 2026
5227e5d
Move Checkout to nav hero.
Vladyslav-Kuksiuk Apr 17, 2026
c2b4e9f
Add product description.
Vladyslav-Kuksiuk Apr 20, 2026
6361d19
Improve naming.
Vladyslav-Kuksiuk Apr 20, 2026
67cb291
Add validation.
Vladyslav-Kuksiuk Apr 20, 2026
bcb6a34
Update API handling.
Vladyslav-Kuksiuk Apr 22, 2026
6fa2cb5
Improve checkout page.
Vladyslav-Kuksiuk Apr 22, 2026
5de1116
Add 'Thank you page'.
Vladyslav-Kuksiuk Apr 22, 2026
50f5028
Improve 'Thank you page' layout.
Vladyslav-Kuksiuk Apr 22, 2026
40ce1c2
Improve page-404.
Vladyslav-Kuksiuk Apr 22, 2026
45fd752
Extract submodels.
Vladyslav-Kuksiuk Apr 22, 2026
7aa1c27
Fix phone country changing.
Vladyslav-Kuksiuk Apr 22, 2026
110d669
Improve layout.
Vladyslav-Kuksiuk Apr 22, 2026
b870307
Improve readability.
Vladyslav-Kuksiuk Apr 22, 2026
5900f5c
Replace `jqXhr` by `fetch`.
Vladyslav-Kuksiuk Apr 22, 2026
7b15f9d
Shorten modal window.
Vladyslav-Kuksiuk Apr 22, 2026
c7f54e8
Improve documentation.
Vladyslav-Kuksiuk Apr 22, 2026
72be807
Add 'Product not found' view.
Vladyslav-Kuksiuk Apr 23, 2026
1496c0c
Add loading state.
Vladyslav-Kuksiuk Apr 23, 2026
c9afe05
Disable `Continue` button before charges calculated.
Vladyslav-Kuksiuk Apr 23, 2026
aab2c1e
Improve requests sending.
Vladyslav-Kuksiuk Apr 23, 2026
e5ad075
Remove checkout page from sitemap.
Vladyslav-Kuksiuk Apr 23, 2026
2fd2ddf
Improve error showing.
Vladyslav-Kuksiuk Apr 23, 2026
f99d9b1
Improve pages layout.
Vladyslav-Kuksiuk Apr 23, 2026
6509c41
Improve oops page view.
Vladyslav-Kuksiuk Apr 23, 2026
c6cecbd
Divide `chekout-pages.js` on logical blocks.
Vladyslav-Kuksiuk Apr 23, 2026
3d734fd
Remove `helpers.js`.
Vladyslav-Kuksiuk Apr 23, 2026
3f5316f
Improve docs.
Vladyslav-Kuksiuk Apr 23, 2026
5fe8941
Improve doc style.
Vladyslav-Kuksiuk Apr 23, 2026
e279094
Improve `charge-controller` readability.
Vladyslav-Kuksiuk Apr 23, 2026
afebc8a
Revert `nav-hero` changes.
Vladyslav-Kuksiuk Apr 23, 2026
ce0dea0
Improve `Ooops` page layout.
Vladyslav-Kuksiuk Apr 27, 2026
05febd9
Improve checkout page style.
Vladyslav-Kuksiuk Apr 27, 2026
4662733
Add copyrights.
Vladyslav-Kuksiuk Apr 27, 2026
a2f77c4
Add copyright for JS files.
Vladyslav-Kuksiuk Apr 27, 2026
5f84738
Improve checkout layout.
Vladyslav-Kuksiuk Apr 27, 2026
b1c2b5f
Fix navbar shadow.
Vladyslav-Kuksiuk Apr 27, 2026
12d1c67
Update font sizes.
Vladyslav-Kuksiuk Apr 27, 2026
06249d6
Revert navbar changes.
Vladyslav-Kuksiuk Apr 27, 2026
f638047
Improve readability.
Vladyslav-Kuksiuk Apr 27, 2026
076054f
Use root pathes.
Vladyslav-Kuksiuk Apr 27, 2026
45c07b1
Update hugo param names.
Vladyslav-Kuksiuk Apr 27, 2026
723d169
Extract dicts.
Vladyslav-Kuksiuk Apr 27, 2026
fff9bb6
Apply new Paygate API.
Vladyslav-Kuksiuk Apr 28, 2026
17b4536
Make address required.
Vladyslav-Kuksiuk Apr 29, 2026
b3f9d06
Improve documentation.
Vladyslav-Kuksiuk Apr 29, 2026
accd762
Apply new error handling.
Vladyslav-Kuksiuk Apr 29, 2026
1465d27
Remove `normalizeServerUrl`.
Vladyslav-Kuksiuk Apr 29, 2026
507904b
Simplify phone field usage.
Vladyslav-Kuksiuk Apr 29, 2026
6e689a3
Remove redundant `204` handling.
Vladyslav-Kuksiuk Apr 29, 2026
a6b80d4
Improve doc.
Vladyslav-Kuksiuk Apr 29, 2026
4b5550f
Extract `delayed-request-controller`.
Vladyslav-Kuksiuk Apr 29, 2026
56e631b
Merge `charge-request` and `charge-controller`.
Vladyslav-Kuksiuk Apr 29, 2026
f8c67e7
Update hugo params.
Vladyslav-Kuksiuk Apr 29, 2026
1377fdd
Add doc.
Vladyslav-Kuksiuk Apr 29, 2026
ff54efc
Add loading and success state to VAT ID field.
Vladyslav-Kuksiuk May 4, 2026
90d26f9
Fix data disappearing after back.
Vladyslav-Kuksiuk May 4, 2026
49a0cde
Reorder fields.
Vladyslav-Kuksiuk May 4, 2026
1bed7d5
Improve country chevrone.
Vladyslav-Kuksiuk May 4, 2026
fd53284
Improve phone country chevrone.
Vladyslav-Kuksiuk May 4, 2026
f34aa5e
Remove redundant CSS.
Vladyslav-Kuksiuk May 4, 2026
062c48a
Remove redundant classes.
Vladyslav-Kuksiuk May 4, 2026
82eeff3
Improve styles.
Vladyslav-Kuksiuk May 4, 2026
4ab6bd3
Improve docs.
Vladyslav-Kuksiuk May 4, 2026
b667591
Improve `result-pane` button styles.
Vladyslav-Kuksiuk May 4, 2026
686cd24
Add consent checkboxes.
Vladyslav-Kuksiuk May 4, 2026
6f5826e
Improve thin layout.
Vladyslav-Kuksiuk May 4, 2026
2530e1e
Align the layout
JuliaEvseeva May 4, 2026
2016eed
Improve input offsets
JuliaEvseeva May 4, 2026
3ce85e8
Improve the layout on mobiles
JuliaEvseeva May 4, 2026
4911c97
Remove consent checkboxes.
Vladyslav-Kuksiuk May 5, 2026
9c5d532
Rearrange fields.
Vladyslav-Kuksiuk May 5, 2026
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
66 changes: 66 additions & 0 deletions site/assets/js/modules/forms/phone-number.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2026, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
* disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

'use strict';

/**
* Removes characters that are not accepted by the phone-number field.
*
* <p>Allowed: digits, parentheses, hyphens, and spaces.
*
* @param {string} value phone-number value to sanitize
* @return {string} sanitized phone-number value
*/
export function sanitizePhoneNumberInput(value) {
Comment thread
Vladyslav-Kuksiuk marked this conversation as resolved.
return String(value || '').replace(/[^0-9\s()-]/g, '');
}

/**
* Builds the phone-number payload with country code and number with digits only.
*
* @param {string} rawCountryCode phone country code
* @param {string} rawNumber local phone number
* @return {{countryCode: number, number: string}|null}
* normalized phone-number payload, or null when incomplete
*/
export function normalizePhoneNumber(rawCountryCode, rawNumber) {
const countryCode = String(rawCountryCode || '').replace(/\D/g, '');
const number = String(rawNumber || '').replace(/\D/g, '');

if (!countryCode || !number) {
return null;
}

const numericCountryCode = Number(countryCode);
if (!Number.isInteger(numericCountryCode) || numericCountryCode <= 0) {
return null;
}

return {
countryCode: numericCountryCode,
number
};
}
266 changes: 266 additions & 0 deletions site/assets/js/modules/paygate/purchases.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
/*
* Copyright 2026, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
* disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

'use strict';

/**
* Paygate currency payload returned inside money values.
*
* @typedef {Object} PaygateCurrency
* @property {string} code ISO currency code
* @property {string} symbol currency symbol
*/

/**
* Paygate money payload.
*
* @typedef {Object} PaygateMoney
* @property {number|string} value decimal amount value
* @property {PaygateCurrency} currency currency metadata
*/

/**
* Paygate product data returned by the product endpoint.
*
* @typedef {Object} PaygateProduct
* @property {string} id product identifier
* @property {string} name product display name
* @property {string} description product description shown on checkout
* @property {PaygateMoney} netAmount product price before VAT
*/

/**
* Paygate response for the `place-order` endpoint.
*
* @typedef {Object} PlaceOrderResponse
* @property {string} orderId created paygate order ID
*/

/**
* Paygate request payload for the `calculate-charges` endpoint.
*
* @typedef {Object} CalculateChargesRequest
* @property {string} orderId paygate order ID
* @property {string} buyerCountryCode iso billing country code
* @property {string} vatId buyer VAT ID
*/

/**
* Paygate response for the `calculate-charges` endpoint.
*
* @typedef {Object} CalculateChargesResponse
* @property {PaygateMoney} netAmount price before VAT
* @property {number|string} vatRate vat rate as a decimal fraction
* @property {PaygateMoney} vatAmount VAT amount for the order
* @property {PaygateMoney} totalAmount total price including VAT
*/

/**
* Billing address submitted to Paygate.
*
* @typedef {Object} BillingAddress
* @property {string} countryCode iso billing country code
* @property {string} city billing city
* @property {string} street combined street address
* @property {string} postalCode billing postal code
*/

/**
* Company billing details submitted to Paygate.
*
* @typedef {Object} BillingCompany
* @property {string} name company legal or display name
* @property {string} vatId company VAT ID
*/

/**
* Phone number submitted to Paygate.
*
* @typedef {Object} PhoneNumber
* @property {number} countryCode dialing country code without a leading plus sign
* @property {string} number local phone number digits
*/

/**
* Billing information submitted to Paygate before redirecting to payment.
*
* @typedef {Object} BillingInfo
* @property {string} name full buyer name or company fallback name
* @property {string} email buyer email address
* @property {BillingAddress} address billing address details
* @property {BillingCompany} company company billing details
* @property {PhoneNumber} [phoneNumber] optional normalized phone number
*/

/**
* Paygate request payload for the `submit-billing-info` endpoint.
*
* @typedef {Object} SubmitBillingInfoRequest
* @property {string} orderId paygate order ID
* @property {BillingInfo} billingInfo billing details for the order
*/

/**
* Paygate response for the billing-info submission step.
*
* @typedef {Object} SubmitBillingInfoResponse
* @property {string} paymentLink hosted payment redirect URL
*/

/**
* Generic Paygate error response used for 400, 404, and 500 responses.
*
* @typedef {Object} PaygateErrorResponse
* @property {string} message human-readable error message
*/

/**
* Charge-calculation validation error response returned with status 422.
*
* @typedef {Object} CalculateChargesFailureResponse
* @property {string|null} vatIdInvalid VAT validation failure reason
*/

/**
* Error object thrown when a Paygate request fails.
*
* @typedef {Object} PurchaseApiError
* @property {number} status http response status code
* @property {string} statusText http response status text
* @property {PaygateErrorResponse|CalculateChargesFailureResponse|string|null} body
* parsed response body, if any
*/

/**
* Paygate purchase endpoint methods used by checkout.
*
* @typedef {Object} PaygatePurchaseClient
* @property {function(string): Promise<PaygateProduct>} getProduct
* loads checkout product data by product ID
* @property {function(string): Promise<PlaceOrderResponse>} placeOrder
* creates a checkout order for the given product ID
* @property {function(CalculateChargesRequest): Promise<CalculateChargesResponse>} calculateCharges
* calculates VAT and totals for the current order
* @property {function(SubmitBillingInfoRequest): Promise<SubmitBillingInfoResponse>} submitBillingInfo
* sends billing details and returns payment redirect data
*/

/**
* Creates a client for Paygate purchase endpoints.
*
* @param {string} serverUrl base URL of the Paygate API server
* @return {PaygatePurchaseClient} paygate purchase endpoint methods
*/
export function createPurchaseClient(serverUrl) {
return {
getProduct(productId) {
return getJson(`${serverUrl}/products/${encodeURIComponent(productId)}`);
},
placeOrder(productId) {
return postJson(`${serverUrl}/purchases/place-order`, {productId});
},
calculateCharges(payload) {
return postJson(`${serverUrl}/purchases/calculate-charges`, payload);
},
submitBillingInfo(payload) {
return postJson(`${serverUrl}/purchases/provide-billing-details`, payload);
}
};
}

/**
* Sends a JSON GET request and returns the parsed response body.
*
* Body will be parsed as JSON or plain text if possible, otherwise `null` is returned.
*
* @param {string} url endpoint URL
* @return {Promise<*>} parsed response body when the request succeeds
*
* @throws {PurchaseApiError} if response status is not OK
*/
async function getJson(url) {
const response = await fetch(url);
const body = await readResponseBody(response);

if (!response.ok) {
throw ({
status: response.status,
statusText: response.statusText,
body
});
}

return body;
}

/**
* Sends a JSON POST request and returns the parsed response body.
*
* Body will be parsed as JSON or plain text if possible, otherwise `null` is returned.
*
* @param {string} url endpoint URL
* @param {Object} payload json request body
* @return {Promise<*>} parsed response body when the request succeeds
*
* @throws {PurchaseApiError} if response status is not OK
*/
async function postJson(url, payload) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
const body = await readResponseBody(response);

if (!response.ok) {
throw ({
status: response.status,
statusText: response.statusText,
body
});
}

return body;
}

/**
* Parses a fetch response body as JSON when possible, otherwise as text.
*
* @param {Response} response fetch response to parse
* @return {Promise<*>} parsed JSON, text, or null when there is no readable body
*/
async function readResponseBody(response) {
const contentType = response.headers.get('content-type') || '';
try {
return contentType.includes('application/json')
? await response.json()
: await response.text();
} catch (ignored) {
return null;
}
}
Loading
Loading