Hosted Experience
Setup
Generate a Session
- From the Merchant's backend, make a call to
/POST sessions
to generatesession
and pass it to the web app - Widget's capabilities can be customized using
config
property insession
request object - Refer Generate a Session for details
/POST session (request/response)
## /POST sessions request
curl --location --request POST '<HCP_DOMAIN>/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>/app?checkoutSessionId=<CHECKOUT_SESSION_ID>"
}
}
Initialize
- use
session_response.hostedUrl
from session response to open the widget as a popup
Launch widget as popup
// update to include left/height dyna props
// openCCGWidget
const openCCGWidget = (hostedUrl) => {
const width = 400;
const height = 850;
const left = window.screen.width / 2 - width / 2;
if(!widgetPopupInstance || widgetPopupInstance?.closed) {
widgetPopupInstance = window.open(
hostedUrl,
"_blank",
`popup=true,width=${width},height=${height},left=${left}`
);
} else {
widgetPopupInstance.location = hostedUrl;
widgetPopupInstance.focus();
}
};
};
Poll for session status
Start polling on session_response.url
from session response to know the session status
Poll session status
// poll for session status
const pollSessionStatus = ({ url, onCompleted, onError }) => {
setTimeout(async () => {
const response = await fetch(url);
const session = await response.json();
if (response.status >= 400 && response.status < 500) {
onError(session);
return;
}
const status = session.status;
if (status === "COMPLETED") {
onCompleted(session);
} else {
console.info(`session status: ${status}; continue to poll...`);
pollSessionStatus({
url,
onCompleted,
onError,
});
}
}, 2000); // Poll every 2 seconds
};
Close widget
We recommend closing the widget upon session completion (success or error).
Close popup (widget)
// closeCCGWidget
const closeCCGWidget = () => {
widgetPopupInstance?.close();
};
Handle close popup
Notifies or Cancels widget being closed, an interval timer defined by POPUP_WINDOW_CLOSED_CHECK_INTERVAL
is used to check if a popup window has been closed. If the popup window is closed, the timer is cleared using window.clearInterval()
.
Cancel popup (widget)
const pollTimer = window.setInterval(() => {
if (win?.closed !== false) {
// react to the closing of popup window.
}
}, POPUP_WINDOW_CLOSED_CHECK_INTERVAL);
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="checkoutBtn">Pay Remaining Balance</button>
<script>
(() => {
let widgetPopupInstance;
let popupWindowCancelPollingInterval;
const POPUP_WINDOW_CLOSED_CHECK_INTERVAL = 500;
const reset = () => {
widgetPopupInstance = null;
};
// create session request object
const createSessionRequestObject = () => {
const customer = {
firstName: "foo",
lastName: "bar",
dateOfBirth: "1970-31-12",
hsid: "120c5730-e796-4448-8da9-081fde4e3e79",
};
const payment = {
merchantTransactionId: "f32736c8-266a-4da1-af16-293fa02a351a",
amount: 1200,
authorizeCard: false,
metadata: {
invoice: "inv-9023",
},
};
const config = {
modes: ["PAYMENT_WITH_WALLET"],
};
return {
customer,
payment,
config,
};
};
// generate session
const generateSessionFromBackend = async () => {
const { customer, payment, config } = createSessionRequestObject();
// Due to security reasons,
// merchant's web app can't directly talk to CCG's API to generate session ID.
// Instead, merchant web app communicates with their backend service,
// which inturn securely talks to CCG's API for generating session.
const response = await fetch("/merchant-api/sessions", {
method: "POST",
body: JSON.stringify({
customer,
payment,
config,
}),
});
return response.json();
};
// poll for session status
const pollSessionStatus = ({ url, onCompleted, onError }) => {
setTimeout(async () => {
const response = await fetch(url);
const session = await response.json();
if (response.status >= 400 && response.status < 500) {
onError(session);
return;
}
const status = session.status;
if (status === "COMPLETED") {
onCompleted(session);
} else {
console.info(`session status: ${status}; continue to poll...`);
pollSessionStatus({
url,
onCompleted,
onError,
});
}
}, 2000); // Poll every 2 seconds
};
// onCompleted callback to handle session completion
const onCompleted = (session) => {
console.info("session complete", session);
closeCCGWidget();
reset();
};
// onError callback to handle session error
const onError = ({ title, detail, status }) => {
console.error("Session errored", { title, detail, status });
closeCCGWidget();
reset();
};
const onClosePopup = () => {
console.log("popup window was closed.");
};
// open CCGWidget
const openCCGWidget = (hostedUrl) => {
const width = 400;
const height = 850;
const left = window.screen.width / 2 - width / 2;
if (!widgetPopupInstance || widgetPopupInstance?.closed) {
widgetPopupInstance = window.open(
hostedUrl,
"_blank",
`popup=true,width=${width},height=${height},left=${left}`
);
} else {
widgetPopupInstance.location = hostedUrl;
widgetPopupInstance.focus();
}
};
// handle popup close
popupWindowCancelPollingInterval = window.setInterval(() => {
if (widgetPopupInstance?.closed !== false) {
onClosePopup();
clearInterval(popupWindowCancelPollingInterval);
}
}, POPUP_WINDOW_CLOSED_CHECK_INTERVAL);
};
// close CCGWidget
const closeCCGWidget = () => {
widgetPopupInstance?.close();
};
// setup EventListeners: wire up checkout button to generate session, launch and manage hosted widget;
const setupEventListeners = () => {
const checkoutBtn = document.querySelector("#checkoutBtn");
checkoutBtn.addEventListener("click", async (event) => {
event.preventDefault();
closeCCGWidget();
const session = await generateSessionFromBackend();
const hostedUrl = session?.data?.hostedUrl;
if (hostedUrl) {
// #3. open widget as popup
openCCGWidget(hostedUrl);
// #4. poll for status
pollSessionStatus({
url: session.url,
onCompleted,
onError,
});
} else {
onError(session);
}
});
};
// Initialize application
const init = () => {
setupEventListeners();
};
// Start here
init();
})();
</script>
</body>
</html>