-
Notifications
You must be signed in to change notification settings - Fork 5
Add checkout page #536
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add checkout page #536
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 2ee4955
Add total section.
Vladyslav-Kuksiuk d5dd929
Fix layout.
Vladyslav-Kuksiuk 3a0759d
Add checkout fields.
Vladyslav-Kuksiuk d5c28a5
Improve fields.
Vladyslav-Kuksiuk 5227e5d
Move Checkout to nav hero.
Vladyslav-Kuksiuk c2b4e9f
Add product description.
Vladyslav-Kuksiuk 6361d19
Improve naming.
Vladyslav-Kuksiuk 67cb291
Add validation.
Vladyslav-Kuksiuk bcb6a34
Update API handling.
Vladyslav-Kuksiuk 6fa2cb5
Improve checkout page.
Vladyslav-Kuksiuk 5de1116
Add 'Thank you page'.
Vladyslav-Kuksiuk 50f5028
Improve 'Thank you page' layout.
Vladyslav-Kuksiuk 40ce1c2
Improve page-404.
Vladyslav-Kuksiuk 45fd752
Extract submodels.
Vladyslav-Kuksiuk 7aa1c27
Fix phone country changing.
Vladyslav-Kuksiuk 110d669
Improve layout.
Vladyslav-Kuksiuk b870307
Improve readability.
Vladyslav-Kuksiuk 5900f5c
Replace `jqXhr` by `fetch`.
Vladyslav-Kuksiuk 7b15f9d
Shorten modal window.
Vladyslav-Kuksiuk c7f54e8
Improve documentation.
Vladyslav-Kuksiuk 72be807
Add 'Product not found' view.
Vladyslav-Kuksiuk 1496c0c
Add loading state.
Vladyslav-Kuksiuk c9afe05
Disable `Continue` button before charges calculated.
Vladyslav-Kuksiuk aab2c1e
Improve requests sending.
Vladyslav-Kuksiuk e5ad075
Remove checkout page from sitemap.
Vladyslav-Kuksiuk 2fd2ddf
Improve error showing.
Vladyslav-Kuksiuk f99d9b1
Improve pages layout.
Vladyslav-Kuksiuk 6509c41
Improve oops page view.
Vladyslav-Kuksiuk c6cecbd
Divide `chekout-pages.js` on logical blocks.
Vladyslav-Kuksiuk 3d734fd
Remove `helpers.js`.
Vladyslav-Kuksiuk 3f5316f
Improve docs.
Vladyslav-Kuksiuk 5fe8941
Improve doc style.
Vladyslav-Kuksiuk e279094
Improve `charge-controller` readability.
Vladyslav-Kuksiuk afebc8a
Revert `nav-hero` changes.
Vladyslav-Kuksiuk ce0dea0
Improve `Ooops` page layout.
Vladyslav-Kuksiuk 05febd9
Improve checkout page style.
Vladyslav-Kuksiuk 4662733
Add copyrights.
Vladyslav-Kuksiuk a2f77c4
Add copyright for JS files.
Vladyslav-Kuksiuk 5f84738
Improve checkout layout.
Vladyslav-Kuksiuk b1c2b5f
Fix navbar shadow.
Vladyslav-Kuksiuk 12d1c67
Update font sizes.
Vladyslav-Kuksiuk 06249d6
Revert navbar changes.
Vladyslav-Kuksiuk f638047
Improve readability.
Vladyslav-Kuksiuk 076054f
Use root pathes.
Vladyslav-Kuksiuk 45c07b1
Update hugo param names.
Vladyslav-Kuksiuk 723d169
Extract dicts.
Vladyslav-Kuksiuk fff9bb6
Apply new Paygate API.
Vladyslav-Kuksiuk 17b4536
Make address required.
Vladyslav-Kuksiuk b3f9d06
Improve documentation.
Vladyslav-Kuksiuk accd762
Apply new error handling.
Vladyslav-Kuksiuk 1465d27
Remove `normalizeServerUrl`.
Vladyslav-Kuksiuk 507904b
Simplify phone field usage.
Vladyslav-Kuksiuk 6e689a3
Remove redundant `204` handling.
Vladyslav-Kuksiuk a6b80d4
Improve doc.
Vladyslav-Kuksiuk 4b5550f
Extract `delayed-request-controller`.
Vladyslav-Kuksiuk 56e631b
Merge `charge-request` and `charge-controller`.
Vladyslav-Kuksiuk f8c67e7
Update hugo params.
Vladyslav-Kuksiuk 1377fdd
Add doc.
Vladyslav-Kuksiuk ff54efc
Add loading and success state to VAT ID field.
Vladyslav-Kuksiuk 90d26f9
Fix data disappearing after back.
Vladyslav-Kuksiuk 49a0cde
Reorder fields.
Vladyslav-Kuksiuk 1bed7d5
Improve country chevrone.
Vladyslav-Kuksiuk fd53284
Improve phone country chevrone.
Vladyslav-Kuksiuk f34aa5e
Remove redundant CSS.
Vladyslav-Kuksiuk 062c48a
Remove redundant classes.
Vladyslav-Kuksiuk 82eeff3
Improve styles.
Vladyslav-Kuksiuk 4ab6bd3
Improve docs.
Vladyslav-Kuksiuk b667591
Improve `result-pane` button styles.
Vladyslav-Kuksiuk 686cd24
Add consent checkboxes.
Vladyslav-Kuksiuk 6f5826e
Improve thin layout.
Vladyslav-Kuksiuk 2530e1e
Align the layout
JuliaEvseeva 2016eed
Improve input offsets
JuliaEvseeva 3ce85e8
Improve the layout on mobiles
JuliaEvseeva 4911c97
Remove consent checkboxes.
Vladyslav-Kuksiuk 9c5d532
Rearrange fields.
Vladyslav-Kuksiuk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) { | ||
| 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 | ||
| }; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.