Skip to content

Commit

Permalink
feat: support for cXML punchout shopping (login, transfer order) (#550)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
shauke committed Apr 1, 2021
1 parent f5a1a2c commit b23d5ef
Show file tree
Hide file tree
Showing 12 changed files with 295 additions and 119 deletions.
5 changes: 5 additions & 0 deletions src/app/core/facades/account.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
loadUserPaymentMethods,
loginUser,
loginUserWithToken,
logoutUser,
requestPasswordReminder,
resetPasswordReminder,
updateCustomer,
Expand Down Expand Up @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface PunchoutSession {
operation: string;
basketId: string;
returnURL: string;
creationDate: string;
buyerCookie: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export interface PunchoutUser {
password?: string;
active: boolean;
}

export type PunchoutType = 'oci' | 'cxml';
137 changes: 92 additions & 45 deletions src/app/extensions/punchout/pages/punchout/punchout-page.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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;
}

Expand All @@ -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
Expand All @@ -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'));
})
);
}
}
Loading

0 comments on commit b23d5ef

Please sign in to comment.