From ced3251f8bd519778e2921801b27919be1dd928f Mon Sep 17 00:00:00 2001 From: PJaneta Date: Thu, 26 Sep 2024 14:47:03 +0200 Subject: [PATCH] AD-315 I can't pay with any method if the first payment attempt is canceled --- .../src/lib/adyen-payments.module.ts | 10 ++--- .../adyen-redirect-error.component.ts | 25 +++++++---- ...checkout-adyen-payment-method.component.ts | 44 ++++++++++++------- .../additional-details.connector.ts | 1 - ...or.ts => adyen-order-connector.service.ts} | 10 +++-- .../occ/adapters/occ-adyen-order.adapter.ts | 43 ++++++++++++++++++ .../occ/adapters/occ-placeorder.adapter.ts | 27 ------------ ...r-confirmation-payment-status.component.ts | 6 +-- .../src/lib/service/adyen-order.service.ts | 31 +++++++++++-- 9 files changed, 131 insertions(+), 66 deletions(-) rename projects/adyen-payments/src/lib/core/connectors/{placeorder.connector.ts => adyen-order-connector.service.ts} (51%) create mode 100644 projects/adyen-payments/src/lib/core/occ/adapters/occ-adyen-order.adapter.ts delete mode 100644 projects/adyen-payments/src/lib/core/occ/adapters/occ-placeorder.adapter.ts diff --git a/projects/adyen-payments/src/lib/adyen-payments.module.ts b/projects/adyen-payments/src/lib/adyen-payments.module.ts index fadfb55..7fbbd0d 100644 --- a/projects/adyen-payments/src/lib/adyen-payments.module.ts +++ b/projects/adyen-payments/src/lib/adyen-payments.module.ts @@ -7,8 +7,6 @@ import {CheckoutAdyenConfigurationService} from "./service/checkout-adyen-config import {CheckoutConfigurationConnector} from "./core/connectors/checkout-configuration.connector"; import {OccCheckoutConfigAdapter} from "./core/occ/adapters/occ-checkout-config.adapter"; import {CheckoutAdyenEventListener} from "./events/checkout-adyen-event.listener"; -import {PlaceOrderConnector} from "./core/connectors/placeorder.connector"; -import {OccPlaceOrderAdapter} from "./core/occ/adapters/occ-placeorder.adapter"; import {AdyenOrderService} from "./service/adyen-order.service"; import {OrderAdapter, OrderConnector, OrderHistoryConnector, OrderHistoryAdapter} from "@spartacus/order/core" import {OccOrderAdapter, OccOrderHistoryAdapter} from "@spartacus/order/occ" @@ -18,6 +16,8 @@ import {OccAdditionalDetailsAdapter} from "./core/occ/adapters/occ-additionaldet import {AdyenRedirectModule} from "./adyen-redirect/adyen-redirect.module"; import {I18nConfig, provideConfig, provideDefaultConfig} from '@spartacus/core'; import {adyenCheckoutTranslationChunksConfig, adyenCheckoutTranslations} from "./assets/translations/translations"; +import {AdyenOrderConnector} from "./core/connectors/adyen-order-connector.service"; +import {OccAdyenOrderAdapter} from "./core/occ/adapters/occ-adyen-order.adapter"; @@ -32,9 +32,9 @@ import {adyenCheckoutTranslationChunksConfig, adyenCheckoutTranslations} from ". providers: [CheckoutAdyenConfigurationService, AdyenOrderService, AdyenAddressService, - PlaceOrderConnector, - AdditionalDetailsConnector, OrderConnector, + AdditionalDetailsConnector, + AdyenOrderConnector, { provide: OrderAdapter, useClass: OccOrderAdapter, @@ -52,7 +52,7 @@ import {adyenCheckoutTranslationChunksConfig, adyenCheckoutTranslations} from ". provide: OrderHistoryAdapter, useClass: OccOrderHistoryAdapter }, - OccPlaceOrderAdapter, + OccAdyenOrderAdapter, OccAdditionalDetailsAdapter, OccCheckoutConfigAdapter, CheckoutAdyenEventListener, diff --git a/projects/adyen-payments/src/lib/adyen-redirect/adyen-redirect-error.component.ts b/projects/adyen-payments/src/lib/adyen-redirect/adyen-redirect-error.component.ts index 0b22a48..c707fb1 100644 --- a/projects/adyen-payments/src/lib/adyen-redirect/adyen-redirect-error.component.ts +++ b/projects/adyen-payments/src/lib/adyen-redirect/adyen-redirect-error.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit} from '@angular/core'; +import {Component, OnDestroy, OnInit} from '@angular/core'; import {CartType, MultiCartFacade} from '@spartacus/cart/base/root'; import { GlobalMessageService, @@ -9,14 +9,16 @@ import { UserIdService } from '@spartacus/core'; import {errorCodePrefix} from "../assets/translations/translations"; +import {Subscription} from "rxjs"; @Component({ selector: 'adyen-redirect-error', templateUrl: './adyen-redirect.component.html', }) -export class AdyenRedirectErrorComponent implements OnInit { +export class AdyenRedirectErrorComponent implements OnInit, OnDestroy { private messageTimeout: number = 20000; private placeOrderErrorCodePrefix: string = errorCodePrefix + '.'; + private subscriptions = new Subscription(); constructor(protected routingService: RoutingService, protected globalMessageService: GlobalMessageService, @@ -27,7 +29,7 @@ export class AdyenRedirectErrorComponent implements OnInit { } private addErrorMessage() { - this.routingService.getParams().subscribe(params => { + let subscribeRouting = this.routingService.getParams().subscribe(params => { let errorCode = params['errorCode']; if (errorCode) { @@ -39,15 +41,19 @@ export class AdyenRedirectErrorComponent implements OnInit { this.multiCartFacade.reloadCart(OCC_CART_ID_CURRENT) - this.userIdService.takeUserId().subscribe((userId) => { + let subscribeUser = this.userIdService.takeUserId().subscribe((userId) => { this.multiCartFacade.loadCart({cartId: OCC_CART_ID_CURRENT, userId}) - this.multiCartFacade.getCartIdByType(CartType.ACTIVE).subscribe((cartId) => { + + let subscribeCart = this.multiCartFacade.getCartIdByType(CartType.ACTIVE).subscribe((cartId) => { this.routingService.go({cxRoute: "checkoutAdyenPaymentDetails"}) - }) - }) + }); + this.subscriptions.add(subscribeCart); + }); + this.subscriptions.add(subscribeUser); } - }) + }); + this.subscriptions.add(subscribeRouting); } @@ -55,4 +61,7 @@ export class AdyenRedirectErrorComponent implements OnInit { this.addErrorMessage(); } + ngOnDestroy(): void { + this.subscriptions.unsubscribe(); + } } diff --git a/projects/adyen-payments/src/lib/checkout-adyen-payment-method/checkout-adyen-payment-method.component.ts b/projects/adyen-payments/src/lib/checkout-adyen-payment-method/checkout-adyen-payment-method.component.ts index faced84..d367475 100644 --- a/projects/adyen-payments/src/lib/checkout-adyen-payment-method/checkout-adyen-payment-method.component.ts +++ b/projects/adyen-payments/src/lib/checkout-adyen-payment-method/checkout-adyen-payment-method.component.ts @@ -1,18 +1,19 @@ import {ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, ViewChild,} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; -import {ActiveCartFacade} from '@spartacus/cart/base/root'; -import {CheckoutDeliveryAddressFacade, CheckoutPaymentFacade,} from '@spartacus/checkout/base/root'; +import {ActiveCartFacade, CartType, MultiCartFacade} from '@spartacus/cart/base/root'; +import {CheckoutDeliveryAddressFacade,} from '@spartacus/checkout/base/root'; import { Address, + EventService, getLastValueSync, - GlobalMessageService, + OCC_CART_ID_CURRENT, PaymentDetails, RoutingService, - TranslationService, + UserIdService, UserPaymentService, } from '@spartacus/core'; import {BehaviorSubject, Subscription,} from 'rxjs'; -import {filter, map, take,switchMap,} from 'rxjs/operators'; +import {filter, map, switchMap, take,} from 'rxjs/operators'; import {CheckoutStepService} from "@spartacus/checkout/base/components"; import AdyenCheckout from '@adyen/adyen-web'; import {CheckoutAdyenConfigurationService} from "../service/checkout-adyen-configuration.service"; @@ -25,8 +26,6 @@ import AdyenCheckoutError from "@adyen/adyen-web/dist/types/core/Errors/AdyenChe import {PlaceOrderResponse} from "../core/models/occ.order.models"; import {AdyenOrderService} from "../service/adyen-order.service"; import {CheckoutAdyenConfigurationReloadEvent} from "../events/checkout-adyen.events"; -import { UserIdService } from '@spartacus/core'; -import { EventService } from '@spartacus/core'; @Component({ selector: 'cx-payment-method', @@ -58,17 +57,15 @@ export class CheckoutAdyenPaymentMethodComponent implements OnInit, OnDestroy { constructor( protected userPaymentService: UserPaymentService, protected checkoutDeliveryAddressFacade: CheckoutDeliveryAddressFacade, - protected checkoutPaymentFacade: CheckoutPaymentFacade, protected activatedRoute: ActivatedRoute, - protected translationService: TranslationService, protected routingService: RoutingService, protected activeCartFacade: ActiveCartFacade, protected checkoutStepService: CheckoutStepService, - protected globalMessageService: GlobalMessageService, protected checkoutAdyenConfigurationService: CheckoutAdyenConfigurationService, protected adyenOrderService: AdyenOrderService, protected eventService: EventService, - private userIdService: UserIdService + private userIdService: UserIdService, + protected multiCartFacade: MultiCartFacade, ) { } @@ -141,7 +138,7 @@ export class CheckoutAdyenPaymentMethodComponent implements OnInit, OnDestroy { holderNameRequired: adyenConfig.cardHolderNameRequired, enableStoreDetails: adyenConfig.showRememberTheseDetails }, - paypal: { + paypal: { intent: "authorize" } }, @@ -166,9 +163,7 @@ export class CheckoutAdyenPaymentMethodComponent implements OnInit, OnDestroy { onPaymentCompleted(data: OnPaymentCompletedData, element?: UIElement) { console.info(data, element); }, - onError(error: AdyenCheckoutError, element?: UIElement) { - console.error(error.name, error.message, error.stack, element); - }, + onError: (error: AdyenCheckoutError) => this.handleError(error), onSubmit: (state: any, element: UIElement) => this.handlePayment(state.data), onAdditionalDetails: (state: any, element?: UIElement) => this.handleAdditionalDetails(state.data), onActionHandled(data: ActionHandledReturnObject) { @@ -233,6 +228,25 @@ export class CheckoutAdyenPaymentMethodComponent implements OnInit, OnDestroy { } } + private handleError(error: AdyenCheckoutError) { + let subscribeCancel = this.adyenOrderService.sendPaymentCancelled().subscribe(() => { + this.multiCartFacade.reloadCart(OCC_CART_ID_CURRENT) + + let subscribeUser = this.userIdService.takeUserId().subscribe((userId) => { + this.multiCartFacade.loadCart({cartId: OCC_CART_ID_CURRENT, userId}) + + let subscribeCart = this.multiCartFacade.getCartIdByType(CartType.ACTIVE).subscribe((cartId) => { + this.eventService.dispatch( + new CheckoutAdyenConfigurationReloadEvent() + ); + }); + subscribeCart.unsubscribe(); + }); + subscribeUser.unsubscribe(); + subscribeCancel.unsubscribe(); + }); + } + private resetDropInComponent() { this.dropIn.unmount(); this.dropIn.mount(this.hook.nativeElement) diff --git a/projects/adyen-payments/src/lib/core/connectors/additional-details.connector.ts b/projects/adyen-payments/src/lib/core/connectors/additional-details.connector.ts index 48fa915..f3a3fc3 100644 --- a/projects/adyen-payments/src/lib/core/connectors/additional-details.connector.ts +++ b/projects/adyen-payments/src/lib/core/connectors/additional-details.connector.ts @@ -1,6 +1,5 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { OccPlaceOrderAdapter } from '../occ/adapters/occ-placeorder.adapter'; import {PlaceOrderRequest, PlaceOrderResponse} from "../models/occ.order.models"; import {OccAdditionalDetailsAdapter} from "../occ/adapters/occ-additionaldetails.adapter"; diff --git a/projects/adyen-payments/src/lib/core/connectors/placeorder.connector.ts b/projects/adyen-payments/src/lib/core/connectors/adyen-order-connector.service.ts similarity index 51% rename from projects/adyen-payments/src/lib/core/connectors/placeorder.connector.ts rename to projects/adyen-payments/src/lib/core/connectors/adyen-order-connector.service.ts index 556d41b..d921893 100644 --- a/projects/adyen-payments/src/lib/core/connectors/placeorder.connector.ts +++ b/projects/adyen-payments/src/lib/core/connectors/adyen-order-connector.service.ts @@ -1,13 +1,17 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { OccPlaceOrderAdapter } from '../occ/adapters/occ-placeorder.adapter'; import {PlaceOrderRequest, PlaceOrderResponse} from "../models/occ.order.models"; +import {OccAdyenOrderAdapter} from "../occ/adapters/occ-adyen-order.adapter"; @Injectable() -export class PlaceOrderConnector { - constructor(protected adapter: OccPlaceOrderAdapter) {} +export class AdyenOrderConnector { + constructor(protected adapter: OccAdyenOrderAdapter) {} placeOrder(userId: string, cartId: string, orderData: PlaceOrderRequest): Observable { return this.adapter.placeOrder(userId, cartId, orderData); } + + paymentCanceled(userId: string, cartId: string, orderCode: string): Observable { + return this.adapter.cancelPayment(userId, cartId, orderCode); + } } diff --git a/projects/adyen-payments/src/lib/core/occ/adapters/occ-adyen-order.adapter.ts b/projects/adyen-payments/src/lib/core/occ/adapters/occ-adyen-order.adapter.ts new file mode 100644 index 0000000..5e557db --- /dev/null +++ b/projects/adyen-payments/src/lib/core/occ/adapters/occ-adyen-order.adapter.ts @@ -0,0 +1,43 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {OccEndpointsService} from '@spartacus/core'; +import {Observable} from 'rxjs'; +import {PlaceOrderRequest, PlaceOrderResponse} from "../../models/occ.order.models"; + +@Injectable() +export class OccAdyenOrderAdapter { + + constructor( + protected http: HttpClient, + protected occEndpoints: OccEndpointsService + ) { + } + + public placeOrder(userId: string, cartId: string, orderData: PlaceOrderRequest): Observable { + return this.http.post(this.getPlaceOrderEndpoint(userId, cartId), orderData); + } + + protected getPlaceOrderEndpoint(userId: string, cartId: string): string { + return this.occEndpoints.buildUrl('users/${userId}/carts/${cartId}/adyen/place-order', { + urlParams: { + userId, + cartId, + } + }); + } + + public cancelPayment(userId: string, cartId: string, orderCode: string): Observable { + return this.http.post(this.getPaymentCanceledEndpoint(userId, cartId, orderCode), {}) + } + + protected getPaymentCanceledEndpoint(userId: string, cartId: string, orderCode: string): string { + return this.occEndpoints.buildUrl('users/${userId}/adyen/payment-canceled/${orderCode}', { + urlParams: { + userId, + cartId, + orderCode + } + }); + } + +} diff --git a/projects/adyen-payments/src/lib/core/occ/adapters/occ-placeorder.adapter.ts b/projects/adyen-payments/src/lib/core/occ/adapters/occ-placeorder.adapter.ts deleted file mode 100644 index 0f5fa7d..0000000 --- a/projects/adyen-payments/src/lib/core/occ/adapters/occ-placeorder.adapter.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { OccEndpointsService } from '@spartacus/core'; -import { Observable } from 'rxjs'; -import {PlaceOrderRequest, PlaceOrderResponse} from "../../models/occ.order.models"; - -@Injectable() -export class OccPlaceOrderAdapter { - - constructor( - protected http: HttpClient, - protected occEndpoints: OccEndpointsService - ) {} - - public placeOrder(userId: string, cartId: string, orderData: PlaceOrderRequest): Observable { - return this.http.post(this.getPlaceOrderEndpoint(userId, cartId), orderData); - } - - protected getPlaceOrderEndpoint(userId: string, cartId: string): string { - return this.occEndpoints.buildUrl('users/${userId}/carts/${cartId}/adyen/place-order', { - urlParams: { - userId, - cartId, - } - }); - } -} diff --git a/projects/adyen-payments/src/lib/order/components/order-confirmation/order-confirmation-payment-status/order-confirmation-payment-status.component.ts b/projects/adyen-payments/src/lib/order/components/order-confirmation/order-confirmation-payment-status/order-confirmation-payment-status.component.ts index 826efbb..5341a27 100644 --- a/projects/adyen-payments/src/lib/order/components/order-confirmation/order-confirmation-payment-status/order-confirmation-payment-status.component.ts +++ b/projects/adyen-payments/src/lib/order/components/order-confirmation/order-confirmation-payment-status/order-confirmation-payment-status.component.ts @@ -14,7 +14,7 @@ export class OrderConfirmationPaymentStatusComponent implements OnInit, OnDestro constructor(protected orderPaymentStatusService: OrderPaymentStatusService, protected adyenOrderService: AdyenOrderService) { adyenOrderService.getOrderDetails().subscribe(orderData => { - this.orderCode = orderData!.code as string; + this.orderCode = orderData? orderData.code : undefined; }) } @@ -22,7 +22,7 @@ export class OrderConfirmationPaymentStatusComponent implements OnInit, OnDestro private numberOfRetries = 30; private currentRetry = 1; - private orderCode: string; + private orderCode?: string; paymentStatus$: BehaviorSubject; @@ -30,7 +30,7 @@ export class OrderConfirmationPaymentStatusComponent implements OnInit, OnDestro private timerCallback() { - if (this.currentRetry <= this.numberOfRetries) { + if (this.currentRetry <= this.numberOfRetries && this.orderCode) { this.orderPaymentStatusService.getOrderStatus(this.orderCode).subscribe((status) => { this.paymentStatus$.next(status); if (status !== 'waiting') { diff --git a/projects/adyen-payments/src/lib/service/adyen-order.service.ts b/projects/adyen-payments/src/lib/service/adyen-order.service.ts index 19b86a5..0fc05a6 100644 --- a/projects/adyen-payments/src/lib/service/adyen-order.service.ts +++ b/projects/adyen-payments/src/lib/service/adyen-order.service.ts @@ -11,9 +11,9 @@ import { UserIdService } from "@spartacus/core"; import {OrderConnector, OrderHistoryConnector, OrderService} from '@spartacus/order/core'; -import {catchError, map, Observable, of, switchMap, tap} from "rxjs"; +import {BehaviorSubject, catchError, map, Observable, of, switchMap, tap} from "rxjs"; import {OrderPlacedEvent} from '@spartacus/order/root'; -import {PlaceOrderConnector} from "../core/connectors/placeorder.connector"; +import {AdyenOrderConnector} from "../core/connectors/adyen-order-connector.service"; import {ActiveCartFacade} from '@spartacus/cart/base/root'; import {AddressData, PlaceOrderRequest, PlaceOrderResponse} from "../core/models/occ.order.models"; import {HttpErrorResponse} from "@angular/common/http"; @@ -24,9 +24,10 @@ import {errorCodePrefix} from "../assets/translations/translations"; @Injectable() export class AdyenOrderService extends OrderService { private messageTimeout: number = 20000; + private placedOrderNumber$ = new BehaviorSubject(undefined); private placeOrderErrorCodePrefix: string = errorCodePrefix + '.'; - constructor(protected placeOrderConnector: PlaceOrderConnector, + constructor(protected placeOrderConnector: AdyenOrderConnector, protected additionalDetailsConnector: AdditionalDetailsConnector, protected override activeCartFacade: ActiveCartFacade, protected override userIdService: UserIdService, @@ -49,6 +50,7 @@ export class AdyenOrderService extends OrderService { this.placeOrderConnector.placeOrder(userId, cartId, AdyenOrderService.preparePlaceOrderRequest(paymentData, billingAddress)).pipe( tap((placeOrderResponse) => { this.placedOrder$.next(placeOrderResponse.orderData); + this.placedOrderNumber$.next(placeOrderResponse.orderNumber) this.eventService.dispatch( { userId, @@ -96,6 +98,7 @@ export class AdyenOrderService extends OrderService { this.additionalDetailsConnector.sendAdditionalDetails(userId, cartId, details).pipe( tap((placeOrderResponse) => { this.placedOrder$.next(placeOrderResponse.orderData); + this.placedOrderNumber$.next(placeOrderResponse.orderNumber); this.eventService.dispatch( { userId, @@ -130,11 +133,31 @@ export class AdyenOrderService extends OrderService { } ); - sendAdditionalDetails(details: any): Observable { return this.sendAdditionalDetailsCommand.execute(details); } + protected sendCancelledPaymentCommand: Command = + this.commandService.create( + () => + this.checkoutPreconditions().pipe( + switchMap(([userId, cartId]) => { + return this.placedOrderNumber$.pipe( + map((orderNumber) => { + this.placeOrderConnector.paymentCanceled(userId, cartId, orderNumber!).subscribe() + })) + } + ) + ), + { + strategy: CommandStrategy.CancelPrevious, + } + ); + + sendPaymentCancelled(): Observable { + return this.sendCancelledPaymentCommand.execute({}); + } + loadOrderDetails(orderCode: string): void { this.userIdService.takeUserId().subscribe( (userId) => {