From 0fb8db28fb4d900a52db06ac2dc73eff56083087 Mon Sep 17 00:00:00 2001 From: Stefan Hauke Date: Thu, 11 Mar 2021 17:29:15 +0100 Subject: [PATCH] feat: support for cXML punchout shopping (login, transfer order) (#550) - login user from cXML PunchOutSetupResponse with access-token from URL - use basket given by cXML session information - transfer basket with cXML PunchOutOrderMessage BREAKING CHANGES: required Intershop Commerce Management version: 7.10.29.0 --- src/app/core/facades/account.facade.ts | 5 + .../punchout-session.model.ts | 7 + .../punchout-user/punchout-user.model.ts | 2 + .../pages/punchout/punchout-page.guard.ts | 137 ++++++++---- .../services/punchout/punchout.service.ts | 210 ++++++++++++++---- .../punchout-transfer-basket.component.html | 2 +- .../punchout-transfer-basket.component.ts | 3 + .../punchout-functions.effects.spec.ts | 24 +- .../punchout-functions.effects.ts | 18 +- src/assets/i18n/de_DE.json | 2 +- src/assets/i18n/en_US.json | 2 +- src/assets/i18n/fr_FR.json | 2 +- 12 files changed, 295 insertions(+), 119 deletions(-) create mode 100644 src/app/extensions/punchout/models/punchout-session/punchout-session.model.ts diff --git a/src/app/core/facades/account.facade.ts b/src/app/core/facades/account.facade.ts index ba6fe0c100..800fad63be 100644 --- a/src/app/core/facades/account.facade.ts +++ b/src/app/core/facades/account.facade.ts @@ -39,6 +39,7 @@ import { loadUserPaymentMethods, loginUser, loginUserWithToken, + logoutUser, requestPasswordReminder, resetPasswordReminder, updateCustomer, @@ -85,6 +86,10 @@ export class AccountFacade { this.store.dispatch(loginUserWithToken({ token })); } + logoutUser() { + this.store.dispatch(logoutUser()); + } + createUser(body: CustomerRegistrationType) { this.store.dispatch(createUser(body)); } diff --git a/src/app/extensions/punchout/models/punchout-session/punchout-session.model.ts b/src/app/extensions/punchout/models/punchout-session/punchout-session.model.ts new file mode 100644 index 0000000000..5320410143 --- /dev/null +++ b/src/app/extensions/punchout/models/punchout-session/punchout-session.model.ts @@ -0,0 +1,7 @@ +export interface PunchoutSession { + operation: string; + basketId: string; + returnURL: string; + creationDate: string; + buyerCookie: string; +} diff --git a/src/app/extensions/punchout/models/punchout-user/punchout-user.model.ts b/src/app/extensions/punchout/models/punchout-user/punchout-user.model.ts index a251291ce1..e4c859aa6c 100644 --- a/src/app/extensions/punchout/models/punchout-user/punchout-user.model.ts +++ b/src/app/extensions/punchout/models/punchout-user/punchout-user.model.ts @@ -5,3 +5,5 @@ export interface PunchoutUser { password?: string; active: boolean; } + +export type PunchoutType = 'oci' | 'cxml'; diff --git a/src/app/extensions/punchout/pages/punchout/punchout-page.guard.ts b/src/app/extensions/punchout/pages/punchout/punchout-page.guard.ts index 831e62f4af..fe9731aa38 100644 --- a/src/app/extensions/punchout/pages/punchout/punchout-page.guard.ts +++ b/src/app/extensions/punchout/pages/punchout/punchout-page.guard.ts @@ -2,11 +2,12 @@ import { isPlatformServer } from '@angular/common'; import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; import { of, race, throwError } from 'rxjs'; -import { catchError, concatMap, mapTo, switchMap, take, tap } from 'rxjs/operators'; +import { catchError, concatMap, delay, first, mapTo, switchMap, take, tap } from 'rxjs/operators'; import { AccountFacade } from 'ish-core/facades/account.facade'; import { AppFacade } from 'ish-core/facades/app.facade'; import { CheckoutFacade } from 'ish-core/facades/checkout.facade'; +import { ApiTokenService } from 'ish-core/utils/api-token/api-token.service'; import { CookiesService } from 'ish-core/utils/cookies/cookies.service'; import { whenTruthy } from 'ish-core/utils/operators'; @@ -19,15 +20,24 @@ export class PunchoutPageGuard implements CanActivate { private appFacade: AppFacade, private accountFacade: AccountFacade, private checkoutFacade: CheckoutFacade, + private apiTokenService: ApiTokenService, private cookiesService: CookiesService, private punchoutService: PunchoutService, @Inject(PLATFORM_ID) private platformId: string ) {} canActivate(route: ActivatedRouteSnapshot) { - // check for a HOOK_URL before doing anything with the punchout route - if (!route.queryParamMap.has('HOOK_URL')) { - this.appFacade.setBusinessError('punchout.error.missing.hook_url'); + // check for required start parameters before doing anything with the punchout route + // 'sid', 'access-token' (cXML) or 'HOOK_URL', 'USERNAME', 'PASSWORD' (OCI) + if ( + !( + (route.queryParamMap.has('sid') && route.queryParamMap.has('access-token')) || + (route.queryParamMap.has('HOOK_URL') && + route.queryParamMap.has('USERNAME') && + route.queryParamMap.has('PASSWORD')) + ) + ) { + this.appFacade.setBusinessError('punchout.error.missing.parameters'); return false; } @@ -36,11 +46,15 @@ export class PunchoutPageGuard implements CanActivate { return this.router.parseUrl('/loading'); } - // initiate the punchout user login with the given credentials - this.accountFacade.loginUser({ - login: route.queryParamMap.get('USERNAME'), - password: route.queryParamMap.get('PASSWORD'), - }); + // initiate the punchout user login with the access-token (cXML) or the given credentials (OCI) + if (route.queryParamMap.has('access-token')) { + this.accountFacade.loginUserWithToken(route.queryParamMap.get('access-token')); + } else { + this.accountFacade.loginUser({ + login: route.queryParamMap.get('USERNAME'), + password: route.queryParamMap.get('PASSWORD'), + }); + } return race( // throw an error if a user login error occurs @@ -56,51 +70,84 @@ export class PunchoutPageGuard implements CanActivate { whenTruthy(), take(1), switchMap(() => { - // save HOOK_URL to 'hookURL' cookie - if (route.queryParamMap.get('HOOK_URL')) { - this.cookiesService.put('hookURL', route.queryParamMap.get('HOOK_URL'), { sameSite: 'Strict' }); - } + // handle cXML punchout with sid + if (route.queryParamMap.get('sid')) { + // fetch sid session information (basketId, returnURL, operation, ...) + return this.punchoutService.getCxmlPunchoutSession(route.queryParamMap.get('sid')).pipe( + // persist cXML session information (sid, returnURL, basketId) in cookies for later basket transfer + tap(data => { + this.cookiesService.put('punchout_SID', route.queryParamMap.get('sid'), { sameSite: 'Strict' }); + this.cookiesService.put('punchout_ReturnURL', data.returnURL, { sameSite: 'Strict' }); + this.cookiesService.put('punchout_BasketID', data.basketId, { sameSite: 'Strict' }); + }), + // use the basketId basket for the current PWA session (instead of default current basket) + // TODO: if load basket error (currently no error page) -> logout and do not use default 'current' basket + // TODO: if loadBasketWithId is faster then the initial loading of the 'current' basket after login the wrong 'current' basket might be used (the additional delay is the current work around) + delay(500), + tap(data => this.checkoutFacade.loadBasketWithId(data.basketId)), + mapTo(this.router.parseUrl('/home')) + ); + + // handle OCI punchout with HOOK_URL + } else if (route.queryParamMap.get('HOOK_URL')) { + // save HOOK_URL to cookie for later basket transfer + this.cookiesService.put('punchout_HookURL', route.queryParamMap.get('HOOK_URL'), { sameSite: 'Strict' }); + + // create a new basket for every punchout session to avoid basket conflicts for concurrent punchout sessions + this.checkoutFacade.createBasket(); - // create a new basket for every punchout session to avoid basket conflicts for concurrent sessions - this.checkoutFacade.createBasket(); + // Product Details + if (route.queryParamMap.get('FUNCTION') === 'DETAIL' && route.queryParamMap.get('PRODUCTID')) { + return of(this.router.parseUrl(`/product/${route.queryParamMap.get('PRODUCTID')}`)); - // Product Details - if (route.queryParamMap.get('FUNCTION') === 'DETAIL' && route.queryParamMap.get('PRODUCTID')) { - return of(this.router.parseUrl(`/product/${route.queryParamMap.get('PRODUCTID')}`)); + // Validation of Products + } else if (route.queryParamMap.get('FUNCTION') === 'VALIDATE' && route.queryParamMap.get('PRODUCTID')) { + return this.punchoutService + .getOciPunchoutProductData( + route.queryParamMap.get('PRODUCTID'), + route.queryParamMap.get('QUANTITY') || '1' + ) + .pipe( + tap(data => this.punchoutService.submitOciPunchoutData(data)), + mapTo(false) + ); - // Validation of Products - } else if (route.queryParamMap.get('FUNCTION') === 'VALIDATE' && route.queryParamMap.get('PRODUCTID')) { - return this.punchoutService - .getProductPunchoutData(route.queryParamMap.get('PRODUCTID'), route.queryParamMap.get('QUANTITY') || '1') - .pipe( - tap(data => this.punchoutService.submitPunchoutData(data)), + // Background Search + } else if ( + route.queryParamMap.get('FUNCTION') === 'BACKGROUND_SEARCH' && + route.queryParamMap.get('SEARCHSTRING') + ) { + return this.punchoutService.getOciPunchoutSearchData(route.queryParamMap.get('SEARCHSTRING')).pipe( + tap(data => this.punchoutService.submitOciPunchoutData(data, false)), mapTo(false) ); - // Background Search - } else if ( - route.queryParamMap.get('FUNCTION') === 'BACKGROUND_SEARCH' && - route.queryParamMap.get('SEARCHSTRING') - ) { - return this.punchoutService.getSearchPunchoutData(route.queryParamMap.get('SEARCHSTRING')).pipe( - tap(data => this.punchoutService.submitPunchoutData(data, false)), - mapTo(false) - ); - - // Login - } else { - return of(this.router.parseUrl('/home')); + // Login + } else { + return of(this.router.parseUrl('/home')); + } } - }) - ) - ).pipe( - catchError(error => - of(this.router.parseUrl('/error')).pipe( - tap(() => { - this.appFacade.setBusinessError(error); - }) + }), + // punchout error after successful authentication (needs to logout) + catchError(error => + this.accountFacade.userLoading$.pipe( + first(loading => !loading), + delay(0), + switchMap(() => { + this.accountFacade.logoutUser(); + this.apiTokenService.removeApiToken(); + this.appFacade.setBusinessError(error); + return of(this.router.parseUrl('/error')); + }) + ) ) ) + ).pipe( + // general punchout error handling (parameter missing, authentication error) + catchError(error => { + this.appFacade.setBusinessError(error); + return of(this.router.parseUrl('/error')); + }) ); } } diff --git a/src/app/extensions/punchout/services/punchout/punchout.service.ts b/src/app/extensions/punchout/services/punchout/punchout.service.ts index e0135c001c..b7b8785755 100644 --- a/src/app/extensions/punchout/services/punchout/punchout.service.ts +++ b/src/app/extensions/punchout/services/punchout/punchout.service.ts @@ -1,18 +1,22 @@ import { HttpHeaders, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Store, select } from '@ngrx/store'; -import { Observable, throwError } from 'rxjs'; -import { map, switchMap, take } from 'rxjs/operators'; +import { Observable, iif, throwError } from 'rxjs'; +import { concatMap, filter, map, switchMap, take } from 'rxjs/operators'; import { Attribute } from 'ish-core/models/attribute/attribute.model'; import { Link } from 'ish-core/models/link/link.model'; import { ApiService, unpackEnvelope } from 'ish-core/services/api/api.service'; +import { getUserPermissions } from 'ish-core/store/customer/authorization'; +import { getCurrentBasketId } from 'ish-core/store/customer/basket'; import { getLoggedInCustomer } from 'ish-core/store/customer/user'; import { CookiesService } from 'ish-core/utils/cookies/cookies.service'; import { whenTruthy } from 'ish-core/utils/operators'; -import { PunchoutUser } from '../../models/punchout-user/punchout-user.model'; +import { PunchoutSession } from '../../models/punchout-session/punchout-session.model'; +import { PunchoutType, PunchoutUser } from '../../models/punchout-user/punchout-user.model'; +// tslint:disable: force-jsdoc-comments @Injectable({ providedIn: 'root' }) export class PunchoutService { constructor(private apiService: ApiService, private cookiesService: CookiesService, private store: Store) {} @@ -26,8 +30,13 @@ export class PunchoutService { Accept: 'application/vnd.intershop.punchout.v2+json', }); - /** - * Gets the list of punchout users. + private getResourceType(punchoutType: PunchoutType): string { + return punchoutType === 'oci' ? 'oci5' : 'cxml1.2'; + } + + // PUNCHOUT USER MANAGEMENT + + /** Gets the list of punchout users. * @returns An array of punchout users. */ getUsers(): Observable { @@ -107,91 +116,210 @@ export class PunchoutService { ); } + // PUNCHOUT SHOPPING FUNCTIONALITY + + /** + * Initiates the punchout basket transfer depending on the user permission with the fitting punchout type (cXML or OCI). + */ + transferPunchoutBasket() { + return this.store.pipe(select(getUserPermissions)).pipe( + whenTruthy(), + switchMap(permissions => + iif( + () => permissions.includes('APP_B2B_SEND_CXML_BASKET'), + this.transferCxmlPunchoutBasket(), + iif(() => permissions.includes('APP_B2B_SEND_OCI_BASKET'), this.transferOciPunchoutBasket()) + ) + ) + ); + } + + /** + * Submits the punchout data via HTML form to the punchout system configured in the given form. + * @param form The prepared HTML form to submit the punchout data. + * @param submit Controls whether the HTML form is actually submitted (default) or not (only created in the document body). + */ + private submitPunchoutForm(form: HTMLFormElement, submit = true) { + if (!form) { + return throwError('submitPunchoutForm() of the punchout service called without a form'); + } + + // replace the document content with the form and submit the form + document.body.innerHTML = ''; + document.body.appendChild(form); + if (submit) { + form.submit(); + } + } + + // cXML SPECIFIC PUNCHOUT SHOPPING FUNCTIONALITY + + /** + * getCxmlPunchoutSession + */ + getCxmlPunchoutSession(sid: string): Observable { + return this.currentCustomer$.pipe( + switchMap(customer => + this.apiService.get( + `customers/${customer.customerNo}/punchouts/${this.getResourceType('cxml')}/sessions/${sid}`, + { + headers: this.punchoutHeaders, + } + ) + ) + ); + } + + /** + * transferCxmlPunchoutBasket + */ + private transferCxmlPunchoutBasket() { + const punchoutSID = this.cookiesService.get('punchout_SID'); + if (!punchoutSID) { + return throwError('no punchout_SID available in cookies for cXML punchout basket transfer'); + } + const returnURL = this.cookiesService.get('punchout_ReturnURL'); + if (!returnURL) { + return throwError('no punchout_ReturnURL available in cookies for cXML punchout basket transfer'); + } + + return this.currentCustomer$.pipe( + switchMap(customer => + this.apiService + .post( + `customers/${customer.customerNo}/punchouts/${this.getResourceType('cxml')}/transfer`, + undefined, + { + headers: new HttpHeaders({ Accept: 'text/xml' }), + params: new HttpParams().set('sid', punchoutSID), + responseType: 'text', + } + ) + .pipe(map(data => this.submitPunchoutForm(this.createCxmlPunchoutForm(data, returnURL)))) + ) + ); + + // TODO: cleanup punchout cookies? + } + + /** + * Creates an cXML punchout compatible form with the given BrowserFormPostURL + * and a hidden input field that contains the cXML PunchOutOrderMessage. + * @param punchOutOrderMessage + * @param browserFormPostUrl + * @returns The cXML punchout form + */ + private createCxmlPunchoutForm(punchOutOrderMessage: string, browserFormPostUrl: string): HTMLFormElement { + const cXmlForm = document.createElement('form'); + cXmlForm.method = 'post'; + cXmlForm.action = browserFormPostUrl; + cXmlForm.enctype = 'application/x-www-form-urlencoded'; + const input = document.createElement('input'); // set the returnURL + input.setAttribute('name', 'cXML-urlencoded'); + input.setAttribute('value', punchOutOrderMessage); // set the cXML value + input.setAttribute('type', 'hidden'); + cXmlForm.appendChild(input); + return cXmlForm; + } + + // OCI PUNCHOUT SHOPPING FUNCTIONALITY + + private transferOciPunchoutBasket() { + return this.store.pipe( + select(getCurrentBasketId), + filter(basketId => !!basketId), + concatMap(basketId => this.getOciPunchoutBasketData(basketId).pipe(map(data => this.submitOciPunchoutData(data)))) + ); + } + /** - * Gets a JSON object with the necessary punchout data for the basket transfer. + * Gets a JSON object with the necessary OCI punchout data for the basket transfer. * @param basketId The basket id for the punchout. */ - getBasketPunchoutData(basketId: string): Observable[]> { + private getOciPunchoutBasketData(basketId: string): Observable[]> { if (!basketId) { - return throwError('getBasketPunchoutData() of the punchout service called without basketId'); + return throwError('getOciPunchoutBasketData() of the punchout service called without basketId'); } return this.currentCustomer$.pipe( switchMap(customer => this.apiService - .post<{ data: Attribute[] }>(`customers/${customer.customerNo}/punchouts/oci5/transfer`, undefined, { - headers: this.punchoutHeaders, - params: new HttpParams().set('basketId', basketId), - }) + .post<{ data: Attribute[] }>( + `customers/${customer.customerNo}/punchouts/${this.getResourceType('oci')}/transfer`, + undefined, + { + headers: this.punchoutHeaders, + params: new HttpParams().set('basketId', basketId), + } + ) .pipe(map(data => data.data)) ) ); } /** - * Gets a JSON object with the necessary punchout data for the product validation. - * @param productSKU The product SKU of the product to validate. + * Gets a JSON object with the necessary OCI punchout data for the product validation. + * @param productId The product id (SKU) of the product to validate. * @param quantity The quantity for the validation (default: '1'). */ - getProductPunchoutData(productId: string, quantity = '1'): Observable[]> { + getOciPunchoutProductData(productId: string, quantity = '1'): Observable[]> { if (!productId) { - return throwError('getProductPunchoutData() of the punchout service called without productSKU'); + return throwError('getOciPunchoutProductData() of the punchout service called without productId'); } return this.currentCustomer$.pipe( switchMap(customer => this.apiService - .get<{ data: Attribute[] }>(`customers/${customer.customerNo}/punchouts/oci5/validate`, { - headers: this.punchoutHeaders, - params: new HttpParams().set('productId', productId).set('quantity', quantity), - }) + .get<{ data: Attribute[] }>( + `customers/${customer.customerNo}/punchouts/${this.getResourceType('oci')}/validate`, + { + headers: this.punchoutHeaders, + params: new HttpParams().set('productId', productId).set('quantity', quantity), + } + ) .pipe(map(data => data.data)) ) ); } /** - * Gets a JSON object with the necessary punchout data for the background search. + * Gets a JSON object with the necessary OCI punchout data for the background search. * @param searchString The search string to search punchout products. */ - getSearchPunchoutData(searchString: string): Observable[]> { + getOciPunchoutSearchData(searchString: string): Observable[]> { if (!searchString) { - return throwError('getSearchPunchoutData() of the punchout service called without searchString'); + return throwError('getOciPunchoutSearchData() of the punchout service called without searchString'); } return this.currentCustomer$.pipe( switchMap(customer => this.apiService - .get<{ data: Attribute[] }>(`customers/${customer.customerNo}/punchouts/oci5/background-search`, { - headers: this.punchoutHeaders, - params: new HttpParams().set('searchString', searchString), - }) + .get<{ data: Attribute[] }>( + `customers/${customer.customerNo}/punchouts/${this.getResourceType('oci')}/background-search`, + { + headers: this.punchoutHeaders, + params: new HttpParams().set('searchString', searchString), + } + ) .pipe(map(data => data.data)) ) ); } /** - * Submits the punchout data via HTML form to the punchout system configured in the HOOK_URL + * Submits the OCI punchout data via HTML form to the OCI punchout system configured in the HOOK_URL * @param data The punchout data retrieved from ICM. * @param submit Controls whether the HTML form is actually submitted (default) or not (only created in the document body). */ - submitPunchoutData(data: Attribute[], submit = true) { - const hookUrl = this.cookiesService.get('hookURL'); - if (!hookUrl) { - return throwError('no HOOK_URL available in cookies to submitPunchoutData()'); - } + submitOciPunchoutData(data: Attribute[], submit = true) { if (!data || !data.length) { - return throwError('submitPunchoutData() of the punchout service called without data'); + return throwError('submitOciPunchoutData() of the punchout service called without data'); } - - // create a form and send it to the hook URL - const form = this.createOciForm(data, hookUrl); - document.body.innerHTML = ''; - document.body.appendChild(form); - if (submit) { - form.submit(); + const hookUrl = this.cookiesService.get('punchout_HookURL'); + if (!hookUrl) { + return throwError('no punchout_HookURL available in cookies for OCI Punchout submitPunchoutData()'); } + this.submitPunchoutForm(this.createOciForm(data, hookUrl), submit); } /** diff --git a/src/app/extensions/punchout/shared/punchout-transfer-basket/punchout-transfer-basket.component.html b/src/app/extensions/punchout/shared/punchout-transfer-basket/punchout-transfer-basket.component.html index e75014202b..a1812a573b 100644 --- a/src/app/extensions/punchout/shared/punchout-transfer-basket/punchout-transfer-basket.component.html +++ b/src/app/extensions/punchout/shared/punchout-transfer-basket/punchout-transfer-basket.component.html @@ -1,3 +1,3 @@ - diff --git a/src/app/extensions/punchout/shared/punchout-transfer-basket/punchout-transfer-basket.component.ts b/src/app/extensions/punchout/shared/punchout-transfer-basket/punchout-transfer-basket.component.ts index bb7f0cd1d4..f616d7b80e 100644 --- a/src/app/extensions/punchout/shared/punchout-transfer-basket/punchout-transfer-basket.component.ts +++ b/src/app/extensions/punchout/shared/punchout-transfer-basket/punchout-transfer-basket.component.ts @@ -11,9 +11,12 @@ import { PunchoutFacade } from '../../facades/punchout.facade'; }) @GenerateLazyComponent() export class PunchoutTransferBasketComponent { + submitted = false; + constructor(private punchoutFacade: PunchoutFacade) {} transferBasket() { + this.submitted = true; this.punchoutFacade.transferBasket(); } } diff --git a/src/app/extensions/punchout/store/punchout-functions/punchout-functions.effects.spec.ts b/src/app/extensions/punchout/store/punchout-functions/punchout-functions.effects.spec.ts index 9c28126002..e107496293 100644 --- a/src/app/extensions/punchout/store/punchout-functions/punchout-functions.effects.spec.ts +++ b/src/app/extensions/punchout/store/punchout-functions/punchout-functions.effects.spec.ts @@ -3,13 +3,12 @@ import { provideMockActions } from '@ngrx/effects/testing'; import { Action, Store } from '@ngrx/store'; import { cold, hot } from 'jest-marbles'; import { Observable, of, throwError } from 'rxjs'; -import { anyString, anything, instance, mock, verify, when } from 'ts-mockito'; +import { instance, mock, verify, when } from 'ts-mockito'; import { Basket } from 'ish-core/models/basket/basket.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { displayErrorMessage } from 'ish-core/store/core/messages'; import { loadBasketSuccess } from 'ish-core/store/customer/basket'; -import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; import { makeHttpError } from 'ish-core/utils/dev/api-service-utils'; import { PunchoutService } from '../../services/punchout/punchout.service'; @@ -29,10 +28,10 @@ describe('Punchout Functions Effects', () => { beforeEach(() => { punchoutService = mock(PunchoutService); - when(punchoutService.getBasketPunchoutData(anyString())).thenReturn(of(undefined)); + when(punchoutService.transferPunchoutBasket()).thenReturn(of(undefined)); TestBed.configureTestingModule({ - imports: [CoreStoreModule.forTesting([]), CustomerStoreModule.forTesting('basket')], + imports: [CoreStoreModule.forTesting([])], providers: [ PunchoutFunctionsEffects, provideMockActions(() => actions$), @@ -56,20 +55,11 @@ describe('Punchout Functions Effects', () => { ); }); - it('should call the service for getting the punchout data', done => { + it('should call the service for transfering the punchout data', done => { actions$ = of(transferPunchoutBasket()); effects.transferPunchoutBasket$.subscribe(() => { - verify(punchoutService.getBasketPunchoutData('BID')).once(); - done(); - }); - }); - - it('should call the service for submitting the punchout data', done => { - actions$ = of(transferPunchoutBasket()); - - effects.transferPunchoutBasket$.subscribe(() => { - verify(punchoutService.submitPunchoutData(anything())).once(); + verify(punchoutService.transferPunchoutBasket()).once(); done(); }); }); @@ -85,9 +75,9 @@ describe('Punchout Functions Effects', () => { expect(effects.transferPunchoutBasket$).toBeObservable(expected$); }); - it('should dispatch a DeletePunchoutUserFail action in case of an error', () => { + it('should dispatch a transferPunchoutBasketFail action in case of an error', () => { const error = makeHttpError({ status: 401, code: 'feld' }); - when(punchoutService.getBasketPunchoutData(anyString())).thenReturn(throwError(error)); + when(punchoutService.transferPunchoutBasket()).thenReturn(throwError(error)); const action = transferPunchoutBasket(); const completion = transferPunchoutBasketFail({ error }); diff --git a/src/app/extensions/punchout/store/punchout-functions/punchout-functions.effects.ts b/src/app/extensions/punchout/store/punchout-functions/punchout-functions.effects.ts index b1d20c42b6..efbb3dc3d3 100644 --- a/src/app/extensions/punchout/store/punchout-functions/punchout-functions.effects.ts +++ b/src/app/extensions/punchout/store/punchout-functions/punchout-functions.effects.ts @@ -1,10 +1,8 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { Store, select } from '@ngrx/store'; -import { concatMap, filter, map, mapTo, withLatestFrom } from 'rxjs/operators'; +import { concatMap, map, mapTo } from 'rxjs/operators'; import { displayErrorMessage } from 'ish-core/store/core/messages'; -import { getCurrentBasketId } from 'ish-core/store/customer/basket'; import { mapErrorToAction, mapToPayloadProperty } from 'ish-core/utils/operators'; import { PunchoutService } from '../../services/punchout/punchout.service'; @@ -17,19 +15,15 @@ import { @Injectable() export class PunchoutFunctionsEffects { - constructor(private punchoutService: PunchoutService, private actions$: Actions, private store: Store) {} + constructor(private punchoutService: PunchoutService, private actions$: Actions) {} transferPunchoutBasket$ = createEffect(() => this.actions$.pipe( ofType(transferPunchoutBasket), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - filter(([, basketId]) => !!basketId), - concatMap(([, basketId]) => - this.punchoutService.getBasketPunchoutData(basketId).pipe( - map(data => this.punchoutService.submitPunchoutData(data)), - mapTo(transferPunchoutBasketSuccess()), - mapErrorToAction(transferPunchoutBasketFail) - ) + concatMap(() => + this.punchoutService + .transferPunchoutBasket() + .pipe(mapTo(transferPunchoutBasketSuccess()), mapErrorToAction(transferPunchoutBasketFail)) ) ) ); diff --git a/src/assets/i18n/de_DE.json b/src/assets/i18n/de_DE.json index 77a4994f21..6f6efa55df 100644 --- a/src/assets/i18n/de_DE.json +++ b/src/assets/i18n/de_DE.json @@ -818,7 +818,7 @@ "promotion.detailslink.label": "Aktionsdetails", "promotion.detailslink.text": "Details", "promotion.removelink.text": "Entfernen", - "punchout.error.missing.hook_url": "Fehler beim Verbinden zum Punchout-Katalog. Der Parameter HOOK_URL fehlt.", + "punchout.error.missing.parameters": "Fehler beim Verbinden zum Punchout-Katalog. Benötigte Start-Parameter fehlen.", "punchout.login_already_exists.error": "Der angegebene Anmeldename wird bereits verwendet. Prüfen Sie bitte die Richtigkeit der Eingabe oder geben Sie einen anderen Anmeldenamen an.", "quickorder.page.add.cart": "In den Warenkorb", "quickorder.page.add.row": "Weitere Zeilen hinzufügen", diff --git a/src/assets/i18n/en_US.json b/src/assets/i18n/en_US.json index 17f46a5a76..d704410cbd 100644 --- a/src/assets/i18n/en_US.json +++ b/src/assets/i18n/en_US.json @@ -820,7 +820,7 @@ "promotion.detailslink.label": "Promotion Details", "promotion.detailslink.text": "Details", "promotion.removelink.text": "Remove", - "punchout.error.missing.hook_url": "Error connecting to punchout catalog. The parameter HOOK_URL is missing.", + "punchout.error.missing.parameters": "Error connecting to the punchout catalog. Required start parameters are missing.", "punchout.login_already_exists.error": "A punchout user with that user name already exists. Please check the information for accuracy or enter a different name.", "quickorder.page.add.cart": "Add to Cart", "quickorder.page.add.row": "Add more rows", diff --git a/src/assets/i18n/fr_FR.json b/src/assets/i18n/fr_FR.json index d6f7fbaa13..405baaa28a 100644 --- a/src/assets/i18n/fr_FR.json +++ b/src/assets/i18n/fr_FR.json @@ -820,7 +820,7 @@ "promotion.detailslink.label": "Détails de promotion", "promotion.detailslink.text": "Détails", "promotion.removelink.text": "Supprimer", - "punchout.error.missing.hook_url": "Erreur de connexion au catalogue punchout. Le paramètre HOOK_URL est manquant.", + "punchout.error.missing.parameters": "Erreur de connexion au catalogue punchout. Le paramètre ... est manquant.", "punchout.login_already_exists.error": "Un utilisateur avec ce nom d’utilisateur existe déjà. Veuillez vérifier l’exactitude des informations ou entrer un nom d’utilisateur différent.", "quickorder.page.add.cart": "Ajouter au panier", "quickorder.page.add.row": "Ajouter lignes supplémentaires",