@faststore/api 4.10.1 → 4.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +49 -46
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/index.mjs +1160 -1030
- package/dist/es/index.mjs.map +1 -1
- package/dist/src/__generated__/schema.d.ts +1 -0
- package/dist/src/__generated__/schema.d.ts.map +1 -1
- package/dist/src/platforms/vtex/clients/commerce/index.d.ts +16 -2
- package/dist/src/platforms/vtex/clients/commerce/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/clients/fetch.d.ts +1 -0
- package/dist/src/platforms/vtex/clients/fetch.d.ts.map +1 -1
- package/dist/src/platforms/vtex/clients/index.d.ts +3 -3
- package/dist/src/platforms/vtex/clients/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/clients/search/index.d.ts +7 -1
- package/dist/src/platforms/vtex/clients/search/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/index.d.ts +1 -1
- package/dist/src/platforms/vtex/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/loaders/sku.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/getOrderEntryOperation.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/index.d.ts +1 -1
- package/dist/src/platforms/vtex/resolvers/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/query.d.ts +1 -1
- package/dist/src/platforms/vtex/resolvers/query.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/subscribeToNewsletter.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/validateSession.d.ts.map +1 -1
- package/dist/src/platforms/vtex/utils/contractAssortment.d.ts +22 -0
- package/dist/src/platforms/vtex/utils/contractAssortment.d.ts.map +1 -0
- package/dist/src/platforms/vtex/utils/intelligentSearchRequest.d.ts +5 -0
- package/dist/src/platforms/vtex/utils/intelligentSearchRequest.d.ts.map +1 -1
- package/dist/src/platforms/vtex/utils/validateSessionHelpers.d.ts +29 -2
- package/dist/src/platforms/vtex/utils/validateSessionHelpers.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__generated__/schema.ts +1 -0
- package/src/platforms/vtex/clients/commerce/index.ts +80 -3
- package/src/platforms/vtex/clients/fetch.ts +52 -1
- package/src/platforms/vtex/clients/search/index.ts +13 -1
- package/src/platforms/vtex/loaders/sku.ts +1 -0
- package/src/platforms/vtex/resolvers/query.ts +51 -37
- package/src/platforms/vtex/resolvers/subscribeToNewsletter.ts +28 -1
- package/src/platforms/vtex/resolvers/validateSession.ts +3 -3
- package/src/platforms/vtex/typeDefs/session.graphql +3 -0
- package/src/platforms/vtex/utils/contractAssortment.ts +87 -0
- package/src/platforms/vtex/utils/intelligentSearchRequest.ts +10 -0
- package/src/platforms/vtex/utils/validateSessionHelpers.ts +47 -5
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
resolveActiveContractIdFromSession,
|
|
45
45
|
resolveDefaultContractId,
|
|
46
46
|
} from '../utils/contract'
|
|
47
|
+
import { assertProductInContractAssortment } from '../utils/contractAssortment'
|
|
47
48
|
import { mutateChannelContext, mutateLocaleContext } from '../utils/contex'
|
|
48
49
|
import { getAuthCookie, parseJwt } from '../utils/cookies'
|
|
49
50
|
import { enhanceSku, type EnhancedSku } from '../utils/enhanceSku'
|
|
@@ -59,6 +60,10 @@ import { getCatalogLocale, isLocalizationEnabled } from '../utils/localization'
|
|
|
59
60
|
import { isValidSkuId, pickBestSku } from '../utils/sku'
|
|
60
61
|
import { slugify } from '../utils/slugify'
|
|
61
62
|
import { resolveSort } from '../utils/sort'
|
|
63
|
+
import {
|
|
64
|
+
firstNonBlank,
|
|
65
|
+
resolveB2bUnitId,
|
|
66
|
+
} from '../utils/validateSessionHelpers'
|
|
62
67
|
import { FACET_CROSS_SELLING_MAP } from './../utils/facets'
|
|
63
68
|
import { StoreCollection } from './collection'
|
|
64
69
|
import { getOrderEntryOperation } from './getOrderEntryOperation'
|
|
@@ -202,25 +207,35 @@ export const Query = {
|
|
|
202
207
|
loaders: { skuLoader },
|
|
203
208
|
} = ctx
|
|
204
209
|
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
const loadSku = async () => {
|
|
211
|
+
try {
|
|
212
|
+
const skuId = id ?? slug?.split('-').pop() ?? ''
|
|
207
213
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
214
|
+
if (!isValidSkuId(skuId)) {
|
|
215
|
+
throw new Error(INVALID_SKU_ID_ERROR)
|
|
216
|
+
}
|
|
211
217
|
|
|
212
|
-
|
|
218
|
+
const sku = await skuLoader.load(skuId)
|
|
213
219
|
|
|
214
|
-
|
|
220
|
+
await assertSkuMatchesSlug(ctx, sku, slug, locale)
|
|
215
221
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
222
|
+
return sku
|
|
223
|
+
} catch (err) {
|
|
224
|
+
if (!shouldFallbackToProductRoute(err)) {
|
|
225
|
+
throw err
|
|
226
|
+
}
|
|
221
227
|
|
|
222
|
-
|
|
228
|
+
return fetchProductBySlugFallback(ctx, slug)
|
|
229
|
+
}
|
|
223
230
|
}
|
|
231
|
+
|
|
232
|
+
const sku = await loadSku()
|
|
233
|
+
|
|
234
|
+
// Runs outside `loadSku` so its NotFoundError is not swallowed by the
|
|
235
|
+
// slug fallback, which would refetch and return the same product.
|
|
236
|
+
assertProductInContractAssortment(ctx, sku)
|
|
237
|
+
|
|
238
|
+
return sku
|
|
224
239
|
},
|
|
225
240
|
collection: (
|
|
226
241
|
_: unknown,
|
|
@@ -871,8 +886,8 @@ export const Query = {
|
|
|
871
886
|
orgUnit: authentication?.unitName?.value ?? '',
|
|
872
887
|
}
|
|
873
888
|
},
|
|
874
|
-
//
|
|
875
|
-
//
|
|
889
|
+
// Buyers with an organizational unit and a contract get the contract
|
|
890
|
+
// information. Everyone else gets the b2c user information.
|
|
876
891
|
accountProfile: async (_: unknown, __: unknown, ctx: GraphqlContext) => {
|
|
877
892
|
const {
|
|
878
893
|
account,
|
|
@@ -886,7 +901,7 @@ export const Query = {
|
|
|
886
901
|
return null
|
|
887
902
|
}
|
|
888
903
|
|
|
889
|
-
if (jwt
|
|
904
|
+
if (resolveB2bUnitId(null, jwt.unitId)) {
|
|
890
905
|
const sessionData = await commerce.session('').catch(() => null)
|
|
891
906
|
|
|
892
907
|
if (!sessionData) {
|
|
@@ -897,13 +912,14 @@ export const Query = {
|
|
|
897
912
|
const shopper = sessionData.namespaces.shopper ?? null
|
|
898
913
|
const authentication = sessionData.namespaces.authentication ?? null
|
|
899
914
|
|
|
900
|
-
const contractId =
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
915
|
+
const contractId = firstNonBlank(
|
|
916
|
+
shopper?.activeContractId?.value,
|
|
917
|
+
authentication?.customerId?.value,
|
|
918
|
+
jwt.customerId
|
|
919
|
+
)
|
|
904
920
|
|
|
905
|
-
let contract = null
|
|
906
921
|
if (contractId) {
|
|
922
|
+
let contract = null
|
|
907
923
|
try {
|
|
908
924
|
contract = await commerce.masterData.getContractById({ contractId })
|
|
909
925
|
} catch (err) {
|
|
@@ -912,23 +928,21 @@ export const Query = {
|
|
|
912
928
|
err
|
|
913
929
|
)
|
|
914
930
|
}
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
const shopperName =
|
|
918
|
-
`${(shopper?.firstName?.value ?? '').trim()} ${(shopper?.lastName?.value ?? '').trim()}`.trim()
|
|
919
|
-
const name =
|
|
920
|
-
resolveActiveContractDisplayName(contract, profile) || shopperName
|
|
921
931
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
profile
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
+
const shopperName =
|
|
933
|
+
`${(shopper?.firstName?.value ?? '').trim()} ${(shopper?.lastName?.value ?? '').trim()}`.trim()
|
|
934
|
+
const name =
|
|
935
|
+
resolveActiveContractDisplayName(contract, profile) || shopperName
|
|
936
|
+
|
|
937
|
+
return {
|
|
938
|
+
name: name || '',
|
|
939
|
+
email:
|
|
940
|
+
profile?.email?.value ||
|
|
941
|
+
authentication?.storeUserEmail?.value ||
|
|
942
|
+
'',
|
|
943
|
+
id: profile?.id?.value || contractId,
|
|
944
|
+
// createdAt: '',
|
|
945
|
+
}
|
|
932
946
|
}
|
|
933
947
|
}
|
|
934
948
|
|
|
@@ -3,12 +3,39 @@ import type {
|
|
|
3
3
|
MutationSubscribeToNewsletterArgs,
|
|
4
4
|
PersonNewsletter,
|
|
5
5
|
} from '../../../__generated__/schema'
|
|
6
|
+
import { BadRequestError } from '../../errors'
|
|
7
|
+
|
|
8
|
+
const EMAIL_MAX_LENGTH = 254
|
|
9
|
+
const NAME_MAX_LENGTH = 100
|
|
10
|
+
// Character classes exclude `.`, so each domain label is matched once and the
|
|
11
|
+
// pattern stays linear on long inputs.
|
|
12
|
+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@.]+(\.[^\s@.]+)+$/
|
|
6
13
|
|
|
7
14
|
export const subscribeToNewsletter = async (
|
|
8
15
|
_: any,
|
|
9
16
|
{ data }: MutationSubscribeToNewsletterArgs,
|
|
10
17
|
{ clients: { commerce } }: GraphqlContext
|
|
11
18
|
): Promise<PersonNewsletter | null> => {
|
|
12
|
-
|
|
19
|
+
// The subscription is written with a store credential, so only validated
|
|
20
|
+
// values reach Master Data. See "Create-only writes from anonymous visitors"
|
|
21
|
+
// in https://developers.vtex.com/docs/guides/faststore/api-extensions-authentication-in-api-extensions
|
|
22
|
+
const email = data.email?.trim().toLowerCase() ?? ''
|
|
23
|
+
const name = data.name?.trim() ?? ''
|
|
24
|
+
|
|
25
|
+
if (email.length > EMAIL_MAX_LENGTH || !EMAIL_PATTERN.test(email)) {
|
|
26
|
+
throw new BadRequestError('Invalid email')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (name.length > NAME_MAX_LENGTH) {
|
|
30
|
+
throw new BadRequestError(
|
|
31
|
+
`Invalid name: must have at most ${NAME_MAX_LENGTH} characters`
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const response = await commerce.subscribeToNewsletter({
|
|
36
|
+
email,
|
|
37
|
+
name: name || undefined,
|
|
38
|
+
})
|
|
39
|
+
|
|
13
40
|
return { id: response?.Id }
|
|
14
41
|
}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
buildPersonFromProfile,
|
|
23
23
|
buildSessionSearchParams,
|
|
24
24
|
getPreciseLocationData,
|
|
25
|
+
resolveB2bUnitId,
|
|
25
26
|
resolveJwtClaims,
|
|
26
27
|
resolveSellerInRegion,
|
|
27
28
|
} from '../utils/validateSessionHelpers'
|
|
@@ -187,7 +188,7 @@ export const validateSession = async (
|
|
|
187
188
|
oldSession.locale
|
|
188
189
|
)
|
|
189
190
|
const marketingData = buildMarketingData(params, oldSession.marketingData)
|
|
190
|
-
const {
|
|
191
|
+
const { customerId, unitId } = await resolveJwtClaims(
|
|
191
192
|
clients,
|
|
192
193
|
headers?.cookie,
|
|
193
194
|
account
|
|
@@ -211,7 +212,7 @@ export const validateSession = async (
|
|
|
211
212
|
const publicData = sessionData?.namespaces.public ?? null
|
|
212
213
|
|
|
213
214
|
let contract = null
|
|
214
|
-
if (
|
|
215
|
+
if (resolveB2bUnitId(authentication, unitId) && profile?.id?.value) {
|
|
215
216
|
try {
|
|
216
217
|
contract = await clients.commerce.masterData.getContractById({
|
|
217
218
|
contractId: profile.id.value,
|
|
@@ -262,7 +263,6 @@ export const validateSession = async (
|
|
|
262
263
|
* - Contract data: `profile` namespace (those info will be available inside Faststore's Session `person` object)
|
|
263
264
|
*/
|
|
264
265
|
b2b: buildB2bSession({
|
|
265
|
-
isRepresentative,
|
|
266
266
|
authentication,
|
|
267
267
|
shopper,
|
|
268
268
|
publicData,
|
|
@@ -191,6 +191,9 @@ type StoreSession {
|
|
|
191
191
|
type StoreB2B {
|
|
192
192
|
customerId: String!
|
|
193
193
|
isRepresentative: Boolean
|
|
194
|
+
@deprecated(
|
|
195
|
+
reason: "Always true when `b2b` is set: a B2B session is identified by `unitId` and `customerId`. Check that `b2b` is not null instead. Scheduled for removal in the next major."
|
|
196
|
+
)
|
|
194
197
|
unitName: String
|
|
195
198
|
unitId: String
|
|
196
199
|
firstName: String
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { NotFoundError } from '../../errors'
|
|
2
|
+
import type { ProductCluster } from '../clients/search/types/ProductSearchResult'
|
|
3
|
+
import type { GraphqlContext } from '../index'
|
|
4
|
+
import type { EnhancedSku } from './enhanceSku'
|
|
5
|
+
import {
|
|
6
|
+
getSegmentExtraFacets,
|
|
7
|
+
parseSegmentCookie,
|
|
8
|
+
} from './intelligentSearchRequest'
|
|
9
|
+
|
|
10
|
+
const PRODUCT_CLUSTER_IDS_KEY = 'productClusterIds'
|
|
11
|
+
const EXCLUDED_PREFIX = 'not:'
|
|
12
|
+
|
|
13
|
+
export interface ProductClusterRestriction {
|
|
14
|
+
allowed: Set<string>
|
|
15
|
+
excluded: Set<string>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Collections the shopper's assortment is restricted to, as carried by the
|
|
20
|
+
* `vtex_segment` cookie (`productClusterIds=X` allows, `productClusterIds=not:Y`
|
|
21
|
+
* excludes). Returns `null` when the segment carries no restriction.
|
|
22
|
+
*/
|
|
23
|
+
export function getProductClusterRestriction(
|
|
24
|
+
segment: Record<string, unknown>
|
|
25
|
+
): ProductClusterRestriction | null {
|
|
26
|
+
const allowed = new Set<string>()
|
|
27
|
+
const excluded = new Set<string>()
|
|
28
|
+
|
|
29
|
+
for (const { key, value } of getSegmentExtraFacets(segment)) {
|
|
30
|
+
if (key !== PRODUCT_CLUSTER_IDS_KEY) continue
|
|
31
|
+
|
|
32
|
+
for (const rawId of value.split(',')) {
|
|
33
|
+
const id = rawId.trim()
|
|
34
|
+
|
|
35
|
+
if (id.startsWith(EXCLUDED_PREFIX)) {
|
|
36
|
+
const excludedId = id.slice(EXCLUDED_PREFIX.length)
|
|
37
|
+
if (excludedId) excluded.add(excludedId)
|
|
38
|
+
} else if (id) {
|
|
39
|
+
allowed.add(id)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (allowed.size === 0 && excluded.size === 0) {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { allowed, excluded }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isProductInAssortment(
|
|
52
|
+
productClusters: ProductCluster[] | undefined,
|
|
53
|
+
{ allowed, excluded }: ProductClusterRestriction
|
|
54
|
+
): boolean {
|
|
55
|
+
const clusterIds = (productClusters ?? []).map(({ id }) => String(id))
|
|
56
|
+
|
|
57
|
+
if (clusterIds.some((id) => excluded.has(id))) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return allowed.size === 0 || clusterIds.some((id) => allowed.has(id))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Listing endpoints get the assortment applied by Intelligent Search through
|
|
66
|
+
* the segment facets, but the `products` endpoint used to fetch a single
|
|
67
|
+
* product does not, so a product outside the shopper's assortment must be
|
|
68
|
+
* rejected here.
|
|
69
|
+
*/
|
|
70
|
+
export function assertProductInContractAssortment(
|
|
71
|
+
ctx: GraphqlContext,
|
|
72
|
+
sku: EnhancedSku
|
|
73
|
+
): void {
|
|
74
|
+
const restriction = getProductClusterRestriction(
|
|
75
|
+
parseSegmentCookie(ctx.headers?.cookie)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (!restriction) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!isProductInAssortment(sku.isVariantOf.productClusters, restriction)) {
|
|
83
|
+
throw new NotFoundError(
|
|
84
|
+
`Product ${sku.isVariantOf.productId} is not in the shopper's assortment`
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -225,6 +225,16 @@ function extractSegmentData(segment: Record<string, unknown>): {
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Non-shipping facets carried by the `vtex_segment` cookie (e.g. the B2B
|
|
230
|
+
* contract's `productClusterIds`), in the order they appear in the cookie.
|
|
231
|
+
*/
|
|
232
|
+
export function getSegmentExtraFacets(
|
|
233
|
+
segment: Record<string, unknown>
|
|
234
|
+
): IntelligentSearchFacet[] {
|
|
235
|
+
return extractSegmentData(segment).extraFacets
|
|
236
|
+
}
|
|
237
|
+
|
|
228
238
|
function appendSegmentParams(
|
|
229
239
|
params: URLSearchParams,
|
|
230
240
|
segmentParams: SegmentParams
|
|
@@ -107,7 +107,6 @@ export async function resolveJwtClaims(
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
return {
|
|
110
|
-
isRepresentative: isValidJwt ? Boolean(jwt?.isRepresentative) : false,
|
|
111
110
|
customerId: isValidJwt ? jwt?.customerId : undefined,
|
|
112
111
|
unitId: isValidJwt ? jwt?.unitId : undefined,
|
|
113
112
|
}
|
|
@@ -140,8 +139,43 @@ function shopperString(value: { value?: unknown } | null | undefined): string {
|
|
|
140
139
|
return typeof value?.value === 'string' ? value.value : ''
|
|
141
140
|
}
|
|
142
141
|
|
|
142
|
+
/**
|
|
143
|
+
* First value that is a non-blank string, trimmed. Session values and JWT
|
|
144
|
+
* claims are only nominally typed, so anything else is skipped.
|
|
145
|
+
*/
|
|
146
|
+
export function firstNonBlank(...values: unknown[]): string {
|
|
147
|
+
for (const value of values) {
|
|
148
|
+
const trimmed = typeof value === 'string' ? value.trim() : ''
|
|
149
|
+
if (trimmed) return trimmed
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return ''
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Organizational unit of the shopper, from the session or the JWT claim.
|
|
157
|
+
* Buyer Portal identifies its buyers by the unit; `isRepresentative` alone is
|
|
158
|
+
* not enough, since it can be true outside B2B.
|
|
159
|
+
*/
|
|
160
|
+
export function resolveB2bUnitId(
|
|
161
|
+
authentication: { unitId?: { value?: string | null } | null } | null,
|
|
162
|
+
jwtUnitId?: unknown
|
|
163
|
+
): string {
|
|
164
|
+
return firstNonBlank(authentication?.unitId?.value, jwtUnitId)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Returns `null` unless the shopper has both an organizational unit and a
|
|
169
|
+
* contract, the rule Buyer Portal uses for a B2B session.
|
|
170
|
+
*
|
|
171
|
+
* The contract falls back from the session's active contract to the session's
|
|
172
|
+
* authentication namespace and then to the JWT claim. Blank values fall
|
|
173
|
+
* through, so an empty session value never hides the claim.
|
|
174
|
+
*
|
|
175
|
+
* The deprecated `isRepresentative` field stays `true` whenever `b2b` is
|
|
176
|
+
* returned, so consumers that still read it keep working.
|
|
177
|
+
*/
|
|
143
178
|
export function buildB2bSession(args: {
|
|
144
|
-
isRepresentative: boolean
|
|
145
179
|
authentication: {
|
|
146
180
|
customerId?: { value?: string | null } | null
|
|
147
181
|
unitName?: { value?: string | null } | null
|
|
@@ -152,6 +186,7 @@ export function buildB2bSession(args: {
|
|
|
152
186
|
firstName?: { value?: unknown } | null
|
|
153
187
|
lastName?: { value?: unknown } | null
|
|
154
188
|
organizationManager?: { value?: boolean | null } | null
|
|
189
|
+
activeContractId?: { value?: string | null } | null
|
|
155
190
|
} | null
|
|
156
191
|
publicData: { postalCode?: { value?: string | null } | null } | null
|
|
157
192
|
profile: Parameters<typeof resolveActiveContractDisplayName>[1]
|
|
@@ -159,7 +194,14 @@ export function buildB2bSession(args: {
|
|
|
159
194
|
customerId?: string
|
|
160
195
|
unitId?: string
|
|
161
196
|
}) {
|
|
162
|
-
|
|
197
|
+
const unitId = resolveB2bUnitId(args.authentication, args.unitId)
|
|
198
|
+
const customerId = firstNonBlank(
|
|
199
|
+
args.shopper?.activeContractId?.value,
|
|
200
|
+
args.authentication?.customerId?.value,
|
|
201
|
+
args.customerId
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if (!unitId || !customerId) {
|
|
163
205
|
return null
|
|
164
206
|
}
|
|
165
207
|
|
|
@@ -168,9 +210,9 @@ export function buildB2bSession(args: {
|
|
|
168
210
|
|
|
169
211
|
return {
|
|
170
212
|
isRepresentative: true,
|
|
171
|
-
customerId
|
|
213
|
+
customerId,
|
|
172
214
|
unitName: args.authentication?.unitName?.value ?? '',
|
|
173
|
-
unitId
|
|
215
|
+
unitId,
|
|
174
216
|
firstName,
|
|
175
217
|
lastName,
|
|
176
218
|
userName: `${firstName} ${lastName}`.trim(),
|