Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion masterdata/returnRequest/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,8 @@
"customerProfileData",
"status",
"sequenceNumber",
"dateSubmitted"
"dateSubmitted",
"items"
],
"v-immediate-indexing": true
}
5 changes: 5 additions & 0 deletions node/clients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { MailClient } from './mail'
import Checkout from './checkout'
import { VtexId } from './vtexId'
import { CatalogGQL } from './catalogGQL'
import { MarketplaceAppClient } from './marketplace'

const ReturnAppSettings = vbaseFor<string, ReturnAppSettings>('appSettings')
const ReturnRequest = masterDataFor<ReturnRequest>('returnRequest')
Expand Down Expand Up @@ -53,4 +54,8 @@ export class Clients extends IOClients {
public get sphinx() {
return this.getOrSet('sphinx', Sphinx)
}

public get marketplace() {
return this.getOrSet('marketplace', MarketplaceAppClient)
}
}
54 changes: 54 additions & 0 deletions node/clients/marketplace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { InstanceOptions, IOContext } from '@vtex/api'
import { AuthType, IOClient } from '@vtex/api'
import {
ReturnRequestList,
ReturnRequest,
MutationUpdateReturnRequestStatusArgs,
} from 'vtex.return-app'

const useHttps = !process.env.VTEX_IO

export class MarketplaceAppClient extends IOClient {
constructor(context: IOContext, options?: InstanceOptions) {
super(context, {
...options,
authType: AuthType.bearer,
baseURL: `${
useHttps ? 'https' : 'http'
}://app.io.vtex.com/vtex.return-app/v3`,
name: 'return-app',
headers: {
...options?.headers,
...(context.authToken
? {
// Will a request coming form the API have adminUserAUthToken? Does it matter?
// context.adminUserAuthToken ?? context.authToken,
// When using adminUserAuthToken, Sphinx parse the user email, not the app calling it. We can use it, but then we need to find a different away to get the seller calling it.
// with authToken we get user as: vrn--vtexsphinx--aws-us-east-1--powerplanet--filarmamvp--link_vtex.return-app@3.5.0
VtexIdclientAutCookie: context.authToken,
}
: null),
'Content-Type': 'application/json',
},
})
}

public getRMAList = async (marketplace: string): Promise<ReturnRequestList> =>
this.http.get(`/${marketplace}/filarmamvp/_v/return-request`)

public getRMADetails = async (
requestId: string,
marketplace: string
): Promise<ReturnRequest> =>
this.http.get(`/${marketplace}/filarmamvp/_v/return-request/${requestId}`)

public updateRMA = (
requestId: string,
marketplace: string,
data: MutationUpdateReturnRequestStatusArgs
): Promise<ReturnRequest> =>
this.http.put(
`/${marketplace}/filarmamvp/_v/return-request/${requestId}`,
data
)
}
7 changes: 7 additions & 0 deletions node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ export default new Service<Clients, State, ParamsContext>({
GET: [errorHandler, auth, getRequest],
PUT: [errorHandler, auth, updateRequestStatus],
}),
_returnRequests: method({
GET: [errorHandler, auth, getRequestList],
}),
_returnRequest: method({
GET: [errorHandler, auth, getRequest],
PUT: [errorHandler, auth, updateRequestStatus],
}),
},
graphql: {
resolvers: {
Expand Down
2 changes: 2 additions & 0 deletions node/middlewares/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export async function auth(ctx: Context, next: () => Promise<void>) {
if (authenticatedUser) {
const isAdmin = await sphinx.isAdmin(authenticatedUser.user)

// user when coming from another account: vrn--vtexsphinx--aws-us-east-1--powerplanet--filarmamvp--link_vtex.return-app@3.5.0
// maybe use it to get the seller name.
const { user, userId } = authenticatedUser

state.userProfile = {
Expand Down
26 changes: 26 additions & 0 deletions node/service.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,32 @@
"returnRequest": {
"path": "/_v/return-request/:requestId",
"public": true
},
"_returnRequests": {
"path": "/_v/return-request",
"public": false,
"policies": [
{
"effect": "allow",
"actions": ["get"],
"principals": [
"vrn:apps:*:*:*:app/vtex.return-app@*"
]
}
]
},
"_returnRequest": {
"path": "/_v/return-request/:requestId",
"public": false,
"policies": [
{
"effect": "allow",
"actions": ["get"],
"principals": [
"vrn:apps:*:*:*:app/vtex.return-app@*"
]
}
]
}
}
}
77 changes: 71 additions & 6 deletions node/services/returnRequestListService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type {
} from 'vtex.return-app'
import { ForbiddenError } from '@vtex/api'

const { VTEX_ACCOUNT } = process.env

const filterDate = (date: string): string => {
const newDate = new Date(date)
const day = newDate.getDate()
Expand Down Expand Up @@ -61,9 +63,10 @@ export const returnRequestListService = async (
getAllFields = false
) => {
const {
clients: { returnRequest: returnRequestClient },
clients: { returnRequest: returnRequestClient, marketplace },
request: { header },
state: { userProfile, appkey },
vtex: { logger },
} = ctx

const { page, perPage, filter } = args
Expand All @@ -73,6 +76,54 @@ export const returnRequestListService = async (
role,
} = userProfile ?? {}

logger.info({
service: 'get return request list',
account: VTEX_ACCOUNT,
vtexProduct: header['x-vtex-product'],
args,
state: {
userProfile,
isAdmin: Boolean(appkey),
},
})

// vrn--vtexsphinx--aws-us-east-1--powerplanet--filarmamvp--link_vtex.return-app@3.5.0
const isAppRequester = userEmailProfile?.includes('vtexsphinx') ?? false

console.log({ userEmailProfile })

const [, , , sellerRequester] =
userEmailProfile && isAppRequester ? userEmailProfile.split('--') : []

// In the marketplace side, add its name into the
const MARKETPLACENAME = 'vtexspain'

// avoid infinite loop on vtexspain
// call marketplace
const marketplaceRequests =
// adapt marketplace.getRMAList to accept and send filter params
VTEX_ACCOUNT === 'powerplanet'
? await marketplace.getRMAList(MARKETPLACENAME)
: null

if (marketplaceRequests) {
console.log({ marketplaceRequests })

logger.info({
service: 'marketplace response',
account: VTEX_ACCOUNT,
marketplaceRequests,
})
}

const adjustedMktPlaceRequests =
// modify marketplace request ids so we can use it in the quer to get the details.
// Should we do that in the mkt place side or seller side?
marketplaceRequests?.list.map(({ id, ...request }: any) => ({
id: `${id}::${MARKETPLACENAME}`,
...request,
})) ?? null

const { userId: userIdArg, userEmail: userEmailArg } = filter ?? {}

const userIsAdmin = Boolean(appkey) || role === 'admin'
Expand All @@ -98,9 +149,11 @@ export const returnRequestListService = async (
throw new ForbiddenError('Missing params to filter by store user')
}

const adjustedFilter = requireFilterByUser
? { ...filter, userId, userEmail }
: filter
const adjustedFilter =
// require user info (store user calling) AND is not a sellerCalling (seller is not admin)
requireFilterByUser && !sellerRequester
? { ...filter, userId, userEmail }
: filter

const resultFields = getAllFields
? ['_all']
Expand All @@ -113,21 +166,33 @@ export const returnRequestListService = async (
'dateSubmitted',
]

const whereFilter = buildWhereClause(adjustedFilter)

const whereWithSeller = whereFilter?.length
? `${whereFilter} AND items.sellerId=${sellerRequester}`
: `items.sellerId=${sellerRequester}`

console.log({ whereWithSeller, sellerRequester })

const rmaSearchResult = await returnRequestClient.searchRaw(
{
page,
pageSize: perPage && perPage <= 100 ? perPage : 25,
},
resultFields,
'dateSubmitted DESC',
buildWhereClause(adjustedFilter)
sellerRequester ? whereWithSeller : whereFilter
)

const { data, pagination } = rmaSearchResult
const { page: currentPage, pageSize, total } = pagination

const finalList = adjustedMktPlaceRequests
? [...adjustedMktPlaceRequests, ...data]
: data

return {
list: data,
list: finalList,
paging: {
total,
perPage: pageSize,
Expand Down
33 changes: 28 additions & 5 deletions node/services/returnRequestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,48 @@ import type { ReturnRequest } from 'vtex.return-app'

export const returnRequestService = async (ctx: Context, requestId: string) => {
const {
clients: { returnRequest: returnRequestClient },
clients: { returnRequest: returnRequestClient, marketplace },
state: { userProfile, appkey },
} = ctx

const { userId, role } = userProfile ?? {}
const { userId, role, email } = userProfile ?? {}

const isAppRequester = email?.includes('vtexsphinx') ?? false

const [, , , sellerRequester] =
email && isAppRequester ? email.split('--') : []

const [requestIdSeller, targetMarketplace] = requestId.split('::')

console.log({
sellerRequester,
requestId,
targetMarketplace,
requestIdSeller,
})

const userIsAdmin = Boolean(appkey) || role === 'admin'

const returnRequestResult = await returnRequestClient.get(requestId, ['_all'])
const returnRequestResult = targetMarketplace
? await marketplace.getRMADetails(requestIdSeller, targetMarketplace)
: await returnRequestClient.get(requestId, ['_all'])

if (!returnRequestResult) {
// Code error 'E_HTTP_404' to match the one when failing to find and order by OMS
throw new ResolverError(`Request ${requestId} not found`, 404, 'E_HTTP_404')
}

const { customerProfileData } = returnRequestResult as ReturnRequest
const { customerProfileData, items } = returnRequestResult as ReturnRequest

const requestBelongsToUser = userId === customerProfileData?.userId

if (!requestBelongsToUser && !userIsAdmin) {
const requestbelongsToSeller = items.some(
(item) => item.sellerId === sellerRequester
)

console.log({ requestbelongsToSeller })

if (!requestBelongsToUser && !userIsAdmin && !requestbelongsToSeller) {
throw new ForbiddenError('User cannot access this request')
}

Expand Down
25 changes: 24 additions & 1 deletion node/services/updateRequestStatusService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { OMS_RETURN_REQUEST_STATUS_UPDATE } from '../utils/constants'
import { OMS_RETURN_REQUEST_STATUS_UPDATE_TEMPLATE } from '../utils/templates'
import type { StatusUpdateMailData } from '../typings/mailClient'

const { VTEX_ACCOUNT } = process.env

// A partial update on MD requires all required field to be sent. https://vtex.slack.com/archives/C8EE14F1C/p1644422359807929
// And the request to update fails when we pass the auto generated ones.
// If any new field is added to the ReturnRequest as required, it has to be added here too.
Expand Down Expand Up @@ -97,15 +99,24 @@ export const updateRequestStatusService = async (
oms,
giftCard: giftCardClient,
mail,
marketplace,
},
vtex: { logger },
} = ctx

const { status, requestId, comment, refundData } = args

const [requestIdSeller, targetMarketplace] = requestId.split('::')

const { role, firstName, lastName, email, userId } = userProfile ?? {}

const isAppRequester = email?.includes('vtexsphinx') ?? false

const [, , , sellerRequester] =
email && isAppRequester ? email.split('--') : []

const requestDate = new Date().toISOString()
// TODO: Check this. When the update come from Seller, who will be the submitter?
const submittedByNameOrEmail =
firstName || lastName ? `${firstName} ${lastName}` : email

Expand All @@ -117,6 +128,11 @@ export const updateRequestStatusService = async (
)
}

if (VTEX_ACCOUNT === 'powerplanet' && targetMarketplace) {
// args has requestId with the postfix ::marketplace. In the middleware, it will be replaced by the one in the URL (without it)
return marketplace.updateRMA(requestIdSeller, targetMarketplace, args)
}

const returnRequest = (await returnRequestClient.get(requestId, [
'_all',
])) as ReturnRequest
Expand All @@ -131,7 +147,11 @@ export const updateRequestStatusService = async (
returnRequest.customerProfileData.userId === userId &&
returnRequest.status === 'new'

if (!userIsAdmin && !belongsToStoreUser) {
const requestBelogsToSeller = returnRequest.items.some(
(item) => item.sellerId === sellerRequester
)

if (!userIsAdmin && !belongsToStoreUser && !requestBelogsToSeller) {
throw new ForbiddenError('Not authorized')
}

Expand Down Expand Up @@ -183,6 +203,8 @@ export const updateRequestStatusService = async (
})
: returnRequest.refundData

// Client for GiftCard uses adminUserAuthToken. How to handle that on a request coming from seller?
// OMS client to create invoice also uses adminUserAuthToken.
const refundReturn = await handleRefund({
currentStatus: requestStatus,
previousStatus: returnRequest.status,
Expand Down Expand Up @@ -236,6 +258,7 @@ export const updateRequestStatusService = async (
)

if (!templateExists) {
// mail.publishTemplate uses adminUserAuthToken. How to handle that on a request coming from seller?
await mail.publishTemplate(
OMS_RETURN_REQUEST_STATUS_UPDATE_TEMPLATE(cultureInfoData?.locale)
)
Expand Down
Loading