Skip to content

Commit

Permalink
Implement Hosted-fields pattern#3 (iframe) (#17664)
Browse files Browse the repository at this point in the history
CXSPA-3881
added submit-complete
  • Loading branch information
FollowTheFlo authored Jul 25, 2023
1 parent 7444bfa commit 9659e33
Show file tree
Hide file tree
Showing 13 changed files with 413 additions and 157 deletions.
12 changes: 12 additions & 0 deletions integration-libs/opf/base/core/connectors/opf-payment.adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import {
OpfPaymentVerificationPayload,
OpfPaymentVerificationResponse,
SubmitCompleteRequest,
SubmitCompleteResponse,
SubmitRequest,
SubmitResponse,
} from '@spartacus/opf/base/root';
Expand All @@ -31,4 +33,14 @@ export abstract class OpfPaymentAdapter {
otpKey: string,
paymentSessionId: string
): Observable<SubmitResponse>;

/**
* Abstract method used to submit-complete payment for hosted-fields pattern
*/

abstract submitCompletePayment(
submitRequest: SubmitCompleteRequest,
otpKey: string,
paymentSessionId: string
): Observable<SubmitCompleteResponse>;
}
14 changes: 14 additions & 0 deletions integration-libs/opf/base/core/connectors/opf-payment.connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { Injectable } from '@angular/core';
import {
OpfPaymentVerificationPayload,
OpfPaymentVerificationResponse,
SubmitCompleteRequest,
SubmitCompleteResponse,
SubmitRequest,
SubmitResponse,
} from '@spartacus/opf/base/root';
Expand All @@ -33,4 +35,16 @@ export class OpfPaymentConnector {
): Observable<SubmitResponse> {
return this.adapter.submitPayment(submitRequest, otpKey, paymentSessionId);
}

public submitCompletePayment(
submitCompleteRequest: SubmitCompleteRequest,
otpKey: string,
paymentSessionId: string
): Observable<SubmitCompleteResponse> {
return this.adapter.submitCompletePayment(
submitCompleteRequest,
otpKey,
paymentSessionId
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class OpfGlobalFunctionsService implements OpfGlobalFunctionsFacade {
vcr?: ViewContainerRef
): void {
this.registerSubmit(paymentSessionId, vcr);
this.registerSubmitComplete(paymentSessionId, vcr);
this._isGlobalServiceInit = true;
}

Expand All @@ -62,6 +63,24 @@ export class OpfGlobalFunctionsService implements OpfGlobalFunctionsFacade {
return window.Opf.payments;
}

protected startLoaderSpinner(vcr: ViewContainerRef) {
return this.launchDialogService.launch(
LAUNCH_CALLER.PLACE_ORDER_SPINNER,
vcr
);
}

protected stopLoaderSpinner(overlayedSpinner: Observable<ComponentRef<any>>) {
overlayedSpinner
.subscribe((component) => {
this.launchDialogService.clear(LAUNCH_CALLER.PLACE_ORDER_SPINNER);
if (component) {
component.destroy();
}
})
.unsubscribe();
}

protected registerSubmit(
paymentSessionId: string,
vcr?: ViewContainerRef
Expand Down Expand Up @@ -90,10 +109,7 @@ export class OpfGlobalFunctionsService implements OpfGlobalFunctionsFacade {
return this.ngZone.run(() => {
let overlayedSpinner: void | Observable<ComponentRef<any> | undefined>;
if (vcr) {
overlayedSpinner = this.launchDialogService.launch(
LAUNCH_CALLER.PLACE_ORDER_SPINNER,
vcr
);
overlayedSpinner = this.startLoaderSpinner(vcr);
}
const callbackArray: [
MerchantCallback,
Expand All @@ -113,16 +129,60 @@ export class OpfGlobalFunctionsService implements OpfGlobalFunctionsFacade {
.pipe(
finalize(() => {
if (overlayedSpinner) {
overlayedSpinner
.subscribe((component) => {
this.launchDialogService.clear(
LAUNCH_CALLER.PLACE_ORDER_SPINNER
);
if (component) {
component.destroy();
}
})
.unsubscribe();
this.stopLoaderSpinner(overlayedSpinner);
}
})
)
.toPromise();
});
};
}

protected registerSubmitComplete(
paymentSessionId: string,
vcr?: ViewContainerRef
): void {
this.getGlobalFunctionContainer().submitComplete = ({
cartId,
additionalData,
submitSuccess = (): void => {
// this is intentional
},
submitPending = (): void => {
// this is intentional
},
submitFailure = (): void => {
// this is intentional
},
}: {
cartId: string;
additionalData: Array<KeyValuePair>;
submitSuccess: MerchantCallback;
submitPending: MerchantCallback;
submitFailure: MerchantCallback;
}): Promise<boolean> => {
return this.ngZone.run(() => {
let overlayedSpinner: void | Observable<ComponentRef<any> | undefined>;
if (vcr) {
overlayedSpinner = this.startLoaderSpinner(vcr);
}
const callbackArray: [
MerchantCallback,
MerchantCallback,
MerchantCallback
] = [submitSuccess, submitPending, submitFailure];

return this.opfPaymentFacade
.submitCompletePayment({
additionalData,
paymentSessionId,
cartId,
callbackArray,
})
.pipe(
finalize(() => {
if (overlayedSpinner) {
this.stopLoaderSpinner(overlayedSpinner);
}
})
)
Expand Down
160 changes: 26 additions & 134 deletions integration-libs/opf/base/core/facade/opf-payment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,18 @@
*/

import { Injectable } from '@angular/core';
import { Command, CommandService, WindowRef } from '@spartacus/core';
import {
Command,
CommandService,
GlobalMessageService,
RoutingService,
UserIdService,
WindowRef,
} from '@spartacus/core';
import {
MerchantCallback,
OpfOrderFacade,
OpfOtpFacade,
OpfPaymentError,
OpfPaymentFacade,
OpfPaymentVerificationPayload,
OpfPaymentVerificationResponse,
PaymentErrorType,
PaymentMethod,
SubmitCompleteInput,
SubmitInput,
SubmitRequest,
SubmitResponse,
SubmitStatus,
defaultError,
} from '@spartacus/opf/base/root';

import { ActiveCartFacade } from '@spartacus/cart/base/root';
import { Order } from '@spartacus/order/root';
import { EMPTY, Observable, combineLatest, from, throwError } from 'rxjs';
import {
catchError,
concatMap,
filter,
map,
switchMap,
take,
tap,
} from 'rxjs/operators';
import { Observable } from 'rxjs';
import { OpfPaymentConnector } from '../connectors/opf-payment.connector';
import { OpfPaymentErrorHandlerService } from '../services/opf-payment-error-handler.service';
import { getBrowserInfo } from '../utils/opf-payment-utils';
import { OpfPaymentHostedFieldsService } from '../services/opf-payment-hosted-fields.service';

@Injectable()
export class OpfPaymentService implements OpfPaymentFacade {
Expand All @@ -67,115 +39,27 @@ export class OpfPaymentService implements OpfPaymentFacade {
},
boolean
> = this.commandService.create((payload) => {
const {
paymentMethod,
cartId,
additionalData,
paymentSessionId,
returnPath,
} = payload.submitInput;

const submitRequest: SubmitRequest = {
paymentMethod,
cartId,
additionalData,
channel: 'BROWSER',
browserInfo: getBrowserInfo(this.winRef.nativeWindow),
};
if (paymentMethod !== PaymentMethod.CREDIT_CARD) {
submitRequest.encryptedToken = '';
}

return combineLatest([
this.userIdService.getUserId(),
this.activeCartFacade.getActiveCartId(),
]).pipe(
switchMap(([userId, activeCartId]: [string, string]) => {
submitRequest.cartId = activeCartId;
return this.opfOtpFacade.generateOtpKey(userId, activeCartId);
}),
filter((response) => Boolean(response?.value)),
take(1),
concatMap(({ value: otpKey }) =>
this.opfPaymentConnector.submitPayment(
submitRequest,
otpKey,
paymentSessionId
)
),
concatMap((response: SubmitResponse) =>
this.submitPaymentResponseHandler(
response,
payload.submitInput.callbackArray
)
),
tap((order: Order) => {
if (order) {
this.routingService.go({ cxRoute: 'orderConfirmation' });
}
}),
map((order: Order) => (order ? true : false)),
catchError((error: OpfPaymentError | undefined) => {
this.opfPaymentErrorHandlerService.handlePaymentError(
error,
returnPath
);
return throwError(error);
})
return this.opfPaymentHostedFieldsService.submitPayment(
payload.submitInput
);
});

protected submitPaymentResponseHandler(
response: SubmitResponse,
[submitSuccess, submitPending, submitFailure]: [
MerchantCallback,
MerchantCallback,
MerchantCallback
]
) {
if (
response.status === SubmitStatus.ACCEPTED ||
response.status === SubmitStatus.DELAYED
) {
return from(Promise.resolve(submitSuccess(response))).pipe(
concatMap(() => this.opfOrderFacade.placeOpfOrder(true))
);
} else if (response.status === SubmitStatus.PENDING) {
return from(Promise.resolve(submitPending(response))).pipe(
concatMap(() => EMPTY)
);
} else if (response.status === SubmitStatus.REJECTED) {
return from(Promise.resolve(submitFailure(response))).pipe(
concatMap(() =>
throwError({
...defaultError,
type: PaymentErrorType.PAYMENT_REJECTED,
})
)
);
} else {
return from(Promise.resolve(submitFailure(response))).pipe(
concatMap(() =>
throwError({
...defaultError,
type: PaymentErrorType.STATUS_NOT_RECOGNIZED,
})
)
);
}
}
protected submitCompletePaymentCommand: Command<
{
submitCompleteInput: SubmitCompleteInput;
},
boolean
> = this.commandService.create((payload) => {
return this.opfPaymentHostedFieldsService.submitCompletePayment(
payload.submitCompleteInput
);
});

constructor(
protected commandService: CommandService,
protected opfPaymentConnector: OpfPaymentConnector,
protected winRef: WindowRef,
protected opfOtpFacade: OpfOtpFacade,
protected activeCartFacade: ActiveCartFacade,
protected userIdService: UserIdService,
protected routingService: RoutingService,
protected opfOrderFacade: OpfOrderFacade,
protected globalMessageService: GlobalMessageService,
protected opfPaymentErrorHandlerService: OpfPaymentErrorHandlerService
protected opfPaymentHostedFieldsService: OpfPaymentHostedFieldsService
) {}

verifyPayment(
Expand All @@ -189,6 +73,14 @@ export class OpfPaymentService implements OpfPaymentFacade {
}

submitPayment(submitInput: SubmitInput): Observable<boolean> {
return this.submitPaymentCommand.execute({ submitInput });
return this.submitPaymentCommand.execute({
submitInput,
});
}

submitCompletePayment(
submitCompleteInput: SubmitCompleteInput
): Observable<boolean> {
return this.submitCompletePaymentCommand.execute({ submitCompleteInput });
}
}
1 change: 1 addition & 0 deletions integration-libs/opf/base/core/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@

export * from './opf-endpoints.service';
export * from './opf-payment-error-handler.service';
export * from './opf-payment-hosted-fields.service';
Loading

0 comments on commit 9659e33

Please sign in to comment.