diff --git a/packages/core/types/src/fulfillment/provider.ts b/packages/core/types/src/fulfillment/provider.ts index 122383bb67f83..df16bb158a049 100644 --- a/packages/core/types/src/fulfillment/provider.ts +++ b/packages/core/types/src/fulfillment/provider.ts @@ -1,18 +1,29 @@ +export type FulfillmentOption = { + /** + * The option's ID. + * + * @example express + */ + id: string + /** + * Whether the option can be used to return items. + */ + is_return?: boolean + [k: string]: unknown +} + export interface IFulfillmentProvider { /** - * @ignore * * Return a unique identifier to retrieve the fulfillment plugin provider */ getIdentifier(): string /** - * @ignore * * Return the available fulfillment options for the given data. */ - getFulfillmentOptions(): Promise[]> + getFulfillmentOptions(): Promise /** - * @ignore * * Validate the given fulfillment data. */ @@ -22,19 +33,16 @@ export interface IFulfillmentProvider { context: Record ): Promise /** - * @ignore * * Validate the given option. */ validateOption(data: Record): Promise /** - * @ignore * * Check if the provider can calculate the fulfillment price. */ canCalculate(data: Record): Promise /** - * @ignore * * Calculate the price for the given fulfillment option. */ @@ -44,7 +52,6 @@ export interface IFulfillmentProvider { context: Record ): Promise /** - * @ignore * * Create a fulfillment for the given data. */ @@ -55,25 +62,21 @@ export interface IFulfillmentProvider { fulfillment: Record ): Promise> /** - * @ignore * * Cancel the given fulfillment. */ cancelFulfillment(fulfillment: Record): Promise /** - * @ignore * * Get the documents for the given fulfillment data. */ getFulfillmentDocuments(data: Record): Promise /** - * @ignore * * Create a return for the given data. */ createReturnFulfillment(fromData: Record): Promise /** - * @ignore * * Get the documents for the given return data. */ @@ -82,13 +85,11 @@ export interface IFulfillmentProvider { documentType: string ): Promise /** - * @ignore * * Get the documents for the given return data. */ getReturnDocuments(data: Record): Promise /** - * @ignore * * Get the documents for the given shipment data. */ diff --git a/packages/core/utils/src/fulfillment/provider.ts b/packages/core/utils/src/fulfillment/provider.ts index bf2e12b111222..89d81a12282df 100644 --- a/packages/core/utils/src/fulfillment/provider.ts +++ b/packages/core/utils/src/fulfillment/provider.ts @@ -1,70 +1,416 @@ -import { IFulfillmentProvider } from "@medusajs/types" +import { + IFulfillmentProvider, + FulfillmentOption +} from "@medusajs/types" +/** + * ### constructor + * + * The constructor allows you to access resources from the module's container using the first parameter, + * and the module's options using the second parameter. + * + * :::note + * + * A module's options are passed when you register it in the Medusa application. + * + * ::: + * + * If you're creating a client or establishing a connection with a third-party service, do it in the constructor. + * + * #### Example + * + * ```ts + * import { AbstractFulfillmentProviderService } from "@medusajs/framework/utils" + * import { Logger } from "@medusajs/framework/types" + * + * type InjectedDependencies = { + * logger: Logger + * } + * + * type Options = { + * apiKey: string + * } + * + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * protected logger_: Logger + * protected options_: Options + * // assuming you're initializing a client + * protected client + * + * constructor( + * { logger }: InjectedDependencies, + * options: Options + * ) { + * super() + * + * this.logger_ = logger + * this.options_ = options + * } + * } + * + * export default MyFulfillmentProviderService + * ``` + */ export class AbstractFulfillmentProviderService implements IFulfillmentProvider { + /** + * The `identifier` property holds a unique identifier of the fulfillment module provider. + * + * You can use the kebab-case name of the provider as its value. + * + * For example: + * + * ```ts + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * static identifier = "my-fulfillment" + * + * // ... + * } + */ static identifier: string + /** + * @ignore + */ static _isFulfillmentService = true + /** + * @ignore + */ static isFulfillmentService(obj) { return obj?.constructor?._isFulfillmentService } /** - * Override this static method in order for the loader to validate the options provided to the module provider. - * @param options + * @ignore + * + * @privateRemarks + * This method is ignored as {@link validateOption} is the one used by the Fulfillment Module. */ static validateOptions(options: Record): void | never {} + /** + * @ignore + */ getIdentifier() { return (this.constructor as any).identifier } - async getFulfillmentOptions(): Promise[]> { + /** + * This method retrieves the shipping options this fulfillment provider supports. + * + * @returns The list of fulfillment options. + * + * @example + * // other imports... + * import { FulfillmentOption } from "@medusajs/framework/types" + * + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * // ... + * async getFulfillmentOptions(): Promise { + * return [ + * { + * id: "express" + * }, + * { + * id: "return-express", + * is_return: true + * } + * ] + * } + * } + */ + async getFulfillmentOptions(): Promise { throw Error("getFulfillmentOptions must be overridden by the child class") } + /** + * This method validates the `data` property of a shipping method and returns it. The returned data + * is stored in the shipping method's `data` property. + * + * Your fulfillment provider can use the `data` property to store additional information useful for + * handling the fulfillment later. For example, you may store an ID from the third-party fulfillment + * system. + * + * @param optionData - The `data` property of the shipping option. + * @param data - The `data` property of the shipping method. + * @param context - Context details, such as context of the cart or customer. + * @returns the data to store in the `data` property of the shipping method. + * + * @example + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * // ... + * async validateFulfillmentData( + * optionData: any, + * data: any, + * context: any + * ): Promise { + * // assuming your client retrieves an ID from the + * // third-party service + * const externalId = await this.client.getId() + * + * return { + * ...data, + * externalId + * } + * } + * } + */ async validateFulfillmentData(optionData, data, context): Promise { throw Error("validateFulfillmentData must be overridden by the child class") } + /** + * This method validates the `data` property of a shipping option when it's created. + * + * The `data` property can hold useful information that's later added to the `data` attribute + * of shipping methods created from this option. + * + * @param data - The data to validate. + * @return Whether the data is valid. + * + * @example + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * // ... + * async validateOption(data: any): Promise { + * return data.external_id !== undefined + * } + * } + */ async validateOption(data): Promise { throw Error("validateOption must be overridden by the child class") } - async canCalculate(data) { + /** + * This method indicates whether a shippin option's price is calculated during + * checkout or is fixed. + * + * @param data - The `data` property of the shipping option. + * @returns Whether the price is calculated for the shipping option. + * + * @example + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * // ... + * async canCalculate(data: any): Promise { + * return data.custom_type !== "fixed" + * } + * } + */ + async canCalculate(data): Promise { throw Error("canCalculate must be overridden by the child class") } - async calculatePrice(optionData, data, cart) { + /** + * This method calculates the price of a shipping option, or a shipping method when it's created. + * + * The Medusa application uses the {@link canCalculate} method first to check whether the shipping option's price is calculated. + * If it returns `true`, Medusa uses this method to retrieve the calculated price. + * + * @param optionData - The `data` property of a shipping option. + * @param data - If the price is calculated for a shipping option, it's the `data` of the shipping option. Otherwise, it's the `data of the shipping method. + * @param cart - The cart details. + * @returns The calculated price + * + * @example + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * // ... + * async calculatePrice(optionData: any, data: any, cart: any): Promise { + * // assuming the client can calculate the price using + * // the third-party service + * const price = await this.client.calculate(data) + * + * return price + * } + * } + */ + async calculatePrice(optionData, data, cart): Promise { throw Error("calculatePrice must be overridden by the child class") } + /** + * This method is used when a fulfillment is created. If the method returns in the object a + * `data` property, it's stored in the fulfillment's `data` property. + * + * The `data` property is useful when handling the fulfillment later, + * as you can access information useful for your integration. + * + * You can also use this method to perform an action with the third-party fulfillment service. + * + * @param data - The `data` property of the shipping method this fulfillment is created for. + * @param items - The items in the fulfillment. + * @param order - The order this fulfillment is created for. + * @param fulfillment - The fulfillment's details. + * @returns The data to store in the fulfillment's `data` property. + * + * @example + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * // ... + * async createFulfillment( + * data: any, + * items: any, + * order: any, + * fulfillment: any + * ): Promise { + * // assuming the client creates a fulfillment + * // in the third-party service + * const externalData = await this.client.create( + * fulfillment, + * items + * ) + * + * return { + * data: { + * ...data, + * ...externalData + * } + * } + * } + * } + */ async createFulfillment(data, items, order, fulfillment): Promise { throw Error("createFulfillment must be overridden by the child class") } + /** + * This method is used when a fulfillment is canceled. Use it to perform operations + * with the third-party fulfillment service. + * + * @param fulfillment - The fulfillment's details. + * + * @example + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * // ... + * async cancelFulfillment(fulfillment: any): Promise { + * // assuming the client cancels a fulfillment + * // in the third-party service + * await this.client.cancel(fulfillment.id) + * } + * } + */ async cancelFulfillment(fulfillment): Promise { throw Error("cancelFulfillment must be overridden by the child class") } + /** + * This method retrieves the documents of a fulfillment. + * + * @param data - The `data` property of the fulfillment. + * @returns The fulfillment's documents. + * + * @example + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * // ... + * async getFulfillmentDocuments(data: any): Promise { + * // assuming the client retrieves documents + * // from a third-party service + * return await this.client.documents(data) + * } + * } + */ async getFulfillmentDocuments(data) { return [] } - async createReturnFulfillment(fromData): Promise { + /** + * This method is used when a fulfillment is created for a return. If the method returns in the object a + * `data` property, it's stored in the fulfillment's `data` property. + * + * The `data` property is useful when handling the fulfillment later, + * as you can access information useful for your integration. + * + * Use this method to perform actions necessary in the third-party fulfillment service. + * + * @param fulfillment - The fulfillment's details. + * @returns The data to store in the fulfillment's `data` property. + * + * @example + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * // ... + * async createReturnFulfillment(fulfillment: any): Promise { + * // assuming the client creates a fulfillment for a return + * // in the third-party service + * const externalData = await this.client.createReturn( + * fulfillment + * ) + * + * return { + * data: { + * ...fulfillment.data, + * ...externalData + * } + * } + * } + * } + */ + async createReturnFulfillment(fulfillment): Promise { throw Error("createReturn must be overridden by the child class") } + /** + * This method retrieves documents for a return's fulfillment. + * + * @param data - The `data` property of the fulfillment. + * @returns The fulfillment's documents. + * + * @example + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * // ... + * async getReturnDocuments(data: any): Promise { + * // assuming the client retrieves documents + * // from a third-party service + * return await this.client.documents(data) + * } + * } + */ async getReturnDocuments(data) { return [] } + /** + * This method retrieves the documents for a shipment. + * + * @param data - The `data` property of the shipmnet. + * @returns The shipment's documents. + * + * @example + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * // ... + * async getShipmentDocuments(data: any): Promise { + * // assuming the client retrieves documents + * // from a third-party service + * return await this.client.documents(data) + * } + * } + * + */ async getShipmentDocuments(data) { return [] } + /** + * This method retrieves the documents of a fulfillment of a certain type. + * + * @param fulfillmentData - The `data` property of the fulfillment. + * @param documentType - The document's type. For example, `invoice`. + * @returns The fulfillment's documents. + * + * @example + * class MyFulfillmentProviderService extends AbstractFulfillmentProviderService { + * // ... + * async retrieveDocuments( + * fulfillmentData: any, + * documentType: any + * ): Promise { + * // assuming the client retrieves documents + * // from a third-party service + * return await this.client.documents( + * fulfillmentData, + * documentType + * ) + * } + * } + */ async retrieveDocuments(fulfillmentData, documentType) { throw Error("retrieveDocuments must be overridden by the child class") } diff --git a/packages/modules/providers/fulfillment-manual/src/services/manual-fulfillment.ts b/packages/modules/providers/fulfillment-manual/src/services/manual-fulfillment.ts index 7fd765dee6404..5734782c92169 100644 --- a/packages/modules/providers/fulfillment-manual/src/services/manual-fulfillment.ts +++ b/packages/modules/providers/fulfillment-manual/src/services/manual-fulfillment.ts @@ -1,4 +1,5 @@ import { AbstractFulfillmentProviderService } from "@medusajs/framework/utils" +import { FulfillmentOption } from "@medusajs/types" // TODO rework type and DTO's @@ -9,7 +10,7 @@ export class ManualFulfillmentService extends AbstractFulfillmentProviderService super() } - async getFulfillmentOptions(): Promise[]> { + async getFulfillmentOptions(): Promise { return [ { id: "manual-fulfillment",