Skip to content

Commit

Permalink
feat: Support for cross selling API (#1396)
Browse files Browse the repository at this point in the history
  • Loading branch information
tlgimenes authored Jul 5, 2022
1 parent 80c81ba commit 98eb7e2
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 16 deletions.
26 changes: 24 additions & 2 deletions packages/api/src/platforms/vtex/clients/commerce/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { fetchAPI } from '../fetch'
import { FACET_CROSS_SELLING_MAP } from "../../utils/facets"
import { fetchAPI } from "../fetch"
import type { PortalProduct } from "./types/Product"
import type { Context, Options } from '../../index'
import type { Brand } from './types/Brand'
import type { CategoryTree } from './types/CategoryTree'
Expand All @@ -13,6 +15,8 @@ import type {
import type { Session } from './types/Session'
import type { Channel } from '../../utils/channel'

type ValueOf<T> = T extends Record<string, infer K> ? K : never;

const BASE_INIT = {
method: 'POST',
headers: {
Expand Down Expand Up @@ -40,6 +44,24 @@ export const VtexCommerce = (
pagetype: (slug: string): Promise<PortalPagetype> =>
fetchAPI(`${base}/api/catalog_system/pub/portal/pagetype/${slug}`),
},
products: {
crossselling: (
{ type, productId, groupByProduct = true }: {
type: ValueOf<typeof FACET_CROSS_SELLING_MAP>;
productId: string;
groupByProduct?: boolean;
},
): Promise<PortalProduct[]> => {
const params = new URLSearchParams({
sc: ctx.storage.channel.salesChannel,
groupByProduct: groupByProduct.toString(),
})

return fetchAPI(
`${base}/api/catalog_system/pub/products/crossselling/${type}/${productId}?${params}`,
)
},
},
},
checkout: {
simulation: (
Expand Down Expand Up @@ -120,7 +142,7 @@ export const VtexCommerce = (
...BASE_INIT,
body: JSON.stringify({ value }),
method: 'PUT',
}
},
)
},
region: async ({
Expand Down
199 changes: 199 additions & 0 deletions packages/api/src/platforms/vtex/clients/commerce/types/Product.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
export interface PortalProduct {
productId: string;
productName: string;
brand: string;
brandId: number;
brandImageUrl: null | string;
linkText: string;
productReference: string;
productReferenceCode: string;
categoryId: string;
productTitle: string;
metaTagDescription: string;
releaseDate: Date;
clusterHighlights: unknown;
productClusters: unknown;
searchableClusters: unknown;
categories: Category[];
categoriesIds: CategoriesId[];
link: string;
description: string;
items: Item[];
}

enum Category {
Office = "/Office/",
OfficeChairs = "/Office/Chairs/",
}

enum CategoriesId {
The9282 = "/9282/",
The92829296 = "/9282/9296/",
}



interface Item {
itemId: string;
name: string;
nameComplete: string;
complementName: string;
ean: string;
referenceId: ReferenceId[];
measurementUnit: MeasurementUnit;
unitMultiplier: number;
modalType: null;
isKit: boolean;
images: Image[];
sellers: Seller[];
videos: unknown[];
estimatedDateArrival: null;
}

interface Image {
imageId: string;
imageLabel: string;
imageTag: string;
imageUrl: string;
imageText: string;
imageLastModified: Date;
}

enum MeasurementUnit {
Un = "un",
}

interface ReferenceId {
key: Key;
value: string;
}

enum Key {
RefId = "RefId",
}

interface Seller {
sellerId: string;
sellerName: SellerName;
addToCartLink: string;
sellerDefault: boolean;
commertialOffer: CommertialOffer;
}

interface CommertialOffer {
deliverySlaSamplesPerRegion: DeliverySlaSamplesPerRegion;
installments: Installment[];
discountHighLight: unknown[];
giftSkuIds: unknown[];
teasers: unknown[];
buyTogether: unknown[];
itemMetadataAttachment: unknown[];
price: number;
listPrice: number;
priceWithoutDiscount: number;
rewardValue: number;
priceValidUntil: Date;
availableQuantity: number;
isAvailable: boolean;
tax: number;
deliverySlaSamples: DeliverySlaSample[];
getInfoErrorMessage: null;
cacheVersionUsedToCallCheckout: string;
paymentOptions: PaymentOptions;
}

interface DeliverySlaSample {
deliverySlaPerTypes: unknown[];
region: null;
}

interface DeliverySlaSamplesPerRegion {
the0: DeliverySlaSample;
}

interface Installment {
value: number;
interestRate: number;
totalValuePlusInterestRate: number;
numberOfInstallments: number;
paymentSystemName: PaymentSystemNameEnum;
paymentSystemGroupName: GroupName;
name: Name;
}

enum Name {
BoletoBancárioÀVista = "Boleto Bancário à vista",
FreeÀVista = "Free à vista",
}

enum GroupName {
BankInvoicePaymentGroup = "bankInvoicePaymentGroup",
Custom201PaymentGroupPaymentGroup = "custom201PaymentGroupPaymentGroup",
}

enum PaymentSystemNameEnum {
BoletoBancário = "Boleto Bancário",
Free = "Free",
}

interface PaymentOptions {
installmentOptions: InstallmentOption[];
paymentSystems: PaymentSystem[];
payments: unknown[];
giftCards: unknown[];
giftCardMessages: unknown[];
availableAccounts: unknown[];
availableTokens: unknown[];
}

interface InstallmentOption {
paymentSystem: string;
bin: null;
paymentName: PaymentSystemNameEnum;
paymentGroupName: GroupName;
value: number;
installments: InstallmentElement[];
}

interface InstallmentElement {
count: number;
hasInterestRate: boolean;
interestRate: number;
value: number;
total: number;
sellerMerchantInstallments?: InstallmentElement[];
id?: Id;
}

enum Id {
Storeframework = "STOREFRAMEWORK",
}

interface PaymentSystem {
id: number;
name: PaymentSystemNameEnum;
groupName: GroupName;
validator: null;
stringId: string;
template: Template;
requiresDocument: boolean;
isCustom: boolean;
description: Description | null;
requiresAuthentication: boolean;
dueDate: Date;
availablePayments: null;
}

enum Description {
FreePayToTestCheckoutPayments = "Free pay to test checkout payments",
}

enum Template {
BankInvoicePaymentGroupTemplate = "bankInvoicePaymentGroup-template",
Custom201PaymentGroupPaymentGroupTemplate =
"custom201PaymentGroupPaymentGroup-template",
}

enum SellerName {
Vtex = "VTEX",
}
53 changes: 39 additions & 14 deletions packages/api/src/platforms/vtex/resolvers/query.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import { NotFoundError, BadRequestError } from '../../errors'
import { mutateChannelContext, mutateLocaleContext } from '../utils/contex'
import { enhanceSku } from '../utils/enhanceSku'
import { FACET_CROSS_SELLING_MAP } from "./../utils/facets"
import { BadRequestError, NotFoundError } from "../../errors"
import { mutateChannelContext, mutateLocaleContext } from "../utils/contex"
import { enhanceSku } from "../utils/enhanceSku"
import {
findChannel,
findCrossSelling,
findLocale,
findSkuId,
findSlug,
transformSelectedFacet,
} from '../utils/facets'
import { SORT_MAP } from '../utils/sort'
import { StoreCollection } from './collection'
} from "../utils/facets"
import { SORT_MAP } from "../utils/sort"
import { StoreCollection } from "./collection"
import type {
QueryProductArgs,
QueryAllCollectionsArgs,
QueryAllProductsArgs,
QuerySearchArgs,
QueryCollectionArgs,
} from '../../../__generated__/schema'
import type { CategoryTree } from '../clients/commerce/types/CategoryTree'
import type { Context } from '../index'
import { isValidSkuId, pickBestSku } from '../utils/sku'
QueryProductArgs,
QuerySearchArgs,
} from "../../../__generated__/schema"
import type { CategoryTree } from "../clients/commerce/types/CategoryTree"
import type { Context } from "../index"
import { isValidSkuId, pickBestSku } from "../utils/sku"

export const Query = {
product: async (_: unknown, { locator }: QueryProductArgs, ctx: Context) => {
Expand Down Expand Up @@ -95,6 +97,7 @@ export const Query = {
// Insert channel in context for later usage
const channel = findChannel(selectedFacets)
const locale = findLocale(selectedFacets)
const crossSelling = findCrossSelling(selectedFacets)

if (channel) {
mutateChannelContext(ctx, channel)
Expand All @@ -104,11 +107,33 @@ export const Query = {
mutateLocaleContext(ctx, locale)
}

let query = term

/**
* In case we are using crossSelling, we need to modify the search
* we will be performing on our search engine. The idea in here
* is to use the cross selling API for fetching the productIds our
* search will return for us.
* Doing this two request workflow makes it possible to have cross
* selling with Search features, like pagination, internationalization
* etc
*/
if (crossSelling) {
const products = await ctx.clients.commerce.catalog.products.crossselling({
type: FACET_CROSS_SELLING_MAP[crossSelling.key],
productId: crossSelling.value,
})

query = `product:${
products.map((x) => x.productId).slice(0, first).join(";")
}`
}

const after = maybeAfter ? Number(maybeAfter) : 0
const searchArgs = {
page: Math.ceil(after / first!),
page: Math.ceil(after / first),
count: first,
query: term,
query,
sort: SORT_MAP[sort ?? 'score_desc'],
selectedFacets: selectedFacets?.flatMap(transformSelectedFacet) ?? [],
}
Expand Down
Loading

1 comment on commit 98eb7e2

@vercel
Copy link

@vercel vercel bot commented on 98eb7e2 Jul 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.