Embedded Experience
Setupβ
Generate a Sessionβ
- From the Merchant's backend, make a call to
/POST sessionsto generatesessionand pass it to the web app. - Widget's capabilities can be customized using
configproperty insessionrequest object - Refer Generate a Session for details
/POST session (request/response)
## /POST sessions request
curl --location --request POST 'https://api.uhg.com/api/financial/commerce/nonprodcheckout/v1/sessions' \
--header 'X-Merchant-Id: <x-merchant-id>' \
--header 'X-Upstream-Env: <x-upstream-env>' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <authorization-token>' \
--data-raw '{
"customer": {
"firstName": "foo",
"lastName": "bar",
"email": "foo.bar@email.com",
"ssnLastFour": "1234",
"dateOfBirth": "1970-31-12",
"phoneNumber": {
"number": "9876543210",
"countryCode": "1"
},
"zip5": "54321",
"hsid": "120c5730-e796-4448-8da9-081fde4e3e79",
"metadata": {}
},
"payment": {
"merchantTransactionId": "f32736c8-266a-4da1-af16-293fa02a351a",
"amount": 1200,
"authorizeCard": false,
},
"config": {
"modes": [
"PAYMENT_METHOD_ENTRY"
]
}
}'
// /POST sessions reponse
{
"url": "/sessions/<CHECKOUT_SESSION_ID>",
"data": {
"sessionId": "<CHECKOUT_SESSION_ID>",
"hostedUrl": "<CCG_DOMAIN>/checkout-sessions/<CHECKOUT_SESSION_ID>"
}
}
Load the widgetβ
<script src="https://<DOMAIN>.healthsafepay.com/wallet/<VERSION>/<FILE_NAME>.js"></script>
- Integrate by adding
<script>tag.optumCCGobject is added to the globalwindowobject
To reduce the load time for widget refer to
DOMAIN: walletstage | walletprod
VERSION: v2 see Version History for more details
FILE_NAME: ccg-widget.js | ccg-widget.min.js
Example:
https://walletstage.healthsafepay.com/wallet/v2/ccg-widget.min.js
https://walletstage.healthsafepay.com/wallet/v2/ccg-widget.js
Access the widget initializerβ
const OptumCCGWidgetInitializer = window.optumCCG.OptumCCGWidgetInitializer;
Initializeβ
const options = {...};
const ccgWidget = OptumCCGWidgetInitializer(options);
OptumCCGWidgetInitializer function is used to setup and manage the widget. This sets up the widget, attaches to DOM and returns the instance
Renderβ
ccgWidget.render();
Makes the embedded payment modal window visible on screen. By default, the widget is not visible when attached to DOM
Full Exampleβ
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CCG Widget Integration</title>
</head>
<body>
<h1>CCG Widget Integration</h1>
<button id="payRemainingBalance">Pay Remaining Balance</button>
<div id="ccg-widget-container"></div>
<!-- 1. Load the widget -->
<script src="https://walletprod.healthsafepay.com/wallet/v2/ccg-widget.min.js"></script>
<script>
let ccgWidget;
document
.querySelector("#payRemainingBalance")
.addEventListener("click", () => {
// 2. Initialize widget once
if (!ccgWiget) {
// 3. Access the widget initializer
const OptumCCGWidgetInitializer =
window.optumCCG.OptumCCGWidgetInitializer;
const options = {
rootElement: document.querySelector("#ccg-widget-container"),
checkoutSessionId: "REPLACE_WITH_CHECKOUT_SESSION_ID",
appEnv: "prod",
onSuccess: (data) => {},
onError: (data) => {},
onEvent: (data) => {},
};
// 4. Initialize
ccgWidget = OptumCCGWidgetInitializer(options);
}
// 5. Render
ccgWidget.render();
});
</script>
</body>
</html>
Removing the widgetβ
ccgWidget.unmount();
ccgWidget = null;
This unmounts the widget from the DOM
Additional Detailsβ
OptumCCGWidgetInitializer method optionsβ
| Option | Values | Description |
|---|---|---|
| rootElement required Element | Valid DOM node | CCG Widget will be injected into this DOM node |
| checkoutSessionId required string | - | Valid checkout session id |
| appEnv required string | stage prod | Widget's environment variable |
appearanceAppearance | default: Optum Theme | Brand/styling configuration see Theming and Styling deprecated >= 2.1.0 Refer /POST sessions on how to pass appearance as part of session creation |
containerConfig objectContainer Config | default: { type: 'modal' } | |
onSuccessFunctionparam type | (data) => void | Called when payment is successful |
onErrorFunctionparam type | (data) => void | Called when there is an error with the checkout session or a payment failure |
onEventFunctionparam type | (data) => void | Called for all other events (i.e., widget loaded, open, closed, PAYMENT_METHOD_SELECTION,SESSION_CONTEXT_UPDATED) |
OptumCCGWidgetInitializer instanceβ
render Functionβ
- show the widget as a modal
- this function would probably be called from your button's onClick handler
unmount Functionβ
- unmount widget from host application
Widget unmounting and re-initializationβ
Following scenarios will require the widget to be unmounted and reinitialized
- When
onErroris called - When
onSuccessis called
To use the widget again you must:
- call unmount on the old instance
ccgWidget.unmount(); - initialize the widget again
and then
ccgWidget = OptumCCGWidgetInitializer(options); // options includes the new checkout session idccgWidget.render();
Guest vs Wallet Experienceβ
Refer Guest vs Wallet Experience
Data Modelsβ
Callback dataβ
{
code: string;
description?: string;
payload?: unknown;
}
Container Configβ
| Option | Values | Description |
|---|---|---|
typemodal, inline, drawer | modal, inline, drawer | Embedded experience variant defaults to modal |
| secondaryContainer Secondary Container | An optional configuration for a inline modal container | |
| autoScrollEnabled Auto Scroll | An optional configuration only for inline container, defaults to false |
Secondary Containerβ
| Property | Values | Description |
|---|---|---|
typemodal | modal |
Auto Scrollβ
| Property | Values | Description |
|---|---|---|
| autoScrollEnabled | true, false | defaults to false |
Container Config Examplesβ
Modalβ
const modalConfig: ContainerConfig = {
type: "modal",
};
Inlineβ
const inlineConfig: ContainerConfig = {
type: "inline",
};
const inlineWithSecondaryConfig: ContainerConfig = {
type: "inline",
secondaryContainer: {
type: "modal",
},
};
Drawerβ
const drawerConfig: ContainerConfig = {
type: "drawer",
};
Inline with Auto Scrollβ
const inlineConfig: ContainerConfig = {
type: "inline",
autoScrollEnabled: true,
};
onSuccess Dataβ
| Property | Values |
|---|---|
| code | PAYMENT_SUCCEEDED |
descriptionstring | |
payload CheckoutSession details |
onError Dataβ
| Property | Type | Values |
|---|---|---|
| code deprecated in favor of title | string | INITIALIZATION_ERROR SESSION_TIMEOUT SESSION_TIMEOUT SESSION_ALREADY_PROCESSED INVALID_REQUEST PAYMENT_METHOD_ERROR |
| description deprecated in favor of description | string | |
| payload (optional) | CheckoutSession | |
| title | string | INITIALIZATION_ERROR SESSION_TIMEOUT SESSION_TIMEOUT SESSION_ALREADY_PROCESSED INVALID_REQUEST PAYMENT_METHOD_ERROR |
| detail | string | |
| status (optional) | number | |
| errorDetails supported starting v2.10.0 populated only for payment related error | null or ErrorDetails |
onEvent Dataβ
| Property | Values |
|---|---|
| code | WIDGET_LOADED WIDGET_OPEN WIDGET_CLOSED PAYMENT_METHOD_SELECTION SESSION_CONTEXT_UPDATED |
description (optional) string |
CheckoutSessionβ
{
checkoutSession: {
id: string;
customerId: string;
paymentId: string;
checkoutSessionStatus: 'COMPLETED' | 'FAILED';
merchantId: string;
vendorMerchantId: string;
checkoutRequest: {
statementDescriptorSuffix: string;
amount: number;
merchantTransactionId: string;
authorizeCard: boolean; // true = PRE_AUTH; false = SALE;
paymentType: string; // DEPRECATED; possible values: SALE, PRE_AUTH;
metadata: {
[key: string]: string | undefined;
};
};
error: null; // backward compatability only; For error details, inspect 'onError' callbacks
};
}
Eventsβ
onError Scenariosβ
Network Error
{
"code": "INITIALIZATION_ERROR",
"description": "Network Error"
}
Attempt to initialize CCG Widget with empty "checkoutSessionId"
{
code: "INVALID_SESSION",
description: "session id is invalid",
}
Attempt to initialize CCG Widget with an invalid value for "checkoutSessionId" (eg: "a12u", doesn't conform to UUID format)
{
code: "INVALID_REQUEST",
description: "Please review API specs https://docs.healthsafepay.com/api-reference/",
}
Attempt to initialize CCG Widget with expired "checkoutSessionId"
{
code: "SESSION_TIMEOUT",
description: "session no longer valid",
}
Attempt to initialize CCG Widget with canceled "checkoutSessionId"
{
code: "SESSION_CANCELED",
description: "Your session is no longer valid",
}
Attempt to initialize CCG Widget with "checkoutSessionId" that's already processed (succeeded/failed)
{
code: "SESSION_ALREADY_PROCESSED",
description: "session is already processed",
}
Payment Failure
{
"code": "PAYMENT_METHOD_ERROR",
"description": "Your card has insufficient funds.",
"payload": {
"checkoutSession": {
"checkoutRequest": {
"merchantTransactionId": "xxx-xxx-xxx-xxx-xxx",
"amount": 1500
},
"paymentId": "xxx-xxx-xxx-xxx-xxx"
}
},
"detail": "Your card has insufficient funds.",
"status": 405,
"title": "PAYMENT_METHOD_ERROR"
}
onEvent Scenariosβ
Widget loaded
{
code: "WIDGET_LOADED";
}
Widget open
{
code: "WIDGET_OPEN";
}
Widget closed
{
code: "WIDGET_CLOSED";
}
Payment Method Selection
{
code: "PAYMENT_METHOD_SELECTION";
}
Session Context Updated
When a user enters a PCI form, then the onEvent callback is called with the following data:
{
code: 'SESSION_CONTEXT_UPDATED',
title: 'SESSION_CONTEXT_UPDATED',
data: {
sessionContext: {
pci: {
active: true
}
}
}
}
When a user exits a PCI form, then the onEvent callback is called with the following data:
{
code: 'SESSION_CONTEXT_UPDATED',
title: 'SESSION_CONTEXT_UPDATED',
data: {
sessionContext: {
pci: {
active: false
}
}
}
}
To know more details on PCI notification click Link to Widget PCI Notification
onSuccess Scenariosβ
Payment Success
{
"code": "PAYMENT_SUCCEEDED",
"description": "Payment was successfully processed",
"payload": {
"checkoutSession": {
"id": "xxx-xxx-xxx-xxx-xxx",
"checkoutRequest": {
"paymentType": "SALE",
"authorizeCard": false,
"merchantTransactionId": "xxx-xxx-xxx-xxx-xxx",
"amount": 10012,
"statementDescriptorSuffix": null
}
}
},
"data": {
"sessionId": "xxx-xxx-xxx-xxx-xxx",
"status": "COMPLETED",
"payment": {
"id": "xxx-xxx-xxx-xxx-xxx",
"amount": 10012,
"description": "xxx-xxx-xxx-xxx-xxx",
"merchantTransactionId": "xxx-xxx-xxx-xxx-xxx",
"merchantId": "xxx-xxx-xxx-xxx-xxx",
"paymentType": "SALE",
"currencyCode": "usd",
"status": "COMPLETED",
"paymentMethodId": "xxx-xxx-xxx-xxx-xxx",
"metadata": {
"checkoutId": "xxx-xxx-xxx-xxx-xxx",
"merchantId": "xxx-xxx-xxx-xxx-xxx",
"referenceId": "xxx-xxx-xxx-xxx-xxx",
"merchantName": "xxx-xxx-xxx-xxx-xxx",
"ccg_nameoncard": "xxx-xxx-xxx-xxx-xxx",
"merchantGroupId": "xxx-xxx-xxx-xxx-xxx",
"merchantTransactionId": "xxx-xxx-xxx-xxx-xxx",
"ccg_paymentmethodnickname": "xxx-xxx-xxx-xxx-xxx"
},
"error": null,
"statementDescriptorSuffix": null,
"paymentDetails": {
"healthcare": {
"iias": {
"qualifiedAmount": 700,
"qualifiedAmountDetails": {
"prescriptionAmount": 1200
}
},
"visionAmount": 25
}
},
"paymentMethod": null
}
}
}
Theming and Stylingβ
- Refer Theming and Styling
Alternative Setup - Module (npm/yarn)β
- v2
- v3
Install the packageβ
This package is available in UHG Shared repository.
yarn add @uhg-ccg-proj/convenient-checkout-ui --save
or
npm install @uhg-ccg-proj/convenient-checkout-ui --save
Load the widgetβ
import { OptumCCGWidgetInitializer } from "@uhg-ccg-proj/convenient-checkout-ui/dist/widget/ccg-widget.min";
Initializeβ
const options = {
rootElement: document.querySelector("#ccg-widget-container"),
checkoutSessionId: "REPLACE_WITH_CHECKOUT_SESSION_ID",
appEnv: "prod",
};
// 3. Initialize
const ccgWidget = OptumCCGWidgetInitializer(options);
Renderβ
ccgWidget.render();
Full Example (npm)β
import React, { useRef, useState } from "react";
/* 1. include ccg widget as COMPILE-TIME dependency */
import { OptumCCGWidgetInitializer } from "@uhg-ccg-proj/convenient-checkout-ui/dist/widget/ccg-widget.min";
const PaymentWidget = ({
appEnv,
checkoutSessionId,
containerType,
onSuccess,
onError,
onEvent,
}) => {
/* 4. CCG widget type (required) */
let ccgWidget: ReturnType<typeof OptumCCGWidgetInitializer>;
const widgetContainer = useRef(null);
useEffect(() => {
if (widgetContainer.current) {
// 5. Initialize
ccgWidget = OptumCCGWidgetInitializer({
rootElement: widgetContainer.current,
checkoutSessionId,
appEnv,
containerConfig: {
type: containerType,
},
onSuccess,
onError,
onEvent,
});
// 6. display
ccgWidget.render();
}
}, []);
return <div ref={widgetContainer}></div>;
};
export default PaymentWidget;
Overview (Bootsrapper Module)β
The @uhg-ccg-proj/convenient-checkout-ui package is a lightweight (~120KB) loader that acts as an integration layer between your application and the full widget hosted on our CDN. Key benefits include:
- Small Bundle Size: Only the bootstrapper is included in your app bundle, while the full widget is fetched from the CDN, resulting in faster load times
- Automatic Business Feature Updates: Consumer apps using the v3 bootstrapper package will continue receiving business feature updates, security patches, and bug fixes automatically without requiring npm version updates or redeployments
- Minimal Maintenance: The widget updates automatically via CDNβno need to monitor releases, update dependencies, or coordinate deployments across teams
- Simple Package Updates: You only need to update the bootstrapper package occasionally for new TypeScript definitions
- Consistent Version: All merchants receive the same certified widget version simultaneously, eliminating version fragmentation
NPM Flow Comparisonβ
This section illustrates how the bootstrapper changes the developer experience and runtime behavior.
π Click to view detailed flow comparison diagrams
v2 NPM Architectureβ
v3 Bootstrapper Architectureβ
v2 NPM Integrationβ
Consumer App
ββ> import OptumCCGWidgetInitializer from '@uhg-ccg-proj/convenient-checkout-ui'
ββ> OptumCCGWidgetInitializer({ ...props })
ββ> Renders Widget (from local node_modules)
v3 NPM Integration with Bootstrapperβ
Consumer App
ββ> import { CCGWidgetInitializer } from '@uhg-ccg-proj/convenient-checkout-ui'
ββ> new CCGWidgetInitializer({ ...props })
ββ> CCGWidgetCdnLoader.load(appEnv)
ββ> Fetch: https://walletprod.healthsafepay.com/wallet/v2/ccg-widget.min.js
ββ> window.optumCCG (OptumCCGWidgetInitializer)
ββ> Renders Widget (from CDN bundle)
Install the packageβ
This package is available in UHG Shared repository.
yarn add @uhg-ccg-proj/convenient-checkout-ui --save
or
npm install @uhg-ccg-proj/convenient-checkout-ui --save
Load the widgetβ
Use the provided CCGWidgetInitializer and types for a fully typed integration:
import { CCGWidgetInitializer } from "@uhg-ccg-proj/convenient-checkout-ui";
Initialize & Render the Widgetβ
let widgetInstance = null;
const widgetOptions = {
rootElement: document.getElementById('widget-container');,
checkoutSessionId: "REPLACE_WITH_CHECKOUT_SESSION_ID",
appEnv: 'prod',
containerConfig: { type: 'modal' },
loaderConfig: { showLoader: true },
onRenderStart: () => {},
onRenderComplete: () => {},
onSuccess: (data) => {},
onError: (error) => {},
onEvent: (event) => {},
};
const widget = new CCGWidgetInitializer(widgetOptions);
widgetInstance = widget;
widget.render();
Accessing Full Type Definitions:β
For detailed intellisense and autocomplete on nested properties (like ContainerConfig, OnSuccessCallback, etc.), import the specific types you need.
Only the types listed below are available for import in your consumer application. These are the complete set of type definitions exported by the @uhg-ccg-proj/convenient-checkout-ui package.
Click to view all available type definitions
import type {
CCGWidgetInitializerArgs,
AppEnv,
ContainerConfig,
OnErrorCallback,
OnEventCallback,
OnSuccessCallback,
LoaderConfig,
ErrorCallbackArgs,
MessageCallbackArgs,
SuccessCallbackArgs,
PreloadStatus,
CheckoutSessionStatus,
PaymentSessionOutcome,
PaymentMethodSessionOutcome,
Agent,
PaymentDetails,
ErrorCode,
MessageCode,
SessionContext,
} from '@uhg-ccg-proj/convenient-checkout-ui';
const containerConfig: ContainerConfig = { type: 'modal' };
const onSuccess: OnSuccessCallback = (data: SuccessCallbackArgs) => {
console.log('Payment successful:', data);
};
React Integration Example (npm)β
Click to view full React integration example
import { useRef, useState, useEffect } from 'react';
import { createCCGBootstrapLoaderSpinner } from './Loader';
import { CCGWidgetInitializer } from '@uhg-ccg-proj/convenient-checkout-ui';
import type {
CCGWidgetInitializerArgs,
ContainerConfig,
AppEnv,
OnErrorCallback,
OnEventCallback,
OnSuccessCallback,
ErrorCallbackArgs,
MessageCallbackArgs,
SuccessCallbackArgs,
LoaderConfig,
PreloadStatus,
} from '@uhg-ccg-proj/convenient-checkout-ui';
function App() {
const [isInitializing, setIsInitializing] = useState(false);
const [showInternalLoader, setShowInternalLoader] = useState(false);
const [preloadStatus, setPreloadStatus] = useState<PreloadStatus>('idle');
const [preloadError, setPreloadError] = useState<string | null>(null);
const widgetContainerRef = useRef<HTMLDivElement>(null);
const widgetInstanceRef = useRef<CCGWidgetInitializer | null>(null);
// loader config
const loaderConfig: LoaderConfig = {
showLoader: false,
};
useEffect(() => {
// Register error callback
CCGWidgetInitializer.onPreloadError((error) => {
console.error('[Test Harness] Preload failed:');
setPreloadStatus('error');
setPreloadError(error.message);
});
// Register success callback
CCGWidgetInitializer.onPreloadSuccess(() => {
console.log('[Test Harness] Preload succeeded! Widget is cached and ready.');
setPreloadStatus('success');
});
// Check initial status
const { status, error } = CCGWidgetInitializer.getPreloadStatus();
setPreloadStatus(status);
if (error) {
setPreloadError(error.message);
}
}, []);
// drive internal loader from isInitializing
useEffect(() => {
if (!loaderConfig.showLoader && isInitializing) {
setShowInternalLoader(true);
} else {
setShowInternalLoader(false);
}
}, [isInitializing, loaderConfig.showLoader]);
const handlePayClick = () => {
if (!widgetContainerRef.current) {
console.error('Cannot initialize widget: root container element not found.');
return;
};
if (!widgetInstanceRef.current) {
const appEnv: AppEnv = 'stage';
const containerConfig: ContainerConfig = { type: 'modal' };
widgetInstanceRef.current = new CCGWidgetInitializer({
rootElement: widgetContainerRef.current,
checkoutSessionId: 'REPLACE_WITH_YOUR_SESSION_ID',
appEnv,
containerConfig,
loaderConfig,
onRenderStart: () => {
console.log('EVENT: onRenderStart triggered.');
setIsInitializing(true);
},
onRenderComplete: () => {
console.log('EVENT: onRenderComplete triggered.');
setIsInitializing(false);
},
onSuccess: (data) => {
console.log('EVENT: onSuccess triggered with data:', data);
alert('Payment Successful!');
},
onError: (error) => {
console.error('EVENT: onError triggered with error:', error);
setIsInitializing(false);
alert(`An error occurred: ${error.detail || 'Unknown error'}`);
},
onEvent: (event) => {
console.log('EVENT: onEvent triggered:', event);
},
});
}
widgetInstanceRef.current.render();
};
useEffect(() => {
const widget = widgetInstanceRef.current;
return () => {
widget?.unmount();
};
}, []);
return (
<div className="App">
{showInternalLoader && (
<div
id="ccg-bootstrapper-loader"
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
zIndex: 2147483647,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div dangerouslySetInnerHTML={{ __html: createCCGBootstrapLoaderSpinner().outerHTML }} />
</div>
</div>
)}
<header className="App-header">
<button onClick={handlePayClick} disabled={isInitializing} className="pay-button">
{isInitializing ? 'Loading...' : 'Pay Now'}
</button>
</header>
<main>
<div id="widget-container" ref={widgetContainerRef}></div>
</main>
</div>
);
}
export default App;
autoPreloadβ
The autoPreload option controls whether widget assets are preloaded automatically in the background when the bootstrapper module is imported. By default, autoPreload is set to true, which means the widget begins downloading from the CDN immediately to ensure faster render times when needed.
To disable automatic preloading, set autoPreload: false in the global configuration before importing the widget module:
window.__CCG_WIDGET_CONFIG__ = {
autoPreload: false
};
Loader Appearanceβ
The bootstrapper provides a default loader spinner during the widget initialization phase. You can disable this default loader by setting loaderConfig.showLoader = false and provide your own custom loader UI.
Monitoring Preload Statusβ
The bootstrapper provides three methods to monitor and respond to background preload operations. These are useful for analytics, error handling, and providing user feedback about widget readiness.
1. onPreloadError - Handle Preload Failuresβ
Register a callback to be notified when automatic background preloading fails. This allows you to track CDN issues, log errors to analytics, or show fallback UI.
CCGWidgetInitializer.onPreloadError((error) => {
console.error('CCG Widget preload failed:', error);
setPreloadStatus('error');
setPreloadError(error.message);
});
2. onPreloadSuccess - Confirm Widget Readinessβ
Register a callback to be notified when preloading succeeds. This confirms the widget is cached and ready for instant rendering.
CCGWidgetInitializer.onPreloadSuccess(() => {
console.log('CCG Widget preload succeeded! Widget is cached and ready.');
setPreloadStatus('success');
});
3. getPreloadStatus - Query Current Stateβ
Check the current preload status synchronously at any time. Useful for conditional logic and debugging.
const { status, error } = CCGWidgetInitializer.getPreloadStatus();
if (status === 'success') {
console.log('Widget cached - ready for instant render');
} else if (status === 'error') {
console.error('Preload failed:', error?.message);
} else if (status === 'loading') {
console.log('Widget preload in progress...');
}
Status values:β
idle - Preload not started (autoPreload disabled or SSR environment)
loading - Background CDN download in progress
success - Widget cached and ready for instant render
error - Preload failed (widget will load on render() instead)
Example with Preload Monitoringβ
Click to view preload monitoring example
const [preloadStatus, setPreloadStatus] = useState<PreloadStatus>('idle');
const [preloadError, setPreloadError] = useState<string | null>(null);
useEffect(() => {
// Register error callback
CCGWidgetInitializer.onPreloadError((error) => {
console.error('[Test Harness] Preload failed:', error);
setPreloadStatus('error');
setPreloadError(error.message);
});
// Register success callback
CCGWidgetInitializer.onPreloadSuccess(() => {
console.log('[Test Harness] Preload succeeded! Widget is cached and ready.');
setPreloadStatus('success');
});
// Check initial status
const { status, error } = CCGWidgetInitializer.getPreloadStatus();
console.log('[Test Harness] Initial preload status:', status, error);
setPreloadStatus(status);
}, []);
- Preload callbacks monitor the background optimization phase (automatic on import).
- onRenderStart/onRenderComplete monitor the user-triggered render phase (when widget displays).
- These are complementary - preload happens once on page load, render happens when user clicks "Pay/Checkout".
- If preload fails, the widget will try to download on render instead.
Configure Global Bootstrapper Optionsβ
Before your app loads, you can set global options for the bootstrapper by defining window. CCG_WIDGET_CONFIG before importing the widget module. This allows you to control asset preloading and select the correct CDN environment.
// Set these BEFORE any module loads
window.__CCG_WIDGET_CONFIG__ = {
autoPreload: false, // disables automatic asset preloading
cdnEnv: 'stage' // downloads CDN asset from the 'stage' environment
};
Option Detailsβ
autoPreload:β
If true (default), widget assets are preloaded in the background for faster user experience at import time of CCGWidgetInitializer. If false, assets are only loaded when the widget is rendered. Set this globally or leave it unset for default behavior.
cdnEnv:β
Controls which CDN environment the widget assets are downloaded from ('prod', 'stage'). Set this to match your consumer app's environment for correct asset loading.
Things to Keep in Mind about CCG Bootstrapper NPMβ
- Use CCGWidgetInitializer and CCGWidgetInitializerArgs for a fully typed, modern integration.
- Set
loaderConfig.showLoadertofalseto display your own custom loader. - Use
onRenderStartandonRenderCompleteto manage UI state during widget initialization. - Use
onPreloadError,onPreloadSuccess, andgetPreloadStatusto monitor background CDN preloading for analytics and error handling. - Set
autoPreload: true(or omit for default) to maximize performance.
Improve Page Load Time with cdnβ
To Improve the page load time use "async" and "defer" which significantly reduces the "DOMContentloadtime" which doesn't block the page and makes it available for users while the script is being loaded in background.
Code Snippet for using async and deferβ
document.getElementById("ccgScript").addEventListener("load", (event) => {
initWidget();
renderWidget();
});
where βccgScriptβ is the id for <script > tag for ccg-widget.min.js.
<script id="ccgScript" src="https://walletprod.healthsafepay.com/wallet/v2/ccg-widget.min.js" async defer></script>
Widget PCI Notificationβ
Aims to implement notifications for merchants when their agents/customers enter or exit forms that handle PCI (Payment Card Industry) data, such as "Add Payment Method" or "One-Time Payment" forms
Forms in Scopeβ
Add Payment MethodandMake A Paymentfor Card and ACH transactions.
Out of Scopeβ
Text-basedorEmail-basedorSycurioflows.
Target Audienceβ
- Agents: Notifications should trigger for
Agentswho interact with the forms. - Authenticated Users/Customers or Guests: Notifications should trigger for
CustomersorGuestswho interact with the forms.
- This is purely a UI change and is not controlled via a feature flag in the Merchant settings.
- Additionally, this change is currently supported only in an
Embedded Experience.