Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
v:2.0:customer_portal [2023/02/17 09:32] – [Beta v4 Instructions] adam | v:2.0:customer_portal [2025/06/25 15:23] (current) – [Incorporating the Customer Portal SSO with an Existing SSO Endpoint] adam | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== | + | ====== Customer Portal |
+ | |||
+ | ===== Setup Instructions ===== | ||
+ | |||
+ | |||
+ | ==== Enabling the portal ==== | ||
+ | |||
+ | The customer portal can be enabled from the Foxy admin at [[https:// | ||
+ | |||
+ | You need to specify an allowlist of domains where the portal is able to be utilised. Note that all domains have to begin with '' | ||
+ | |||
+ | You can also configure additional options for the portal: | ||
+ | |||
+ | * SSO (single sign-on) - enable this option if you want to allow customers who are logged in to the portal to be automatically logged in to your website (requires some additional configuration, | ||
+ | * Customer registration - enable this option to allow customers to create a new account directly on the login form, as opposed to completing a purchase through the checkout | ||
+ | * Frequency changes for subscriptions - enabling this option allows customer to changes the frequency of their subscriptions. You can define rules for what options a customer can change a subscription too, depending what subscription it is. | ||
+ | * Next payment date changes for subscriptions - if enabled, customers will be able to modify the next date of their subscription. This can be limited based on rules to restrict how far or to what days a customer can change the next date to. | ||
+ | * Session lifespan - allows you to define how long a customer can remain logged in to the portal for before being automatically logged out | ||
+ | * JWT shared secret - this secret is used to sign the tokens returned as part of authenticating with the portal. Changing this secret will invalidate any active authenticated sessions on the customer portal. Unless you have specific needs here, you can let this value be set automatically. | ||
+ | |||
+ | ==== Setup Instructions ==== | ||
+ | |||
+ | You can [[http:// | ||
+ | |||
+ | A default install will look like this, noting that you need to update the '' | ||
+ | |||
+ | <code html> | ||
+ | < | ||
+ | </ | ||
+ | |||
+ | <script type=" | ||
+ | import ' | ||
+ | |||
+ | const I18nElement = customElements.get(' | ||
+ | const i18nBase = ' | ||
+ | I18nElement.onResourceFetch((ns, | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | The CDN url's for the customer portal is set to the stable release and will update according to the '' | ||
+ | |||
+ | <WRAP center round info 95%> | ||
+ | **If you are customising your instance of the customer portal such as adding custom elements or translations**, | ||
+ | |||
+ | To do that, you would specify the specific release instead of '' | ||
+ | </ | ||
+ | |||
+ | If you //really// want to, it's also possible to use third-party CDN's, such as jsdeliver, though we recommend our own unless you have a compelling reason: | ||
+ | |||
+ | == jsDeliver == | ||
+ | < | ||
+ | https:// | ||
+ | https:// | ||
+ | </ | ||
+ | |||
+ | |||
+ | ==== Styling ==== | ||
+ | If you want to customise the styles of your portal to better match your branding, you can do that by including a style block after the '' | ||
+ | |||
+ | The output from the lumo editor will look like this: | ||
+ | |||
+ | <code html> | ||
+ | < | ||
+ | < | ||
+ | html { | ||
+ | --lumo-border-radius: | ||
+ | --lumo-shade-5pct: | ||
+ | --lumo-shade-10pct: | ||
+ | --lumo-shade-20pct: | ||
+ | --lumo-shade-30pct: | ||
+ | --lumo-shade-40pct: | ||
+ | --lumo-shade-50pct: | ||
+ | --lumo-shade-60pct: | ||
+ | --lumo-shade-70pct: | ||
+ | --lumo-shade-80pct: | ||
+ | --lumo-shade-90pct: | ||
+ | --lumo-shade: | ||
+ | --lumo-primary-text-color: | ||
+ | --lumo-primary-color-50pct: | ||
+ | --lumo-primary-color-10pct: | ||
+ | --lumo-primary-color: | ||
+ | --lumo-body-text-color: | ||
+ | --lumo-secondary-text-color: | ||
+ | --lumo-tertiary-text-color: | ||
+ | --lumo-disabled-text-color: | ||
+ | --lumo-header-text-color: | ||
+ | } | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Note that dark mode is not currently supported by the customer portal, and if you use the " | ||
+ | |||
+ | If you want to include the styles in the '' | ||
+ | |||
+ | ==== Languages ==== | ||
+ | |||
+ | Languages are handled separately for the customer portal than for the cart/ | ||
+ | |||
+ | <code html> | ||
+ | < | ||
+ | </ | ||
+ | |||
+ | === Customising language strings === | ||
+ | |||
+ | If you need to adjust some individual language strings within your portal installation beyond our default strings, you can do that with some customisations to your portal page. There are two changes that need to be made, one to the HTML element, and then adding some additional javascript to define your customisations. | ||
+ | |||
+ | To the '' | ||
+ | |||
+ | <code html> | ||
+ | < | ||
+ | </ | ||
+ | |||
+ | Then in your javascript, you define lines for each language string you want to customise, specifically after this line in the existing javascript from the installation instructions above: | ||
+ | |||
+ | <code javascript> | ||
+ | I18nElement.onResourceFetch((ns, | ||
+ | </ | ||
+ | |||
+ | The javascript line to customise a language string takes the following format: | ||
+ | |||
+ | <code javascript> | ||
+ | I18nElement.i18next.addResource(' | ||
+ | </ | ||
+ | |||
+ | - The first argument ('' | ||
+ | - The second argument ('' | ||
+ | - The third argument ('' | ||
+ | - The fourth argument ('' | ||
+ | |||
+ | To find the string identifier, you can [[https:// | ||
+ | |||
+ | For example, let's replace the "Get temporary password" | ||
+ | |||
+ | <code json> | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | That would make the string identifier as '' | ||
+ | |||
+ | <code javascript> | ||
+ | I18nElement.i18next.addResource(' | ||
+ | </ | ||
+ | |||
+ | Note that some language strings are nested in multiple objects, so ensure you include each nested key to reach the desired language string in the identifier you use. As an example, the " | ||
+ | |||
+ | You can include as many language customisations as you need, but each different string is a distinct line of javascript. | ||
+ | |||
+ | ==== Advanced customisation ==== | ||
+ | |||
+ | You can see additional demos and examples at [[https:// | ||
+ | |||
+ | ===== Single Sign-On with the Customer Portal Session ===== | ||
+ | |||
+ | <WRAP info> | ||
+ | |||
+ | <WRAP important> | ||
+ | |||
+ | ==== Standalone SSO with the Portal ==== | ||
+ | |||
+ | <WRAP tip>This section is for if you're not using SSO elsewhere.</ | ||
+ | |||
+ | To ensure your customers don't need to login to the checkout after they' | ||
+ | |||
+ | When you do a '' | ||
+ | |||
+ | - Login to the Foxy administration for your store, and go to the " | ||
+ | - Create a SSO URL for a guest (ie. '' | ||
+ | - Create a new page on your website (at the same domain as the page that contains the ''< | ||
+ | < | ||
+ | <html class=" | ||
+ | < | ||
+ | <meta charset=" | ||
+ | < | ||
+ | <meta name=" | ||
+ | < | ||
+ | function parseJWT(token) { | ||
+ | let base64Url = token.split(' | ||
+ | let base64 = base64Url.replace(/ | ||
+ | let jsonPayload = decodeURIComponent(atob(base64).split('' | ||
+ | return ' | ||
+ | }).join('' | ||
+ | |||
+ | return JSON.parse(jsonPayload); | ||
+ | } | ||
+ | function getSSOUrl() { | ||
+ | // Look for a cookie | ||
+ | var value = "; " + document.cookie; | ||
+ | var parts = value.split("; | ||
+ | if (parts.length == 2) return decodeURIComponent(parts.pop().split(";" | ||
+ | |||
+ | // Look for localstorage | ||
+ | try { | ||
+ | let sessionStore = JSON.parse(localStorage.getItem(" | ||
+ | if (sessionStore && sessionStore.hasOwnProperty(" | ||
+ | let sessionData = parseJWT(sessionStore.jwt); | ||
+ | let expires = Math.floor(new Date(sessionStore.date_created).getTime() / 1000) + sessionStore.expires_in; | ||
+ | if ( | ||
+ | expires > Math.floor(Date.now() / 1000) && | ||
+ | sessionData.hasOwnProperty(" | ||
+ | sessionStore.hasOwnProperty(" | ||
+ | ) { | ||
+ | return sessionStore.sso; | ||
+ | } | ||
+ | } | ||
+ | } catch (error) { | ||
+ | console.log(" | ||
+ | } | ||
+ | |||
+ | return false; | ||
+ | } | ||
+ | |||
+ | function getUrlParameter(name) { | ||
+ | return new URLSearchParams(location.search).get(' | ||
+ | } | ||
+ | |||
+ | if (getSSOUrl()) { | ||
+ | window.location.replace(getSSOUrl() + "& | ||
+ | } else { | ||
+ | window.location.replace(" | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | - Go to the " | ||
+ | - Test this by logging into the portal, then proceeding to checkout. You should be logged into the checkout. | ||
+ | |||
+ | ==== Incorporating the Customer Portal SSO with an Existing SSO Endpoint ==== | ||
+ | |||
+ | If you've already got SSO enabled and configured on your Foxy store, you'll need to handle things separately. We can't give step-by-step instructions, | ||
+ | |||
+ | * **Create the customer portal session + cookie when a customer logs in.** So when a customer enters their username and password, you can make a quick '' | ||
+ | * **If you don't have access to the customer' | ||
+ | * Unlike the checkout, you likely would not want to allow login to the portal if the customer' | ||
+ | |||
+ | |||
+ | |||
+ | ===== Some Ideas and Best Practices ===== | ||
+ | |||
+ | ==== Loyalty Points ==== | ||
+ | |||
+ | An often-requested feature (that we will likely add as a native feature, but it's not available yet) is the idea of " | ||
+ | |||
+ | - Create custom code on your end that processes the Foxy webhook. According to your own logic, calculate the number of points that a transaction should generate. For instance, the product total less coupons. (So points would only be generated for the amount paid, excluding shipping and tax.) | ||
+ | - Check the '' | ||
+ | - Retrieve the [[https:// | ||
+ | |||
+ | That gets you keeping track of points, and the '' | ||
+ | |||
+ | To allow customers to redeem their points, you'd need to add some logic to your portal and your backend custom code. It'd look like this, roughly: | ||
+ | |||
+ | - Customer clicks the button to redeem their points. Fires an AJAX request to your endpoint. (Use the JWT returned in the '' | ||
+ | - Your backend retrieves the customer' | ||
+ | - If everything looks good, your backend creates a [[https:// | ||
+ | - Your portal javascript receives the coupon code, and applies it to the customer' | ||
+ | |||
+ | This is obviously a bit of work, and requires some more advanced programming knowledge. But hopefully it helps with some ideas of how you can use the Foxy backend API, paired with the front-end javascript portal API, to create more advanced functionality. | ||
+ | |||
+ | ===== Building your own Customer Portal (Advanced) | ||
<WRAP info> | <WRAP info> | ||
- | //**OUR BETA IS OPEN!**// | + | Note that using the new '' |
- | If you' | + | |
</ | </ | ||
Line 484: | Line 759: | ||
* The URI is returned in the '' | * The URI is returned in the '' | ||
* Response: The response will be an object of the subscription. | * Response: The response will be an object of the subscription. | ||
- | |||
- | |||
- | |||
- | ===== Single Sign-On with the Customer Portal Session ===== | ||
- | |||
- | <WRAP info> | ||
- | |||
- | <WRAP important> | ||
- | |||
- | ==== Standalone SSO with the Portal ==== | ||
- | |||
- | <WRAP tip>This section is for if you're not using SSO elsewhere.</ | ||
- | |||
- | To ensure your customers don't need to login to the checkout after they' | ||
- | |||
- | When you do a '' | ||
- | |||
- | - Login to the Foxy administration for your store, and go to the " | ||
- | - Create a SSO URL for a guest (ie. '' | ||
- | - Create a new page on your website (at the same domain as the page that contains the ''< | ||
- | < | ||
- | <html class=" | ||
- | < | ||
- | <meta charset=" | ||
- | < | ||
- | <meta name=" | ||
- | < | ||
- | function parseJWT(token) { | ||
- | let base64Url = token.split(' | ||
- | let base64 = base64Url.replace(/ | ||
- | let jsonPayload = decodeURIComponent(atob(base64).split('' | ||
- | return ' | ||
- | }).join('' | ||
- | |||
- | return JSON.parse(jsonPayload); | ||
- | } | ||
- | function getSSOUrl() { | ||
- | // Look for a cookie | ||
- | var value = "; " + document.cookie; | ||
- | var parts = value.split("; | ||
- | if (parts.length == 2) return decodeURIComponent(parts.pop().split(";" | ||
- | |||
- | // Look for localstorage | ||
- | try { | ||
- | let sessionStore = JSON.parse(localStorage.getItem(' | ||
- | if (sessionStore && sessionStore.hasOwnProperty(' | ||
- | let sessionData = parseJWT(sessionStore.jwt); | ||
- | |||
- | if (sessionData.exp > (Date.now() / 1000) && sessionData.hasOwnProperty(' | ||
- | return sessionStore.sso; | ||
- | } | ||
- | } | ||
- | } catch (error) { | ||
- | console.log(" | ||
- | } | ||
- | |||
- | return false; | ||
- | } | ||
- | |||
- | function getUrlParameter(name) { | ||
- | return new URLSearchParams(location.search).get(' | ||
- | } | ||
- | |||
- | if (getSSOUrl()) { | ||
- | window.location.replace(getSSOUrl() + "& | ||
- | } else { | ||
- | window.location.replace(" | ||
- | } | ||
- | |||
- | </ | ||
- | </ | ||
- | < | ||
- | </ | ||
- | </ | ||
- | - Go to the " | ||
- | - Test this by logging into the portal, then proceeding to checkout. You should be logged into the checkout. | ||
- | |||
- | ==== Incorporating the Customer Portal SSO with an Existing SSO Endpoint ==== | ||
- | |||
- | If you've already got SSO enabled and configured on your Foxy store, you'll need to handle things separately. We can't give step-by-step instructions, | ||
- | |||
- | * **Create the customer portal session + cookie when a customer logs in.** So when a customer enters their username and password, you can make a quick '' | ||
- | * **If you don't have access to the customer' | ||
- | * Unlike the checkout, you likely would not want to allow login to the portal if the customer' | ||
- | |||
- | ===== BETA Setup Instructions ===== | ||
- | <WRAP center round info 90%> | ||
- | Note that the customer portal is currently in public beta, and **we have 2 different versions**. Use the '' | ||
- | </ | ||
- | |||
- | |||
- | Also note that our portal functionality requires modern browsers. This excludes IE11 and non-Chromium Edge. | ||
- | |||
- | ==== Beta v4 Instructions ==== | ||
- | |||
- | Our v4 beta moves the customer portal to our official "Foxy Elements" | ||
- | |||
- | <code html> | ||
- | < | ||
- | </ | ||
- | |||
- | <script type=" | ||
- | import ' | ||
- | |||
- | const I18nElement = customElements.get(' | ||
- | const i18nBase = ' | ||
- | I18nElement.onResourceFetch((ns, | ||
- | </ | ||
- | </ | ||
- | |||
- | The CDN url's for the customer portal is set to the stable release and will update according to the '' | ||
- | |||
- | It's also possible to use third-party CDN's, such as jsdeliver and unpkg, if desired: | ||
- | |||
- | == jsDeliver == | ||
- | < | ||
- | https:// | ||
- | https:// | ||
- | </ | ||
- | |||
- | == unpkg == | ||
- | < | ||
- | https:// | ||
- | https:// | ||
- | </ | ||
- | |||
- | === Styling === | ||
- | If you want to customise the styles of your portal to better match your branding, you can do that by including a style block after the '' | ||
- | |||
- | The output from the lumo editor will look like this: | ||
- | |||
- | <code html> | ||
- | < | ||
- | < | ||
- | html { | ||
- | --lumo-border-radius: | ||
- | --lumo-shade-5pct: | ||
- | --lumo-shade-10pct: | ||
- | --lumo-shade-20pct: | ||
- | --lumo-shade-30pct: | ||
- | --lumo-shade-40pct: | ||
- | --lumo-shade-50pct: | ||
- | --lumo-shade-60pct: | ||
- | --lumo-shade-70pct: | ||
- | --lumo-shade-80pct: | ||
- | --lumo-shade-90pct: | ||
- | --lumo-shade: | ||
- | --lumo-primary-text-color: | ||
- | --lumo-primary-color-50pct: | ||
- | --lumo-primary-color-10pct: | ||
- | --lumo-primary-color: | ||
- | --lumo-body-text-color: | ||
- | --lumo-secondary-text-color: | ||
- | --lumo-tertiary-text-color: | ||
- | --lumo-disabled-text-color: | ||
- | --lumo-header-text-color: | ||
- | } | ||
- | </ | ||
- | </ | ||
- | </ | ||
- | |||
- | Note that dark mode is not currently supported by the customer portal, and if you use the " | ||
- | |||
- | If you want to include the styles in the '' | ||
- | |||
- | ==== Beta v3 Instructions ==== | ||
- | |||
- | Insert the following code in your page ('' | ||
- | |||
- | <code html> | ||
- | <!-- The following 2 lines are required. --> | ||
- | <script type=" | ||
- | <script nomodule src=" | ||
- | <!-- The following style block is optional. You can customize your styles at https:// | ||
- | Simply change all the `--lumo-` prefixes to `--foxy-`, and insert them below. --> | ||
- | < | ||
- | html { | ||
- | --foxy-border-radius: | ||
- | --foxy-shade-5pct: | ||
- | --foxy-shade-10pct: | ||
- | --foxy-shade-20pct: | ||
- | --foxy-shade-30pct: | ||
- | --foxy-shade-40pct: | ||
- | --foxy-shade-50pct: | ||
- | --foxy-shade-60pct: | ||
- | --foxy-shade-70pct: | ||
- | --foxy-shade-80pct: | ||
- | --foxy-shade-90pct: | ||
- | --foxy-shade: | ||
- | --foxy-primary-text-color: | ||
- | --foxy-primary-color-50pct: | ||
- | --foxy-primary-color-10pct: | ||
- | --foxy-primary-color: | ||
- | --foxy-body-text-color: | ||
- | --foxy-secondary-text-color: | ||
- | --foxy-tertiary-text-color: | ||
- | --foxy-disabled-text-color: | ||
- | --foxy-header-text-color: | ||
- | |||
- | background: var(--foxy-shade-5pct); | ||
- | } | ||
- | </ | ||
- | </ | ||
- | |||
- | Add the following code into your page where you want the portal to be shown: | ||
- | <code html> | ||
- | <!-- Change the `endpoint` value to match your Foxy store domain. --> | ||
- | < | ||
- | <!-- If you'd like to add a note above the login section, uncomment the below --> | ||
- | < | ||
- | < | ||
- | <p>If you have any trouble logging in, please try resetting your password below. Contact us if you can't get it working.</ | ||
- | </ | ||
- | <!-- If you're not using subscriptions, | ||
- | <!-- <div slot=" | ||
- | </ | ||
- | </ | ||
- | |||
- | |||
- | ===== Some Ideas and Best Practices ===== | ||
- | |||
- | ==== Loyalty Points ==== | ||
- | |||
- | An often-requested feature (that we will likely add as a native feature, but it's not available yet) is the idea of " | ||
- | |||
- | - Create custom code on your end that processes the Foxy webhook. According to your own logic, calculate the number of points that a transaction should generate. For instance, the product total less coupons. (So points would only be generated for the amount paid, excluding shipping and tax.) | ||
- | - Check the '' | ||
- | - Retrieve the [[https:// | ||
- | |||
- | That gets you keeping track of points, and the '' | ||
- | |||
- | To allow customers to redeem their points, you'd need to add some logic to your portal and your backend custom code. It'd look like this, roughly: | ||
- | |||
- | - Customer clicks the button to redeem their points. Fires an AJAX request to your endpoint. (Use the JWT returned in the '' | ||
- | - Your backend retrieves the customer' | ||
- | - If everything looks good, your backend creates a [[https:// | ||
- | - Your portal javascript receives the coupon code, and applies it to the customer' | ||
- | |||
- | This is obviously a bit of work, and requires some more advanced programming knowledge. But hopefully it helps with some ideas of how you can use the Foxy backend API, paired with the front-end javascript portal API, to create more advanced functionality. |