Skip to content

Commit

Permalink
fix: reopen the pwa in the user's language after coming back from pay…
Browse files Browse the repository at this point in the history
…ment redirect (#55)
  • Loading branch information
SGrueber authored and dhhyi committed Dec 16, 2019
1 parent 4c4bc40 commit 8638bc0
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 44 deletions.
2 changes: 2 additions & 0 deletions src/app/core/services/basket/basket-payment.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -39,6 +40,7 @@ describe('Basket Payment Service', () => {
ngrxTesting({
reducers: {
checkout: combineReducers(checkoutReducers),
locale: localeReducer,
},
}),
],
Expand Down
87 changes: 52 additions & 35 deletions src/app/core/services/basket/basket-payment.service.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
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';
import { PaymentMethodMapper } from 'ish-core/models/payment-method/payment-method.mapper';
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({
Expand Down Expand Up @@ -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<string> {
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.
Expand Down
14 changes: 14 additions & 0 deletions src/app/core/services/order/order.service.spec.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand All @@ -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));
Expand Down
25 changes: 16 additions & 9 deletions src/app/core/services/order/order.service.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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({
Expand Down Expand Up @@ -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<Order> {
private sendRedirectUrlsIfRequired(order: Order, lang: string): Observable<Order> {
const loc = location.origin;
if (
order.orderCreation &&
order.orderCreation.status === 'STOPPED' &&
Expand All @@ -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',
},
Expand Down

0 comments on commit 8638bc0

Please sign in to comment.