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.
| Parameter | Type | Description |
|---|---|---|
merchantId | string (UUID) | Merchant identifier — must match the pay_merchant_id claim in the JWT |
payAuthToken | string (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:
| Property | Value |
|---|---|
| Registry base URL | https://app-shared-registry-ukw-prod.azurewebsites.net/registry |
| Component name | pay-shared-tax |
| Current stable version | 0.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:
| Claim | Type | Description |
|---|---|---|
pay_merchant_id | GUID | Must match a valid merchant GUID. The same value must also be passed as the merchantId query parameter on the <oc-component> href |
pay_oc_tax | boolean | Must 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.
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:
- Load the OC client script from the registry.
- Insert an
<oc-component>element with the correcthrefinto the DOM. - Call
window.oc.renderUnloadedComponents()viawindow.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
| Behaviour | How it's handled |
|---|---|
| OC client already loaded | Checks 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 error | Removes the old <script>, deletes window.oc, increments a key to re-run the effect |
| Cleanup on unmount | cancelled flag prevents stale state updates if the component unmounts mid-load |
| Host div always visible | Loading and error states render as siblings — never hide the host <div> with display: none |
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.
| Origin | What it serves |
|---|---|
https://app-shared-registry-ukw-prod.azurewebsites.net | OC client script, component manifest |
https://sasharedregistrydev.blob.core.windows.net | Component template.js and static assets |
https://unpkg.com | React and ReactDOM UMD bundles (peer dependencies) |
https://group-dev.pay.accessacloud.com | Tax 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;
'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 wrote | Effect on the component |
|---|---|
* { margin: 0; padding: 0 } universal reset | Strips spacing inside the component |
color on :root or body | Overrides the component's text colours |
CSS custom properties (--text, --color-*) on :root | Inherited 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 container | Reshapes the component's layout |
display: none on the host <div> while loading | offsetHeight 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
coloror font CSS variables on:rootif names could collide with the component's tokens. - Do not wrap the component in a container with
overflow: hidden, fixed height, ordisplay: 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:
- Confirm the new version is published:
GET {registryUrl}/pay-shared-tax/{newVersion}/~info - Update the version constant in your integration code.
- 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
| Scenario | Behaviour |
|---|---|
merchantId missing or not a valid UUID | Host should display an "Invalid Request" message — a valid merchant identifier is required |
payAuthToken missing or empty | Host should display an "Authentication Required" message |
OC client <script> fires onerror | Host should display "Component Load Error" with the registry URL and retry guidance |
window.oc.cmd undefined after script load | Host should display "Component Load Error" — the OC client did not initialise |
| JWT expired | Component 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
| Symptom | Likely cause | Fix |
|---|---|---|
| Component renders blank | payAuthToken expired or pay_merchant_id claim does not match merchantId | Inspect the JWT (exp, pay_merchant_id) and the request to oc-client/client.js in DevTools Network |
| CSP violations in console | Missing origin in CSP directives | Add the blocked origin per the Content Security Policy section; ensure 'unsafe-eval' is present in script-src |
| Fees accordion stuck collapsed | Host <div> was hidden with display: none while the component initialised | Never hide the host div — render loading/error states as siblings (see Embedding) |
| Component text appears grey | Global color or CSS variables on :root cascade into the component | Move color and colour tokens off :root and onto a scoped selector (e.g. .my-app) |
window.oc is not defined after load | Cached script tag did not re-execute, or an extension stripped the oc global | Cache-bust the client script (client.js?v=0.0.25); disable interfering extensions |
| Token-expired badge shown immediately | JWT TTL has elapsed | Request 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.