@foxy.io/sdk
Version:
Universal SDK for a full server-side and a limited in-browser access to Foxy hAPI.
167 lines (166 loc) • 6.91 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
/**
* A convenience wrapper for the payment card embed iframe. You don't have to use
* this class to embed the payment card iframe, but it provides a more convenient
* way to interact with the iframe and listen to its events.
*
* @example
* const embed = new PaymentCardEmbed({
* url: 'https://embed.foxy.io/v1.html?template_set_id=123'
* });
*
* await embed.mount(document.body);
* console.log('Token:', await embed.tokenize());
*/
export class PaymentCardEmbed {
constructor(_a) {
var { url } = _a, config = __rest(_a, ["url"]);
/**
* An event handler that is triggered when Enter is pressed in the card form.
* This feature is not available for template sets configured with the `stripe_connect`
* hosted payment gateway due to the limitations of Stripe.js.
*/
this.onsubmit = null;
this.__tokenizationRequests = [];
this.__iframeMessageHandler = (evt) => {
var _a, _b;
const data = JSON.parse(evt.data);
switch (data.type) {
case 'tokenization_response': {
const request = this.__tokenizationRequests.find(r => r.id === data.id);
data.token ? request === null || request === void 0 ? void 0 : request.resolve(data.token) : request === null || request === void 0 ? void 0 : request.reject();
this.__tokenizationRequests = this.__tokenizationRequests.filter(r => r.id !== data.id);
break;
}
case 'submit': {
(_a = this.onsubmit) === null || _a === void 0 ? void 0 : _a.call(this);
break;
}
case 'resize': {
if (this.__iframe)
this.__iframe.style.height = data.height;
break;
}
case 'ready': {
this.configure(this.__config);
(_b = this.__mountingTask) === null || _b === void 0 ? void 0 : _b.resolve();
break;
}
}
};
this.__iframeLoadHandler = (evt) => {
if (this.__channel) {
const contentWindow = evt.currentTarget.contentWindow;
if (!contentWindow)
throw new Error('Content window is not available.');
contentWindow.postMessage('connect', '*', [this.__channel.port2]);
}
};
this.__mountingTask = null;
this.__channel = null;
this.__iframe = null;
this.__config = config;
this.__url = url;
}
/**
* Updates the configuration of the payment card embed.
* You can change style, translations, language and interactivity settings.
* To change the URL of the payment card embed, you need to create a new instance.
* You are not required to provide the full configuration object, only the properties you want to change.
*
* @param config - The new configuration.
*/
configure(config) {
var _a;
this.__config = config;
const message = Object.assign({ type: 'config' }, config);
(_a = this.__channel) === null || _a === void 0 ? void 0 : _a.port1.postMessage(JSON.stringify(message));
}
/**
* Requests the tokenization of the card data.
*
* @returns A promise that resolves with the tokenized card data.
*/
tokenize() {
return new Promise((resolve, reject) => {
if (this.__channel) {
const id = this._createId();
this.__tokenizationRequests.push({ id, reject, resolve });
this.__channel.port1.postMessage(JSON.stringify({ id, type: 'tokenization_request' }));
}
else {
reject();
}
});
}
/**
* Safely removes the embed iframe from the parent node,
* closing the message channel and cleaning up event listeners.
*/
unmount() {
var _a, _b, _c, _d, _e, _f;
(_a = this.__channel) === null || _a === void 0 ? void 0 : _a.port1.removeEventListener('message', this.__iframeMessageHandler);
(_b = this.__channel) === null || _b === void 0 ? void 0 : _b.port1.close();
(_c = this.__channel) === null || _c === void 0 ? void 0 : _c.port2.close();
this.__channel = null;
(_d = this.__iframe) === null || _d === void 0 ? void 0 : _d.removeEventListener('load', this.__iframeLoadHandler);
(_e = this.__iframe) === null || _e === void 0 ? void 0 : _e.remove();
this.__iframe = null;
(_f = this.__mountingTask) === null || _f === void 0 ? void 0 : _f.reject();
this.__mountingTask = null;
}
/**
* Mounts the payment card embed in the given root element. If the embed is already mounted,
* it will be unmounted first.
*
* @param root - The root element to mount the embed in.
* @returns A promise that resolves when the embed is mounted.
*/
mount(root) {
this.unmount();
this.__channel = this._createMessageChannel();
this.__channel.port1.addEventListener('message', this.__iframeMessageHandler);
this.__channel.port1.start();
this.__iframe = this._createIframe(root);
this.__iframe.addEventListener('load', this.__iframeLoadHandler);
this.__iframe.style.transition = 'height 0.15s ease';
this.__iframe.style.margin = '-2px';
this.__iframe.style.height = '100px';
this.__iframe.style.width = 'calc(100% + 4px)';
this.__iframe.src = this.__url;
root.append(this.__iframe);
return new Promise((resolve, reject) => {
this.__mountingTask = { reject, resolve };
});
}
/**
* Clears the card data from the embed.
* No-op if the embed is not mounted.
*/
clear() {
var _a;
(_a = this.__channel) === null || _a === void 0 ? void 0 : _a.port1.postMessage(JSON.stringify({ type: 'clear' }));
}
/* v8 ignore next */
_createMessageChannel() {
return new MessageChannel();
}
/* v8 ignore next */
_createIframe(root) {
return root.ownerDocument.createElement('iframe');
}
/* v8 ignore next */
_createId() {
return `${Date.now()}${Math.random().toFixed(6).slice(2)}`;
}
}