1 import noop from '@proton/utils/noop';
7 CbIframeResponseStatus,
8 ChargebeeSavedCardAuthorizationSuccess,
9 ChargebeeSubmitDirectDebitEventPayload,
10 ChargebeeSubmitEventPayload,
11 ChargebeeSubmitEventResponse,
12 ChargebeeVerifySavedCardEventPayload,
14 GetHeightResponsePayload,
16 PaypalAuthorizedPayload,
17 PaypalCancelledMessage,
20 SavedCardVerificationFailureMessage,
21 SavedCardVerificationSuccessMessage,
22 ThreeDsChallengeMessage,
23 ThreeDsChallengePayload,
25 ThreeDsRequiredForSavedCardMessage,
26 UnhandledErrorMessage,
30 paypalAuthorizedMessageType,
31 paypalCancelledMessageType,
32 paypalClickedMessageType,
33 paypalFailedMessageType,
34 threeDsChallengeMessageType,
37 import { addCheckpoint, chargebeeWrapperVersion, getCheckpoints } from './checkpoints';
39 function isChargebeeEvent(event: any): boolean {
40 return !!event?.cbEvent;
43 type SendResponseToParent<T> = (response: MessageBusResponse<T>) => void;
45 // SetConfigurationEvent
47 export type SetConfigurationEvent = {
48 type: 'set-configuration';
49 correlationId: string;
52 function isSetConfigurationEvent(event: any): event is SetConfigurationEvent {
53 return event?.type === 'set-configuration';
58 export type ChargebeeSubmitEvent = {
59 type: 'chargebee-submit';
60 correlationId: string;
61 } & ChargebeeSubmitEventPayload;
63 export function isChargebeeSubmitEvent(event: any): event is ChargebeeSubmitEvent {
64 return event?.type === 'chargebee-submit';
67 export type OnSubmitHandler = (
68 event: ChargebeeSubmitEvent,
69 sendResponseToParent: SendResponseToParent<ChargebeeSubmitEventResponse>
72 export type SetPaypalPaymentIntentEvent = {
73 type: 'set-paypal-payment-intent';
74 correlationId: string;
75 paypalButtonHeight?: number;
76 } & ChargebeeSubmitEventPayload;
78 export function isSetPaypalPaymentIntentEvent(event: any): event is SetPaypalPaymentIntentEvent {
79 return event?.type === 'set-paypal-payment-intent';
84 export type GetHeightEvent = {
86 correlationId: string;
89 export function isGetHeightEvent(event: any): event is GetHeightEvent {
90 return event?.type === 'get-height';
93 export type OnSetPaypalPaymentIntentHandler = (
94 event: SetPaypalPaymentIntentEvent,
95 sendResponseToParent: SendResponseToParent<void>
100 export type GetBinEvent = {
102 correlationId: string;
105 export function isGetBinEvent(event: any): event is GetBinEvent {
106 return event?.type === 'get-bin';
109 export type OnGetBinHandler = (event: GetBinEvent, sendResponseToParent: SendResponseToParent<BinData | null>) => void;
113 export type ValidateFormEvent = {
114 type: 'validate-form';
115 correlationId: string;
118 export function isValidateFormEvent(event: any): event is ValidateFormEvent {
119 return event?.type === 'validate-form';
122 export type OnValidateFormHandler = (
123 event: ValidateFormEvent,
124 sendResponseToParent: SendResponseToParent<FormValidationErrors>
127 // VerifySavedCardEvent
129 export const verifySavedCardMessageType = 'chargebee-verify-saved-card';
131 export type VerifySavedCardEvent = {
132 type: typeof verifySavedCardMessageType;
133 correlationId: string;
134 } & ChargebeeVerifySavedCardEventPayload;
136 export function isVerifySavedCardEvent(event: any): event is VerifySavedCardEvent {
137 return event?.type === verifySavedCardMessageType;
140 export type OnVerifySavedCardHandler = (
141 event: VerifySavedCardEvent,
142 sendResponseToParent: SendResponseToParent<ChargebeeSubmitEventResponse>
145 // ChangeRenderModeEvent
147 export const changeRenderModeMessageType = 'change-render-mode';
149 export type ChangeRenderModeEvent = {
150 type: typeof changeRenderModeMessageType;
151 correlationId: string;
152 renderMode: CardFormRenderMode;
155 export function isChangeRenderModeEvent(event: any): event is ChangeRenderModeEvent {
156 return event?.type === changeRenderModeMessageType;
159 export type OnChangeRenderModeHandler = (
160 event: ChangeRenderModeEvent,
161 sendResponseToParent: SendResponseToParent<{}>
166 export const updateFieldsMessageType = 'update-fields';
168 export type UpdateFieldsEvent = {
169 type: typeof updateFieldsMessageType;
170 correlationId: string;
171 } & UpdateFieldsPayload;
173 export function isUpdateFieldsEvent(event: any): event is UpdateFieldsEvent {
174 return event?.type === updateFieldsMessageType;
177 export type OnUpdateFieldsHandler = (event: UpdateFieldsEvent, sendResponseToParent: SendResponseToParent<{}>) => void;
179 // onDirectDebitSubmit handler
180 export const directDebitSubmitMessageType = 'direct-debit-submit';
182 export type DirectDebitSubmitEvent = {
183 type: typeof directDebitSubmitMessageType;
184 correlationId: string;
185 } & ChargebeeSubmitDirectDebitEventPayload;
187 export function isDirectDebitSubmitEvent(event: any): event is DirectDebitSubmitEvent {
188 return event?.type === directDebitSubmitMessageType;
191 export type OnDirectDebitSubmitHandler = (
192 event: DirectDebitSubmitEvent,
193 sendResponseToParent: SendResponseToParent<{}>
196 export interface ParentMessagesProps {
197 onSetConfiguration?: (event: SetConfigurationEvent, sendResponseToParent: SendResponseToParent<{}>) => void;
198 onSubmit?: OnSubmitHandler;
199 onSetPaypalPaymentIntent?: OnSetPaypalPaymentIntentHandler;
200 onGetHeight?: (event: GetHeightEvent, sendResponseToParent: SendResponseToParent<GetHeightResponsePayload>) => void;
201 onGetBin?: OnGetBinHandler;
202 onValidateForm?: OnValidateFormHandler;
203 onVerifySavedCard?: OnVerifySavedCardHandler;
204 onChangeRenderMode?: OnChangeRenderModeHandler;
205 onUpdateFields?: OnUpdateFieldsHandler;
206 onDirectDebitSubmit?: OnDirectDebitSubmitHandler;
209 // the event handler function must be async to make sure that we catch all errors, sync and async
210 const getEventListener = (messageBus: MessageBus) => async (e: MessageEvent) => {
211 const parseEvent = (data: any) => {
212 if (typeof data !== 'string') {
218 props = JSON.parse(data);
225 const event = parseEvent(e.data);
228 if (isSetConfigurationEvent(event)) {
229 // Do not remove await here or anywhere else in the function. It ensures that all errors are caught
230 await messageBus.onSetConfiguration(event, (result) => {
231 messageBus.sendMessage({
232 type: 'set-configuration-response',
233 correlationId: event.correlationId,
237 } else if (isChargebeeSubmitEvent(event)) {
238 await messageBus.onSubmit(event, (result) => {
239 messageBus.sendMessage({
240 type: 'chargebee-submit-response',
241 correlationId: event.correlationId,
245 } else if (isSetPaypalPaymentIntentEvent(event)) {
246 await messageBus.onSetPaypalPaymentIntent(event, (result) => {
247 messageBus.sendMessage({
248 type: 'set-paypal-payment-intent-response',
249 correlationId: event.correlationId,
253 } else if (isGetHeightEvent(event)) {
254 await messageBus.onGetHeight(event, (result) => {
255 messageBus.sendMessage({
256 type: 'get-height-response',
257 correlationId: event.correlationId,
261 } else if (isGetBinEvent(event)) {
262 await messageBus.onGetBin(event, (result) => {
263 messageBus.sendMessage({
264 type: 'get-bin-response',
265 correlationId: event.correlationId,
269 } else if (isValidateFormEvent(event)) {
270 await messageBus.onValidateForm(event, (result) => {
271 messageBus.sendMessage({
272 type: 'validate-form-response',
273 correlationId: event.correlationId,
277 } else if (isVerifySavedCardEvent(event)) {
278 await messageBus.onVerifySavedCard(event, (result) => {
279 messageBus.sendMessage({
280 type: 'chargebee-verify-saved-card-response',
281 correlationId: event.correlationId,
285 } else if (isChangeRenderModeEvent(event)) {
286 await messageBus.onChangeRenderMode(event, (result) => {
287 messageBus.sendMessage({
288 type: 'change-render-mode-response',
289 correlationId: event.correlationId,
293 } else if (isUpdateFieldsEvent(event)) {
294 await messageBus.onUpdateFields(event, (result) => {
295 messageBus.sendMessage({
296 type: 'update-fields-response',
297 correlationId: event.correlationId,
301 } else if (isDirectDebitSubmitEvent(event)) {
302 await messageBus.onDirectDebitSubmit(event, (result) => {
303 messageBus.sendMessage({
304 type: 'direct-debit-submit-response',
305 correlationId: event.correlationId,
309 } else if (isChargebeeEvent(event)) {
310 // ignore chargebee event
312 // ignore unknown event
315 addCheckpoint('failed_to_handle_parent_message', { error, event, eventRawData: e.data });
316 messageBus.sendUnhandledErrorMessage(error);
320 export class MessageBus {
321 public onSetConfiguration;
325 public onSetPaypalPaymentIntent;
331 public onValidateForm;
333 public onVerifySavedCard;
335 public onChangeRenderMode;
337 public onUpdateFields;
339 public onDirectDebitSubmit;
341 private eventListener: ((e: MessageEvent) => void) | null = null;
346 onSetPaypalPaymentIntent,
354 }: ParentMessagesProps) {
355 this.onSetConfiguration = onSetConfiguration ?? noop;
356 this.onSubmit = onSubmit ?? noop;
357 this.onSetPaypalPaymentIntent = onSetPaypalPaymentIntent ?? noop;
358 this.onGetHeight = onGetHeight ?? noop;
359 this.onGetBin = onGetBin ?? noop;
360 this.onValidateForm = onValidateForm ?? noop;
361 this.onVerifySavedCard = onVerifySavedCard ?? noop;
362 this.onChangeRenderMode = onChangeRenderMode ?? noop;
363 this.onUpdateFields = onUpdateFields ?? noop;
364 this.onDirectDebitSubmit = onDirectDebitSubmit ?? noop;
368 this.eventListener = getEventListener(this);
369 window.addEventListener('message', this.eventListener);
373 if (this.eventListener) {
374 window.removeEventListener('message', this.eventListener);
375 this.eventListener = null;
379 sendPaypalAuthorizedMessage(data: PaypalAuthorizedPayload) {
380 const message: MessageBusResponse<PaypalAuthorizedPayload> = {
386 type: paypalAuthorizedMessageType,
391 sendPaypalFailedMessage(error: any) {
392 const message: PaypalFailedMessage = {
393 type: paypalFailedMessageType,
398 this.sendMessage(message);
401 sendPaypalClickedMessage() {
402 const message: PaypalClickedMessage = {
403 type: paypalClickedMessageType,
408 this.sendMessage(message);
411 sendPaypalCancelledMessage() {
412 const message: PaypalCancelledMessage = {
413 type: paypalCancelledMessageType,
418 this.sendMessage(message);
421 send3dsChallengeMessage(data: ThreeDsChallengePayload, correlationId?: string) {
422 const message: ThreeDsChallengeMessage = {
423 type: threeDsChallengeMessageType,
428 this.sendMessage({ ...message, correlationId });
431 send3dsFailedMessage(error: any, correlationId?: string) {
432 const message: ThreeDsFailedMessage = {
433 type: `chargebee-submit-response`,
438 this.sendMessage({ ...message, correlationId });
441 sendFormValidationErrorMessage(errors: FormValidationErrors, correlationId?: string) {
442 const message: MessageBusResponse<FormValidationErrors> = {
448 type: 'chargebee-submit-response',
454 send3dsSuccessMessage(paymentIntent: ChargebeeSubmitEventResponse, correlationId?: string) {
456 type: 'chargebee-submit-response',
463 send3dsRequiredForSavedCardMessage(data: ThreeDsChallengePayload, correlationId?: string) {
464 const message: ThreeDsRequiredForSavedCardMessage = {
465 type: threeDsChallengeMessageType,
470 this.sendMessage({ ...message, correlationId });
473 sendSavedCardVerificationSuccessMessage(payload: ChargebeeSavedCardAuthorizationSuccess, correlationId?: string) {
474 const message: SavedCardVerificationSuccessMessage = {
475 type: 'chargebee-verify-saved-card-response',
480 this.sendMessage({ ...message, correlationId });
483 sendSavedCardVerificationFailureMessage(error: any, correlationId?: string) {
484 const message: SavedCardVerificationFailureMessage = {
485 type: 'chargebee-verify-saved-card-response',
490 this.sendMessage({ ...message, correlationId });
493 sendDirectDebitSuccessMessage(data: any, correlationId?: string) {
495 type: 'direct-debit-submit-response',
502 sendDirectDebitFailureMessage(error: any, correlationId?: string) {
504 type: 'direct-debit-submit-response',
511 sendUnhandledErrorMessage(errorObj: any) {
514 ...this.formatError(errorObj),
515 checkpoints: getCheckpoints(),
516 chargebeeWrapperVersion,
517 origin: window?.location?.origin,
520 const message: UnhandledErrorMessage = {
521 type: unhandledError,
526 this.sendMessage(message);
528 console.error('Failed to send error message to parent');
533 sendMessage(message: {
535 correlationId?: string;
536 status?: CbIframeResponseStatus;
540 const messageToSend = {
542 error: this.formatError(message.error),
545 window.parent.postMessage(JSON.stringify(messageToSend), '*');
548 private isPlainError(error: any): error is Error {
550 !!error && !!error.message && !error.checkpoints && !error.chargebeeWrapperVersion && !Array.isArray(error)
554 private formatError(error: any) {
555 if (this.isPlainError(error)) {
557 message: error.message,
567 let messageBus: MessageBus | null = null;
569 export function createMessageBus(props: ParentMessagesProps) {
570 let parentMessages = new MessageBus(props);
571 parentMessages.initialize();
573 messageBus = parentMessages;
575 return parentMessages;
578 export function getMessageBus(): MessageBus {
580 throw new Error('MessageBus is not initialized');