Skip to main content

Tax

The Tax component (pay-shared-tax) provides a self-contained tax settings interface for a merchant — VAT registration, payment breakdowns, and compliance details. It is served from the PAY shared OpenComponents registry and embedded directly into the host application.

Loaded via: Direct OpenComponent embed (requires a JWT issued by AccessPayHub with the pay_oc_tax claim)


How It Works​

The host application loads the OpenComponents (OC) client script from the PAY shared registry, then inserts an <oc-component> element pointing at pay-shared-tax. The OC client fetches the component bundle and hydrates it inline. The component then calls the Tax API directly using the JWT.

Host Application
|
| Loads oc-client/client.js
| Inserts <oc-component href="..."> with payAuthToken + merchantId
v
OC Client
|
| Fetches pay-shared-tax bundle (HTML/CSS/JS)
| Hydrates inside <oc-component> element
v
pay-shared-tax (client)
|
| Calls Tax API using payAuthToken (JWT)
v
PAY Tax API
|
| Returns merchant tax data, fee breakdowns

Parameters​

Parameters are passed by the host application as query string values on the <oc-component> href. Both values must be URL-encoded.

ParameterTypeDescription
merchantIdstring (UUID)Merchant identifier — must match the pay_merchant_id claim in the JWT
payAuthTokenstring (JWT)RS256 JWT issued by AccessPayHub. Must contain pay_oc_tax: true and a matching pay_merchant_id. TTL ~1 hour — the host must refresh and re-pass

Component coordinates:

PropertyValue
Registry base URLhttps://app-shared-registry-ukw-prod.azurewebsites.net/registry
Component namepay-shared-tax
Current stable version0.0.25
OC client script{registryUrl}/oc-client/client.js

<oc-component> href format:

{registryUrl}/pay-shared-tax/{version}/?payAuthToken={TOKEN}&merchantId={UUID}

Full example:

https://app-shared-registry-ukw-prod.azurewebsites.net/registry/pay-shared-tax/0.0.25/?payAuthToken=eyJ...&merchantId=25362a37-2635-4273-866d-8123f195380c

Obtaining a payAuthToken​

The payAuthToken is a signed JWT issued by AccessPayHub. The host application obtains it by calling the Loader API's Create Token endpoint, supplying the claims required by pay-shared-tax in the request body.

Required Claims​

The token request must include the following claims:

ClaimTypeDescription
pay_merchant_idGUIDMust match a valid merchant GUID. The same value must also be passed as the merchantId query parameter on the <oc-component> href
pay_oc_taxbooleanMust be exactly true

Example Token Request​

{
"claims": {
"pay_merchant_id": "25362a37-2635-4273-866d-8123f195380c",
"pay_oc_tax": "true"
}
}

The endpoint returns a JWT in the token field of the response, which can be passed directly to the component as payAuthToken. For full request and response details, see Loader API → Create Token.

info

Both claim values are submitted as strings — the Loader API's claims object is a flat dictionary of string keys to string values. Downstream services parse pay_oc_tax as a boolean.


Embedding​

Embedding requires three steps:

  1. Load the OC client script from the registry.
  2. Insert an <oc-component> element with the correct href into the DOM.
  3. Call window.oc.renderUnloadedComponents() via window.oc.cmd.push().

React example​

OcTaxComponent.tsx:

import { useEffect, useRef, useState } from "react";

const REGISTRY_URL = "https://app-shared-registry-ukw-prod.azurewebsites.net/registry";
const COMPONENT_VERSION = "0.0.25";
const CLIENT_SCRIPT_SRC = `${REGISTRY_URL}/oc-client/client.js`;

declare global {
interface Window {
oc?: {
cmd: Array<() => void>;
renderUnloadedComponents: () => void;
};
}
}

interface OcTaxComponentProps {
merchantId: string;
payAuthToken: string;
}

function buildHref(merchantId: string, payAuthToken: string): string {
return (
`${REGISTRY_URL}/pay-shared-tax/${encodeURIComponent(COMPONENT_VERSION)}/` +
`?payAuthToken=${encodeURIComponent(payAuthToken)}` +
`&merchantId=${encodeURIComponent(merchantId)}`
);
}

type Status = "loading" | "ready" | "error";

export function OcTaxComponent({ merchantId, payAuthToken }: OcTaxComponentProps) {
const [status, setStatus] = useState<Status>("loading");
const [errorMsg, setErrorMsg] = useState("");
const [key, setKey] = useState(0);
const hostRef = useRef<HTMLDivElement>(null);
const scriptRef = useRef<HTMLScriptElement | null>(null);

useEffect(() => {
let cancelled = false;

function showError(msg: string) {
if (!cancelled) { setErrorMsg(msg); setStatus("error"); }
}

function mountComponent() {
if (cancelled || !hostRef.current) return;
if (!window.oc || typeof window.oc.cmd === "undefined") {
showError("The OC client loaded but could not initialise.");
return;
}

hostRef.current.innerHTML = "";

const ocEl = document.createElement("oc-component");
ocEl.setAttribute("href", buildHref(merchantId, payAuthToken));
hostRef.current.appendChild(ocEl);

window.oc.cmd = window.oc.cmd || [];
window.oc.cmd.push(() => {
window.oc!.renderUnloadedComponents();
if (!cancelled) setStatus("ready");
});
}

if (window.oc) {
mountComponent();
return () => { cancelled = true; };
}

const script = document.createElement("script");
script.src = CLIENT_SCRIPT_SRC;
script.async = true;

script.onload = () => mountComponent();
script.onerror = () =>
showError(`Failed to load the OC client from ${REGISTRY_URL}. Check that the registry is reachable.`);

document.head.appendChild(script);
scriptRef.current = script;

return () => { cancelled = true; };
}, [merchantId, payAuthToken, key]);

function retry() {
scriptRef.current?.remove();
scriptRef.current = null;
delete window.oc;
setStatus("loading");
setErrorMsg("");
setKey(k => k + 1);
}

return (
<>
{status === "loading" && <p>Loading tax settings…</p>}

{status === "error" && (
<div>
<p>Component load error: {errorMsg}</p>
<button onClick={retry}>Retry</button>
</div>
)}

{/* Always mounted — hiding with display:none breaks accordion height calculations */}
<div ref={hostRef} />
</>
);
}

Usage​

import { OcTaxComponent } from "./OcTaxComponent";

const MERCHANT_ID = "25362a37-2635-4273-866d-8123f195380c";
const PAY_AUTH_TOKEN = "eyJ...";

export default function TaxSettingsPage() {
return (
<main>
<h1>Tax settings</h1>
<OcTaxComponent
merchantId={MERCHANT_ID}
payAuthToken={PAY_AUTH_TOKEN}
/>
</main>
);
}

Implementation notes​

BehaviourHow it's handled
OC client already loadedChecks window.oc before injecting a second <script> — safe to mount multiple instances
Prop changes (new token)useEffect dependency array includes merchantId and payAuthToken — re-mounts automatically
Retry after errorRemoves the old <script>, deletes window.oc, increments a key to re-run the effect
Cleanup on unmountcancelled flag prevents stale state updates if the component unmounts mid-load
Host div always visibleLoading and error states render as siblings — never hide the host <div> with display: none
warning

JWTs issued by AccessPayHub have a short TTL (~1 hour). The host application must refresh the token before it expires and pass the new value to the component.


Content Security Policy​

The component loads resources from four origins. All must be whitelisted or the component will be blocked.

OriginWhat it serves
https://app-shared-registry-ukw-prod.azurewebsites.netOC client script, component manifest
https://sasharedregistrydev.blob.core.windows.netComponent template.js and static assets
https://unpkg.comReact and ReactDOM UMD bundles (peer dependencies)
https://group-dev.pay.accessacloud.comTax API (merchant data, fee breakdowns)

Required directives:

script-src 'self' 'unsafe-inline' 'unsafe-eval'
https://app-shared-registry-ukw-prod.azurewebsites.net
https://sasharedregistrydev.blob.core.windows.net
https://unpkg.com;

connect-src 'self'
https://app-shared-registry-ukw-prod.azurewebsites.net
https://sasharedregistrydev.blob.core.windows.net
https://unpkg.com
https://group-dev.pay.accessacloud.com;

frame-src 'self'
https://app-shared-registry-ukw-prod.azurewebsites.net
https://sasharedregistrydev.blob.core.windows.net;

style-src 'self' 'unsafe-inline'
https://fonts.googleapis.com
https://sasharedregistrydev.blob.core.windows.net;

font-src 'self' https://fonts.gstatic.com;
info

'unsafe-eval' is required by the OC client to hydrate client-side templates and cannot be removed. connect-src must include group-dev.pay.accessacloud.com because the component fetches merchant tax data directly from the PAY API at runtime.


CSS Isolation​

The component renders directly into the page's DOM — it is not inside an iframe or shadow DOM. Global styles cascade into it.

Rules that will break the component:

What you wroteEffect on the component
* { margin: 0; padding: 0 } universal resetStrips spacing inside the component
color on :root or bodyOverrides the component's text colours
CSS custom properties (--text, --color-*) on :rootInherited by the component if names collide
Unscoped element selectors (label, input, h1, etc.)Override the component's own element styles
display: flex or display: grid on the host containerReshapes the component's layout
display: none on the host <div> while loadingoffsetHeight returns 0 — accordions get stuck closed

Recommendations:

  • Scope resets and element selectors to your own UI (e.g. .my-app *, .my-app label).
  • Do not set color or font CSS variables on :root if names could collide with the component's tokens.
  • Do not wrap the component in a container with overflow: hidden, fixed height, or display: none.
  • Keep the host <div> always visible — render loading/error states as siblings above it.

Versioning​

The component href includes a pinned version:

/pay-shared-tax/0.0.25/?...

To upgrade:

  1. Confirm the new version is published: GET {registryUrl}/pay-shared-tax/{newVersion}/~info
  2. Update the version constant in your integration code.
  3. Test in a non-production environment before rolling out.

To use the latest version automatically (not recommended for production), omit the version segment:

/pay-shared-tax/?payAuthToken=...&merchantId=...

Error Handling​

ScenarioBehaviour
merchantId missing or not a valid UUIDHost should display an "Invalid Request" message — a valid merchant identifier is required
payAuthToken missing or emptyHost should display an "Authentication Required" message
OC client <script> fires onerrorHost should display "Component Load Error" with the registry URL and retry guidance
window.oc.cmd undefined after script loadHost should display "Component Load Error" — the OC client did not initialise
JWT expiredComponent renders a token-expired badge — host must refresh the token and re-pass it

merchantId must be a lowercase hyphenated UUID. Validate with /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.


Troubleshooting​

SymptomLikely causeFix
Component renders blankpayAuthToken expired or pay_merchant_id claim does not match merchantIdInspect the JWT (exp, pay_merchant_id) and the request to oc-client/client.js in DevTools Network
CSP violations in consoleMissing origin in CSP directivesAdd the blocked origin per the Content Security Policy section; ensure 'unsafe-eval' is present in script-src
Fees accordion stuck collapsedHost <div> was hidden with display: none while the component initialisedNever hide the host div — render loading/error states as siblings (see Embedding)
Component text appears greyGlobal color or CSS variables on :root cascade into the componentMove color and colour tokens off :root and onto a scoped selector (e.g. .my-app)
window.oc is not defined after loadCached script tag did not re-execute, or an extension stripped the oc globalCache-bust the client script (client.js?v=0.0.25); disable interfering extensions
Token-expired badge shown immediatelyJWT TTL has elapsedRequest a new token from the PAY Auth service; implement proactive refresh before expiry

All API calls from the component are authenticated using the JWT passed in payAuthToken.