From 8638bc0b40804e8662b4db9d2697768e71f9dbdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Silke=20Gr=C3=BCber?= <57660644+SGrueber@users.noreply.github.com> Date: Mon, 16 Dec 2019 11:06:18 +0100 Subject: [PATCH] fix: reopen the pwa in the user's language after coming back from payment redirect (#55) --- .../basket/basket-payment.service.spec.ts | 2 + .../services/basket/basket-payment.service.ts | 87 +++++++++++-------- .../core/services/order/order.service.spec.ts | 14 +++ src/app/core/services/order/order.service.ts | 25 ++++-- 4 files changed, 84 insertions(+), 44 deletions(-) diff --git a/src/app/core/services/basket/basket-payment.service.spec.ts b/src/app/core/services/basket/basket-payment.service.spec.ts index d07c208bef..2b270a3174 100644 --- a/src/app/core/services/basket/basket-payment.service.spec.ts +++ b/src/app/core/services/basket/basket-payment.service.spec.ts @@ -5,6 +5,7 @@ import { anyString, anything, instance, mock, verify, when } from 'ts-mockito'; import { ApiService } from 'ish-core/services/api/api.service'; import { checkoutReducers } from 'ish-core/store/checkout/checkout-store.module'; +import { localeReducer } from 'ish-core/store/locale/locale.reducer'; import { ngrxTesting } from 'ish-core/utils/dev/ngrx-testing'; import { BasketPaymentService } from './basket-payment.service'; @@ -39,6 +40,7 @@ describe('Basket Payment Service', () => { ngrxTesting({ reducers: { checkout: combineReducers(checkoutReducers), + locale: localeReducer, }, }), ], diff --git a/src/app/core/services/basket/basket-payment.service.ts b/src/app/core/services/basket/basket-payment.service.ts index e85786694a..e2b48a1af1 100644 --- a/src/app/core/services/basket/basket-payment.service.ts +++ b/src/app/core/services/basket/basket-payment.service.ts @@ -1,8 +1,9 @@ import { HttpHeaders, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Params } from '@angular/router'; +import { Store, select } from '@ngrx/store'; import { Observable, of, throwError } from 'rxjs'; -import { concatMap, map, mapTo } from 'rxjs/operators'; +import { concatMap, map, mapTo, withLatestFrom } from 'rxjs/operators'; import { PaymentInstrument } from 'ish-core/models/payment-instrument/payment-instrument.model'; import { PaymentMethodBaseData } from 'ish-core/models/payment-method/payment-method.interface'; @@ -10,13 +11,14 @@ import { PaymentMethodMapper } from 'ish-core/models/payment-method/payment-meth import { PaymentMethod } from 'ish-core/models/payment-method/payment-method.model'; import { Payment } from 'ish-core/models/payment/payment.model'; import { ApiService } from 'ish-core/services/api/api.service'; +import { getCurrentLocale } from 'ish-core/store/locale'; /** * The Basket Service handles the interaction with the 'baskets' REST API. */ @Injectable({ providedIn: 'root' }) export class BasketPaymentService { - constructor(private apiService: ApiService) {} + constructor(private apiService: ApiService, private store: Store<{}>) {} // http header for Basket API v1 private basketHeaders = new HttpHeaders({ @@ -70,42 +72,57 @@ export class BasketPaymentService { map(({ data, included }) => data && data.paymentMethod && included ? included.paymentMethod[data.paymentMethod] : undefined ), - concatMap(pm => { - if ( - !pm || - !pm.capabilities || - !pm.capabilities.some(data => ['RedirectBeforeCheckout'].includes(data)) // ToDo: add RedirectAfterCheckout here, if placeholders are supported by the ICM server - ) { - return of(paymentInstrument); - // send redirect urls if there is a redirect required - } else { - const redirect = { - successUrl: `${location.origin}/checkout/review?redirect=success`, - cancelUrl: `${location.origin}/checkout/payment?redirect=cancel`, - failureUrl: `${location.origin}/checkout/payment?redirect=failure`, - }; - - if (pm.capabilities.some(data => ['RedirectAfterCheckout'].includes(data))) { - redirect.successUrl = `${location.origin}/checkout/receipt?redirect=success&orderId=*orderID*`; // *OrderID* will be replaced by the ICM server - redirect.cancelUrl = `${location.origin}/checkout/payment?redirect=cancel&orderId=*orderID*`; - redirect.failureUrl = `${location.origin}/checkout/payment?redirect=failure&orderId=*orderID*`; - } - - const body = { - paymentInstrument, - redirect, - }; - - return this.apiService - .put(`baskets/${basketId}/payments/open-tender`, body, { - headers: this.basketHeaders, - }) - .pipe(mapTo(paymentInstrument)); - } - }) + withLatestFrom(this.store.pipe(select(getCurrentLocale))), + concatMap(([pm, currentLocale]) => + this.sendRedirectUrlsIfRequired(pm, paymentInstrument, basketId, currentLocale && currentLocale.lang) + ) ); } + /** + * Checks, if RedirectUrls are requested by the server and sends them if it is necessary. + * @param pm The payment method to determine if redirect is required. + * @param paymentInstrument The payment instrument id. + * @param basketId The basket id. + * @param lang The language code of the current locale, e.g. en_US + * @returns The payment instrument id. + */ + private sendRedirectUrlsIfRequired( + pm: PaymentMethodBaseData, + paymentInstrument: string, + basketId: string, + lang: string + ): Observable { + const loc = location.origin; + if (!pm || !pm.capabilities || !pm.capabilities.some(data => ['RedirectBeforeCheckout'].includes(data))) { + return of(paymentInstrument); + // send redirect urls if there is a redirect required + } else { + const redirect = { + successUrl: `${loc}/checkout/review;lang=${lang}?redirect=success`, + cancelUrl: `${loc}/checkout/payment;lang=${lang}?redirect=cancel`, + failureUrl: `${loc}/checkout/payment;lang=${lang}?redirect=failure`, + }; + + if (pm.capabilities.some(data => ['RedirectAfterCheckout'].includes(data))) { + // *OrderID* will be replaced by the ICM server + redirect.successUrl = `${loc}/checkout/receipt;lang=${lang}?redirect=success&orderId=*orderID*`; + redirect.cancelUrl = `${loc}/checkout/payment;lang=${lang}?redirect=cancel&orderId=*orderID*`; + redirect.failureUrl = `${loc}/checkout/payment;lang=${lang}?redirect=failure&orderId=*orderID*`; + } + + const body = { + paymentInstrument, + redirect, + }; + + return this.apiService + .put(`baskets/${basketId}/payments/open-tender`, body, { + headers: this.basketHeaders, + }) + .pipe(mapTo(paymentInstrument)); + } + } /** * Creates a payment instrument for the selected basket. * @param basketId The basket id. diff --git a/src/app/core/services/order/order.service.spec.ts b/src/app/core/services/order/order.service.spec.ts index daccb845ef..23784416a6 100644 --- a/src/app/core/services/order/order.service.spec.ts +++ b/src/app/core/services/order/order.service.spec.ts @@ -1,18 +1,24 @@ import { HttpHeaders } from '@angular/common/http'; import { TestBed } from '@angular/core/testing'; +import { Store } from '@ngrx/store'; import { of } from 'rxjs'; import { anything, capture, instance, mock, verify, when } from 'ts-mockito'; +import { Locale } from 'ish-core/models/locale/locale.model'; import { OrderBaseData } from 'ish-core/models/order/order.interface'; import { Order } from 'ish-core/models/order/order.model'; import { ApiService } from 'ish-core/services/api/api.service'; +import { SelectLocale, SetAvailableLocales } from 'ish-core/store/locale'; +import { localeReducer } from 'ish-core/store/locale/locale.reducer'; import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data'; +import { ngrxTesting } from 'ish-core/utils/dev/ngrx-testing'; import { OrderService } from './order.service'; describe('Order Service', () => { let orderService: OrderService; let apiService: ApiService; + let store$: Store<{}>; const basketMock = BasketMockData.getBasket(); const orderMockData = { data: { @@ -28,13 +34,21 @@ describe('Order Service', () => { when(apiService.icmServerURL).thenReturn('http://server'); TestBed.configureTestingModule({ + imports: ngrxTesting({ + reducers: { locale: localeReducer }, + }), providers: [OrderService, { provide: ApiService, useFactory: () => instance(apiService) }], }); orderService = TestBed.get(OrderService); + store$ = TestBed.get(Store); }); describe('createOrder', () => { + beforeEach(() => { + store$.dispatch(new SetAvailableLocales({ locales: [{ lang: 'en_US' } as Locale] })); + store$.dispatch(new SelectLocale({ lang: 'en_US' })); + }); it('should create an order when it is called', done => { when(apiService.post(anything(), anything(), anything())).thenReturn(of(orderMockData)); when(apiService.patch(anything(), anything(), anything())).thenReturn(of(orderMockData)); diff --git a/src/app/core/services/order/order.service.ts b/src/app/core/services/order/order.service.ts index 15ace26604..e25bc43202 100644 --- a/src/app/core/services/order/order.service.ts +++ b/src/app/core/services/order/order.service.ts @@ -1,14 +1,16 @@ import { HttpHeaders, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Params } from '@angular/router'; +import { Store, select } from '@ngrx/store'; import { EMPTY, Observable, of, throwError } from 'rxjs'; -import { catchError, concatMap, map, mapTo } from 'rxjs/operators'; +import { catchError, concatMap, map, mapTo, withLatestFrom } from 'rxjs/operators'; import { Basket } from 'ish-core/models/basket/basket.model'; import { OrderData } from 'ish-core/models/order/order.interface'; import { OrderMapper } from 'ish-core/models/order/order.mapper'; import { Order } from 'ish-core/models/order/order.model'; import { ApiService } from 'ish-core/services/api/api.service'; +import { getCurrentLocale } from 'ish-core/store/locale'; type OrderIncludeType = | 'invoiceToAddress' @@ -26,7 +28,7 @@ type OrderIncludeType = */ @Injectable({ providedIn: 'root' }) export class OrderService { - constructor(private apiService: ApiService) {} + constructor(private apiService: ApiService, private store: Store<{}>) {} // http header for Order API v1 private orderHeaders = new HttpHeaders({ @@ -73,16 +75,21 @@ export class OrderService { ) .pipe( map(OrderMapper.fromData), - concatMap(order => this.sendRedirectUrlsIfRequired(order)) + withLatestFrom(this.store.pipe(select(getCurrentLocale))), + concatMap(([order, currentLocale]) => + this.sendRedirectUrlsIfRequired(order, currentLocale && currentLocale.lang) + ) ); } /** * Checks, if RedirectUrls are requested by the server and sends them if it is necessary. - * @param order The order. - * @returns The (updated) order. + * @param order The order. + * @param lang The language code of the current locale, e.g. en_US + * @returns The (updated) order. */ - private sendRedirectUrlsIfRequired(order: Order): Observable { + private sendRedirectUrlsIfRequired(order: Order, lang: string): Observable { + const loc = location.origin; if ( order.orderCreation && order.orderCreation.status === 'STOPPED' && @@ -92,9 +99,9 @@ export class OrderService { const body = { orderCreation: { redirect: { - cancelUrl: `${location.origin}/checkout/payment?redirect=cancel&orderId=${order.id}`, - failureUrl: `${location.origin}/checkout/payment?redirect=failure&orderId=${order.id}`, - successUrl: `${location.origin}/checkout/receipt?redirect=success&orderId=${order.id}`, + cancelUrl: `${loc}/checkout/payment;lang=${lang}?redirect=cancel&orderId=${order.id}`, + failureUrl: `${loc}/checkout/payment;lang=${lang}?redirect=failure&orderId=${order.id}`, + successUrl: `${loc}/checkout/receipt;lang=${lang}?redirect=success&orderId=${order.id}`, }, status: 'CONTINUE', },