Documentation You are here: start » v » 2.0 » customer_portal

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
v:2.0:customer_portal [2023/02/17 09:32] – [Beta v4 Instructions] adamv:2.0:customer_portal [2024/03/15 09:35] (current) – [Languages] adam
Line 1: Line 1:
-====== Building a Customer Portal (New Functionality) ======+====== Customer Portal ====== 
 + 
 +===== BETA Setup Instructions ===== 
 +<WRAP center round info 60%> 
 +Note that the customer portal is currently in public beta. Please [[https://airtable.com/shruOtVjh0UYJmifs|complete this form to request access]] to have it enabled for your store before following the following steps. 
 +</WRAP> 
 + 
 +Also note that our portal functionality requires modern browsers. This excludes IE11 and non-Chromium Edge. 
 + 
 +==== Beta Instructions ==== 
 + 
 +You can [[http://foxy.io/examples/customer-portal-demo|see an example of our customer portal element on our website here]]. 
 + 
 +A default install will look like this, noting that you need to update the ''YOUR_FOXY_STORE.foxycart.com'' to your correct Foxy store subdomain (or custom subdomain if configured): 
 + 
 +<code html> 
 + <foxy-customer-portal base="https://YOUR_FOXY_STORE.foxycart.com/s/customer/"> 
 + </foxy-customer-portal> 
 + 
 + <script type="module"> 
 + import 'https://cdn-js.foxy.io/elements@1/foxy-customer-portal.js'; 
 + 
 + const I18nElement = customElements.get('foxy-i18n'); 
 + const i18nBase = 'https://cdn-js.foxy.io/elements@1/translations'; 
 + I18nElement.onResourceFetch((ns, lang) => fetch(`${i18nBase}/${ns}/${lang}.json`)); 
 + </script> 
 +</code> 
 + 
 +The CDN url's for the customer portal is set to the stable release and will update according to the ''@1'' release tag. If you want to try the bleeding-edge beta releases, you can switch the URL's to use ''elements@beta'' instead of ''elements@1'', but we don't recommend that for production situations. 
 + 
 +It's also possible to use third-party CDN's, such as jsdeliver and unpkg, if desired: 
 + 
 +== jsDeliver == 
 +<code> 
 +https://cdn.jsdelivr.net/npm/@foxy.io/elements@1/dist/cdn/foxy-customer-portal.js 
 +https://cdn.jsdelivr.net/npm/@foxy.io/elements@1/dist/cdn/translations 
 +</code> 
 + 
 +== unpkg == 
 +<code> 
 +https://unpkg.com/@foxy.io/elements@1/dist/cdn/foxy-customer-portal.js 
 +https://unpkg.com/@foxy.io/elements@1/dist/cdn/translations 
 +</code> 
 + 
 +==== 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 ''script'' tag shown above. You can customize your styles at [[https://demo.vaadin.com/lumo-editor/]]. 
 + 
 +The output from the lumo editor will look like this: 
 + 
 +<code html> 
 +<custom-styles> 
 + <style> 
 + html { 
 +     --lumo-border-radius: 0.5em; 
 +     --lumo-shade-5pct: rgba(41, 41, 41, 0.05); 
 +     --lumo-shade-10pct: rgba(41, 41, 41, 0.1); 
 +     --lumo-shade-20pct: rgba(41, 41, 41, 0.2); 
 +     --lumo-shade-30pct: rgba(41, 41, 41, 0.3); 
 +     --lumo-shade-40pct: rgba(41, 41, 41, 0.4); 
 +     --lumo-shade-50pct: rgba(41, 41, 41, 0.5); 
 +     --lumo-shade-60pct: rgba(41, 41, 41, 0.6); 
 +     --lumo-shade-70pct: rgba(41, 41, 41, 0.7); 
 +     --lumo-shade-80pct: rgba(41, 41, 41, 0.8); 
 +     --lumo-shade-90pct: rgba(41, 41, 41, 0.9); 
 +     --lumo-shade: hsl(214, 0%, 16%); 
 +     --lumo-primary-text-color: rgb(83, 39, 94); 
 +     --lumo-primary-color-50pct: rgba(83, 39, 94, 0.5); 
 +     --lumo-primary-color-10pct: rgba(83, 39, 94, 0.1); 
 +     --lumo-primary-color: #53275E; 
 +     --lumo-body-text-color: hsl(214, 0%, 16%); 
 +     --lumo-secondary-text-color: hsl(214, 0%, 42%); 
 +     --lumo-tertiary-text-color: rgba(87, 87, 87, 0.5); 
 +     --lumo-disabled-text-color: rgba(173, 173, 173, 0.3); 
 +     --lumo-header-text-color: hsl(214, 0%, 16%); 
 +
 + </style> 
 +</custom-styles> 
 +</code> 
 + 
 +Note that dark mode is not currently supported by the customer portal, and if you use the "advanced" options on the "typography" page, our elements don't utilise the "3X large" size. 
 + 
 +If you want to include the styles in the ''head'' of your page, you can do that by changing the ''html'' style declaration to instead be targeting '':root''
 + 
 +==== Languages ==== 
 + 
 +Languages are handled separately for the customer portal than for the cart/checkout/receipt, and so aren't managed via the Foxy administration. Instead, you can specify a ''lang'' attribute on the ''foxy-customer-portal'' HTML element. We currently support the following language codes: ''en'', ''es'', ''de'', ''pl'', ''zh-HK'', ''se'', ''nl'', ''fr''. As an example, to set your customer portal to French, it would look like this (with your store domain of course): 
 + 
 +<code html> 
 +<foxy-customer-portal base="https://YOUR_FOXY_STORE.foxycart.com/s/customer/" lang="fr"></foxy-customer-portal> 
 +</code> 
 + 
 +=== 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 ''foxy-customer-portal'' HTML element, you will need to define a namespace ''ns'' element, with a value of ''my-portal customer-portal''. (You can specify a different value than ''my-portal'' if you want, you'll just need to remember that in the next portion for the javascript.) That would make the HTML tag look like this: 
 + 
 +<code html> 
 +<foxy-customer-portal base="https://YOUR_FOXY_STORE.foxycart.com/s/customer/" ns="my-portal customer-portal"></foxy-customer-portal> 
 +</code> 
 + 
 +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, lang) => fetch(`${i18nBase}/${ns}/${lang}.json`)); 
 +</code> 
 + 
 +The javascript line to customise a language string takes the following format: 
 + 
 +<code javascript> 
 +I18nElement.i18next.addResource('en', 'my-portal', 'STRING_IDENTIFIER', 'My New String')
 +</code> 
 + 
 +  - The first argument (''en'') is the language code that contains the string you're updating 
 +  - The second argument (''my-portal'') matches the custom namespace defined in the ''ns'' attribute 
 +  - The third argument (''STRING_IDENTIFIER'') matches the string that you're wanting to update from the language file (details on that below) 
 +  - The fourth argument (''My New String'') defines your own version of the language string that you want to use instead 
 + 
 +To find the string identifier, you can [[https://github.com/Foxy/foxy-elements/tree/main/src/static/translations/customer-portal|reference the language strings for the customer portal element on the Github repository here]]. After opening the specific language you are replacing a string for, you will find all the strings in a JSON structure. By finding the language string there, you can then create the string identifier to match to it, which begins with ''customer-portal'', followed by the nested object keys and the language string key, separated by periods. 
 + 
 +For example, let's replace the "Get temporary password" language string to be "Forgot password?" instead. In the ''en'' [[https://github.com/Foxy/foxy-elements/blob/main/src/static/translations/customer-portal/en.json|language file]], that string is located in this portion of the document: 
 + 
 +<code json> 
 +  "sign-in-form":
 +    "email": "Email", 
 +    "invalid_credential_error": "Incorrect email or password. Please check your credentials and try again.", 
 +    "password": "Password", 
 +    "recover_access": "Get temporary password", 
 +    "sign_in": "Sign in", 
 +    "sign_in_hint": "Please enter your email and password", 
 +    "sign_up": "Create account", 
 +    "unknown_error": "An unknown error has occured. Please try again later.", 
 +    "v8n_invalid_email": "Invalid email", 
 +    "v8n_required": "Required", 
 +    "spinner":
 +      "loading_busy": "Loading", 
 +      "loading_error": "Failed to load" 
 +    } 
 +  } 
 +</code> 
 + 
 +That would make the string identifier as ''customer-portal.sign-in-form.recover_access'', and the line of javascript added into the customer portal page would look like this: 
 + 
 +<code javascript> 
 +I18nElement.i18next.addResource('en', 'my-portal', 'customer-portal.sign-in-form.recover_access', 'Forgot Password?'); 
 +</code> 
 + 
 +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 "Loading" string above would be ''customer-portal.sign-in-form.spinner.loading_busy''
 + 
 +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://elements.foxy.dev/?path=/story/other-customerportal--playground|elements.foxy.dev]] for configuration options and demo code. 
 + 
 +===== Single Sign-On with the Customer Portal Session ===== 
 + 
 +<WRAP info>Note: This functionality may become native in the future. For the time being, SSO between the portal and the checkout requires just a bit of extra setup.</WRAP> 
 + 
 +<WRAP important>If you're using SSO elsewhere in your site, be aware that enabling it in the portal bypasses your own endpoint. If you're only doing authentication, this is generally not a problem, but if you're doing other things with your SSO endpoint (like checking inventory, verifying cart contents, etc.), this will bypass that.</WRAP> 
 + 
 +==== Standalone SSO with the Portal ==== 
 + 
 +<WRAP tip>This section is for if you're not using SSO elsewhere.</WRAP> 
 + 
 +To ensure your customers don't need to login to the checkout after they've already logged into the portal, you can set up [[https://wiki.foxycart.com/v/2.0/sso|Foxy's SSO functionality]] following the steps below. If you're //already using SSO//, you'll want to approach this in a different way, depending on your other systems. 
 + 
 +When you do a ''GET /s/customer?sso=true'' (which happens by default if you're using the normal portal functionality), it includes the ''._links["fx:checkout"].ref'' value, which is a URI that will load the checkout with your customer already logged in, and sets a cookie named ''fx.customer.sso''. From there, you'll need to do the following: 
 + 
 +  - Login to the Foxy administration for your store, and go to the "advanced" setting page. Copy the "store secret" value - noting that if you're using the [[..:store_secret|JSON approach to specify unique secrets]] for different features, that you get the value for the ''sso''
 +  - Create a SSO URL for a guest (ie. ''customer_id=0''). (You can use an online tool like [[https://gchq.github.io/CyberChef/#recipe=SHA1()&input=MHwxODkzNDU2MDAwfFlPVVJfU0VDUkVUX0hFUkU|CyberChef]] to generate a SHA1 hash of something like ''0|1893456000|YOUR_SECRET_HERE'', replacing ''YOUR_SECRET_HERE'' with the store secret you copied earlier.) Using that tool, you'll then copy the **Output** value to use as your ''fc_auth_token'' value. 
 +  - Create a new page on your website (at the same domain as the page that contains the ''<foxy-customer-portal />'' element) with the following code. Replace ''YOUR_FOXY_DOMAIN'' with your store domain (like ''mystore.foxycart.com'' or ''secure.mystore.com''), and the ''fc_auth_token=REPLACE_THIS'' bit with the SSO URL token from the above step. (NOTE: The below code includes the doctype, html, head, and body tags. If you're inserting this into an existing template, you just want to grab the ''<script />'' block, and make sure the page is set to not be indexed by search engines. Also try to keep the page as small as possible - removing any extra CSS or javascript files you may be loading, to help the page load as quick as possible.) <code html> 
 +<!doctype html> 
 +<html class="no-js" lang=""> 
 +<head> 
 +    <meta charset="utf-8"> 
 +    <title>SSO Redirect</title> 
 +    <meta name="ROBOTS" CONTENT="NOINDEX, NOFOLLOW"> 
 +    <script> 
 +        function parseJWT(token) { 
 +            let base64Url = token.split('.')[1]; 
 +            let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); 
 +            let jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) { 
 +                return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); 
 +            }).join('')); 
 + 
 +            return JSON.parse(jsonPayload); 
 +        } 
 +        function getSSOUrl() { 
 +            // Look for a cookie 
 +            var value = "; " + document.cookie; 
 +            var parts = value.split("; " + "fx.customer.sso" + "="); 
 +            if (parts.length == 2) return decodeURIComponent(parts.pop().split(";").shift()); 
 + 
 +            // Look for localstorage 
 +            try { 
 +                let sessionStore = JSON.parse(localStorage.getItem("session")); 
 +                if (sessionStore && sessionStore.hasOwnProperty("jwt")) { 
 +                    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("customer_id") && 
 +                        sessionStore.hasOwnProperty("sso"
 +                    ) { 
 +                        return sessionStore.sso; 
 +                    } 
 +                } 
 +            } catch (error) { 
 +                console.log("Error trying to get SSO URL:", error); 
 +            } 
 + 
 +            return false; 
 +        } 
 +  
 +        function getUrlParameter(name) { 
 +            return new URLSearchParams(location.search).get('fcsid'); 
 +        } 
 +  
 +        if (getSSOUrl()) { 
 +            window.location.replace(getSSOUrl() + "&fcsid=" + getUrlParameter("fcsid")); 
 +        } else { 
 +            window.location.replace("https://YOUR_FOXY_DOMAIN/checkout?fc_customer_id=0&timestamp=1893456000&fc_auth_token=REPLACE_THIS&fcsid=" + getUrlParameter("fcsid")); 
 +        } 
 +  
 +    </script> 
 +</head> 
 +<body></body> 
 +</html> 
 +</code> 
 + - Go to the "advanced settings" page in your Foxy admin, check the "enable single sign on", then enter the URL of the page you created in the step above. (NOTE: This page must be publicly accessible.) Save the settings. 
 + - 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, as there are countless ways to handle SSO, but here are the general ideas: 
 + 
 +  * **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 ''POST /s/customer/authenticate'' request with their entered username + password. Set the ''fx.customer'' cookie with the value you receive. Note that this approach requires the Foxy password to be synced with your own auth system. 
 +  * **If you don't have access to the customer's password...** You can use your store's configured [[.:unified_order_entry|UOE password]] password. (This should //only ever// be serverside, as you should never share that password in the browser.) 
 +  * Unlike the checkout, you likely would not want to allow login to the portal if the customer's not already logged into your system. Instead, redirect the customer to your login page so they'll be fully logged in (instead of logged into the Foxy customer portal, but not to your system or the checkout). 
 + 
 + 
 + 
 +===== 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 "loyalty points" that can be redeemed or exchanged for discounts. Using the API and the customer portal functionality described on this page, you can whip something up. The idea is, roughly: 
 + 
 +  - 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 ''attributes'' for the transaction and look for an attribute named ''Loyalty_Points_Applied_On''. If it exists, you've already applied the points for this transaction, so exit. 
 +  - Retrieve the [[https://api.foxycart.com/rels/attributes|attributes for the customer]] record via the API. If a ''Loyalty_Points'' attribute exists, update the value accordingly. On success, add the ''Loyalty_Points_Applied_On'' attribute to the transaction, so you don't re-apply points if the webhook fires again. (If the ''Loyalty_Points'' attribute doesn't exist, create it and make sure the visibility is ''public'', so it will be returned in the customer portal front-end.) 
 + 
 +That gets you keeping track of points, and the ''public'' visibility ensures that your customers can see their points value when they login. 
 + 
 +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 ''authenticate'' action to validate the request.) 
 +  - Your backend retrieves the customer's attributes via the Foxy API. 
 +  - If everything looks good, your backend creates a [[https://api.foxycart.com/rels/coupon_codes|coupon code]] for a pre-existing coupon, and returns the code to the client. (You may also want to add this code as an attribute to the customer, for later retrieval.) 
 +  - Your portal javascript receives the coupon code, and applies it to the customer's session. 
 + 
 +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 ''/s/customer'' endpoint is a more advanced approach. If you're not sure if you actually need to use this, please contact us to discuss your needs, as you may want to just use our "ready to go" portal front-end detailed above instead of building your own.
-If you're landing on this page, please contact us to discuss your needs, as you may want to just use our "ready to go" portal front-end instead of building your own.+
 </WRAP> </WRAP>
  
Line 484: Line 747:
     * The URI is returned in the ''_links.self'' value (seen above).     * The URI is returned in the ''_links.self'' value (seen above).
   * 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>Note: This functionality may become native in the future. For the time being, SSO between the portal and the checkout requires just a bit of extra setup.</WRAP> 
- 
-<WRAP important>If you're using SSO elsewhere in your site, be aware that enabling it in the portal bypasses your own endpoint. If you're only doing authentication, this is generally not a problem, but if you're doing other things with your SSO endpoint (like checking inventory, verifying cart contents, etc.), this will bypass that.</WRAP> 
- 
-==== Standalone SSO with the Portal ==== 
- 
-<WRAP tip>This section is for if you're not using SSO elsewhere.</WRAP> 
- 
-To ensure your customers don't need to login to the checkout after they've already logged into the portal, you can set up [[https://wiki.foxycart.com/v/2.0/sso|Foxy's SSO functionality]] following the steps below. If you're //already using SSO//, you'll want to approach this in a different way, depending on your other systems. 
- 
-When you do a ''GET /s/customer?sso=true'' (which happens by default if you're using the normal portal functionality), it includes the ''._links["fx:checkout"].ref'' value, which is a URI that will load the checkout with your customer already logged in, and sets a cookie named ''fx.customer.sso''. From there, you'll need to do the following: 
- 
-  - Login to the Foxy administration for your store, and go to the "advanced" setting page. Copy the "store secret" value - noting that if you're using the [[..:store_secret|JSON approach to specify unique secrets]] for different features, that you get the value for the ''sso''. 
-  - Create a SSO URL for a guest (ie. ''customer_id=0''). (You can use an online tool like [[https://gchq.github.io/CyberChef/#recipe=SHA1()&input=MHwxODkzNDU2MDAwfFlPVVJfU0VDUkVUX0hFUkU|CyberChef]] to generate a SHA1 hash of something like ''0|1893456000|YOUR_SECRET_HERE'', replacing ''YOUR_SECRET_HERE'' with the store secret you copied earlier.) Using that tool, you'll then copy the **Output** value to use as your ''fc_auth_token'' value. 
-  - Create a new page on your website (at the same domain as the page that contains the ''<foxy-customer-portal />'' element) with the following code. Replace ''YOUR_FOXY_DOMAIN'' with your store domain (like ''mystore.foxycart.com'' or ''secure.mystore.com''), and the ''fc_auth_token=REPLACE_THIS'' bit with the SSO URL token from the above step. (NOTE: The below code includes the doctype, html, head, and body tags. If you're inserting this into an existing template, you just want to grab the ''<script />'' block, and make sure the page is set to not be indexed by search engines. Also try to keep the page as small as possible - removing any extra CSS or javascript files you may be loading, to help the page load as quick as possible.) <code html> 
-<!doctype html> 
-<html class="no-js" lang=""> 
-<head> 
-    <meta charset="utf-8"> 
-    <title>SSO Redirect</title> 
-    <meta name="ROBOTS" CONTENT="NOINDEX, NOFOLLOW"> 
-    <script> 
-        function parseJWT(token) { 
-            let base64Url = token.split('.')[1]; 
-            let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); 
-            let jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) { 
-                return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); 
-            }).join('')); 
- 
-            return JSON.parse(jsonPayload); 
-        } 
-        function getSSOUrl() { 
-            // Look for a cookie 
-            var value = "; " + document.cookie; 
-            var parts = value.split("; " + "fx.customer.sso" + "="); 
-            if (parts.length == 2) return decodeURIComponent(parts.pop().split(";").shift()); 
-  
-            // Look for localstorage 
-            try { 
-                let sessionStore = JSON.parse(localStorage.getItem('session')); 
-                if (sessionStore && sessionStore.hasOwnProperty('jwt')) { 
-                    let sessionData = parseJWT(sessionStore.jwt); 
- 
-                    if (sessionData.exp > (Date.now() / 1000) && sessionData.hasOwnProperty('customer_id') && sessionStore.hasOwnProperty('sso')) { 
-                        return sessionStore.sso; 
-                    } 
-                } 
-            } catch (error) { 
-                console.log("Error trying to get SSO URL:", error); 
-            } 
-  
-            return false; 
-        } 
-  
-        function getUrlParameter(name) { 
-            return new URLSearchParams(location.search).get('fcsid'); 
-        } 
-  
-        if (getSSOUrl()) { 
-            window.location.replace(getSSOUrl() + "&fcsid=" + getUrlParameter("fcsid")); 
-        } else { 
-            window.location.replace("https://YOUR_FOXY_DOMAIN/checkout?fc_customer_id=0&timestamp=1893456000&fc_auth_token=REPLACE_THIS&fcsid=" + getUrlParameter("fcsid")); 
-        } 
-  
-    </script> 
-</head> 
-<body></body> 
-</html> 
-</code> 
- - Go to the "advanced settings" page in your Foxy admin, check the "enable single sign on", then enter the URL of the page you created in the step above. (NOTE: This page must be publicly accessible.) Save the settings. 
- - 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, as there are countless ways to handle SSO, but here are the general ideas: 
- 
-  * **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 ''POST /s/customer/authenticate'' request with their entered username + password. Set the ''fx.customer'' cookie with the value you receive. Note that this approach requires the Foxy password to be synced with your own auth system. 
-  * **If you don't have access to the customer's password...** You can use your store's configured [[.:unified_order_entry|UOE password]] password. (This should //only ever// be serverside, as you should never share that password in the browser.) 
-  * Unlike the checkout, you likely would not want to allow login to the portal if the customer's not already logged into your system. Instead, redirect the customer to your login page so they'll be fully logged in (instead of logged into the Foxy customer portal, but not to your system or the checkout). 
- 
-===== 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 ''v4'' unless otherwise directed. Please [[https://zfrmz.com/7bxJCixfvLFLYQvlcWgK|complete this form to request access]] to have it enabled for your store before following the following steps. 
-</WRAP> 
- 
- 
-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" repository, which is the foundation for all our future UI. See the demos and examples at [[https://elements.foxy.dev/?path=/story/other-customerportal--playground|elements.foxy.dev]] for configuration options and demo code. A default install will look like this, noting that you need to update the ''YOUR_FOXY_STORE.foxycart.com'' to your correct Foxy store subdomain (or custom subdomain if configured): 
- 
-<code html> 
- <foxy-customer-portal base="https://YOUR_FOXY_STORE.foxycart.com/s/customer/"> 
- </foxy-customer-portal> 
- 
- <script type="module"> 
- import 'https://cdn-js.foxy.io/elements@1/foxy-customer-portal.js'; 
- 
- const I18nElement = customElements.get('foxy-i18n'); 
- const i18nBase = 'https://cdn-js.foxy.io/elements@1/translations'; 
- I18nElement.onResourceFetch((ns, lang) => fetch(`${i18nBase}/${ns}/${lang}.json`)); 
- </script> 
-</code> 
- 
-The CDN url's for the customer portal is set to the stable release and will update according to the ''@1'' release tag. If you want to try the bleeding-edge beta releases, you can switch the URL's to use ''elements@beta'' instead of ''elements@1'', but we don't recommend that for production situations. 
- 
-It's also possible to use third-party CDN's, such as jsdeliver and unpkg, if desired: 
- 
-== jsDeliver == 
-<code> 
-https://cdn.jsdelivr.net/npm/@foxy.io/elements@1/dist/cdn/foxy-customer-portal.js 
-https://cdn.jsdelivr.net/npm/@foxy.io/elements@1/dist/cdn/translations 
-</code> 
- 
-== unpkg == 
-<code> 
-https://unpkg.com/@foxy.io/elements@1/dist/cdn/foxy-customer-portal.js 
-https://unpkg.com/@foxy.io/elements@1/dist/cdn/translations 
-</code> 
- 
-=== 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 ''script'' tag shown above. You can customize your styles at [[https://demo.vaadin.com/lumo-editor/]]. 
- 
-The output from the lumo editor will look like this: 
- 
-<code html> 
-<custom-styles> 
- <style> 
- html { 
-     --lumo-border-radius: 0.5em; 
-     --lumo-shade-5pct: rgba(41, 41, 41, 0.05); 
-     --lumo-shade-10pct: rgba(41, 41, 41, 0.1); 
-     --lumo-shade-20pct: rgba(41, 41, 41, 0.2); 
-     --lumo-shade-30pct: rgba(41, 41, 41, 0.3); 
-     --lumo-shade-40pct: rgba(41, 41, 41, 0.4); 
-     --lumo-shade-50pct: rgba(41, 41, 41, 0.5); 
-     --lumo-shade-60pct: rgba(41, 41, 41, 0.6); 
-     --lumo-shade-70pct: rgba(41, 41, 41, 0.7); 
-     --lumo-shade-80pct: rgba(41, 41, 41, 0.8); 
-     --lumo-shade-90pct: rgba(41, 41, 41, 0.9); 
-     --lumo-shade: hsl(214, 0%, 16%); 
-     --lumo-primary-text-color: rgb(83, 39, 94); 
-     --lumo-primary-color-50pct: rgba(83, 39, 94, 0.5); 
-     --lumo-primary-color-10pct: rgba(83, 39, 94, 0.1); 
-     --lumo-primary-color: #53275E; 
-     --lumo-body-text-color: hsl(214, 0%, 16%); 
-     --lumo-secondary-text-color: hsl(214, 0%, 42%); 
-     --lumo-tertiary-text-color: rgba(87, 87, 87, 0.5); 
-     --lumo-disabled-text-color: rgba(173, 173, 173, 0.3); 
-     --lumo-header-text-color: hsl(214, 0%, 16%); 
- } 
- </style> 
-</custom-styles> 
-</code> 
- 
-Note that dark mode is not currently supported by the customer portal, and if you use the "advanced" options on the "typography" page, our elements don't utilise the "3X large" size. 
- 
-If you want to include the styles in the ''head'' of your page, you can do that by changing the ''html'' style declaration to instead be targeting '':root''. 
- 
-==== Beta v3 Instructions ==== 
- 
-Insert the following code in your page (''head'' or footer, depending on your requirements; you may remove the comments, of course): 
- 
-<code html> 
-<!-- The following 2 lines are required. --> 
-<script type="module" src="https://static.www.foxycart.com/beta/s/customer-portal/v1.0.0-beta.11/dist/lumo/foxy/foxy.esm.js"></script> 
-<script nomodule src="https://static.www.foxycart.com/beta/s/customer-portal/v1.0.0-beta.11/dist/lumo/foxy.js"></script> 
-<!-- The following style block is optional. You can customize your styles at https://demo.vaadin.com/lumo-editor/. 
-    Simply change all the `--lumo-` prefixes to `--foxy-`, and insert them below. --> 
-<style> 
-html { 
-    --foxy-border-radius: 0.5em; 
-    --foxy-shade-5pct: rgba(41, 41, 41, 0.05); 
-    --foxy-shade-10pct: rgba(41, 41, 41, 0.1); 
-    --foxy-shade-20pct: rgba(41, 41, 41, 0.2); 
-    --foxy-shade-30pct: rgba(41, 41, 41, 0.3); 
-    --foxy-shade-40pct: rgba(41, 41, 41, 0.4); 
-    --foxy-shade-50pct: rgba(41, 41, 41, 0.5); 
-    --foxy-shade-60pct: rgba(41, 41, 41, 0.6); 
-    --foxy-shade-70pct: rgba(41, 41, 41, 0.7); 
-    --foxy-shade-80pct: rgba(41, 41, 41, 0.8); 
-    --foxy-shade-90pct: rgba(41, 41, 41, 0.9); 
-    --foxy-shade: hsl(214, 0%, 16%); 
-    --foxy-primary-text-color: rgb(83, 39, 94); 
-    --foxy-primary-color-50pct: rgba(83, 39, 94, 0.5); 
-    --foxy-primary-color-10pct: rgba(83, 39, 94, 0.1); 
-    --foxy-primary-color: #53275E; 
-    --foxy-body-text-color: hsl(214, 0%, 16%); 
-    --foxy-secondary-text-color: hsl(214, 0%, 42%); 
-    --foxy-tertiary-text-color: rgba(87, 87, 87, 0.5); 
-    --foxy-disabled-text-color: rgba(173, 173, 173, 0.3); 
-    --foxy-header-text-color: hsl(214, 0%, 16%); 
- 
-    background: var(--foxy-shade-5pct); 
-} 
-</style> 
-</code> 
- 
-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. --> 
-<foxy-customer-portal endpoint="https://your-api-endpoint.tld"> 
-  <!-- If you'd like to add a note above the login section, uncomment the below --> 
-  <foxy-sign-in slot="sign-in"> 
-    <h1>Hello! Please login…</h1> 
-    <p>If you have any trouble logging in, please try resetting your password below. Contact us if you can't get it working.</p> 
-  </foxy-sign-in> 
-  <!-- If you're not using subscriptions, uncomment the following line to override that default block. --> 
-  <!-- <div slot="subscriptions-container"></div> --> 
-</foxy-customer-portal> 
-</code> 
- 
- 
-===== 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 "loyalty points" that can be redeemed or exchanged for discounts. Using the API and the customer portal functionality described on this page, you can whip something up. The idea is, roughly: 
- 
-  - 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 ''attributes'' for the transaction and look for an attribute named ''Loyalty_Points_Applied_On''. If it exists, you've already applied the points for this transaction, so exit. 
-  - Retrieve the [[https://api.foxycart.com/rels/attributes|attributes for the customer]] record via the API. If a ''Loyalty_Points'' attribute exists, update the value accordingly. On success, add the ''Loyalty_Points_Applied_On'' attribute to the transaction, so you don't re-apply points if the webhook fires again. (If the ''Loyalty_Points'' attribute doesn't exist, create it and make sure the visibility is ''public'', so it will be returned in the customer portal front-end.) 
- 
-That gets you keeping track of points, and the ''public'' visibility ensures that your customers can see their points value when they login. 
- 
-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 ''authenticate'' action to validate the request.) 
-  - Your backend retrieves the customer's attributes via the Foxy API. 
-  - If everything looks good, your backend creates a [[https://api.foxycart.com/rels/coupon_codes|coupon code]] for a pre-existing coupon, and returns the code to the client. (You may also want to add this code as an attribute to the customer, for later retrieval.) 
-  - Your portal javascript receives the coupon code, and applies it to the customer's session. 
- 
-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. 

Site Tools