ouisys-widget-cc-pay
v1.0.7
Published
DynamicCCPay — embeddable credit-card / Apple Pay / Google Pay subscription widget.
Readme
ouisys-widget-cc-pay — DynamicCCPay
Full API reference:
src/embed/README.md
An embeddable credit-card / Apple Pay / Google Pay subscription widget. A host page
adds one <script> (or one npm import) and a <div>; the widget loads its config,
renders the payment methods (wallets + card), and processes payment inline — packaged as a
single self-contained file.
<script src="https://<cdn>/dynamic-cc-pay.js"></script>
<div id="pay"></div>
<script>
// rockmanId is optional — the widget resolves/generates one itself.
DynamicCCPay.mount('#pay', { xcid: 'xph20' });
</script>Table of contents
- Install
- Quick start
- Loading the script (
ccWidgetHash) - Config: two modes (
xcidvs inline) - Visitor id (
rockmanId) - Mount config reference
- Payment methods, modes & consent
- Callbacks & host tracking
- Styling
- Development
- Build & deploy
- Prerequisites (backend)
- Project layout
Install
npm install ouisys-widget-cc-pay
# or
yarn add ouisys-widget-cc-payPeer dependencies:
react >= 18andreact-dom >= 18must be installed in the host project.
import { DynamicCCPay } from 'ouisys-widget-cc-pay';
DynamicCCPay.mount('#pay', {
xcid: 'xph20',
mode: 'card-applepay-google-pay',
onSuccess: (result) => console.log('paid', result),
onError: (err) => console.warn('payment failed', err)
});For pure script-tag usage (no bundler), load dynamic-cc-pay.js from the CDN and call the
global DynamicCCPay.mount(...) — see Loading the script.
Quick start
mount(target, config) returns a promise resolving to a MountResult:
const widget = await DynamicCCPay.mount('#pay', { xcid: 'xph20' });
widget.unmount(); // tear down + clean up
widget.tracker; // push host product events (see Host tracking)targetmay be a CSS selector or anHTMLElement.- Only a config object is required;
rockmanIdis optional (resolved/generated — see below). - All markup is scoped under
.dynamic-cc-payand never leaks into the host page.
Loading the script (ccWidgetHash)
The bundle is content-hashed (dynamic-cc-pay.<hash>.js) so it can be cached forever and
busted atomically. The host needs to know which hash is current. Preferred: read it
inline from the server-injected page config — no extra round-trip.
<script>
(function () {
var base = '/path/to/widget/'; // your CDN base path
var hash = window.configJson
&& window.configJson.pageConfigs
&& window.configJson.pageConfigs.ccWidgetHash;
function load(src) {
var s = document.createElement('script');
s.src = src; s.defer = true;
s.onload = boot; // your DynamicCCPay.mount(...) call
s.onerror = function () { console.error('[cc] widget failed to load: ' + src); };
document.head.appendChild(s);
}
if (hash) {
load(base + 'dynamic-cc-pay.' + hash + '.js'); // fast path — no round-trip
} else {
fetch(base + 'embed-manifest.json') // fallback: discover the hash
.then(function (r) { return r.json(); })
.then(function (m) { load(base + m.main); })
.catch(function (e) { console.error('[cc] manifest fallback failed', e); });
}
})();
</script>| State | Behavior |
| --- | --- |
| ccWidgetHash present | Loads dynamic-cc-pay.<hash>.js directly. No lookup. |
| ccWidgetHash absent | Falls back to embed-manifest.json to discover the hash (one extra round-trip). |
| Hash absent and manifest fetch fails | onerror / .catch fires; widget does not mount. |
Plain stable URL (
dynamic-cc-pay.js, no hash) also works and always tracks the latest deploy, but it's fronted by a CDN with a short TTL and query strings are not in the cache key (?cb=won't bust it). Use the hashed URL when you need a fresh build immediately (e.g. verifying a deploy). This is what the demo pages use.
Config: two modes
A) By xcid (recommended)
The widget fetches the published page and scrapes the server-injected window.configJson
from its HTML — the same config object the landing page runs on.
DynamicCCPay.mount('#pay', {
xcid: 'xph20', // published page id (`xid` alias accepted)
apiBaseUrl: 'https://<api-host>' // API host; also the page host by default
});- Fetches
${pageUrl}/<xcid>;pageUrldefaults toapiBaseUrl, then''(same-origin). ?xcid=(alias?xid=) query param overrides the hostxcid— it always wins and is not persisted (each load re-reads it). Handy for pointing the same embed at a different published page for testing without touching the integration.- Same-page fast path: mounted on its own published page, the widget reuses the
already-inlined
window.configJsonand skips the fetch when its page matches the mount'sxcid. PassforceFetchConfig: trueto always re-fetch (debugging). - Optional
configUrl: if a JSON config endpoint exists, the widget GETs${configUrl}?id=<xcid>and parses{ pageConfigs }instead of scraping HTML.
B) Inline (no xcid)
Pass the full config directly — no network fetch. Useful when the host already holds the merchant data, or for local testing.
DynamicCCPay.mount('#pay', {
apiBaseUrl: 'https://<api-host>',
slug: 'cc_celerispay-example50_001-',
gateway: 'celeris',
service: { id: 'example-service', displayName: 'Example Service' },
trialPrice: '0.01',
isLocalCurrency: true,
countryCode: 'SE',
payments: {
applePay: {
bankId: 0,
merchantIdentifier: 'merchant.com.example.service',
label: 'Example Service',
supportedNetworks: ['visa', 'masterCard']
},
googlePay: { bankId: 0 /* + gateway params Google Pay isReadyToPay requires */ }
}
});Visitor id (rockmanId)
rockmanId is the per-visitor id every payment and analytics call carries. It is
optional — the widget resolves it in this order:
?rockmanId=URL query param (alias?rockman_id=) — always wins.config.rockmanIdpassed tomount().- A persisted id (
sessionStorage) — reused across page→page navigation so the whole funnel keeps one visitor id. - A freshly generated id — minted on the first load of a tab and on every hard refresh (a reload deliberately starts a new visitor session).
When no host/query id is supplied, the generated id behaves like this:
| Action | Result |
| --- | --- |
| Navigate page → page (same tab) | Same id (from sessionStorage) |
| Back / forward | Same id |
| Hard refresh (F5 / reload) | New id |
| New tab / close + reopen tab | New id (sessionStorage is per-tab) |
| ?rockmanId= present in URL | That id is used regardless |
Reload vs. navigation is distinguished via the Navigation Timing API. If sessionStorage is unavailable (private mode), it degrades to a fresh id per load.
Production note: the analytics backend only accepts events whose
rockmanIdis a real server-issued 32-hex id. A generated fallback id still drives the payment UI and API calls, but its tracking events are rejected (HTTP 400). Pass a real id in production; the fallback exists for embeds that can't.
The resolved id is mirrored to window.pac_analytics.userId and
window.pac_analytics.visitor.rockmanId, so the wallet hooks, card flow, and analytics all
share one consistent value.
Mount config reference
| Key | Type | Default | Notes |
| --- | --- | --- | --- |
| xcid / xid | string | — | Published page id (xcid mode). Overridable via ?xcid=. |
| rockmanId | string | resolved/generated | Per-visitor id. See Visitor id. |
| apiBaseUrl | string | '' (same-origin) | Host for /api/v1/frontend/*; also default page host. |
| pageUrl | string | apiBaseUrl | Override the published-page host for the config scrape. |
| configUrl | string | — | Dedicated JSON config endpoint instead of HTML scrape. |
| forceFetchConfig | boolean | false | Skip the same-page fast path and always re-fetch. |
| analyticsUrl | string | derived | Only if analytics lives on a different host. |
| countryCode | string | — | Sets d_country / d_currency; required for the card-flow slug. |
| paymentMethods | string[] | ['googlePay','applePay','card'] | Which methods, in order. Aliases accepted. |
| mode | string | 'tab' | Layout — see modes. |
| requireConsent | boolean | true | Render legal consent checkboxes; block payment until ticked. |
| walletRequireConsent | boolean | mirrors requireConsent | Consent toggle for wallet tabs independently. |
| slug, gateway, service, trialPrice, isLocalCurrency, payments | — | — | Inline-mode config fields. |
| onSuccess(result) | fn | — | Payment API success (before gateway redirect). |
| onError(error) | fn | — | Merchant-validation / payment failure. |
Payment methods, modes & consent
Payment methods (paymentMethods)
- Canonical ids:
'card','applePay','googlePay'. Aliases are case/space-insensitive ('apple','apple pay','gpay','credit-card', …). - Default:
['googlePay', 'applePay', 'card']. - A listed method only renders if the device supports it (Apple Pay needs an Apple device;
Google Pay needs
isReadyToPay; the card form is always available).
Display mode (mode)
| Value | Layout |
| --- | --- |
| 'tab' (default) | Two grouped tabs — Pay (Apple Pay + Google Pay) and Card. Tab bar shows only when both groups are available. |
| 'inline' | Wallet group and card form stacked with an "or" divider. No tab bar. |
| 'card-applepay-google-pay' | One tab per method — Card → Apple Pay → Google Pay. |
| 'applepay-google-pay-card' | One tab per method — Apple Pay → Google Pay → Card. |
Consent gating (requireConsent / walletRequireConsent)
| requireConsent | walletRequireConsent | Card | Apple Pay / Google Pay |
| --- | --- | --- | --- |
| true (default) | not set | consent required | consent required |
| false | not set | no consent | no consent |
| true | false | consent required | no consent |
| false | true | no consent | consent required |
Use requireConsent: false when the host page collects consent before mounting.
Callbacks & host tracking
onSuccess(result) fires when the payment API returns success, before the gateway
redirect. onError(error) fires on merchant-validation or payment failure. Both are
advisory — a throwing callback never breaks the flow, and the redirect still happens.
The host can push its own product events through the widget's already-configured
analytics pipeline (same endpoint + rockmanId):
const widget = await DynamicCCPay.mount('#pay', { xcid: 'xph20' });
widget.tracker.customEvent(category, action, label, data); // generic event
widget.tracker.advancedInFlow(flow, step, data); // funnel forward
widget.tracker.recedeInFlow(flow, step, data); // funnel back / drop
widget.tracker.advancedInPreFlow(trigger, data); // pre-payment CTAThe same object is exposed as DynamicCCPay.tracker. It is wired once mount() resolves;
calling earlier is a safe no-op that logs a warning.
Styling
All visual properties read --dccp-* CSS custom properties. Set them on .dynamic-cc-pay
(or any ancestor) to re-theme without a rebuild:
.dynamic-cc-pay {
--dccp-accent: #e11d48; /* primary CTA / focus / active tab */
--dccp-accent-2: #9f1239; /* gradient end */
--dccp-radius: 16px; /* field + button corners */
--dccp-font: 'Poppins', sans-serif;
--dccp-text: #1e293b;
--dccp-muted: #64748b;
--dccp-border: #cbd5e1;
--dccp-field-bg: #ffffff;
--dccp-field-height: 48px;
--dccp-error: #dc2626;
}Development
yarn install
yarn dev # run the widget as a React app w/ live reload → http://localhost:8093
yarn build:embed # → dist/dynamic-cc-pay.js
yarn demo:embed # build + serve demos on http://localhost:8000yarn dev mounts the widget (inline config, src/embed/dev.tsx) in a dev page and
hot-reloads on edits to src/embed/*. No backend needed to render the UI.
Demos (after yarn demo:embed):
embed-xcid-demo-8.html— full interactive demo: mode / methods / consent toggles, host-tracker button.embed-inline-demo.html— inline config (no backend needed to render), with a tracking-verification panel.embed-xcid-demo.html— xcid (page-scrape) config example.
The demo pages load the stable
dynamic-cc-pay.jsURL so they always track the latest deploy. After a deploy, allow a short interval for the CDN to refresh.
Build & deploy
yarn build:embed # single self-contained file, assets inlined
yarn build:lib # library build (dist-lib) for npm consumers — runs on `npm publish`
yarn deploy:embed # build:embed + upload to S3/CDN (needs AWS credentials)deploy:embed uploads a stable key, a content-hashed sibling
(dynamic-cc-pay.<md5>.js, served fresh by the CDN immediately), an embed-manifest.json,
and the demo HTML files.
- Configure the target S3 bucket and key prefix in
upload-embed.js. - Credentials: supply standard AWS credentials via the env vars the deploy script reads.
Publishing to npm
This package publishes as a public unscoped package (publishConfig.access).
prepublishOnly runs build:lib automatically.
npm login # an account with publish rights to this package
npm publishPrerequisites (backend)
The UI renders anywhere, but payments will not complete until these are in place:
apiBaseUrl— required off the SamMedia origin, or all/api/v1/frontend/*calls 404.- CORS — the SamMedia backend must allow the host origin (preflight +
POST) on/api/v1/frontend/ap-validate,/ap-payment,/gp-payment,/initiate-payment-generic, and the analytics store endpoint. - Apple Pay domain registration — the host domain must be registered with Apple and
served over HTTPS, or
ApplePaySessionwon't validate the merchant. (Google Pay needs none.) rockmanId— optional, but supply a real server-issued id in production so tracking is accepted.- Google Pay config —
payments.googlePaymust include the gateway'sallowedCardNetworks/ merchant params, orisReadyToPayfails and the button is hidden. countryCode— the card flow appendsd_country/d_currencyto build the final slug. Supported local-currency countries:se no gb dk us sa ca pl ae qa kw om bh jo is au nz.
Quick triage: buttons missing → config didn't load (check the GET to
${pageUrl||apiBaseUrl}/<xcid>: 404 = wrong xcid/host, CORS error = pages host not allowing the origin) or wallet unavailable on device. Buttons present but tap fails → CORS / 404 on/api/v1/frontend/*or Apple domain not registered.
Project layout
src/embed/ widget entry, mount, config resolution, tracker, styles, README
src/components/ payment UI reused by the widget (card form, wallets, …)
src/providers/ RootContext (shared runtime state)
src/services/ Apple Pay / Google Pay API calls
src/utils/ wallet handlers, config maps, rockmanId helpers
src/localization/ react-intl messages + translations
src/__doNotModify/ ouisys-engine store / flows wiring
webpack.embed.js single-file build (assets inlined)
webpack.lib.js library build (dist-lib) for npm consumers
upload-embed.js S3/CDN deploy (stable + content-hashed + manifest)The complete, exhaustive API reference lives in src/embed/README.md.
