@faststore/api 4.6.0-dev.0 → 4.6.0-dev.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 +51 -45
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/index.mjs +1345 -1237
- package/dist/es/index.mjs.map +1 -1
- package/dist/src/__generated__/schema.d.ts +6 -0
- package/dist/src/__generated__/schema.d.ts.map +1 -1
- package/dist/src/platforms/vtex/clients/commerce/index.d.ts +8 -1
- package/dist/src/platforms/vtex/clients/commerce/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/clients/index.d.ts +2 -1
- package/dist/src/platforms/vtex/clients/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/index.d.ts +1 -0
- package/dist/src/platforms/vtex/index.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 -0
- package/dist/src/platforms/vtex/resolvers/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/mutation.d.ts +1 -0
- package/dist/src/platforms/vtex/resolvers/mutation.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/query.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/validateCart.d.ts +1 -0
- package/dist/src/platforms/vtex/resolvers/validateCart.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/validateSession.d.ts.map +1 -1
- package/dist/src/platforms/vtex/utils/cartSalesChannel.d.ts +11 -0
- package/dist/src/platforms/vtex/utils/cartSalesChannel.d.ts.map +1 -0
- package/dist/src/platforms/vtex/utils/sessionChannel.d.ts +10 -0
- package/dist/src/platforms/vtex/utils/sessionChannel.d.ts.map +1 -0
- package/dist/src/platforms/vtex/utils/validateSessionHelpers.d.ts +88 -0
- package/dist/src/platforms/vtex/utils/validateSessionHelpers.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/__generated__/schema.ts +6 -0
- package/src/platforms/vtex/clients/commerce/index.ts +17 -2
- package/src/platforms/vtex/resolvers/validateCart.ts +86 -8
- package/src/platforms/vtex/resolvers/validateSession.ts +63 -140
- package/src/platforms/vtex/typeDefs/order.graphql +6 -0
- package/src/platforms/vtex/utils/cartSalesChannel.ts +31 -0
- package/src/platforms/vtex/utils/sessionChannel.ts +32 -0
- package/src/platforms/vtex/utils/validateSessionHelpers.ts +202 -0
|
@@ -3,39 +3,29 @@ import deepEquals from 'fast-deep-equal'
|
|
|
3
3
|
import type { GraphqlContext } from '..'
|
|
4
4
|
import type {
|
|
5
5
|
MutationValidateSessionArgs,
|
|
6
|
-
StoreMarketingData,
|
|
7
6
|
StoreSession,
|
|
8
7
|
} from '../../../__generated__/schema'
|
|
9
8
|
import ChannelMarshal from '../utils/channel'
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
longitude: address.geoCoordinates[0],
|
|
28
|
-
}
|
|
29
|
-
: null
|
|
30
|
-
|
|
31
|
-
return { city: address.city, geoCoordinates }
|
|
32
|
-
} catch (err) {
|
|
33
|
-
console.error(
|
|
34
|
-
`Error while getting geo coordinates for the current postal code (${postalCode}) and country (${country}).\n`
|
|
35
|
-
)
|
|
9
|
+
import { channelAfterSessionManager } from '../utils/sessionChannel'
|
|
10
|
+
import {
|
|
11
|
+
buildB2bSession,
|
|
12
|
+
buildMarketingData,
|
|
13
|
+
buildPersonFromProfile,
|
|
14
|
+
buildSessionSearchParams,
|
|
15
|
+
getPreciseLocationData,
|
|
16
|
+
resolveJwtClaims,
|
|
17
|
+
resolveSellerInRegion,
|
|
18
|
+
} from '../utils/validateSessionHelpers'
|
|
19
|
+
|
|
20
|
+
type SessionStoreNamespace = {
|
|
21
|
+
channel?: { value?: string | null } | null
|
|
22
|
+
currencyCode?: { value?: string | null } | null
|
|
23
|
+
currencySymbol?: { value?: string | null } | null
|
|
24
|
+
countryCode?: { value?: string | null } | null
|
|
25
|
+
}
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
}
|
|
27
|
+
type SessionCheckoutNamespace = {
|
|
28
|
+
regionId?: { value?: string | null } | null
|
|
39
29
|
}
|
|
40
30
|
|
|
41
31
|
export const validateSession = async (
|
|
@@ -49,7 +39,6 @@ export const validateSession = async (
|
|
|
49
39
|
let city = oldSession.city ?? null
|
|
50
40
|
let geoCoordinates = oldSession.geoCoordinates ?? null
|
|
51
41
|
|
|
52
|
-
// Update location data if postal code and country are provided
|
|
53
42
|
const shouldGetPreciseLocation = !city || !geoCoordinates
|
|
54
43
|
if (shouldGetPreciseLocation && postalCode !== '' && country !== '') {
|
|
55
44
|
const preciseLocation = await getPreciseLocationData(
|
|
@@ -65,64 +54,20 @@ export const validateSession = async (
|
|
|
65
54
|
* The Session Manager API (https://developers.vtex.com/docs/api-reference/session-manager-api#patch-/api/sessions) adds the query params to the session public namespace.
|
|
66
55
|
* This is used by Checkout (checkout-session) and Intelligent Search (search-session)
|
|
67
56
|
*/
|
|
68
|
-
const params =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
params
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!!country) {
|
|
83
|
-
params.set('country', country)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!!geoCoordinates) {
|
|
87
|
-
params.set(
|
|
88
|
-
'geoCoordinates',
|
|
89
|
-
`${geoCoordinates.longitude},${geoCoordinates.latitude}` // long,lat is the format expected
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Sending the locale to the session, the store-session app will update cultureInfo
|
|
94
|
-
params.set('locale', oldSession.locale)
|
|
95
|
-
|
|
96
|
-
const { marketingData: oldMarketingData } = oldSession
|
|
97
|
-
|
|
98
|
-
const marketingData: StoreMarketingData = {
|
|
99
|
-
utmCampaign:
|
|
100
|
-
params.get('utm_campaign') ?? oldMarketingData?.utmCampaign ?? '',
|
|
101
|
-
utmMedium: params.get('utm_medium') ?? oldMarketingData?.utmMedium ?? '',
|
|
102
|
-
utmSource: params.get('utm_source') ?? oldMarketingData?.utmSource ?? '',
|
|
103
|
-
utmiCampaign: params.get('utmi_cp') ?? oldMarketingData?.utmiCampaign ?? '',
|
|
104
|
-
utmiPage: params.get('utmi_p') ?? oldMarketingData?.utmiPage ?? '',
|
|
105
|
-
utmiPart: params.get('utmi_pc') ?? oldMarketingData?.utmiPart ?? '',
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const jwt = parseJwt(getAuthCookie(headers?.cookie ?? '', account))
|
|
109
|
-
|
|
110
|
-
// Validate JWT token if it exists
|
|
111
|
-
let isValidJwt = false
|
|
112
|
-
if (jwt) {
|
|
113
|
-
try {
|
|
114
|
-
const vtexIdResponse = await clients.commerce.vtexid.validate()
|
|
115
|
-
isValidJwt = vtexIdResponse?.authStatus?.toLowerCase() === 'success'
|
|
116
|
-
} catch (error) {
|
|
117
|
-
console.warn('JWT validation failed:', error)
|
|
118
|
-
isValidJwt = false
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Only use JWT data if the token is valid
|
|
123
|
-
const isRepresentative = isValidJwt ? jwt?.isRepresentative : false
|
|
124
|
-
const customerId = isValidJwt ? jwt?.customerId : undefined
|
|
125
|
-
const unitId = isValidJwt ? jwt?.unitId : undefined
|
|
57
|
+
const params = buildSessionSearchParams(
|
|
58
|
+
search,
|
|
59
|
+
channel,
|
|
60
|
+
postalCode,
|
|
61
|
+
country,
|
|
62
|
+
geoCoordinates,
|
|
63
|
+
oldSession.locale
|
|
64
|
+
)
|
|
65
|
+
const marketingData = buildMarketingData(params, oldSession.marketingData)
|
|
66
|
+
const { isRepresentative, customerId, unitId } = await resolveJwtClaims(
|
|
67
|
+
clients,
|
|
68
|
+
headers?.cookie,
|
|
69
|
+
account
|
|
70
|
+
)
|
|
126
71
|
|
|
127
72
|
const sessionData = await clients.commerce
|
|
128
73
|
.session(params.toString())
|
|
@@ -130,37 +75,34 @@ export const validateSession = async (
|
|
|
130
75
|
|
|
131
76
|
const profile = sessionData?.namespaces.profile ?? null
|
|
132
77
|
const shopper = sessionData?.namespaces.shopper ?? null
|
|
133
|
-
const store = sessionData?.namespaces.store ??
|
|
78
|
+
const store = (sessionData?.namespaces.store ??
|
|
79
|
+
null) as SessionStoreNamespace | null
|
|
134
80
|
const authentication = sessionData?.namespaces.authentication ?? null
|
|
135
|
-
const checkout = sessionData?.namespaces.checkout ??
|
|
81
|
+
const checkout = (sessionData?.namespaces.checkout ??
|
|
82
|
+
null) as SessionCheckoutNamespace | null
|
|
136
83
|
const publicData = sessionData?.namespaces.public ?? null
|
|
137
84
|
|
|
138
|
-
// Fetch contract data for B2B representatives
|
|
139
85
|
let contract = null
|
|
140
86
|
if (isRepresentative && profile?.id?.value) {
|
|
141
87
|
try {
|
|
142
88
|
contract = await clients.commerce.masterData.getContractById({
|
|
143
89
|
contractId: profile.id.value,
|
|
144
90
|
})
|
|
145
|
-
} catch
|
|
91
|
+
} catch {
|
|
146
92
|
console.error(
|
|
147
93
|
`Error while getting contract data for profile ID (${profile.id.value}).\n`
|
|
148
94
|
)
|
|
149
95
|
}
|
|
150
96
|
}
|
|
151
97
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
})
|
|
161
|
-
const region = regionData?.[0]
|
|
162
|
-
seller = region?.sellers.find((seller) => channel.seller === seller.id)
|
|
163
|
-
}
|
|
98
|
+
const sellerId = await resolveSellerInRegion(
|
|
99
|
+
clients,
|
|
100
|
+
channel,
|
|
101
|
+
postalCode,
|
|
102
|
+
geoCoordinates,
|
|
103
|
+
country,
|
|
104
|
+
params.get('sc') ?? channel.salesChannel
|
|
105
|
+
)
|
|
164
106
|
|
|
165
107
|
const newSession = {
|
|
166
108
|
...oldSession,
|
|
@@ -169,49 +111,30 @@ export const validateSession = async (
|
|
|
169
111
|
symbol: store?.currencySymbol?.value ?? oldSession.currency.symbol,
|
|
170
112
|
},
|
|
171
113
|
country: store?.countryCode?.value ?? country,
|
|
172
|
-
channel:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
114
|
+
channel: channelAfterSessionManager(
|
|
115
|
+
channel,
|
|
116
|
+
store?.channel?.value,
|
|
117
|
+
checkout?.regionId?.value,
|
|
118
|
+
sellerId
|
|
119
|
+
),
|
|
178
120
|
/**
|
|
179
121
|
* B2B data structure in Session:
|
|
180
122
|
* - Logged user data (shopper): `shopper` namespace
|
|
181
123
|
* - Unit data: `authentication` namespace
|
|
182
124
|
* - Contract data: `profile` namespace (those info will be available inside Faststore's Session `person` object)
|
|
183
125
|
*/
|
|
184
|
-
b2b:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
lastName:
|
|
195
|
-
typeof shopper?.lastName?.value === 'string'
|
|
196
|
-
? shopper.lastName.value
|
|
197
|
-
: '',
|
|
198
|
-
userName:
|
|
199
|
-
`${typeof shopper?.firstName?.value === 'string' ? shopper.firstName.value : ''} ${typeof shopper?.lastName?.value === 'string' ? shopper.lastName.value : ''}`.trim(),
|
|
200
|
-
userEmail: authentication?.storeUserEmail.value ?? '',
|
|
201
|
-
savedPostalCode: publicData?.postalCode?.value ?? '',
|
|
202
|
-
contractName: resolveActiveContractDisplayName(contract, profile),
|
|
203
|
-
organizationManager: shopper?.organizationManager?.value ?? false,
|
|
204
|
-
}
|
|
205
|
-
: null,
|
|
126
|
+
b2b: buildB2bSession({
|
|
127
|
+
isRepresentative,
|
|
128
|
+
authentication,
|
|
129
|
+
shopper,
|
|
130
|
+
publicData,
|
|
131
|
+
profile,
|
|
132
|
+
contract,
|
|
133
|
+
customerId,
|
|
134
|
+
unitId,
|
|
135
|
+
}),
|
|
206
136
|
marketingData,
|
|
207
|
-
person: profile
|
|
208
|
-
? {
|
|
209
|
-
id: profile.id?.value ?? '',
|
|
210
|
-
email: profile.email?.value ?? '',
|
|
211
|
-
givenName: profile.firstName?.value ?? '',
|
|
212
|
-
familyName: profile.lastName?.value ?? '',
|
|
213
|
-
}
|
|
214
|
-
: null,
|
|
137
|
+
person: buildPersonFromProfile(profile),
|
|
215
138
|
geoCoordinates:
|
|
216
139
|
(geoCoordinates?.latitude &&
|
|
217
140
|
geoCoordinates?.longitude &&
|
|
@@ -14,6 +14,12 @@ type StoreOrder {
|
|
|
14
14
|
Indicates whether or not items with attachments should be split.
|
|
15
15
|
"""
|
|
16
16
|
shouldSplitItem: Boolean
|
|
17
|
+
"""
|
|
18
|
+
Sales channel of the underlying orderForm when FastStore adopted it because
|
|
19
|
+
the browser session was stale (e.g. after Quick Order). Clients should align
|
|
20
|
+
`session.channel` to this value. Null when no SC adoption happened.
|
|
21
|
+
"""
|
|
22
|
+
salesChannel: String
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
"""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Channel } from './channel'
|
|
2
|
+
import ChannelMarshal from './channel'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* When session SC and orderForm SC diverge, keep Checkout on the orderForm
|
|
6
|
+
* trade policy. Used after external cart changes (stale etag / Quick Order) and
|
|
7
|
+
* on later validations while the browser session still lags.
|
|
8
|
+
*
|
|
9
|
+
* Forcing the session SC (refetch / item updates with `sc=session`) would drop
|
|
10
|
+
* items that exist only on the orderForm's sales channel.
|
|
11
|
+
*/
|
|
12
|
+
export function channelWhenSessionDivergesFromOrderForm(
|
|
13
|
+
currentChannel: Required<Channel>,
|
|
14
|
+
orderFormSalesChannel: string | null | undefined
|
|
15
|
+
): string | null {
|
|
16
|
+
if (orderFormSalesChannel == null || orderFormSalesChannel === '') {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (
|
|
21
|
+
String(orderFormSalesChannel) === String(currentChannel.salesChannel ?? '')
|
|
22
|
+
) {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return ChannelMarshal.stringify({
|
|
27
|
+
...currentChannel,
|
|
28
|
+
salesChannel: String(orderFormSalesChannel),
|
|
29
|
+
hasOnlyDefaultSalesChannel: false,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Channel } from './channel'
|
|
2
|
+
import ChannelMarshal from './channel'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds the session `channel` string after Session Manager responds.
|
|
6
|
+
*
|
|
7
|
+
* When the client already pinned an explicit SC (e.g. adopted from the
|
|
8
|
+
* orderForm after Quick Order), keep it. Session Manager often still reports
|
|
9
|
+
* the default SC and would otherwise overwrite the adoption.
|
|
10
|
+
*/
|
|
11
|
+
export function channelAfterSessionManager(
|
|
12
|
+
channel: Required<Channel>,
|
|
13
|
+
storeChannelValue: string | null | undefined,
|
|
14
|
+
regionId: string | null | undefined,
|
|
15
|
+
sellerId: string | undefined
|
|
16
|
+
): string {
|
|
17
|
+
const hasExplicitSalesChannel = channel.hasOnlyDefaultSalesChannel === false
|
|
18
|
+
|
|
19
|
+
const storeSalesChannel = storeChannelValue ?? undefined
|
|
20
|
+
const resolvedRegionId = regionId ?? channel.regionId
|
|
21
|
+
|
|
22
|
+
return ChannelMarshal.stringify({
|
|
23
|
+
salesChannel: hasExplicitSalesChannel
|
|
24
|
+
? channel.salesChannel || storeSalesChannel
|
|
25
|
+
: (storeSalesChannel ?? channel.salesChannel),
|
|
26
|
+
regionId: resolvedRegionId ?? undefined,
|
|
27
|
+
seller: sellerId,
|
|
28
|
+
hasOnlyDefaultSalesChannel: hasExplicitSalesChannel
|
|
29
|
+
? false
|
|
30
|
+
: !storeSalesChannel,
|
|
31
|
+
})
|
|
32
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { GraphqlContext } from '..'
|
|
2
|
+
import type {
|
|
3
|
+
StoreMarketingData,
|
|
4
|
+
StoreSession,
|
|
5
|
+
} from '../../../__generated__/schema'
|
|
6
|
+
import type { Channel } from './channel'
|
|
7
|
+
import { resolveActiveContractDisplayName } from './contract'
|
|
8
|
+
import { getAuthCookie, parseJwt } from './cookies'
|
|
9
|
+
|
|
10
|
+
export async function getPreciseLocationData(
|
|
11
|
+
clients: GraphqlContext['clients'],
|
|
12
|
+
country: string,
|
|
13
|
+
postalCode: string
|
|
14
|
+
) {
|
|
15
|
+
try {
|
|
16
|
+
const address = await clients.commerce.checkout.address({
|
|
17
|
+
postalCode,
|
|
18
|
+
country,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const geoCoordinates = address.geoCoordinates
|
|
22
|
+
? {
|
|
23
|
+
latitude: address.geoCoordinates[1],
|
|
24
|
+
longitude: address.geoCoordinates[0],
|
|
25
|
+
}
|
|
26
|
+
: null
|
|
27
|
+
|
|
28
|
+
return { city: address.city, geoCoordinates }
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(
|
|
31
|
+
`Error while getting geo coordinates for the current postal code (${postalCode}) and country (${country}).\n`
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
throw err
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function buildSessionSearchParams(
|
|
39
|
+
search: string,
|
|
40
|
+
channel: Required<Channel>,
|
|
41
|
+
postalCode: string,
|
|
42
|
+
country: string,
|
|
43
|
+
geoCoordinates: StoreSession['geoCoordinates'],
|
|
44
|
+
locale: string
|
|
45
|
+
) {
|
|
46
|
+
const params = new URLSearchParams(search)
|
|
47
|
+
|
|
48
|
+
// Remove facets so they do not interfere with session data / vtex_segment
|
|
49
|
+
if (params.has('facets')) {
|
|
50
|
+
params.delete('facets')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
params.set('sc', params.get('sc') ?? channel.salesChannel)
|
|
54
|
+
|
|
55
|
+
if (postalCode) {
|
|
56
|
+
params.set('postalCode', postalCode)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (country) {
|
|
60
|
+
params.set('country', country)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (geoCoordinates) {
|
|
64
|
+
params.set(
|
|
65
|
+
'geoCoordinates',
|
|
66
|
+
`${geoCoordinates.longitude},${geoCoordinates.latitude}` // long,lat is the format expected
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Sending the locale to the session, the store-session app will update cultureInfo
|
|
71
|
+
params.set('locale', locale)
|
|
72
|
+
|
|
73
|
+
return params
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function buildMarketingData(
|
|
77
|
+
params: URLSearchParams,
|
|
78
|
+
oldMarketingData: StoreSession['marketingData']
|
|
79
|
+
): StoreMarketingData {
|
|
80
|
+
return {
|
|
81
|
+
utmCampaign:
|
|
82
|
+
params.get('utm_campaign') ?? oldMarketingData?.utmCampaign ?? '',
|
|
83
|
+
utmMedium: params.get('utm_medium') ?? oldMarketingData?.utmMedium ?? '',
|
|
84
|
+
utmSource: params.get('utm_source') ?? oldMarketingData?.utmSource ?? '',
|
|
85
|
+
utmiCampaign: params.get('utmi_cp') ?? oldMarketingData?.utmiCampaign ?? '',
|
|
86
|
+
utmiPage: params.get('utmi_p') ?? oldMarketingData?.utmiPage ?? '',
|
|
87
|
+
utmiPart: params.get('utmi_pc') ?? oldMarketingData?.utmiPart ?? '',
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function resolveJwtClaims(
|
|
92
|
+
clients: GraphqlContext['clients'],
|
|
93
|
+
cookie: string | undefined,
|
|
94
|
+
account: string
|
|
95
|
+
) {
|
|
96
|
+
const jwt = parseJwt(getAuthCookie(cookie ?? '', account))
|
|
97
|
+
let isValidJwt = false
|
|
98
|
+
|
|
99
|
+
if (jwt) {
|
|
100
|
+
try {
|
|
101
|
+
const vtexIdResponse = await clients.commerce.vtexid.validate()
|
|
102
|
+
isValidJwt = vtexIdResponse?.authStatus?.toLowerCase() === 'success'
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.warn('JWT validation failed:', error)
|
|
105
|
+
isValidJwt = false
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
isRepresentative: isValidJwt ? Boolean(jwt?.isRepresentative) : false,
|
|
111
|
+
customerId: isValidJwt ? jwt?.customerId : undefined,
|
|
112
|
+
unitId: isValidJwt ? jwt?.unitId : undefined,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function resolveSellerInRegion(
|
|
117
|
+
clients: GraphqlContext['clients'],
|
|
118
|
+
channel: Required<Channel>,
|
|
119
|
+
postalCode: string,
|
|
120
|
+
geoCoordinates: StoreSession['geoCoordinates'],
|
|
121
|
+
country: string,
|
|
122
|
+
salesChannel: string
|
|
123
|
+
) {
|
|
124
|
+
if (!channel.seller || !(postalCode || geoCoordinates)) {
|
|
125
|
+
return undefined
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const regionData = await clients.commerce.checkout.region({
|
|
129
|
+
postalCode,
|
|
130
|
+
geoCoordinates,
|
|
131
|
+
country,
|
|
132
|
+
salesChannel,
|
|
133
|
+
})
|
|
134
|
+
const region = regionData?.[0]
|
|
135
|
+
|
|
136
|
+
return region?.sellers.find((seller) => channel.seller === seller.id)?.id
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function shopperString(value: { value?: unknown } | null | undefined): string {
|
|
140
|
+
return typeof value?.value === 'string' ? value.value : ''
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function buildB2bSession(args: {
|
|
144
|
+
isRepresentative: boolean
|
|
145
|
+
authentication: {
|
|
146
|
+
customerId?: { value?: string | null } | null
|
|
147
|
+
unitName?: { value?: string | null } | null
|
|
148
|
+
unitId?: { value?: string | null } | null
|
|
149
|
+
storeUserEmail?: { value?: string | null } | null
|
|
150
|
+
} | null
|
|
151
|
+
shopper: {
|
|
152
|
+
firstName?: { value?: unknown } | null
|
|
153
|
+
lastName?: { value?: unknown } | null
|
|
154
|
+
organizationManager?: { value?: boolean | null } | null
|
|
155
|
+
} | null
|
|
156
|
+
publicData: { postalCode?: { value?: string | null } | null } | null
|
|
157
|
+
profile: Parameters<typeof resolveActiveContractDisplayName>[1]
|
|
158
|
+
contract: Parameters<typeof resolveActiveContractDisplayName>[0]
|
|
159
|
+
customerId?: string
|
|
160
|
+
unitId?: string
|
|
161
|
+
}) {
|
|
162
|
+
if (!args.isRepresentative) {
|
|
163
|
+
return null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const firstName = shopperString(args.shopper?.firstName)
|
|
167
|
+
const lastName = shopperString(args.shopper?.lastName)
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
isRepresentative: true,
|
|
171
|
+
customerId: args.authentication?.customerId?.value ?? args.customerId ?? '',
|
|
172
|
+
unitName: args.authentication?.unitName?.value ?? '',
|
|
173
|
+
unitId: args.authentication?.unitId?.value ?? args.unitId ?? '',
|
|
174
|
+
firstName,
|
|
175
|
+
lastName,
|
|
176
|
+
userName: `${firstName} ${lastName}`.trim(),
|
|
177
|
+
userEmail: args.authentication?.storeUserEmail?.value ?? '',
|
|
178
|
+
savedPostalCode: args.publicData?.postalCode?.value ?? '',
|
|
179
|
+
contractName: resolveActiveContractDisplayName(args.contract, args.profile),
|
|
180
|
+
organizationManager: args.shopper?.organizationManager?.value ?? false,
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function buildPersonFromProfile(
|
|
185
|
+
profile: {
|
|
186
|
+
id?: { value?: string | null } | null
|
|
187
|
+
email?: { value?: string | null } | null
|
|
188
|
+
firstName?: { value?: string | null } | null
|
|
189
|
+
lastName?: { value?: string | null } | null
|
|
190
|
+
} | null
|
|
191
|
+
) {
|
|
192
|
+
if (!profile?.id) {
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
id: profile.id?.value ?? '',
|
|
198
|
+
email: profile.email?.value ?? '',
|
|
199
|
+
givenName: profile.firstName?.value ?? '',
|
|
200
|
+
familyName: profile.lastName?.value ?? '',
|
|
201
|
+
}
|
|
202
|
+
}
|