docs: migrate guides to TSDoc references (#6100)
This commit is contained in:
1 parent
85dad169bb
commit
4792c55226
980 files changed
+195537
-160619
No files matched your search
@@ -0,0 +1,11 @@
|
||||
---
|
||||
"@medusajs/file-local": patch
|
||||
"medusa-file-minio": patch
|
||||
"medusa-file-s3": patch
|
||||
"@medusajs/medusa": patch
|
||||
---
|
||||
|
||||
chore(@medusajs/medusa): add missing constructor to some services
|
||||
fix(@medusajs/file-local): Fix argument passed to the constructor
|
||||
fix(medusa-file-minio): Fix argument passed to the constructor
|
||||
fix(medusa-file-s3): Fix argument passed to the constructor
|
||||
@@ -17,6 +17,7 @@ module.exports = {
|
||||
excludeExternals: true,
|
||||
excludeReferences: true,
|
||||
disableSources: true,
|
||||
sort: ["source-order"],
|
||||
validation: {
|
||||
notExported: false,
|
||||
// invalidLink: false,
|
||||
|
||||
@@ -15,6 +15,7 @@ const baseSectionsOptions = {
|
||||
member_signature_title: false,
|
||||
member_signature_returns: false,
|
||||
title_reflectionPath: false,
|
||||
member_declaration_children: false,
|
||||
}
|
||||
|
||||
const modulesSectionsOptions = {
|
||||
@@ -104,18 +105,64 @@ module.exports = {
|
||||
maxLevel: 1,
|
||||
},
|
||||
|
||||
// FULFILLMENT CONFIG
|
||||
"^fulfillment": {
|
||||
// FILE CONFIG
|
||||
"^file": {
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "core",
|
||||
},
|
||||
},
|
||||
"^file/.*AbstractFileService": {
|
||||
reflectionGroups: {
|
||||
Properties: false,
|
||||
},
|
||||
reflectionDescription: `In this document, you’ll learn how to create a file service in the Medusa backend and the methods you must implement in it.`,
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "core",
|
||||
slug: "/development/file-service/create-file-service",
|
||||
},
|
||||
reflectionTitle: {
|
||||
fullReplacement: "How to Create a File Service",
|
||||
},
|
||||
endSections: [
|
||||
`## Test Implementation
|
||||
|
||||
:::note
|
||||
|
||||
If you created your file service in a plugin, refer to [this guide on how to test plugins](https://docs.medusajs.com/development/plugins/create#test-your-plugin).
|
||||
|
||||
:::
|
||||
|
||||
After finishing your file service implementation:
|
||||
|
||||
1\\. Run the \`build\` command in the root of your Medusa backend:
|
||||
|
||||
\`\`\`bash npm2yarn
|
||||
npm run build
|
||||
\`\`\`
|
||||
|
||||
2\\. Start the backend with the \`develop\` command:
|
||||
|
||||
\`\`\`bash
|
||||
npx medusa develop
|
||||
\`\`\`
|
||||
|
||||
3\\. Upload a file using the [Admin REST APIs](https://docs.medusajs.com/api/admin#uploads_postuploads) or using the Medusa admin, for example, to [upload a product's thumbnail](https://docs.medusajs.com/user-guide/products/manage#manage-thumbnails).
|
||||
`,
|
||||
],
|
||||
},
|
||||
|
||||
// FULFILLMENT CONFIG
|
||||
"^fulfillment": {
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "modules",
|
||||
},
|
||||
maxLevel: 2,
|
||||
},
|
||||
"^fulfillment/.*AbstractFulfillmentService": {
|
||||
reflectionDescription: `In this document, you’ll learn how to create a fulfillment provider to a Medusa backend and the methods you must implement in it. If you’re unfamiliar with the Shipping architecture in Medusa, make sure to [check out the overview first](https://docs.medusajs.com/modules/carts-and-checkout/shipping).`,
|
||||
reflectionGroups: {
|
||||
Properties: false,
|
||||
},
|
||||
reflectionDescription: `In this document, you’ll learn how to create a fulfillment provider in the Medusa backend and the methods you must implement in it. If you’re unfamiliar with the Shipping architecture in Medusa, make sure to [check out the overview first](https://docs.medusajs.com/modules/carts-and-checkout/shipping).`,
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "modules",
|
||||
slug: "/modules/carts-and-checkout/backend/add-fulfillment-provider",
|
||||
@@ -123,6 +170,34 @@ module.exports = {
|
||||
reflectionTitle: {
|
||||
fullReplacement: "How to Create a Fulfillment Provider",
|
||||
},
|
||||
endSections: [
|
||||
`## Test Implementation
|
||||
|
||||
:::note
|
||||
|
||||
If you created your fulfillment provider in a plugin, refer to [this guide on how to test plugins](https://docs.medusajs.com/development/plugins/create#test-your-plugin).
|
||||
|
||||
:::
|
||||
|
||||
After finishing your fulfillment provider implementation:
|
||||
|
||||
1\\. Run the \`build\` command in the root of your Medusa backend:
|
||||
|
||||
\`\`\`bash npm2yarn
|
||||
npm run build
|
||||
\`\`\`
|
||||
|
||||
2\\. Start the backend with the \`develop\` command:
|
||||
|
||||
\`\`\`bash
|
||||
npx medusa develop
|
||||
\`\`\`
|
||||
|
||||
3\\. Enable your fulfillment provider in one or more regions. You can do that either using the [Admin APIs](https://docs.medusajs.com/api/admin#regions_postregionsregionfulfillmentproviders) or the [Medusa Admin](https://docs.medusajs.com/user-guide/regions/providers#manage-fulfillment-providers).
|
||||
|
||||
4\\. To test out your fulfillment provider implementation, create a cart and complete an order. You can do that either using the [Next.js starter](https://docs.medusajs.com/starters/nextjs-medusa-starter) or [using Medusa's APIs and clients](https://docs.medusajs.com/modules/carts-and-checkout/storefront/implement-cart).
|
||||
`,
|
||||
],
|
||||
},
|
||||
|
||||
// INVENTORY CONFIG
|
||||
@@ -191,6 +266,36 @@ module.exports = {
|
||||
maxLevel: 3,
|
||||
},
|
||||
|
||||
// MEDUSA CONFIG CONFIG
|
||||
"^medusa_config": {
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "core",
|
||||
},
|
||||
expandMembers: true,
|
||||
},
|
||||
"^medusa_config/.*ConfigModule": {
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "core",
|
||||
slug: "/development/backend/configurations",
|
||||
},
|
||||
reflectionDescription: `In this document, you’ll learn how to create a file service in the Medusa backend and the methods you must implement in it.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This document assumes you already followed along with the [Prepare Environment documentation](https://docs.medusajs.com/development/backend/prepare-environment) and have a Medusa backend installed.
|
||||
|
||||
---`,
|
||||
reflectionTitle: "Configure Medusa Backend",
|
||||
expandMembers: true,
|
||||
expandProperties: true,
|
||||
// parameterStyle: "list",
|
||||
sections: {
|
||||
...baseSectionsOptions,
|
||||
member_declaration_title: false,
|
||||
member_declaration_children: true,
|
||||
},
|
||||
},
|
||||
|
||||
// MEDUSA REACT CONFIG
|
||||
"^medusa_react": {
|
||||
frontmatterData: {
|
||||
@@ -317,6 +422,86 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
|
||||
// NOTIFICATION CONFIG
|
||||
"^notification": {
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "core",
|
||||
},
|
||||
},
|
||||
"^notification/.*AbstractNotificationService": {
|
||||
reflectionGroups: {
|
||||
Properties: false,
|
||||
},
|
||||
reflectionDescription: `In this document, you’ll learn how to create a notification provider in the Medusa backend and the methods you must implement in it. Learn more about the notification architecture in [this documentation](https://docs.medusajs.com/development/notification/overview)`,
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "core",
|
||||
slug: "/development/notification/create-notification-provider",
|
||||
},
|
||||
reflectionTitle: {
|
||||
fullReplacement: "How to Create a Notification Provider",
|
||||
},
|
||||
endSections: [
|
||||
`## Subscribe with Loaders
|
||||
|
||||
After creating your Notification Provider Service, you must create a [Loader](https://docs.medusajs.com/development/loaders/overview) that registers this Service as a notification handler of events.
|
||||
|
||||
For example, to register the \`email-sender\` Notification Provider as a handler for the \`order.placed\` event, create the file \`src/loaders/notification.ts\` with the following content:
|
||||
|
||||
\`\`\`ts title="src/loaders/notification.ts"
|
||||
import {
|
||||
MedusaContainer,
|
||||
NotificationService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async (
|
||||
container: MedusaContainer
|
||||
): Promise<void> => {
|
||||
const notificationService = container.resolve<
|
||||
NotificationService
|
||||
>("notificationService")
|
||||
|
||||
notificationService.subscribe(
|
||||
"order.placed",
|
||||
"email-sender"
|
||||
)
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
This loader accesses the \`notificationService\` through the [MedusaContainer](https://docs.medusajs.com/development/fundamentals/dependency-injection). The \`notificationService\` has a \`subscribe\` method that accepts 2 parameters. The first one is the name of the event to subscribe to, and the second is the identifier of the Notification Provider that's subscribing to that event.`,
|
||||
`## Test Sending a Notification
|
||||
|
||||
Make sure you have an event bus module configured in your Medusa backend. You can learn more on how to do that in the [Configurations guide](https://docs.medusajs.com/development/backend/configurations#modules).
|
||||
|
||||
Then:
|
||||
|
||||
1\\. Run the \`build\` command in the root directory of your Medusa backend:
|
||||
|
||||
\`\`\`bash npm2yarn
|
||||
npm run build
|
||||
\`\`\`
|
||||
|
||||
2\\. Start your Medusa backend:
|
||||
|
||||
\`\`\`bash npm2yarn
|
||||
npx medusa develop
|
||||
\`\`\`
|
||||
|
||||
3\\. Place an order either using the [REST APIs](https://docs.medusajs.com/api/store) or using the [storefront](https://docs.medusajs.com/starters/nextjs-medusa-starter).
|
||||
|
||||
4\\. After placing an order, you can see in your console the message “Notification Sent”. If you added your own notification sending logic, you should receive an email or alternatively the type of notification you’ve set up.`,
|
||||
`## Test Resending a Notification
|
||||
|
||||
To test resending a notification:
|
||||
|
||||
1. Retrieve the ID of the notification you just sent using the [List Notifications API Route](https://docs.medusajs.com/api/admin#notifications_getnotifications). You can pass as a body parameter the \`to\` or \`event_name\` parameters to filter out the notification you just sent.
|
||||
|
||||
2. Send a request to the [Resend Notification API Route](https://docs.medusajs.com/api/admin#notifications_postnotificationsnotificationresend) using the ID retrieved from the previous request. You can pass the \`to\` parameter in the body to change the receiver of the notification.
|
||||
|
||||
3. You should see the message “Notification Resent” in your console.
|
||||
`,
|
||||
],
|
||||
},
|
||||
|
||||
// PAYMENT CONFIG
|
||||
"^payment": {
|
||||
frontmatterData: {
|
||||
@@ -336,6 +521,80 @@ module.exports = {
|
||||
reflectionGroups: {
|
||||
Properties: false,
|
||||
},
|
||||
endSections: [
|
||||
`## Test Implementation
|
||||
|
||||
:::note
|
||||
|
||||
If you created your payment processor in a plugin, refer to [this guide on how to test plugins](https://docs.medusajs.com/development/plugins/create#test-your-plugin).
|
||||
|
||||
:::
|
||||
|
||||
After finishing your payment processor implementation:
|
||||
|
||||
1\\. Run the \`build\` command in the root of your Medusa backend:
|
||||
|
||||
\`\`\`bash npm2yarn
|
||||
npm run build
|
||||
\`\`\`
|
||||
|
||||
2\\. Start the backend with the \`develop\` command:
|
||||
|
||||
\`\`\`bash
|
||||
npx medusa develop
|
||||
\`\`\`
|
||||
|
||||
3\\. Enable your payment processor in one or more regions. You can do that either using the [Admin APIs](https://docs.medusajs.com/api/admin#regions_postregionsregionpaymentproviders) or the [Medusa Admin](https://docs.medusajs.com/user-guide/regions/providers#manage-payment-providers).
|
||||
|
||||
4\\. There are different ways to test out your payment processor, such as authorizing payment on order completion or capturing payment of an order. You test payment in a checkout flow either using the [Next.js starter](https://docs.medusajs.com/starters/nextjs-medusa-starter) or [using Medusa's APIs and clients](https://docs.medusajs.com/modules/carts-and-checkout/storefront/implement-checkout-flow).
|
||||
`,
|
||||
],
|
||||
},
|
||||
|
||||
// PRICE SELECTION CONFIG
|
||||
"^price_selection": {
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "modules",
|
||||
},
|
||||
},
|
||||
"^price_selection/.*AbstractPriceSelectionStrategy": {
|
||||
reflectionDescription: `In this document, you’ll learn what the price selection strategy and how to override it in the Medusa backend.`,
|
||||
reflectionGroups: {
|
||||
Properties: false,
|
||||
},
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "modules",
|
||||
slug: "/modules/price-lists/price-selection-strategy",
|
||||
},
|
||||
reflectionTitle: {
|
||||
fullReplacement: "How to Override the Price Selection Strategy",
|
||||
},
|
||||
endSections: [
|
||||
`## Test Implementation
|
||||
|
||||
:::note
|
||||
|
||||
If you created your price selection strategy in a plugin, refer to [this guide on how to test plugins](https://docs.medusajs.com/development/plugins/create#test-your-plugin).
|
||||
|
||||
:::
|
||||
|
||||
After finishing your price selection strategy implementation:
|
||||
|
||||
1\\. Run the \`build\` command in the root of your Medusa backend:
|
||||
|
||||
\`\`\`bash npm2yarn
|
||||
npm run build
|
||||
\`\`\`
|
||||
|
||||
2\\. Start the backend with the \`develop\` command:
|
||||
|
||||
\`\`\`bash
|
||||
npx medusa develop
|
||||
\`\`\`
|
||||
|
||||
3\\. To test out your price selection strategy implementation, you can retrieve a product and it's variants by specifying pricing parameters as explained in [this guide](https://docs.medusajs.com/modules/products/storefront/show-products#product-pricing-parameters).
|
||||
`,
|
||||
],
|
||||
},
|
||||
|
||||
// PRICING CONFIG
|
||||
@@ -424,6 +683,62 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
|
||||
// SEARCH CONFIG
|
||||
"^search": {
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "core",
|
||||
},
|
||||
},
|
||||
"^search/.*AbstractSearchService": {
|
||||
reflectionDescription: `In this document, you’ll learn how to create a search service in the Medusa backend and the methods you must implement in it.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
A search service must extend the \`AbstractSearchService\` class imported from the \`@medusajs/utils\` package. If you don’t already have the package
|
||||
installed, run the following command to install it within your project:
|
||||
|
||||
\`\`\`bash npm2yarn
|
||||
npm install @medusajs/utils
|
||||
\`\`\`
|
||||
|
||||
---`,
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "core",
|
||||
slug: "/development/search/create",
|
||||
},
|
||||
reflectionTitle: {
|
||||
fullReplacement: "How to Create a Search Service",
|
||||
},
|
||||
endSections: [
|
||||
`## Test Implementation
|
||||
|
||||
:::note
|
||||
|
||||
If you created your search service in a plugin, refer to [this guide on how to test plugins](https://docs.medusajs.com/development/plugins/create#test-your-plugin).
|
||||
|
||||
:::
|
||||
|
||||
After finishing your search service implementation:
|
||||
|
||||
1\\. Make sure you have an [event bus module](https://docs.medusajs.com/development/events/modules/redis) installed that triggers search indexing when the Medusa backend loads.
|
||||
|
||||
2\\. Run the \`build\` command in the root of your Medusa backend:
|
||||
|
||||
\`\`\`bash npm2yarn
|
||||
npm run build
|
||||
\`\`\`
|
||||
|
||||
3\\. Start the backend with the \`develop\` command:
|
||||
|
||||
\`\`\`bash
|
||||
npx medusa develop
|
||||
\`\`\`
|
||||
|
||||
4\\. Try to search for products either using the [Search Products API Route](https://docs.medusajs.com/api/store#products_postproductssearch). The results returned by your search service's \`search\` method is returned in the \`hits\` response field.
|
||||
`,
|
||||
],
|
||||
},
|
||||
|
||||
// SERVICES CONFIG
|
||||
"^services": {
|
||||
frontmatterData: {
|
||||
@@ -467,6 +782,100 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
|
||||
// TAX CALCULATION CONFIG
|
||||
"^tax_calculation": {
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "modules",
|
||||
},
|
||||
},
|
||||
"^tax_calculation/.*AbstractTaxCalculationStrategy": {
|
||||
reflectionGroups: {
|
||||
Properties: false,
|
||||
},
|
||||
reflectionDescription: `In this document, you’ll learn how to override the tax calculations strategy in the Medusa backend and the methods you must implement in it.`,
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "modules",
|
||||
slug: "/modules/taxes/backend/tax-calculation-strategy",
|
||||
},
|
||||
reflectionTitle: {
|
||||
fullReplacement: "How to Override a Tax Calculation Strategy",
|
||||
},
|
||||
endSections: [
|
||||
`## Test Implementation
|
||||
|
||||
:::note
|
||||
|
||||
If you created your tax calculation strategy in a plugin, refer to [this guide on how to test plugins](https://docs.medusajs.com/development/plugins/create#test-your-plugin).
|
||||
|
||||
:::
|
||||
|
||||
After finishing your tax calculation strategy implementation:
|
||||
|
||||
1\\. Run the \`build\` command in the root of your Medusa backend:
|
||||
|
||||
\`\`\`bash npm2yarn
|
||||
npm run build
|
||||
\`\`\`
|
||||
|
||||
2\\. Start the backend with the \`develop\` command:
|
||||
|
||||
\`\`\`bash
|
||||
npx medusa develop
|
||||
\`\`\`
|
||||
|
||||
3\\. To test out your tax calculation strategy implementation, you can [trigger taxes calculation manually](https://docs.medusajs.com/modules/taxes/storefront/manual-calculation).
|
||||
`,
|
||||
],
|
||||
},
|
||||
|
||||
// TAX PROVIDER CONFIG
|
||||
"^tax/": {
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "modules",
|
||||
},
|
||||
},
|
||||
"^tax/.*AbstractTaxService": {
|
||||
reflectionGroups: {
|
||||
Properties: false,
|
||||
},
|
||||
reflectionDescription: `In this document, you’ll learn how to create a tax provider in the Medusa backend and the methods you must implement in it.`,
|
||||
frontmatterData: {
|
||||
displayed_sidebar: "modules",
|
||||
slug: "/modules/taxes/backend/create-tax-provider",
|
||||
},
|
||||
reflectionTitle: {
|
||||
fullReplacement: "How to Create a Tax Provider",
|
||||
},
|
||||
endSections: [
|
||||
`## Test Implementation
|
||||
|
||||
:::note
|
||||
|
||||
If you created your tax provider in a plugin, refer to [this guide on how to test plugins](https://docs.medusajs.com/development/plugins/create#test-your-plugin).
|
||||
|
||||
:::
|
||||
|
||||
After finishing your tax provider implementation:
|
||||
|
||||
1\\. Run the \`build\` command in the root of your Medusa backend:
|
||||
|
||||
\`\`\`bash npm2yarn
|
||||
npm run build
|
||||
\`\`\`
|
||||
|
||||
2\\. Start the backend with the \`develop\` command:
|
||||
|
||||
\`\`\`bash
|
||||
npx medusa develop
|
||||
\`\`\`
|
||||
|
||||
3\\. Use the tax provider in a region. You can do that either using the [Admin APIs](https://docs.medusajs.com/modules/taxes/admin/manage-tax-settings#change-tax-provider-of-a-region) or the [Medusa Admin](https://docs.medusajs.com/user-guide/taxes/manage#change-tax-provider).
|
||||
|
||||
4\\. To test out your tax provider implementation, you can [trigger taxes calculation manually](https://docs.medusajs.com/modules/taxes/storefront/manual-calculation).
|
||||
`,
|
||||
],
|
||||
},
|
||||
|
||||
// WORKFLOWS CONFIG
|
||||
"^workflows": {
|
||||
expandMembers: true,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
"extends": [
|
||||
"../../../../packages/utils/tsconfig.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const getConfig = require("./utils/get-config")
|
||||
|
||||
module.exports = getConfig({
|
||||
entryPointPath: "packages/medusa/src/interfaces/file-service.ts",
|
||||
tsConfigName: "medusa.json",
|
||||
name: "file",
|
||||
parentIgnore: true,
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const getConfig = require("./utils/get-config")
|
||||
|
||||
module.exports = getConfig({
|
||||
entryPointPath: "packages/types/src/common/config-module.ts",
|
||||
tsConfigName: "types.json",
|
||||
name: "medusa-config",
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const getConfig = require("./utils/get-config")
|
||||
|
||||
module.exports = getConfig({
|
||||
entryPointPath: "packages/medusa/src/interfaces/notification-service.ts",
|
||||
tsConfigName: "medusa.json",
|
||||
name: "notification",
|
||||
parentIgnore: true,
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const getConfig = require("./utils/get-config")
|
||||
|
||||
module.exports = getConfig({
|
||||
entryPointPath: "packages/medusa/src/interfaces/price-selection-strategy.ts",
|
||||
tsConfigName: "medusa.json",
|
||||
name: "price-selection",
|
||||
parentIgnore: true,
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const getConfig = require("./utils/get-config")
|
||||
|
||||
module.exports = getConfig({
|
||||
entryPointPath: "packages/utils/src/search/abstract-service.ts",
|
||||
tsConfigName: "utils.json",
|
||||
name: "search",
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const getConfig = require("./utils/get-config")
|
||||
|
||||
module.exports = getConfig({
|
||||
entryPointPath: "packages/medusa/src/interfaces/tax-calculation-strategy.ts",
|
||||
tsConfigName: "medusa.json",
|
||||
name: "tax-calculation",
|
||||
parentIgnore: true,
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const getConfig = require("./utils/get-config")
|
||||
|
||||
module.exports = getConfig({
|
||||
entryPointPath: "packages/medusa/src/interfaces/tax-service.ts",
|
||||
tsConfigName: "medusa.json",
|
||||
name: "tax",
|
||||
parentIgnore: true,
|
||||
})
|
||||
@@ -16,7 +16,7 @@ import ifShowReturnsHelper from "./resources/helpers/if-show-returns"
|
||||
import ifShowTypeHierarchyHelper from "./resources/helpers/if-show-type-hierarchy"
|
||||
import indexSignatureTitleHelper from "./resources/helpers/index-signature-title"
|
||||
import parameterTableHelper from "./resources/helpers/parameter-table"
|
||||
import objectLiteralMemberHelper from "./resources/helpers/type-declaration-object-literal"
|
||||
import objectLiteralMemberHelper from "./resources/helpers/type-declaration-members"
|
||||
import referenceMember from "./resources/helpers/reference-member"
|
||||
import reflectionPathHelper from "./resources/helpers/reflection-path"
|
||||
import reflectionTitleHelper from "./resources/helpers/reflection-title"
|
||||
@@ -58,6 +58,11 @@ import ifHasMutationReturnHelper from "./resources/helpers/if-has-mutation-retur
|
||||
import reactQueryMutationReturnHelper from "./resources/helpers/react-query-mutation-return"
|
||||
import ifHasQueryReturnHelper from "./resources/helpers/if-has-query-return"
|
||||
import reactQueryQueryReturnHelper from "./resources/helpers/react-query-query-return"
|
||||
import endSectionsHelper from "./resources/helpers/end-sections"
|
||||
import getDeclarationChildrenHelper from "./resources/helpers/get-declaration-children"
|
||||
import ifShowSeparatorForTitleLevelHelper from "./resources/helpers/if-show-separator-for-title-level"
|
||||
import shouldExpandPropertiesHelper from "./resources/helpers/should-expand-properties"
|
||||
import shouldExpandDeclarationChildrenHelper from "./resources/helpers/should-expand-declaration-children"
|
||||
import { MarkdownTheme } from "./theme"
|
||||
|
||||
const TEMPLATE_PATH = path.join(__dirname, "resources", "templates")
|
||||
@@ -144,4 +149,9 @@ export function registerHelpers(theme: MarkdownTheme) {
|
||||
reactQueryMutationReturnHelper(theme)
|
||||
ifHasQueryReturnHelper(theme)
|
||||
reactQueryQueryReturnHelper(theme)
|
||||
endSectionsHelper(theme)
|
||||
getDeclarationChildrenHelper(theme)
|
||||
ifShowSeparatorForTitleLevelHelper(theme)
|
||||
shouldExpandPropertiesHelper(theme)
|
||||
shouldExpandDeclarationChildrenHelper(theme)
|
||||
}
|
||||
+1
@@ -55,6 +55,7 @@ export default function (theme: MarkdownTheme) {
|
||||
` = \`${stripLineBreaks(stripComments(`${this.defaultValue}`))}\``
|
||||
)
|
||||
}
|
||||
|
||||
return md.join("")
|
||||
}
|
||||
)
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import * as Handlebars from "handlebars"
|
||||
import { MarkdownTheme } from "../../theme"
|
||||
|
||||
export default function (theme: MarkdownTheme) {
|
||||
Handlebars.registerHelper("endSections", function () {
|
||||
const { endSections } = theme.getFormattingOptionsForLocation()
|
||||
|
||||
if (!endSections?.length) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const lineBreaks = "\n\n"
|
||||
const separator = `---${lineBreaks}`
|
||||
|
||||
return `${separator}${endSections.join(`${lineBreaks}${separator}`)}`
|
||||
})
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import * as Handlebars from "handlebars"
|
||||
import { DeclarationReflection } from "typedoc"
|
||||
import { getTypeChildren } from "utils"
|
||||
import { MarkdownTheme } from "../../theme"
|
||||
|
||||
export default function (theme: MarkdownTheme) {
|
||||
Handlebars.registerHelper(
|
||||
"getDeclarationChildren",
|
||||
function (this: DeclarationReflection) {
|
||||
const { maxLevel } = theme.getFormattingOptionsForLocation()
|
||||
|
||||
if (!this.children && !this.type) {
|
||||
return []
|
||||
}
|
||||
|
||||
return (
|
||||
this.children ||
|
||||
getTypeChildren({
|
||||
reflectionType: this.type!,
|
||||
project: this.project,
|
||||
maxLevel,
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import * as Handlebars from "handlebars"
|
||||
import { MarkdownTheme } from "../../theme"
|
||||
|
||||
export default function (theme: MarkdownTheme) {
|
||||
Handlebars.registerHelper(
|
||||
"ifShowSeparatorForTitleLevel",
|
||||
function (this: unknown, options: Handlebars.HelperOptions) {
|
||||
const { currentTitleLevel } = theme
|
||||
|
||||
return currentTitleLevel <= 2 ? options.fn(this) : options.inverse(this)
|
||||
}
|
||||
)
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import * as Handlebars from "handlebars"
|
||||
import { MarkdownTheme } from "../../theme"
|
||||
|
||||
export default function (theme: MarkdownTheme) {
|
||||
Handlebars.registerHelper("shouldExpandDeclarationChildren", function () {
|
||||
const { currentTitleLevel } = theme
|
||||
const { expandMembers } = theme.getFormattingOptionsForLocation()
|
||||
|
||||
return expandMembers && currentTitleLevel <= 3
|
||||
})
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import * as Handlebars from "handlebars"
|
||||
import { MarkdownTheme } from "../../theme"
|
||||
|
||||
export default function (theme: MarkdownTheme) {
|
||||
Handlebars.registerHelper("shouldExpandProperties", function (title: string) {
|
||||
const { currentTitleLevel } = theme
|
||||
const { expandProperties = false } = theme.getFormattingOptionsForLocation()
|
||||
|
||||
return title === "Properties" && expandProperties && currentTitleLevel <= 3
|
||||
})
|
||||
}
|
||||
-2
@@ -7,8 +7,6 @@ export default function (theme: MarkdownTheme) {
|
||||
function (title: string) {
|
||||
const { parameterStyle } = theme.getFormattingOptionsForLocation()
|
||||
|
||||
// console.log(parameterStyle, title)
|
||||
|
||||
return parameterStyle === "component" && title === "Properties"
|
||||
}
|
||||
)
|
||||
|
||||
File renamed without changes.
+25
-1
@@ -86,7 +86,7 @@
|
||||
|
||||
{{#with type.declaration}}
|
||||
|
||||
{{{titleLevel}}} Type declaration
|
||||
{{{titleLevel}}} Properties
|
||||
|
||||
{{/with}}
|
||||
|
||||
@@ -102,4 +102,28 @@
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if (sectionEnabled "member_declaration_children")}}
|
||||
|
||||
{{#if (shouldExpandDeclarationChildren)}}
|
||||
|
||||
{{#each (getDeclarationChildren)}}
|
||||
|
||||
{{> member}}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#with (getDeclarationChildren)}}
|
||||
|
||||
{{{titleLevel}}} Properties
|
||||
|
||||
{{{typeDeclarationMembers}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{> member.sources}}
|
||||
+11
-9
@@ -56,14 +56,6 @@
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#unless @last}}
|
||||
{{#unless hasOwnDocument}}
|
||||
{{#unless removeSeparator}}
|
||||
___
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
|
||||
{{#unless hasOwnDocument}}
|
||||
|
||||
{{#if name}}
|
||||
@@ -72,4 +64,14 @@ ___
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
|
||||
{{#ifShowSeparatorForTitleLevel}}
|
||||
{{#unless @last}}
|
||||
{{#unless hasOwnDocument}}
|
||||
{{#unless removeSeparator}}
|
||||
___
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
{{/ifShowSeparatorForTitleLevel}}
|
||||
+20
-2
@@ -4,9 +4,11 @@
|
||||
|
||||
{{#each categories}}
|
||||
|
||||
{{#ifShowSeparatorForTitleLevel}}
|
||||
{{#unless @first}}
|
||||
___
|
||||
{{/unless}}
|
||||
{{/ifShowSeparatorForTitleLevel}}
|
||||
|
||||
{{#unless (getFormattingOption "expandMembers")}}
|
||||
|
||||
@@ -20,7 +22,15 @@ ___
|
||||
|
||||
{{/unless}}
|
||||
|
||||
{{#if (showPropertiesAsComponent title)}}
|
||||
{{#if (shouldExpandProperties title)}}
|
||||
|
||||
{{#each children}}
|
||||
|
||||
{{> member }}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{else if (showPropertiesAsComponent title)}}
|
||||
|
||||
{{#with children}}
|
||||
|
||||
@@ -64,7 +74,15 @@ ___
|
||||
|
||||
{{/unless}}
|
||||
|
||||
{{#if (showPropertiesAsComponent title)}}
|
||||
{{#if (shouldExpandProperties title)}}
|
||||
|
||||
{{#each children}}
|
||||
|
||||
{{> member }}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{else if (showPropertiesAsComponent title)}}
|
||||
|
||||
{{#with children}}
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
{{#each categories}}
|
||||
|
||||
{{#ifShowSeparatorForTitleLevel}}
|
||||
{{#unless @first}}
|
||||
___
|
||||
{{/unless}}
|
||||
{{/ifShowSeparatorForTitleLevel}}
|
||||
|
||||
{{#unless allChildrenHaveOwnDocument}}
|
||||
|
||||
@@ -44,9 +46,11 @@ ___
|
||||
|
||||
{{#unless allChildrenHaveOwnDocument}}
|
||||
|
||||
{{#ifShowSeparatorForTitleLevel}}
|
||||
{{#unless @first}}
|
||||
___
|
||||
{{/unless}}
|
||||
{{/ifShowSeparatorForTitleLevel}}
|
||||
|
||||
{{> members.group}}
|
||||
|
||||
|
||||
@@ -7,3 +7,5 @@
|
||||
{{{comment this}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{{ endSections }}}
|
||||
+3
-1
@@ -144,4 +144,6 @@
|
||||
|
||||
{{> main}}
|
||||
|
||||
{{/with}}
|
||||
{{/with}}
|
||||
|
||||
{{{ endSections }}}
|
||||
+2
@@ -11,3 +11,5 @@
|
||||
{{decrementCurrentTitleLevel}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{{ endSections }}}
|
||||
+34
-20
@@ -31,7 +31,7 @@ export function getReflectionTypeParameters({
|
||||
comment,
|
||||
level = 1,
|
||||
maxLevel,
|
||||
wrapObject = false,
|
||||
wrapObject,
|
||||
isReturn = true,
|
||||
}: GetReflectionTypeParametersParams): Parameter[] {
|
||||
const typeName = getType({
|
||||
@@ -52,6 +52,20 @@ export function getReflectionTypeParameters({
|
||||
})
|
||||
|
||||
const formatParameter = () => {
|
||||
let description = ""
|
||||
|
||||
if (comment) {
|
||||
if (isReturn) {
|
||||
description = getReturnComment(comment)
|
||||
}
|
||||
|
||||
if (!description.length) {
|
||||
description = Handlebars.helpers.comment(comment.summary)
|
||||
}
|
||||
} else if (!isReturn) {
|
||||
description = loadComment(typeName, project)
|
||||
}
|
||||
|
||||
return {
|
||||
name: "name" in reflectionType ? reflectionType.name : typeName,
|
||||
type,
|
||||
@@ -65,13 +79,7 @@ export function getReflectionTypeParameters({
|
||||
reflectionType.declaration as DeclarationReflection
|
||||
) || ""
|
||||
: "",
|
||||
description: comment
|
||||
? isReturn
|
||||
? getReturnComment(comment)
|
||||
: Handlebars.helpers.comment(comment.summary)
|
||||
: !comment && !isReturn
|
||||
? loadComment(typeName, project)
|
||||
: "",
|
||||
description,
|
||||
expandable: comment?.hasModifier(`@expandable`) || false,
|
||||
featureFlag: Handlebars.helpers.featureFlag(comment),
|
||||
children: [],
|
||||
@@ -99,20 +107,25 @@ export function getReflectionTypeParameters({
|
||||
if (!reflectionTypeArg) {
|
||||
return
|
||||
}
|
||||
const typeArgComponent = getReflectionTypeParameters({
|
||||
reflectionType: reflectionTypeArg,
|
||||
project,
|
||||
level: level + 1,
|
||||
maxLevel,
|
||||
})
|
||||
|
||||
componentItem[parentKey - 1].children?.push(...typeArgComponent)
|
||||
componentItem[parentKey - 1].children?.push(
|
||||
...getReflectionTypeParameters({
|
||||
reflectionType: reflectionTypeArg,
|
||||
project,
|
||||
level: level + 1,
|
||||
maxLevel,
|
||||
isReturn: false,
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const reflection = (reflectionType.reflection ||
|
||||
getProjectChild(project, reflectionType.name)) as DeclarationReflection
|
||||
const parentKey = wrapObject
|
||||
const shouldWrapObject =
|
||||
wrapObject ||
|
||||
(wrapObject === undefined && level > 1 && canRetrieveChildren)
|
||||
const parentKey = shouldWrapObject
|
||||
? componentItem.push(formatParameter())
|
||||
: undefined
|
||||
if (reflection) {
|
||||
@@ -149,10 +162,8 @@ export function getReflectionTypeParameters({
|
||||
? componentItem[parentKey - 1].children?.push(childParameter)
|
||||
: componentItem.push(childParameter)
|
||||
}
|
||||
} else {
|
||||
parentKey
|
||||
? componentItem[parentKey - 1].children?.push(formatParameter())
|
||||
: componentItem.push(formatParameter())
|
||||
} else if (!parentKey) {
|
||||
componentItem.push(formatParameter())
|
||||
}
|
||||
}
|
||||
} else if (reflectionType.type === "array") {
|
||||
@@ -163,6 +174,7 @@ export function getReflectionTypeParameters({
|
||||
project,
|
||||
level: level + 1,
|
||||
maxLevel,
|
||||
isReturn: false,
|
||||
})
|
||||
componentItem[parentKey - 1].children?.push(...elementTypeItem)
|
||||
}
|
||||
@@ -186,6 +198,7 @@ export function getReflectionTypeParameters({
|
||||
project,
|
||||
level: level + 1,
|
||||
maxLevel,
|
||||
isReturn: false,
|
||||
})
|
||||
pushTo.push(...elementTypeItem)
|
||||
})
|
||||
@@ -200,6 +213,7 @@ export function getReflectionTypeParameters({
|
||||
project,
|
||||
level: level + 1,
|
||||
maxLevel,
|
||||
isReturn: false,
|
||||
})
|
||||
|
||||
parentKey
|
||||
|
||||
+2
@@ -62,6 +62,7 @@ export type FormattingOptionType = {
|
||||
}
|
||||
reflectionDescription?: string
|
||||
expandMembers?: boolean
|
||||
expandProperties?: boolean
|
||||
showCommentsAsHeader?: boolean
|
||||
showCommentsAsDetails?: boolean
|
||||
parameterStyle?: ParameterStyle
|
||||
@@ -71,6 +72,7 @@ export type FormattingOptionType = {
|
||||
mdxImports?: string[]
|
||||
maxLevel?: number
|
||||
fileNameSeparator?: string
|
||||
endSections?: string[]
|
||||
}
|
||||
|
||||
export declare module "typedoc" {
|
||||
|
||||
+118366
-115681
File diff suppressed because it is too large.
Load diff
+49976
-34226
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -14,7 +14,7 @@ class LocalService extends AbstractFileService implements IFileService {
|
||||
protected backendUrl_: string
|
||||
|
||||
constructor({}, options) {
|
||||
super({}, options)
|
||||
super(arguments[0], options)
|
||||
|
||||
this.uploadDir_ = options.upload_dir || "uploads"
|
||||
this.backendUrl_ = options.backend_url || "http://localhost:9000"
|
||||
|
||||
@@ -26,7 +26,7 @@ class MinioService extends AbstractFileService implements IFileService {
|
||||
protected downloadUrlDuration: string | number
|
||||
|
||||
constructor({}, options) {
|
||||
super({}, options)
|
||||
super(arguments[0], options)
|
||||
|
||||
this.bucket_ = options.bucket
|
||||
this.accessKeyId_ = options.access_key_id
|
||||
|
||||
@@ -33,7 +33,7 @@ class S3Service extends AbstractFileService implements IFileService {
|
||||
protected client_: S3Client
|
||||
|
||||
constructor({ logger }, options) {
|
||||
super({}, options)
|
||||
super(arguments[0], options)
|
||||
|
||||
this.prefix_ = options.prefix ? `${options.prefix}/` : ''
|
||||
this.bucket_ = options.bucket
|
||||
|
||||
@@ -195,6 +195,16 @@ export const useAdminCancelClaim = (
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The details of the claim's fulfillment.
|
||||
*/
|
||||
export type AdminFulfillClaimReq = AdminPostOrdersOrderClaimsClaimFulfillmentsReq & {
|
||||
/**
|
||||
* The claim's ID.
|
||||
*/
|
||||
claim_id: string
|
||||
}
|
||||
|
||||
/**
|
||||
* This hook creates a Fulfillment for a Claim, and change its fulfillment status to `partially_fulfilled` or `fulfilled` depending on whether all the items were fulfilled.
|
||||
* It may also change the status to `requires_action` if any actions are required.
|
||||
@@ -238,12 +248,7 @@ export const useAdminFulfillClaim = (
|
||||
options?: UseMutationOptions<
|
||||
Response<AdminOrdersRes>,
|
||||
Error,
|
||||
AdminPostOrdersOrderClaimsClaimFulfillmentsReq & {
|
||||
/**
|
||||
* The claim's ID.
|
||||
*/
|
||||
claim_id: string
|
||||
}
|
||||
AdminFulfillClaimReq
|
||||
>
|
||||
) => {
|
||||
const { client } = useMedusa()
|
||||
@@ -253,7 +258,7 @@ export const useAdminFulfillClaim = (
|
||||
({
|
||||
claim_id,
|
||||
...payload
|
||||
}: AdminPostOrdersOrderClaimsClaimFulfillmentsReq & { claim_id: string }) =>
|
||||
}: AdminFulfillClaimReq) =>
|
||||
client.admin.orders.fulfillClaim(orderId, claim_id, payload),
|
||||
buildOptions(
|
||||
queryClient,
|
||||
|
||||
@@ -203,6 +203,16 @@ export const useCreatePaymentSession = (
|
||||
return useMutation(() => client.carts.createPaymentSessions(cartId), options)
|
||||
}
|
||||
|
||||
/**
|
||||
* The details of the payment session to update.
|
||||
*/
|
||||
export type UpdatePaymentSessionReq = StorePostCartsCartPaymentSessionUpdateReq & {
|
||||
/**
|
||||
* The payment provider's identifier.
|
||||
*/
|
||||
provider_id: string
|
||||
}
|
||||
|
||||
/**
|
||||
* This hook updates a Payment Session with additional data. This can be useful depending on the payment provider used.
|
||||
* All payment sessions are updated and cart totals are recalculated afterwards.
|
||||
@@ -248,17 +258,12 @@ export const useUpdatePaymentSession = (
|
||||
options?: UseMutationOptions<
|
||||
StoreCartsRes,
|
||||
Error,
|
||||
{
|
||||
/**
|
||||
* The payment provider's identifier.
|
||||
*/
|
||||
provider_id: string
|
||||
} & StorePostCartsCartPaymentSessionUpdateReq
|
||||
UpdatePaymentSessionReq
|
||||
>
|
||||
) => {
|
||||
const { client } = useMedusa()
|
||||
return useMutation(
|
||||
({ data, provider_id }) =>
|
||||
({ data, provider_id }: UpdatePaymentSessionReq) =>
|
||||
client.carts.updatePaymentSession(cartId, provider_id, { data }),
|
||||
options
|
||||
)
|
||||
|
||||
@@ -4,62 +4,342 @@ import {
|
||||
FileServiceGetUploadStreamResult,
|
||||
FileServiceUploadResult,
|
||||
GetUploadedFileType,
|
||||
MedusaContainer,
|
||||
UploadStreamDescriptorType,
|
||||
} from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* ## Overview
|
||||
*
|
||||
* A file service class is defined in a TypeScript or JavaScript file that’s created in the `src/services` directory.
|
||||
* The class must extend the `AbstractFileService` class imported from the `@medusajs/medusa` package.
|
||||
*
|
||||
* Based on services’ naming conventions, the file’s name should be the slug version of the file service’s name
|
||||
* without `service`, and the class’s name should be the pascal case of the file service’s name following by `Service`.
|
||||
*
|
||||
* For example, create the file `src/services/local-file.ts` with the following content:
|
||||
*
|
||||
* ```ts title="src/services/local-file.ts"
|
||||
* import { AbstractFileService } from "@medusajs/medusa"
|
||||
* import {
|
||||
* DeleteFileType,
|
||||
* FileServiceGetUploadStreamResult,
|
||||
* FileServiceUploadResult,
|
||||
* GetUploadedFileType,
|
||||
* UploadStreamDescriptorType,
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
* async upload(
|
||||
* fileData: Express.Multer.File
|
||||
* ): Promise<FileServiceUploadResult> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* async uploadProtected(
|
||||
* fileData: Express.Multer.File
|
||||
* ): Promise<FileServiceUploadResult> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* async delete(fileData: DeleteFileType): Promise<void> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* async getUploadStreamDescriptor(
|
||||
* fileData: UploadStreamDescriptorType
|
||||
* ): Promise<FileServiceGetUploadStreamResult> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* async getDownloadStream(
|
||||
* fileData: GetUploadedFileType
|
||||
* ): Promise<NodeJS.ReadableStream> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* async getPresignedDownloadUrl(
|
||||
* fileData: GetUploadedFileType
|
||||
* ): Promise<string> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* export default LocalFileService
|
||||
* ```
|
||||
*
|
||||
* :::note[Multer Typing]
|
||||
*
|
||||
* The examples implement a file service supporting local uploads.
|
||||
*
|
||||
* If you’re using TypeScript and you're following along with the implementation,
|
||||
* you should install the Multer types package in the root of your Medusa backend to resolve errors within your file service types:
|
||||
*
|
||||
* ```bash npm2yarn
|
||||
* npm install @types/multer
|
||||
* ```
|
||||
*
|
||||
* :::
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export interface IFileService extends TransactionBaseService {
|
||||
/**
|
||||
* upload file to fileservice
|
||||
* @param file Multer file from express multipart/form-data
|
||||
* */
|
||||
* This method is used to upload a file to the Medusa backend.
|
||||
*
|
||||
* @param {Express.Multer.File} file - A [multer file object](http://expressjs.com/en/resources/middleware/multer.html#file-information).
|
||||
* The file is uploaded to a temporary directory by default. Among the file’s details, you can access the file’s path in the `path` property of the file object.
|
||||
* @returns {Promise<FileServiceUploadResult>} The details of the upload's result.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
* // ...
|
||||
* async upload(
|
||||
* fileData: Express.Multer.File
|
||||
* ): Promise<FileServiceUploadResult> {
|
||||
* const filePath =
|
||||
* `${this.publicPath}/${fileData.originalname}`
|
||||
* fs.copyFileSync(fileData.path, filePath)
|
||||
* return {
|
||||
* url: `${this.serverUrl}/${filePath}`,
|
||||
* key: filePath,
|
||||
* }
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* :::tip
|
||||
*
|
||||
* This example does not account for duplicate names to maintain simplicity in this guide. So, an uploaded file can replace another existing file that has the same name.
|
||||
*
|
||||
* :::
|
||||
*/
|
||||
upload(file: Express.Multer.File): Promise<FileServiceUploadResult>
|
||||
|
||||
/**
|
||||
* upload private file to fileservice
|
||||
* @param file Multer file from express multipart/form-data
|
||||
* */
|
||||
* This method is used to upload a file to the Medusa backend, but to a protected storage. Typically, this would be used to store files that
|
||||
* shouldn’t be accessible by using the file’s URL or should only be accessible by authenticated users. For example, exported or imported
|
||||
* CSV files.
|
||||
*
|
||||
* @param {Express.Multer.File} file - A [multer file object](http://expressjs.com/en/resources/middleware/multer.html#file-information).
|
||||
* The file is uploaded to a temporary directory by default. Among the file’s details, you can access the file’s path in the `path` property of the file object.
|
||||
* @returns {Promise<FileServiceUploadResult>} The details of the upload's result.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
* // ...
|
||||
* async uploadProtected(
|
||||
* fileData: Express.Multer.File
|
||||
* ): Promise<FileServiceUploadResult> {
|
||||
* const filePath =
|
||||
* `${this.protectedPath}/${fileData.originalname}`
|
||||
* fs.copyFileSync(fileData.path, filePath)
|
||||
* return {
|
||||
* url: `${this.serverUrl}/${filePath}`,
|
||||
* key: filePath
|
||||
* }
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* :::tip
|
||||
*
|
||||
* This example does not account for duplicate names to maintain simplicity in this guide. So, an uploaded file can replace another existing file that has the same name.
|
||||
*
|
||||
* :::
|
||||
*/
|
||||
uploadProtected(file: Express.Multer.File): Promise<FileServiceUploadResult>
|
||||
|
||||
/**
|
||||
* remove file from fileservice
|
||||
* @param fileData Remove file described by record
|
||||
* */
|
||||
* This method is used to delete a file from storage.
|
||||
*
|
||||
* @param {DeleteFileType} fileData - The details of the file to remove.
|
||||
* @returns {Promise<void>} Resolves when the file is deleted successfully.
|
||||
*
|
||||
* @example
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
*
|
||||
* async delete(
|
||||
* fileData: DeleteFileType
|
||||
* ): Promise<void> {
|
||||
* fs.rmSync(fileData.fileKey)
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
delete(fileData: DeleteFileType): Promise<void>
|
||||
|
||||
/**
|
||||
* upload file to fileservice from stream
|
||||
* @param fileData file metadata relevant for fileservice to create and upload the file
|
||||
* */
|
||||
* This method is used to retrieve a write stream to be used to upload a file.
|
||||
*
|
||||
* @param {UploadStreamDescriptorType} fileData - The details of the file being uploaded.
|
||||
* @returns {Promise<FileServiceGetUploadStreamResult>} The result of the file-stream upload.
|
||||
*
|
||||
* @example
|
||||
* // ...
|
||||
* import { Stream } from "stream"
|
||||
*
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
* // ...
|
||||
* async getUploadStreamDescriptor({
|
||||
* name,
|
||||
* ext,
|
||||
* isPrivate = true,
|
||||
* }: UploadStreamDescriptorType
|
||||
* ): Promise<FileServiceGetUploadStreamResult> {
|
||||
* const filePath = `${isPrivate ?
|
||||
* this.publicPath : this.protectedPath
|
||||
* }/${name}.${ext}`
|
||||
*
|
||||
* const pass = new Stream.PassThrough()
|
||||
* const writeStream = fs.createWriteStream(filePath)
|
||||
*
|
||||
* pass.pipe(writeStream)
|
||||
*
|
||||
* return {
|
||||
* writeStream: pass,
|
||||
* promise: Promise.resolve(),
|
||||
* url: `${this.serverUrl}/${filePath}`,
|
||||
* fileKey: filePath,
|
||||
* }
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
getUploadStreamDescriptor(
|
||||
fileData: UploadStreamDescriptorType
|
||||
): Promise<FileServiceGetUploadStreamResult>
|
||||
|
||||
/**
|
||||
* download file from fileservice as stream
|
||||
* @param fileData file metadata relevant for fileservice to download the file
|
||||
* @returns readable stream of the file to download
|
||||
* */
|
||||
* This method is used to retrieve a read stream for a file, which can then be used to download the file.
|
||||
*
|
||||
* @param {GetUploadedFileType} fileData - The details of the file.
|
||||
* @returns {Promise<NodeJS.ReadableStream>} The [read stream](https://nodejs.org/api/webstreams.html#class-readablestream) to read and download the file.
|
||||
*
|
||||
* @example
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
*
|
||||
* async getDownloadStream({
|
||||
* fileKey,
|
||||
* isPrivate = true,
|
||||
* }: GetUploadedFileType
|
||||
* ): Promise<NodeJS.ReadableStream> {
|
||||
* const filePath = `${isPrivate ?
|
||||
* this.publicPath : this.protectedPath
|
||||
* }/${fileKey}`
|
||||
* const readStream = fs.createReadStream(filePath)
|
||||
*
|
||||
* return readStream
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
getDownloadStream(
|
||||
fileData: GetUploadedFileType
|
||||
): Promise<NodeJS.ReadableStream>
|
||||
|
||||
/**
|
||||
* Generate a presigned download url to obtain a file
|
||||
* @param fileData file metadata relevant for fileservice to download the file
|
||||
* @returns presigned url to download the file
|
||||
* */
|
||||
* This method is used to retrieve a download URL of the file. For some file services, such as S3, a presigned URL indicates a temporary URL to get access to a file.
|
||||
*
|
||||
* If your file service doesn’t perform or offer a similar functionality, you can just return the URL to download the file.
|
||||
*
|
||||
* @param {GetUploadedFileType} fileData - The details of the file.
|
||||
* @returns {Promise<string>} The presigned URL to download the file
|
||||
*
|
||||
* @example
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
*
|
||||
* async getPresignedDownloadUrl({
|
||||
* fileKey,
|
||||
* isPrivate = true,
|
||||
* }: GetUploadedFileType
|
||||
* ): Promise<string> {
|
||||
* // Local upload doesn't provide
|
||||
* // support for presigned URLs,
|
||||
* // so just return the file's URL.
|
||||
*
|
||||
* const filePath = `${isPrivate ?
|
||||
* this.publicPath : this.protectedPath
|
||||
* }/${fileKey}`
|
||||
* return `${this.serverUrl}/${filePath}`
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
getPresignedDownloadUrl(fileData: GetUploadedFileType): Promise<string>
|
||||
}
|
||||
|
||||
/**
|
||||
* @parentIgnore activeManager_,atomicPhase_,shouldRetryTransaction_,withTransaction
|
||||
*/
|
||||
export abstract class AbstractFileService
|
||||
extends TransactionBaseService
|
||||
implements IFileService
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static _isFileService = true
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static isFileService(object): object is AbstractFileService {
|
||||
return object?.constructor?._isFileService
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your file service to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs,
|
||||
* you can initialize it in the constructor and use it in other methods in the service.
|
||||
*
|
||||
* Additionally, if you’re creating your file service as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin,
|
||||
* you can access them in the constructor.
|
||||
*
|
||||
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.
|
||||
* @param {Record<string, unknown>} config - If this file service is created in a plugin, the plugin's options are passed in this parameter.
|
||||
*
|
||||
* @example
|
||||
* // ...
|
||||
* import { Logger } from "@medusajs/medusa"
|
||||
* import * as fs from "fs"
|
||||
*
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
* // can also be replaced by an environment variable
|
||||
* // or a plugin option
|
||||
* protected serverUrl = "http://localhost:9000"
|
||||
* protected publicPath = "uploads"
|
||||
* protected protectedPath = "protected-uploads"
|
||||
* protected logger_: Logger
|
||||
*
|
||||
* constructor({ logger }: InjectedDependencies) {
|
||||
* // @ts-ignore
|
||||
* super(...arguments)
|
||||
* this.logger_ = logger
|
||||
*
|
||||
* // for public uploads
|
||||
* if (!fs.existsSync(this.publicPath)) {
|
||||
* fs.mkdirSync(this.publicPath)
|
||||
* }
|
||||
*
|
||||
* // for protected uploads
|
||||
* if (!fs.existsSync(this.protectedPath)) {
|
||||
* fs.mkdirSync(this.protectedPath)
|
||||
* }
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
protected constructor(
|
||||
protected readonly container: MedusaContainer,
|
||||
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
) {
|
||||
super(container, config)
|
||||
}
|
||||
|
||||
abstract upload(
|
||||
fileData: Express.Multer.File
|
||||
): Promise<FileServiceUploadResult>
|
||||
|
||||
@@ -411,6 +411,7 @@ export abstract class AbstractFulfillmentService
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your fulfillment provider to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.
|
||||
* Additionally, if you’re creating your fulfillment provider as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor.
|
||||
*
|
||||
|
||||
@@ -1,18 +1,196 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { TransactionBaseService } from "./transaction-base-service"
|
||||
|
||||
type ReturnedData = {
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The details of a sent or resent notification.
|
||||
*/
|
||||
export type ReturnedData = {
|
||||
/**
|
||||
* The receiver of the Notification. For example, if you sent an email to the customer then `to` is the email address of the customer.
|
||||
* In other cases, it might be a phone number or a username.
|
||||
*/
|
||||
to: string
|
||||
/**
|
||||
* The status of the sent notification. There are no restriction on the returned status.
|
||||
*/
|
||||
status: string
|
||||
/**
|
||||
* The data used to send the Notification. For example, if you sent an order confirmation email to the customer, then the `data` object
|
||||
* might include the order items or the subject of the email. This `data` is necessary if the notification is resent later as you can use the same data.
|
||||
*/
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* ## Overview
|
||||
*
|
||||
* :::note[Prerequisites]
|
||||
*
|
||||
* Before creating a Notification Provider, [install an event bus module](https://docs.medusajs.com/development/events/modules/redis).
|
||||
*
|
||||
* :::
|
||||
*
|
||||
* A Notification Provider is a provider that handles sending and resending of notifications.
|
||||
*
|
||||
* To create a Notification Provider, create a TypeScript or JavaScript file in `src/services`. The name of the file is the name of the provider
|
||||
* (for example, `sendgrid.ts`). The file must export a class that extends the `AbstractNotificationService` class imported from `@medusajs/medusa`.
|
||||
*
|
||||
* For example, create the file `src/services/email-sender.ts` with the following content:
|
||||
*
|
||||
* ```ts title="src/services/email-sender.ts"
|
||||
* import { AbstractNotificationService } from "@medusajs/medusa"
|
||||
* import { EntityManager } from "typeorm"
|
||||
*
|
||||
* class EmailSenderService extends AbstractNotificationService {
|
||||
* protected manager_: EntityManager
|
||||
* protected transactionManager_: EntityManager
|
||||
*
|
||||
* sendNotification(
|
||||
* event: string,
|
||||
* data: unknown,
|
||||
* attachmentGenerator: unknown
|
||||
* ): Promise<{
|
||||
* to: string;
|
||||
* status: string;
|
||||
* data: Record<string, unknown>;
|
||||
* }> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* resendNotification(
|
||||
* notification: unknown,
|
||||
* config: unknown,
|
||||
* attachmentGenerator: unknown
|
||||
* ): Promise<{
|
||||
* to: string;
|
||||
* status: string;
|
||||
* data: Record<string, unknown>;
|
||||
* }> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* export default EmailSenderService
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* ## Identifier Property
|
||||
*
|
||||
* The `NotificationProvider` entity has 2 properties: `identifier` and `is_installed`. The value of the `identifier` property in the notification provider
|
||||
* class is used when the Notification Provider is created in the database.
|
||||
*
|
||||
* The value of this property is also used later when you want to subscribe the Notification Provider to events in a [Loader](https://docs.medusajs.com/development/loaders/overview).
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```ts
|
||||
* class EmailSenderService extends AbstractNotificationService {
|
||||
* static identifier = "email-sender"
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export interface INotificationService extends TransactionBaseService {
|
||||
/**
|
||||
* When an event is triggered that your Notification Provider is registered as a handler for, the [`NotificationService`](https://docs.medusajs.com/references/services/classes/services.NotificationService)
|
||||
* in the Medusa backend executes this method of your Notification Provider.
|
||||
*
|
||||
* In this method, you can perform the necessary operation to send the Notification. For example, you can send an email to the customer when they place an order.
|
||||
*
|
||||
* @param {string} event - The name of the event that was triggered. For example, `order.placed`.
|
||||
* @param {unknown} data - The data payload of the event that was triggered. For example, if the `order.placed` event is triggered,
|
||||
* the `eventData` object contains the property `id` which is the ID of the order that was placed. You can refer to the
|
||||
* [Events reference](https://docs.medusajs.com/development/events/events-list) for information on all events and their payloads.
|
||||
* @param {unknown} attachmentGenerator - If you’ve previously register an attachment generator to the `NotificationService` using the
|
||||
* [`registerAttachmentGenerator`](https://docs.medusajs.com/references/services/classes/services.NotificationService#registerattachmentgenerator) method,
|
||||
* you have access to it here. You can use the `attachmentGenerator` to generate on-demand invoices or other documents. The default value of this parameter is `null`.
|
||||
* @returns {Promise<ReturnedData>} The sending details.
|
||||
*
|
||||
* @example
|
||||
* class EmailSenderService extends AbstractNotificationService {
|
||||
* // ...
|
||||
* async sendNotification(
|
||||
* event: string,
|
||||
* data: any,
|
||||
* attachmentGenerator: unknown
|
||||
* ): Promise<{
|
||||
* to: string;
|
||||
* status: string;
|
||||
* data: Record<string, unknown>;
|
||||
* }> {
|
||||
* if (event === "order.placed") {
|
||||
* // retrieve order
|
||||
* const order = await this.orderService.retrieve(data.id)
|
||||
* // TODO send email
|
||||
*
|
||||
* console.log("Notification sent")
|
||||
* return {
|
||||
* to: order.email,
|
||||
* status: "done",
|
||||
* data: {
|
||||
* // any data necessary to send the email
|
||||
* // for example:
|
||||
* subject: "You placed a new order!",
|
||||
* items: order.items,
|
||||
* },
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
sendNotification(
|
||||
event: string,
|
||||
data: unknown,
|
||||
attachmentGenerator: unknown
|
||||
): Promise<ReturnedData>
|
||||
|
||||
/**
|
||||
* This method is used to resend notifications, which is typically triggered by the
|
||||
* [Resend Notification API Route](https://docs.medusajs.com/api/admin#notifications_postnotificationsnotificationresend).
|
||||
*
|
||||
* @param {unknown} notification - The original [Notification record](https://docs.medusajs.com/references/entities/classes/Notification) that was created after you sent the
|
||||
* notification with `sendNotification`. It includes the `to` and `data` attributes which are populated originally using the `to` and `data` properties of
|
||||
* the object you return in {@link sendNotification}.
|
||||
* @param {unknown} config - The new configuration used to resend the notification. The [Resend Notification API Route](https://docs.medusajs.com/api/admin#notifications_postnotificationsnotificationresend),
|
||||
* allows you to pass a new `to` field. If specified, it will be available in this config object.
|
||||
* @param {unknown} attachmentGenerator - f you’ve previously register an attachment generator to the `NotificationService` using the
|
||||
* [`registerAttachmentGenerator`](https://docs.medusajs.com/references/services/classes/services.NotificationService#registerattachmentgenerator) method,
|
||||
* you have access to it here. You can use the `attachmentGenerator` to generate on-demand invoices or other documents. The default value of this parameter is `null`.
|
||||
* @returns {Promise<ReturnedData>} The resend details.
|
||||
*
|
||||
* @example
|
||||
* class EmailSenderService extends AbstractNotificationService {
|
||||
* // ...
|
||||
* async resendNotification(
|
||||
* notification: any,
|
||||
* config: any,
|
||||
* attachmentGenerator: unknown
|
||||
* ): Promise<{
|
||||
* to: string;
|
||||
* status: string;
|
||||
* data: Record<string, unknown>;
|
||||
* }> {
|
||||
* // check if the receiver should be changed
|
||||
* const to: string = config.to || notification.to
|
||||
*
|
||||
* // TODO resend the notification using the same data
|
||||
* // that is saved under notification.data
|
||||
*
|
||||
* console.log("Notification resent")
|
||||
* return {
|
||||
* to,
|
||||
* status: "done",
|
||||
* data: notification.data, // make changes to the data
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
resendNotification(
|
||||
notification: unknown,
|
||||
config: unknown,
|
||||
@@ -20,21 +198,78 @@ export interface INotificationService extends TransactionBaseService {
|
||||
): Promise<ReturnedData>
|
||||
}
|
||||
|
||||
/**
|
||||
* @parentIgnore activeManager_,atomicPhase_,shouldRetryTransaction_,withTransaction
|
||||
*/
|
||||
export abstract class AbstractNotificationService
|
||||
extends TransactionBaseService
|
||||
implements INotificationService
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static _isNotificationService = true
|
||||
static identifier: string
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static isNotificationService(object): boolean {
|
||||
return object?.constructor?._isNotificationService
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
getIdentifier(): string {
|
||||
return (this.constructor as any).identifier
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your notification provider to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs,
|
||||
* you can initialize it in the constructor and use it in other methods in the service.
|
||||
*
|
||||
* Additionally, if you’re creating your notification provider as an external plugin to be installed on any Medusa backend and you want to access the options
|
||||
* added for the plugin, you can access it in the constructor.
|
||||
*
|
||||
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.
|
||||
* @param {Record<string, unknown>} config - If this notification provider is created in a plugin, the plugin's options are passed in this parameter.
|
||||
*
|
||||
* @example
|
||||
* // ...
|
||||
* import { AbstractNotificationService, OrderService } from "@medusajs/medusa"
|
||||
* import { EntityManager } from "typeorm"
|
||||
*
|
||||
* class EmailSenderService extends AbstractNotificationService {
|
||||
* // ...
|
||||
* protected orderService: OrderService
|
||||
*
|
||||
* constructor(container, options) {
|
||||
* super(container)
|
||||
* // you can access options here in case you're
|
||||
* // using a plugin
|
||||
*
|
||||
* this.orderService = container.orderService
|
||||
*
|
||||
* // you can also initialize a client that
|
||||
* // communicates with a third-party service.
|
||||
* this.client = new Client(options)
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* export default EmailSenderService
|
||||
*/
|
||||
protected constructor(
|
||||
protected readonly container: MedusaContainer,
|
||||
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
) {
|
||||
super(container, config)
|
||||
}
|
||||
|
||||
abstract sendNotification(
|
||||
event: string,
|
||||
data: unknown,
|
||||
|
||||
@@ -1,24 +1,153 @@
|
||||
import { MoneyAmount } from "../models"
|
||||
import { PriceListType } from "../types/price-list"
|
||||
import { TaxServiceRate } from "../types/tax-service"
|
||||
import { ITransactionBaseService } from "@medusajs/types"
|
||||
import { ITransactionBaseService, MedusaContainer } from "@medusajs/types"
|
||||
import { TransactionBaseService } from "./transaction-base-service"
|
||||
|
||||
/**
|
||||
* ## Overview
|
||||
*
|
||||
* The price selection strategy retrieves the best price for a product variant for a specific context such as selected region, taxes applied,
|
||||
* the quantity in cart, and more.
|
||||
*
|
||||
* Medusa provides a default price selection strategy, but you can override it. A price selecion strategy is a TypeScript or JavaScript file in the `src/strategies` directory of your Medusa backend project. It exports a class that extends the `AbstractPriceSelectionStrategy` class.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```ts title="src/strategies/price.ts"
|
||||
* import {
|
||||
* AbstractPriceSelectionStrategy,
|
||||
* PriceSelectionContext,
|
||||
* PriceSelectionResult,
|
||||
* } from "@medusajs/medusa"
|
||||
*
|
||||
* export default class MyStrategy extends
|
||||
* AbstractPriceSelectionStrategy {
|
||||
*
|
||||
* async calculateVariantPrice(
|
||||
* data: {
|
||||
* variantId: string;
|
||||
* quantity?: number
|
||||
* }[],
|
||||
* context: PriceSelectionContext
|
||||
* ): Promise<Map<string, PriceSelectionResult>> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export interface IPriceSelectionStrategy extends ITransactionBaseService {
|
||||
/**
|
||||
* Calculate the original and discount price for a given variant in a set of
|
||||
* circumstances described in the context.
|
||||
* @return pricing details in an object containing the calculated lowest price,
|
||||
* the default price an all valid prices for the given variant
|
||||
* This method retrieves one or more product variants' prices. It's used when retrieving product variants or their associated line items.
|
||||
* It's also used when retrieving other entities that product variants and line items belong to, such as products and carts respectively.
|
||||
*
|
||||
* @param data - The necessary data to perform the price selection for each variant ID.
|
||||
* @param context - The context of the price selection.
|
||||
* @returns {Promise<Map<string, PriceSelectionResult>>} A map, each key is an ID of a variant, and its value is an object holding the price selection result.
|
||||
*
|
||||
* @example
|
||||
* For example, here's a snippet of how the price selection strategy is implemented in the Medusa backend:
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* AbstractPriceSelectionStrategy,
|
||||
* CustomerService,
|
||||
* PriceSelectionContext,
|
||||
* PriceSelectionResult,
|
||||
* } from "@medusajs/medusa"
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* customerService: CustomerService
|
||||
* }
|
||||
*
|
||||
* export default class MyStrategy extends
|
||||
* AbstractPriceSelectionStrategy {
|
||||
*
|
||||
* async calculateVariantPrice(
|
||||
* data: {
|
||||
* variantId: string
|
||||
* quantity?: number
|
||||
* }[],
|
||||
* context: PriceSelectionContext
|
||||
* ): Promise<Map<string, PriceSelectionResult>> {
|
||||
* const dataMap = new Map(data.map((d) => [d.variantId, d]))
|
||||
*
|
||||
* const cacheKeysMap = new Map(
|
||||
* data.map(({ variantId, quantity }) => [
|
||||
* variantId,
|
||||
* this.getCacheKey(variantId, { ...context, quantity }),
|
||||
* ])
|
||||
* )
|
||||
*
|
||||
* const nonCachedData: {
|
||||
* variantId: string
|
||||
* quantity?: number
|
||||
* }[] = []
|
||||
*
|
||||
* const variantPricesMap = new Map<string, PriceSelectionResult>()
|
||||
*
|
||||
* if (!context.ignore_cache) {
|
||||
* const cacheHits = await promiseAll(
|
||||
* [...cacheKeysMap].map(async ([, cacheKey]) => {
|
||||
* return await this.cacheService_.get<PriceSelectionResult>(cacheKey)
|
||||
* })
|
||||
* )
|
||||
*
|
||||
* if (!cacheHits.length) {
|
||||
* nonCachedData.push(...dataMap.values())
|
||||
* }
|
||||
*
|
||||
* for (const [index, cacheHit] of cacheHits.entries()) {
|
||||
* const variantId = data[index].variantId
|
||||
* if (cacheHit) {
|
||||
* variantPricesMap.set(variantId, cacheHit)
|
||||
* continue
|
||||
* }
|
||||
*
|
||||
* nonCachedData.push(dataMap.get(variantId)!)
|
||||
* }
|
||||
* } else {
|
||||
* nonCachedData.push(...dataMap.values())
|
||||
* }
|
||||
*
|
||||
* let results: Map<string, PriceSelectionResult> = new Map()
|
||||
*
|
||||
* if (
|
||||
* this.featureFlagRouter_.isFeatureEnabled(
|
||||
* TaxInclusivePricingFeatureFlag.key
|
||||
* )
|
||||
* ) {
|
||||
* results = await this.calculateVariantPrice_new(nonCachedData, context)
|
||||
* } else {
|
||||
* results = await this.calculateVariantPrice_old(nonCachedData, context)
|
||||
* }
|
||||
*
|
||||
* await promiseAll(
|
||||
* [...results].map(async ([variantId, prices]) => {
|
||||
* variantPricesMap.set(variantId, prices)
|
||||
* if (!context.ignore_cache) {
|
||||
* await this.cacheService_.set(cacheKeysMap.get(variantId)!, prices)
|
||||
* }
|
||||
* })
|
||||
* )
|
||||
*
|
||||
* return variantPricesMap
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
calculateVariantPrice(
|
||||
data: {
|
||||
/**
|
||||
* The variant id of the variant for which to retrieve prices
|
||||
* The ID of the variant to retrieve its prices.
|
||||
*/
|
||||
variantId: string
|
||||
/**
|
||||
* The variant's quantity.
|
||||
* The variant's quantity in the cart, if available.
|
||||
*/
|
||||
quantity?: number
|
||||
}[],
|
||||
@@ -29,25 +158,114 @@ export interface IPriceSelectionStrategy extends ITransactionBaseService {
|
||||
): Promise<Map<string, PriceSelectionResult>>
|
||||
|
||||
/**
|
||||
* Notify price selection strategy that variants prices have been updated.
|
||||
* @param variantIds The ids of the updated variants
|
||||
* This method is called when prices of product variants have changed.
|
||||
* You can use it to invalidate prices stored in the cache.
|
||||
*
|
||||
* @param {string[]} variantIds - The IDs of the updated variants.
|
||||
* @returns {Promise<void>} Resolves after any necessary actions are performed.
|
||||
*
|
||||
* @example
|
||||
* For example, this is how this method is implemented in the Medusa backend's default
|
||||
* price selection strategy:
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* AbstractPriceSelectionStrategy,
|
||||
* CustomerService,
|
||||
* } from "@medusajs/medusa"
|
||||
* import { promiseAll } from "@medusajs/utils"
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* customerService: CustomerService
|
||||
* }
|
||||
*
|
||||
* export default class MyStrategy extends
|
||||
* AbstractPriceSelectionStrategy {
|
||||
*
|
||||
* public async onVariantsPricesUpdate(variantIds: string[]): Promise<void> {
|
||||
* await promiseAll(
|
||||
* variantIds.map(
|
||||
* async (id: string) => await this.cacheService_.invalidate(`ps:${id}:*`)
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* :::note
|
||||
*
|
||||
* Learn more about the cache service in [this documentation](https://docs.medusajs.com/development/cache/overview).
|
||||
*
|
||||
* :::
|
||||
*/
|
||||
onVariantsPricesUpdate(variantIds: string[]): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* @parentIgnore activeManager_,atomicPhase_,shouldRetryTransaction_,withTransaction
|
||||
*/
|
||||
export abstract class AbstractPriceSelectionStrategy
|
||||
extends TransactionBaseService
|
||||
implements IPriceSelectionStrategy
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static _isPriceSelectionStrategy = true
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static isPriceSelectionStrategy(object): boolean {
|
||||
return object?.constructor?._isPriceSelectionStrategy
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your price-selection strategy to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.
|
||||
* @param {Record<string, unknown>} config - If this price-selection strategy is created in a plugin, the plugin's options are passed in this parameter.
|
||||
*
|
||||
* @example
|
||||
* // ...
|
||||
* import {
|
||||
* AbstractPriceSelectionStrategy,
|
||||
* CustomerService,
|
||||
* } from "@medusajs/medusa"
|
||||
* type InjectedDependencies = {
|
||||
* customerService: CustomerService
|
||||
* }
|
||||
*
|
||||
* class MyStrategy extends
|
||||
* AbstractPriceSelectionStrategy {
|
||||
*
|
||||
* protected customerService_: CustomerService
|
||||
*
|
||||
* constructor(container: InjectedDependencies) {
|
||||
* super(container)
|
||||
* this.customerService_ = container.customerService
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* export default MyStrategy
|
||||
*/
|
||||
protected constructor(
|
||||
protected readonly container: MedusaContainer,
|
||||
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
) {
|
||||
super(container, config)
|
||||
}
|
||||
|
||||
public abstract calculateVariantPrice(
|
||||
data: {
|
||||
variantId: string
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
taxRates: TaxServiceRate[]
|
||||
quantity?: number
|
||||
}[],
|
||||
@@ -59,18 +277,59 @@ export abstract class AbstractPriceSelectionStrategy
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The context of the price selection.
|
||||
*/
|
||||
export type PriceSelectionContext = {
|
||||
/**
|
||||
* The cart's ID. This is used when the prices are being retrieved for the variant of a line item,
|
||||
* as it is used to determine the current region and currency code of the context.
|
||||
*/
|
||||
cart_id?: string
|
||||
/**
|
||||
* The ID of the customer viewing the variant.
|
||||
*/
|
||||
customer_id?: string
|
||||
/**
|
||||
* The region's ID.
|
||||
*/
|
||||
region_id?: string
|
||||
/**
|
||||
* The quantity of the item in the cart. This is used to filter out price lists that have
|
||||
* `min_quantity` or `max_quantity` conditions set.
|
||||
*/
|
||||
quantity?: number
|
||||
/**
|
||||
* The currency code the customer is using.
|
||||
*/
|
||||
currency_code?: string
|
||||
/**
|
||||
* Whether the price list's prices should be retrieved or not.
|
||||
*/
|
||||
include_discount_prices?: boolean
|
||||
/**
|
||||
* The tax rates to be applied. This is only used for
|
||||
* [Tax-Inclusive Pricing](https://docs.medusajs.com/modules/taxes/inclusive-pricing).
|
||||
*/
|
||||
tax_rates?: TaxServiceRate[]
|
||||
/**
|
||||
* Whether to calculate the prices even if the value of an earlier price calculation
|
||||
* is available in the cache.
|
||||
*/
|
||||
ignore_cache?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @enum
|
||||
*
|
||||
* The type of default price type.
|
||||
*/
|
||||
enum DefaultPriceType {
|
||||
/**
|
||||
* The `calculatedPrice` is the original price.
|
||||
*/
|
||||
DEFAULT = "default",
|
||||
}
|
||||
|
||||
@@ -78,11 +337,38 @@ enum DefaultPriceType {
|
||||
export type PriceType = DefaultPriceType | PriceListType
|
||||
export const PriceType = { ...DefaultPriceType, ...PriceListType }
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The price selection result of a variant.
|
||||
*/
|
||||
export type PriceSelectionResult = {
|
||||
/**
|
||||
* The original price of the variant which depends on the selected region or currency code in the context object.
|
||||
* If both region ID and currency code are available in the context object, the region has higher precedence.
|
||||
*/
|
||||
originalPrice: number | null
|
||||
/**
|
||||
* Whether the original price includes taxes or not. This is only available
|
||||
* for [Tax-Inclusive Pricing](https://docs.medusajs.com/modules/taxes/inclusive-pricing).
|
||||
*/
|
||||
originalPriceIncludesTax?: boolean | null
|
||||
/**
|
||||
* The lowest price among the prices of the product variant retrieved using the context object.
|
||||
*/
|
||||
calculatedPrice: number | null
|
||||
/**
|
||||
* Whether the calculated price includes taxes or not.
|
||||
* This is only available for [Tax-Inclusive Pricing](https://docs.medusajs.com/modules/taxes/inclusive-pricing).
|
||||
*/
|
||||
calculatedPriceIncludesTax?: boolean | null
|
||||
/**
|
||||
* The type of price applied in `calculatedPrice`.
|
||||
*/
|
||||
calculatedPriceType?: PriceType
|
||||
prices: MoneyAmount[] // prices is an array of all possible price for the input customer and region prices
|
||||
/**
|
||||
* All possible prices of the variant that are retrieved using the `context` object.
|
||||
* It can include its original price and its price lists if there are any.
|
||||
*/
|
||||
prices: MoneyAmount[]
|
||||
}
|
||||
@@ -3,15 +3,95 @@ import { TaxCalculationContext } from "./tax-service"
|
||||
import { LineItemTaxLine } from "../models/line-item-tax-line"
|
||||
import { ShippingMethodTaxLine } from "../models/shipping-method-tax-line"
|
||||
import { TransactionBaseService } from "./transaction-base-service"
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* ## Overview
|
||||
*
|
||||
* A tax calculation strategy is used to calculate taxes when calculating a cart's totals. The Medusa
|
||||
* backend provides a tax calculation strategy that handles calculating the taxes, taking into account the
|
||||
* defined tax rates and settings such as whether tax-inclusive pricing is enabled.
|
||||
*
|
||||
* You can override the tax calculation strategy to implement different calculation logic or to
|
||||
* integrate a third-party service that handles the tax calculation. You can override it either
|
||||
* in a Medusa backend setup or in a plugin.
|
||||
*
|
||||
* A tax calculation strategy should be defined in a TypeScript or JavaScript file created under the `src/strategies` directory.
|
||||
* The class must also implement the `ITaxCalculationStrategy` interface imported from the `@medusajs/medusa` package.
|
||||
*
|
||||
* For example, you can create the file `src/strategies/tax-calculation.ts` with the following content:
|
||||
*
|
||||
* ```ts title="src/strategies/tax-calculation.ts"
|
||||
* import {
|
||||
* ITaxCalculationStrategy,
|
||||
* LineItem,
|
||||
* LineItemTaxLine,
|
||||
* ShippingMethodTaxLine,
|
||||
* TaxCalculationContext,
|
||||
* } from "@medusajs/medusa"
|
||||
*
|
||||
* class TaxCalculationStrategy
|
||||
* implements ITaxCalculationStrategy {
|
||||
*
|
||||
* async calculate(
|
||||
* items: LineItem[],
|
||||
* taxLines: (ShippingMethodTaxLine | LineItemTaxLine)[],
|
||||
* calculationContext: TaxCalculationContext
|
||||
* ): Promise<number> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* export default TaxCalculationStrategy
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export interface ITaxCalculationStrategy {
|
||||
/**
|
||||
* Calculates the tax amount for a given set of line items under applicable
|
||||
* This method calculates the tax amount for a given set of line items under applicable
|
||||
* tax conditions and calculation contexts.
|
||||
* @param items - the line items to calculate the tax total for
|
||||
* @param taxLines - the tax lines that applies to the calculation
|
||||
* @param calculationContext - other details relevant for the calculation
|
||||
* @return the tax total
|
||||
*
|
||||
* This method is used whenever taxes are calculated. If automatic tax calculation is disabled in a region,
|
||||
* then it's only triggered when taxes are calculated manually as explained in
|
||||
* [this guide](https://docs.medusajs.com/modules/taxes/storefront/manual-calculation).
|
||||
*
|
||||
* @param {LineItem[]} items - The line items to calculate the tax total for.
|
||||
* @param {(ShippingMethodTaxLine | LineItemTaxLine)[]} taxLines - The tax lines used for the calculation
|
||||
* @param {TaxCalculationContext} calculationContext - Other details relevant for the calculation
|
||||
* @returns {Promise<number>} The calculated tax total
|
||||
*
|
||||
* @example
|
||||
* An example of the general implementation of this method in the Medusa backend's tax calculation strategy:
|
||||
*
|
||||
* ```ts
|
||||
* async calculate(
|
||||
* items: LineItem[],
|
||||
* taxLines: (ShippingMethodTaxLine | LineItemTaxLine)[],
|
||||
* calculationContext: TaxCalculationContext
|
||||
* ): Promise<number> {
|
||||
* const lineItemsTaxLines = taxLines.filter(
|
||||
* (tl) => "item_id" in tl
|
||||
* ) as LineItemTaxLine[]
|
||||
* const shippingMethodsTaxLines = taxLines.filter(
|
||||
* (tl) => "shipping_method_id" in tl
|
||||
* ) as ShippingMethodTaxLine[]
|
||||
*
|
||||
* const lineItemsTax = this.calculateLineItemsTax(
|
||||
* items,
|
||||
* lineItemsTaxLines,
|
||||
* calculationContext
|
||||
* )
|
||||
*
|
||||
* const shippingMethodsTax = this.calculateShippingMethodsTax(
|
||||
* calculationContext.shipping_methods,
|
||||
* shippingMethodsTaxLines
|
||||
* )
|
||||
*
|
||||
* return Math.round(lineItemsTax + shippingMethodsTax)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
calculate(
|
||||
items: LineItem[],
|
||||
@@ -20,12 +100,21 @@ export interface ITaxCalculationStrategy {
|
||||
): Promise<number>
|
||||
}
|
||||
|
||||
/**
|
||||
* @parentIgnore activeManager_,atomicPhase_,shouldRetryTransaction_,withTransaction
|
||||
*/
|
||||
export abstract class AbstractTaxCalculationStrategy
|
||||
extends TransactionBaseService
|
||||
implements ITaxCalculationStrategy
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static _isTaxCalculationStrategy = true
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static isTaxCalculationStrategy(object): boolean {
|
||||
return (
|
||||
typeof object.calculate === "function" ||
|
||||
@@ -33,6 +122,46 @@ export abstract class AbstractTaxCalculationStrategy
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your tax calculation strategy to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.
|
||||
* Additionally, if you’re creating your tax calculation strategy as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor.
|
||||
*
|
||||
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.
|
||||
* @param {Record<string, unknown>} config - If this tax calculation strategy is created in a plugin, the plugin's options are passed in this parameter.
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* ITaxCalculationStrategy,
|
||||
* LineItemService,
|
||||
* } from "@medusajs/medusa"
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* lineItemService: LineItemService
|
||||
* }
|
||||
*
|
||||
* class TaxCalculationStrategy
|
||||
* implements ITaxCalculationStrategy {
|
||||
*
|
||||
* protected readonly lineItemService_: LineItemService
|
||||
*
|
||||
* constructor({ lineItemService }: InjectedDependencies) {
|
||||
* this.lineItemService_ = lineItemService
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* export default TaxCalculationStrategy
|
||||
*/
|
||||
protected constructor(
|
||||
protected readonly container: MedusaContainer,
|
||||
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
) {
|
||||
super(container, config)
|
||||
}
|
||||
|
||||
abstract calculate(
|
||||
items: LineItem[],
|
||||
taxLines: (ShippingMethodTaxLine | LineItemTaxLine)[],
|
||||
|
||||
@@ -6,22 +6,35 @@ import { Customer } from "../models/customer"
|
||||
import { ProviderTaxLine, TaxServiceRate } from "../types/tax-service"
|
||||
import { LineAllocationsMap } from "../types/totals"
|
||||
import { TransactionBaseService } from "./transaction-base-service"
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* A shipping method and the tax rates that have been configured to apply to the
|
||||
* A shipping method and the tax rates configured to apply to the
|
||||
* shipping method.
|
||||
*/
|
||||
export type ShippingTaxCalculationLine = {
|
||||
/**
|
||||
* The shipping method to calculate taxes for.
|
||||
*/
|
||||
shipping_method: ShippingMethod
|
||||
/**
|
||||
* The rates applicable on the shipping method.
|
||||
*/
|
||||
rates: TaxServiceRate[]
|
||||
}
|
||||
|
||||
/**
|
||||
* A line item and the tax rates that have been configured to apply to the
|
||||
* A line item and the tax rates configured to apply to the
|
||||
* product contained in the line item.
|
||||
*/
|
||||
export type ItemTaxCalculationLine = {
|
||||
/**
|
||||
* The line item to calculate taxes for.
|
||||
*/
|
||||
item: LineItem
|
||||
/**
|
||||
* The rates applicable on the item.
|
||||
*/
|
||||
rates: TaxServiceRate[]
|
||||
}
|
||||
|
||||
@@ -30,27 +43,135 @@ export type ItemTaxCalculationLine = {
|
||||
* the items are going.
|
||||
*/
|
||||
export type TaxCalculationContext = {
|
||||
/**
|
||||
* The shipping address used in the cart.
|
||||
*/
|
||||
shipping_address: Address | null
|
||||
/**
|
||||
* The customer that the cart belongs to.
|
||||
*/
|
||||
customer: Customer
|
||||
/**
|
||||
* The cart's region.
|
||||
*/
|
||||
region: Region
|
||||
/**
|
||||
* Whether the cart is used in a return flow.
|
||||
*/
|
||||
is_return: boolean
|
||||
/**
|
||||
* The shipping methods used in the cart.
|
||||
*/
|
||||
shipping_methods: ShippingMethod[]
|
||||
/**
|
||||
* The gift cards and discounts applied on line items.
|
||||
* Each object key or property is an ID of a line item
|
||||
*/
|
||||
allocation_map: LineAllocationsMap
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to be implemented by tax provider plugins. The interface defines a
|
||||
* single method `getTaxLines` that returns numerical rates to apply to line
|
||||
* items and shipping methods.
|
||||
* ## Overview
|
||||
*
|
||||
* A tax provider is used to retrieve the tax lines in a cart. The Medusa backend provides a default `system` provider. You can create your own tax provider,
|
||||
* either in a plugin or directly in your Medusa backend, then use it in any region.
|
||||
*
|
||||
* A tax provider class is defined in a TypeScript or JavaScript file under the `src/services` directory and the class must extend the
|
||||
* `AbstractTaxService` class imported from `@medusajs/medusa`. The file's name is the tax provider's class name as a slug and without the word `Service`.
|
||||
*
|
||||
* For example, you can create the file `src/services/my-tax.ts` with the following content:
|
||||
*
|
||||
* ```ts title="src/services/my-tax.ts"
|
||||
* import {
|
||||
* AbstractTaxService,
|
||||
* ItemTaxCalculationLine,
|
||||
* ShippingTaxCalculationLine,
|
||||
* TaxCalculationContext,
|
||||
* } from "@medusajs/medusa"
|
||||
* import {
|
||||
* ProviderTaxLine,
|
||||
* } from "@medusajs/medusa/dist/types/tax-service"
|
||||
*
|
||||
* class MyTaxService extends AbstractTaxService {
|
||||
* async getTaxLines(
|
||||
* itemLines: ItemTaxCalculationLine[],
|
||||
* shippingLines: ShippingTaxCalculationLine[],
|
||||
* context: TaxCalculationContext):
|
||||
* Promise<ProviderTaxLine[]> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* export default MyTaxService
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* ## Identifier Property
|
||||
*
|
||||
* The `TaxProvider` entity has 2 properties: `identifier` and `is_installed`. The `identifier` property in the tax provider service is used when the tax provider is added to the database.
|
||||
*
|
||||
* The value of this property is also used to reference the tax provider throughout Medusa. For example, it is used to [change the tax provider](https://docs.medusajs.com/modules/taxes/admin/manage-tax-settings#change-tax-provider-of-a-region) to a region.
|
||||
*
|
||||
* ```ts title="src/services/my-tax.ts"
|
||||
* class MyTaxService extends AbstractTaxService {
|
||||
* static identifier = "my-tax"
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export interface ITaxService {
|
||||
/**
|
||||
* Retrieves the numerical tax lines for a calculation context.
|
||||
* @param itemLines - the line item calculation lines
|
||||
* @param itemLines - the shipping calculation lines
|
||||
* @param context - other details relevant to the tax determination
|
||||
* @return numerical tax rates that should apply to the provided calculation
|
||||
* lines
|
||||
* This method is used when retrieving the tax lines for line items and shipping methods.
|
||||
* This occurs during checkout or when calculating totals for orders, swaps, or returns.
|
||||
*
|
||||
* @param {ItemTaxCalculationLine[]} itemLines - The line item lines to calculate taxes for.
|
||||
* @param {ShippingTaxCalculationLine[]} shippingLines - The shipping method lines to calculate taxes for.
|
||||
* @param {TaxCalculationContext} context - Context relevant and useful for the taxes calculation.
|
||||
* @return {Promise<ProviderTaxLine[]>} The list of calculated line item and shipping method tax lines.
|
||||
* If an item in the array has the `shipping_method_id` property, then it's a shipping method tax line. Otherwise, if it has
|
||||
* the `item_id` property, then it's a line item tax line.
|
||||
*
|
||||
* @example
|
||||
* An example of how this method is implemented in the `system` provider implemented in the Medusa backend:
|
||||
*
|
||||
* ```ts
|
||||
* // ...
|
||||
*
|
||||
* class SystemTaxService extends AbstractTaxService {
|
||||
* // ...
|
||||
*
|
||||
* async getTaxLines(
|
||||
* itemLines: ItemTaxCalculationLine[],
|
||||
* shippingLines: ShippingTaxCalculationLine[],
|
||||
* context: TaxCalculationContext
|
||||
* ): Promise<ProviderTaxLine[]> {
|
||||
* let taxLines: ProviderTaxLine[] = itemLines.flatMap((l) => {
|
||||
* return l.rates.map((r) => ({
|
||||
* rate: r.rate || 0,
|
||||
* name: r.name,
|
||||
* code: r.code,
|
||||
* item_id: l.item.id,
|
||||
* }))
|
||||
* })
|
||||
*
|
||||
* taxLines = taxLines.concat(
|
||||
* shippingLines.flatMap((l) => {
|
||||
* return l.rates.map((r) => ({
|
||||
* rate: r.rate || 0,
|
||||
* name: r.name,
|
||||
* code: r.code,
|
||||
* shipping_method_id: l.shipping_method.id,
|
||||
* }))
|
||||
* })
|
||||
* )
|
||||
*
|
||||
* return taxLines
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
getTaxLines(
|
||||
itemLines: ItemTaxCalculationLine[],
|
||||
@@ -59,17 +180,70 @@ export interface ITaxService {
|
||||
): Promise<ProviderTaxLine[]>
|
||||
}
|
||||
|
||||
/**
|
||||
* @parentIgnore activeManager_,atomicPhase_,shouldRetryTransaction_,withTransaction
|
||||
*/
|
||||
export abstract class AbstractTaxService
|
||||
extends TransactionBaseService
|
||||
implements ITaxService
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static _isTaxService = true
|
||||
protected static identifier: string
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static isTaxService(object): boolean {
|
||||
return object?.constructor?._isTaxService
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your tax provider to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.
|
||||
* Additionally, if you’re creating your tax provider as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor.
|
||||
*
|
||||
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.
|
||||
* @param {Record<string, unknown>} config - If this tax provider is created in a plugin, the plugin's options are passed in this parameter.
|
||||
*
|
||||
* @example
|
||||
* // ...
|
||||
* import { LineItemService } from "@medusajs/medusa"
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* lineItemService: LineItemService
|
||||
* }
|
||||
*
|
||||
* class MyTaxService extends AbstractTaxService {
|
||||
* protected readonly lineItemService_: LineItemService
|
||||
*
|
||||
* constructor({ lineItemService }: InjectedDependencies) {
|
||||
* super(arguments[0])
|
||||
* this.lineItemService_ = lineItemService
|
||||
*
|
||||
* // you can also initialize a client that
|
||||
* // communicates with a third-party service.
|
||||
* this.client = new Client(options)
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* export default MyTaxService
|
||||
*/
|
||||
protected constructor(
|
||||
protected readonly container: MedusaContainer,
|
||||
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
) {
|
||||
super(container, config)
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
public getIdentifier(): string {
|
||||
if (!(this.constructor as typeof AbstractTaxService).identifier) {
|
||||
throw new Error(`Missing static property "identifier".`)
|
||||
|
||||
@@ -9,8 +9,10 @@ import { ProviderTaxLine } from "../types/tax-service"
|
||||
class SystemTaxService extends AbstractTaxService {
|
||||
static identifier = "system"
|
||||
|
||||
constructor() {
|
||||
super({})
|
||||
constructor(...args: any[]) {
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
super(...arguments)
|
||||
}
|
||||
|
||||
async getTaxLines(
|
||||
|
||||
@@ -31,10 +31,25 @@ export type TaxServiceRate = {
|
||||
* The tax line properties for a given shipping method.
|
||||
*/
|
||||
export type ProviderShippingMethodTaxLine = {
|
||||
/**
|
||||
* The tax rate.
|
||||
*/
|
||||
rate: number
|
||||
/**
|
||||
* The tax rate's name.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The tax code.
|
||||
*/
|
||||
code: string | null
|
||||
/**
|
||||
* Holds any necessary additional data to be added to the shipping method tax lines.
|
||||
*/
|
||||
metadata?: Record<string, unknown>
|
||||
/**
|
||||
* The shipping method's ID.
|
||||
*/
|
||||
shipping_method_id: string
|
||||
}
|
||||
|
||||
@@ -42,10 +57,25 @@ export type ProviderShippingMethodTaxLine = {
|
||||
* The tax line properties for a given line item.
|
||||
*/
|
||||
export type ProviderLineItemTaxLine = {
|
||||
/**
|
||||
* The tax rate.
|
||||
*/
|
||||
rate: number
|
||||
/**
|
||||
* The tax rate's name.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The tax code.
|
||||
*/
|
||||
code: string | null
|
||||
/**
|
||||
* The line item's ID.
|
||||
*/
|
||||
item_id: string
|
||||
/**
|
||||
* Holds any necessary additional data to be added to the line item tax lines.
|
||||
*/
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,16 @@ export type DiscountAllocation = {
|
||||
* allocations
|
||||
*/
|
||||
export type LineAllocationsMap = {
|
||||
[K: string]: { gift_card?: GiftCardAllocation; discount?: DiscountAllocation }
|
||||
[K: string]: {
|
||||
/**
|
||||
* The gift card applied on the line item.
|
||||
*/
|
||||
gift_card?: GiftCardAllocation
|
||||
/**
|
||||
* The discount applied on the line item.
|
||||
*/
|
||||
discount?: DiscountAllocation
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,56 +5,541 @@ import {
|
||||
InternalModuleDeclaration,
|
||||
} from "../modules-sdk"
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* Options to pass to `express-session`.
|
||||
*/
|
||||
type SessionOptions = {
|
||||
/**
|
||||
* The name of the session ID cookie to set in the response (and read from in the request). The default value is `connect.sid`.
|
||||
* Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#name) for more details.
|
||||
*/
|
||||
name?: string
|
||||
/**
|
||||
* Whether the session should be saved back to the session store, even if the session was never modified during the request. The default value is `true`.
|
||||
* Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#resave) for more details.
|
||||
*/
|
||||
resave?: boolean
|
||||
/**
|
||||
* Whether the session identifier cookie should be force-set on every response. The default value is `false`.
|
||||
* Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#rolling) for more details.
|
||||
*/
|
||||
rolling?: boolean
|
||||
/**
|
||||
* Whether a session that is "uninitialized" is forced to be saved to the store. The default value is `true`.
|
||||
* Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#saveUninitialized) for more details.
|
||||
*/
|
||||
saveUninitialized?: boolean
|
||||
/**
|
||||
* The secret to sign the session ID cookie. By default, the value of `cookie_secret` is used.
|
||||
* Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#secret) for details.
|
||||
*/
|
||||
secret?: string
|
||||
/**
|
||||
* Used when calculating the `Expires` `Set-Cookie` attribute of cookies. By default, its value is `10 * 60 * 60 * 1000`.
|
||||
* Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#cookiemaxage) for details.
|
||||
*/
|
||||
ttl?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* HTTP compression configurations.
|
||||
*/
|
||||
export type HttpCompressionOptions = {
|
||||
/**
|
||||
* Whether HTTP compression is enabled. By default, it's `false`.
|
||||
*/
|
||||
enabled?: boolean
|
||||
/**
|
||||
* The level of zlib compression to apply to responses. A higher level will result in better compression but will take longer to complete.
|
||||
* A lower level will result in less compression but will be much faster. The default value is `6`.
|
||||
*/
|
||||
level?: number
|
||||
/**
|
||||
* How much memory should be allocated to the internal compression state. It's an integer in the range of 1 (minimum level) and 9 (maximum level).
|
||||
* The default value is `8`.
|
||||
*/
|
||||
memLevel?: number
|
||||
/**
|
||||
* The minimum response body size that compression is applied on. Its value can be the number of bytes or any string accepted by the
|
||||
* [bytes](https://www.npmjs.com/package/bytes) module. The default value is `1024`.
|
||||
*/
|
||||
threshold?: number | string
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* Essential configurations related to the Medusa backend, such as database and CORS configurations.
|
||||
*/
|
||||
export type ProjectConfigOptions = {
|
||||
redis_url?: string
|
||||
redis_prefix?: string
|
||||
redis_options?: RedisOptions
|
||||
|
||||
session_options?: SessionOptions
|
||||
|
||||
jwt_secret?: string
|
||||
/**
|
||||
* The Medusa backend’s API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes.
|
||||
*
|
||||
* `store_cors` is a string used to specify the accepted URLs or patterns for store API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins.
|
||||
*
|
||||
* Every origin in that list must either be:
|
||||
*
|
||||
* 1. A URL. For example, `http://localhost:8000`. The URL must not end with a backslash;
|
||||
* 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`.
|
||||
*
|
||||
* @example
|
||||
* Some example values of common use cases:
|
||||
*
|
||||
* ```bash
|
||||
* # Allow different ports locally starting with 800
|
||||
* STORE_CORS=/http:\/\/localhost:800\d+$/
|
||||
*
|
||||
* # Allow any origin ending with vercel.app. For example, storefront.vercel.app
|
||||
* STORE_CORS=/vercel\.app$/
|
||||
*
|
||||
* # Allow all HTTP requests
|
||||
* STORE_CORS=/http:\/\/.+/
|
||||
* ```
|
||||
*
|
||||
* Then, set the configuration in `medusa-config.js`:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* store_cors: process.env.STORE_CORS,
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* If you’re adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* store_cors: "/vercel\\.app$/",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
store_cors?: string
|
||||
/**
|
||||
* The Medusa backend’s API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes.
|
||||
*
|
||||
* `admin_cors` is a string used to specify the accepted URLs or patterns for admin API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins.
|
||||
*
|
||||
* Every origin in that list must either be:
|
||||
*
|
||||
* 1. A URL. For example, `http://localhost:7001`. The URL must not end with a backslash;
|
||||
* 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`.
|
||||
*
|
||||
* @example
|
||||
* Some example values of common use cases:
|
||||
*
|
||||
* ```bash
|
||||
* # Allow different ports locally starting with 700
|
||||
* ADMIN_CORS=/http:\/\/localhost:700\d+$/
|
||||
*
|
||||
* # Allow any origin ending with vercel.app. For example, admin.vercel.app
|
||||
* ADMIN_CORS=/vercel\.app$/
|
||||
*
|
||||
* # Allow all HTTP requests
|
||||
* ADMIN_CORS=/http:\/\/.+/
|
||||
* ```
|
||||
*
|
||||
* Then, set the configuration in `medusa-config.js`:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* admin_cors: process.env.ADMIN_CORS,
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* If you’re adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* admin_cors: "/http:\\/\\/localhost:700\\d+$/",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
admin_cors?: string
|
||||
/**
|
||||
* A random string used to create cookie tokens. Although this configuration option is not required, it’s highly recommended to set it for better security.
|
||||
*
|
||||
* In a development environment, if this option is not set, the default secret is `supersecret` However, in production, if this configuration is not set, an error is thrown and
|
||||
* the backend crashes.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* cookie_secret: process.env.COOKIE_SECRET ||
|
||||
* "supersecret",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
cookie_secret?: string
|
||||
|
||||
database_url?: string
|
||||
/**
|
||||
* A random string used to create authentication tokens. Although this configuration option is not required, it’s highly recommended to set it for better security.
|
||||
*
|
||||
* In a development environment, if this option is not set the default secret is `supersecret` However, in production, if this configuration is not set an error, an
|
||||
* error is thrown and the backend crashes.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* jwt_secret: process.env.JWT_SECRET ||
|
||||
* "supersecret",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
jwt_secret?: string
|
||||
|
||||
/**
|
||||
* The name of the database to connect to. If specified in `database_url`, then it’s not required to include it.
|
||||
*
|
||||
* Make sure to create the PostgreSQL database before using it. You can check how to create a database in
|
||||
* [PostgreSQL's documentation](https://www.postgresql.org/docs/current/sql-createdatabase.html).
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* database_database: process.env.DATABASE_DATABASE ||
|
||||
* "medusa-store",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
database_database?: string
|
||||
|
||||
/**
|
||||
* The connection URL of the database. The format of the connection URL for PostgreSQL is:
|
||||
*
|
||||
* ```bash
|
||||
* postgres://[user][:password]@[host][:port]/[dbname]
|
||||
* ```
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* - `[user]`: (required) your PostgreSQL username. If not specified, the system's username is used by default. The database user that you use must have create privileges. If you're using the `postgres` superuser, then it should have these privileges by default. Otherwise, make sure to grant your user create privileges. You can learn how to do that in [PostgreSQL's documentation](https://www.postgresql.org/docs/current/ddl-priv.html).
|
||||
* - `[:password]`: an optional password for the user. When provided, make sure to put `:` before the password.
|
||||
* - `[host]`: (required) your PostgreSQL host. When run locally, it should be `localhost`.
|
||||
* - `[:post]`: an optional port that the PostgreSQL server is listening on. By default, it's `5432`. When provided, make sure to put `:` before the port.
|
||||
* - `[dbname]`: (required) the name of the database.
|
||||
*
|
||||
* You can learn more about the connection URL format in [PostgreSQL’s documentation](https://www.postgresql.org/docs/current/libpq-connect.html).
|
||||
*
|
||||
* @example
|
||||
* For example, set the following database URL in your environment variables:
|
||||
*
|
||||
* ```bash
|
||||
* DATABASE_URL=postgres://postgres@localhost/medusa-store
|
||||
* ```
|
||||
*
|
||||
* Then, use the value in `medusa-config.js`:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* database_url: process.env.DATABASE_URL,
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
database_url?: string
|
||||
/**
|
||||
* The database schema to connect to. This is not required to provide if you’re using the default schema, which is `public`.
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* database_schema: process.env.DATABASE_SCHEMA ||
|
||||
* "custom",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
database_schema?: string
|
||||
|
||||
/**
|
||||
* This configuration specifies what database messages to log. Its value can be one of the following:
|
||||
*
|
||||
* - (default) A boolean value that indicates whether any messages should be logged.
|
||||
* - The string value `all` that indicates all types of messages should be logged.
|
||||
* - An array of log-level strings to indicate which type of messages to show in the logs. The strings can be `query`, `schema`, `error`, `warn`, `info`, `log`, or `migration`. Refer to [Typeorm’s documentation](https://typeorm.io/logging#logging-options) for more details on what each of these values means.
|
||||
*
|
||||
* If this configuration isn't set, its default value is `false`, meaning no database messages are logged.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* database_logging: [
|
||||
* "query", "error",
|
||||
* ],
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
database_logging: LoggerOptions
|
||||
|
||||
// @deprecated - only postgres is supported, so this config has no effect
|
||||
/**
|
||||
* @ignore
|
||||
* @deprecated
|
||||
*
|
||||
* @privateRemark
|
||||
* only postgres is supported, so this config has no effect
|
||||
*/
|
||||
database_type?: string
|
||||
|
||||
http_compression?: HttpCompressionOptions
|
||||
|
||||
/**
|
||||
* An object that includes additional configurations to pass to the database connection. You can pass any configuration. One defined configuration to pass is
|
||||
* `ssl` which enables support for TLS/SSL connections.
|
||||
*
|
||||
* This is useful for production databases, which can be supported by setting the `rejectUnauthorized` attribute of `ssl` object to `false`.
|
||||
* During development, it’s recommended not to pass this option.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* database_extra:
|
||||
* process.env.NODE_ENV !== "development"
|
||||
* ? { ssl: { rejectUnauthorized: false } }
|
||||
* : {},
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
database_extra?: Record<string, unknown> & {
|
||||
ssl: { rejectUnauthorized: false }
|
||||
/**
|
||||
* Configure support for TLS/SSL connection
|
||||
*/
|
||||
ssl: {
|
||||
/**
|
||||
* Whether to fail connection if the server certificate is verified against the list of supplied CAs and the hostname and no match is found.
|
||||
*/
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
}
|
||||
store_cors?: string
|
||||
admin_cors?: string
|
||||
|
||||
/**
|
||||
* Used to specify the URL to connect to Redis. This is only used for scheduled jobs. If you omit this configuration, scheduled jobs won't work.
|
||||
*
|
||||
* :::note
|
||||
*
|
||||
* You must first have Redis installed. You can refer to [Redis's installation guide](https://redis.io/docs/getting-started/installation/).
|
||||
*
|
||||
* :::
|
||||
*
|
||||
* The Redis connection URL has the following format:
|
||||
*
|
||||
* ```bash
|
||||
* redis[s]://[[username][:password]@][host][:port][/db-number]
|
||||
* ```
|
||||
*
|
||||
* For a local Redis installation, the connection URL should be `redis://localhost:6379` unless you’ve made any changes to the Redis configuration during installation.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* redis_url: process.env.REDIS_URL ||
|
||||
* "redis://localhost:6379",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
redis_url?: string
|
||||
|
||||
/**
|
||||
* The prefix set on all keys stored in Redis. The default value is `sess:`.
|
||||
*
|
||||
* If this configuration option is provided, it is prepended to `sess:`.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* redis_prefix: process.env.REDIS_PREFIX ||
|
||||
* "medusa:",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
redis_prefix?: string
|
||||
|
||||
/**
|
||||
* An object of options to pass ioredis. You can refer to [ioredis’s RedisOptions documentation](https://redis.github.io/ioredis/index.html#RedisOptions)
|
||||
* for the list of available options.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* redis_options: {
|
||||
* connectionName: process.env.REDIS_CONNECTION_NAME ||
|
||||
* "medusa",
|
||||
* },
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
redis_options?: RedisOptions
|
||||
|
||||
/**
|
||||
* An object of options to pass to [express-session](https://www.npmjs.com/package/express-session).
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* session_options: {
|
||||
* name: process.env.SESSION_NAME ||
|
||||
* "custom",
|
||||
* },
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
session_options?: SessionOptions
|
||||
|
||||
/**
|
||||
* Configure HTTP compression from the application layer. If you have access to the HTTP server, the recommended approach would be to enable it there.
|
||||
* However, some platforms don't offer access to the HTTP layer and in those cases, this is a good alternative.
|
||||
*
|
||||
* Its value is an object that has the following properties:
|
||||
*
|
||||
* If you enable HTTP compression and you want to disable it for specific API Routes, you can pass in the request header `"x-no-compression": true`.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* http_compression: {
|
||||
* enabled: true,
|
||||
* level: 6,
|
||||
* memLevel: 8,
|
||||
* threshold: 1024,
|
||||
* },
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
http_compression?: HttpCompressionOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The configurations for your Medusa backend are in `medusa-config.js` located in the root of your Medusa project. The configurations include database, modules, and plugin configurations, among other configurations.
|
||||
*
|
||||
* `medusa-config.js` exports an object having the following properties:
|
||||
*
|
||||
* - {@link ConfigModule.projectConfig | projectConfig}: (required): An object that holds general configurations related to the Medusa backend, such as database or CORS configurations.
|
||||
* - {@link ConfigModule.plugins | plugins}: An array of plugin configurations that defines what plugins are installed and optionally specifies each of their configurations.
|
||||
* - {@link ConfigModule.modules | modules}: An object that defines what modules are installed and optionally specifies each of their configurations.
|
||||
* - {@link ConfigModule.featureFlags | featureFlags}: An object that enables or disables features guarded by a feature flag.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig,
|
||||
* plugins,
|
||||
* modules,
|
||||
* featureFlags,
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* ## Environment Variables
|
||||
*
|
||||
* It's highly recommended to store the values of configurations in environment variables, then reference them within `medusa-config.js`.
|
||||
*
|
||||
* During development, you can set your environment variables in the `.env` file at the root of your Medusa backend project. In production,
|
||||
* setting the environment variables depends on the hosting provider.
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export type ConfigModule = {
|
||||
/**
|
||||
* This property holds essential configurations related to the Medusa backend, such as database and CORS configurations.
|
||||
*/
|
||||
projectConfig: ProjectConfigOptions
|
||||
featureFlags: Record<string, boolean | string>
|
||||
modules?: Record<
|
||||
string,
|
||||
boolean | Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
|
||||
>
|
||||
|
||||
/**
|
||||
* On your Medusa backend, you can use [Plugins](https://docs.medusajs.com/development/plugins/overview) to add custom features or integrate third-party services.
|
||||
* For example, installing a plugin to use Stripe as a payment processor.
|
||||
*
|
||||
* Aside from installing the plugin with NPM, you need to pass the plugin you installed into the `plugins` array defined in `medusa-config.js`.
|
||||
*
|
||||
* The items in the array can either be:
|
||||
*
|
||||
* - A string, which is the name of the plugin to add. You can pass a plugin as a string if it doesn’t require any configurations.
|
||||
* - An object having the following properties:
|
||||
* - `resolve`: The name of the plugin.
|
||||
* - `options`: An object that includes the plugin’s options. These options vary for each plugin, and you should refer to the plugin’s documentation for available options.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* plugins: [
|
||||
* `medusa-my-plugin-1`,
|
||||
* {
|
||||
* resolve: `medusa-my-plugin`,
|
||||
* options: {
|
||||
* apiKey: process.env.MY_API_KEY ||
|
||||
* `test`,
|
||||
* },
|
||||
* },
|
||||
* // ...
|
||||
* ],
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
plugins: (
|
||||
| {
|
||||
resolve: string
|
||||
@@ -62,4 +547,77 @@ export type ConfigModule = {
|
||||
}
|
||||
| string
|
||||
)[]
|
||||
|
||||
/**
|
||||
* In Medusa, commerce and core logic are modularized to allow developers to extend or replace certain [modules](https://docs.medusajs.com/development/modules/overview)
|
||||
* with custom implementations.
|
||||
*
|
||||
* Aside from installing the module with NPM, you must add it to the exported object in `medusa-config.js`.
|
||||
*
|
||||
* The keys of the `modules` configuration object refer to the type of module. Its value can be one of the following:
|
||||
*
|
||||
* 1. A boolean value indicating whether the module type is enabled;
|
||||
* 2. Or a string value indicating the name of the module to be used for the module type. This can be used if the module does not require any options;
|
||||
* 3. Or an object having the following properties, but typically you would mainly use the `resolve` and `options` properties only:
|
||||
* 1. `resolve`: a string indicating the name of the module.
|
||||
* 2. `options`: an object indicating the options to pass to the module. These options vary for each module, and you should refer to the module’s documentation for details on them.
|
||||
* 3. `resources`: a string indicating whether the module shares the dependency container with the Medusa core. Its value can either be `shared` or `isolated`. Refer to the [Modules documentation](https://docs.medusajs.com/development/modules/create#module-scope) for more details.
|
||||
* 4. `alias`: a string indicating a unique alias to register the module under. Other modules can’t use the same alias.
|
||||
* 5. `main`: a boolean value indicating whether this module is the main registered module. This is useful when an alias is used.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* modules: {
|
||||
* eventBus: {
|
||||
* resolve: "@medusajs/event-bus-local",
|
||||
* },
|
||||
* cacheService: {
|
||||
* resolve: "@medusajs/cache-redis",
|
||||
* options: {
|
||||
* redisUrl: process.env.CACHE_REDIS_URL,
|
||||
* ttl: 30,
|
||||
* },
|
||||
* },
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
modules?: Record<
|
||||
string,
|
||||
boolean | Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
|
||||
>
|
||||
|
||||
/**
|
||||
* Some features in the Medusa backend are guarded by a feature flag. This ensures constant shipping of new features while maintaining the engine’s stability.
|
||||
*
|
||||
* You can specify whether a feature should or shouldn’t be used in your backend by enabling its feature flag. Feature flags can be enabled through either environment
|
||||
* variables or through this configuration exported in `medusa-config.js`.
|
||||
*
|
||||
* If you want to use the environment variables method, learn more about it in the [Feature Flags documentation](https://docs.medusajs.com/development/feature-flags/toggle#method-one-using-environment-variables).
|
||||
*
|
||||
* The `featureFlags` configuration is an object. Its properties are the names of the feature flags. Each property’s value is a boolean indicating whether the feature flag is enabled.
|
||||
*
|
||||
* You can find available feature flags and their key name [here](https://github.com/medusajs/medusa/tree/master/packages/medusa/src/loaders/feature-flags).
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* featureFlags: {
|
||||
* product_categories: true,
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* :::note
|
||||
*
|
||||
* After enabling a feature flag, make sure to [run migrations](https://docs.medusajs.com/development/entities/migrations/overview#migrate-command) as it may require making changes to the database.
|
||||
*
|
||||
* :::
|
||||
*/
|
||||
featureFlags: Record<string, boolean | string>
|
||||
}
|
||||
@@ -1,7 +1,17 @@
|
||||
import { AwilixContainer } from "awilix"
|
||||
|
||||
/**
|
||||
* The Medusa Container extends [Awilix](https://github.com/jeffijoe/awilix) to
|
||||
* provide dependency injection functionalities.
|
||||
*/
|
||||
export type MedusaContainer = AwilixContainer & {
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
registerAdd: <T>(name: string, registration: T) => MedusaContainer
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
createScope: () => MedusaContainer
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,95 @@
|
||||
import stream from "stream"
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* Details of a file upload's result.
|
||||
*/
|
||||
export type FileServiceUploadResult = {
|
||||
/**
|
||||
* The file's URL.
|
||||
*/
|
||||
url: string
|
||||
/**
|
||||
* The file's key. This key is used in other operations,
|
||||
* such as deleting a file.
|
||||
*/
|
||||
key: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The relevant details to upload a file through a stream.
|
||||
*/
|
||||
export type FileServiceGetUploadStreamResult = {
|
||||
/**
|
||||
* A [PassThrough](https://nodejs.org/api/stream.html#class-streampassthrough) write stream object to be used to write the file.
|
||||
*/
|
||||
writeStream: stream.PassThrough
|
||||
/**
|
||||
* A promise that should resolved when the writing process is done to finish the upload.
|
||||
*/
|
||||
promise: Promise<any>
|
||||
/**
|
||||
* The URL of the file once it’s uploaded.
|
||||
*/
|
||||
url: string
|
||||
/**
|
||||
* The identifier of the file in the storage. For example, for a local file service, this can be the file's name.
|
||||
*/
|
||||
fileKey: string
|
||||
[x: string]: unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The details of a file to retrieve.
|
||||
*/
|
||||
export type GetUploadedFileType = {
|
||||
/**
|
||||
* The file's key.
|
||||
*/
|
||||
fileKey: string
|
||||
/**
|
||||
* Whether the file is private.
|
||||
*/
|
||||
isPrivate?: boolean
|
||||
[x: string]: unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The details of the file to remove.
|
||||
*/
|
||||
export type DeleteFileType = {
|
||||
/**
|
||||
* The file's key. When uploading a file, the
|
||||
* returned key is used here.
|
||||
*/
|
||||
fileKey: string
|
||||
[x: string]: unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The details of the file being uploaded through a stream.
|
||||
*/
|
||||
export type UploadStreamDescriptorType = {
|
||||
/**
|
||||
* The name of the file.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The extension of the file.
|
||||
*/
|
||||
ext?: string
|
||||
/**
|
||||
* Whether the file should be uploaded to a private bucket or location. By convention, the default value of this property is `true`.
|
||||
*/
|
||||
isPrivate?: boolean
|
||||
[x: string]: unknown
|
||||
}
|
||||
@@ -1,53 +1,380 @@
|
||||
import { SearchTypes } from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* ## Overview
|
||||
*
|
||||
* A search service class is in a TypeScript or JavaScript file created in the `src/services` directory. The class must extend the `AbstractSearchService` class imported
|
||||
* from the `@medusajs/utils` package.
|
||||
*
|
||||
* Based on services’ naming conventions, the file’s name should be the slug version of the search service’s name without `service`, and the class’s name should be the
|
||||
* pascal case of the search service’s name following by `Service`.
|
||||
*
|
||||
* For example, create the `MySearchService` class in the file `src/services/my-search.ts`:
|
||||
*
|
||||
* ```ts title="src/services/my-search.ts"
|
||||
* import { AbstractSearchService } from "@medusajs/utils"
|
||||
*
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* isDefault = false
|
||||
*
|
||||
* createIndex(indexName: string, options: Record<string, any>) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* getIndex(indexName: string) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* addDocuments(
|
||||
* indexName: string,
|
||||
* documents: Record<string, any>[],
|
||||
* type: string
|
||||
* ) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* replaceDocuments(
|
||||
* indexName: string,
|
||||
* documents: Record<string, any>[],
|
||||
* type: string
|
||||
* ) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* deleteDocument(
|
||||
* indexName: string,
|
||||
* document_id: string | number
|
||||
* ) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* deleteAllDocuments(indexName: string) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* search(
|
||||
* indexName: string,
|
||||
* query: string,
|
||||
* options: Record<string, any>
|
||||
* ) {
|
||||
* return {
|
||||
* message: "test",
|
||||
* }
|
||||
* }
|
||||
* updateSettings(
|
||||
* indexName: string,
|
||||
* settings: Record<string, any>
|
||||
* ) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* export default MySearchService
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* ## Notes About Class Methods
|
||||
*
|
||||
* Although there are several helper methods in this class, the main methods used by the Medusa backend are `addDocuments`, `deleteDocument`, and `search`.
|
||||
* The rest of the methods are provided in case you need them for custom use cases.
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export abstract class AbstractSearchService
|
||||
implements SearchTypes.ISearchService
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static _isSearchService = true
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static isSearchService(obj) {
|
||||
return obj?.constructor?._isSearchService
|
||||
}
|
||||
|
||||
/**
|
||||
* This property is used to pinpoint the default search service defined in the Medusa core. For custom search services, the `isDefault` property must be `false`.
|
||||
*/
|
||||
abstract readonly isDefault
|
||||
/**
|
||||
* If your search service is created in a plugin, the plugin's options will be available in this property.
|
||||
*/
|
||||
protected readonly options_: Record<string, unknown>
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
get options(): Record<string, unknown> {
|
||||
return this.options_
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your search service to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs,
|
||||
* you can initialize it in the constructor and use it in other methods in the service.
|
||||
*
|
||||
* Additionally, if you’re creating your search service as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin,
|
||||
* you can access them in the constructor. The default constructor already sets the value of the class proeprty `options_` to the passed options.
|
||||
*
|
||||
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.
|
||||
* @param {Record<string, unknown>} options - If this search service is created in a plugin, the plugin's options are passed in this parameter.
|
||||
*
|
||||
* @example
|
||||
* // ...
|
||||
* import { ProductService } from "@medusajs/medusa"
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* productService: ProductService
|
||||
* }
|
||||
*
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
* protected readonly productService_: ProductService
|
||||
*
|
||||
* constructor({ productService }: InjectedDependencies) {
|
||||
* // @ts-expect-error prefer-rest-params
|
||||
* super(...arguments)
|
||||
* this.productService_ = productService
|
||||
*
|
||||
* // you can also initialize a client that
|
||||
* // communicates with a third-party service.
|
||||
* this.client = new Client(options)
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
protected constructor(container, options) {
|
||||
this.options_ = options
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to create an index in the search engine.
|
||||
*
|
||||
* @param {string} indexName - The name of the index to create.
|
||||
* @param {unknown} options - Any options that may be relevant to your search service. This parameter doesn't have
|
||||
* any defined format as it depends on your custom implementation.
|
||||
* @returns {unknown} No required format of returned data, as it depends on your custom implementation.
|
||||
*
|
||||
* @example
|
||||
* An example implementation, assuming `client` would interact with a third-party service:
|
||||
*
|
||||
* ```ts title="src/services/my-search.ts"
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
* createIndex(indexName: string, options: Record<string, any>) {
|
||||
* return this.client_.initIndex(indexName)
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Another example of how the [MeiliSearch plugin](https://docs.medusajs.com/plugins/search/meilisearch) uses the
|
||||
* `options` parameter:
|
||||
*
|
||||
* ```ts
|
||||
* class MeiliSearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
* async createIndex(
|
||||
* indexName: string,
|
||||
* options: Record<string, unknown> = { primaryKey: "id" }
|
||||
* ) {
|
||||
* return await this.client_.createIndex(indexName, options)
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
abstract createIndex(indexName: string, options: unknown): unknown
|
||||
|
||||
/**
|
||||
* This method is used to retrieve an index’s results from the search engine.
|
||||
*
|
||||
* @param {string} indexName - The name of the index
|
||||
* @returns {unknown} No required format of returned data, as it depends on your custom implementation.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* getIndex(indexName: string) {
|
||||
* return this.client_.getIndex(indexName)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract getIndex(indexName: string): unknown
|
||||
|
||||
/**
|
||||
* This method is used to add a document to an index in the search engine.
|
||||
*
|
||||
* When the Medusa backend loads, it triggers indexing for all products available in the Medusa backend, which uses this method to add or update documents.
|
||||
* It’s also used whenever a new product is added or a product is updated.
|
||||
*
|
||||
* @param {string} indexName - The name of the index to add the documents to.
|
||||
* @param {unknown} documents - The list of documents to add. For example, an array of {@link entities!Product | products}.
|
||||
* @param {string} type - The type of documents being indexed. For example, `products`.
|
||||
* @returns {unknown} The response of saving the documents in the search engine, but there’s no required format of the response.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* async addDocuments(
|
||||
* indexName: string,
|
||||
* documents: Record<string, any>[],
|
||||
* type: string
|
||||
* ) {
|
||||
* return await this.client_
|
||||
* .addDocuments(indexName, documents)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract addDocuments(
|
||||
indexName: string,
|
||||
documents: unknown,
|
||||
type: string
|
||||
): unknown
|
||||
|
||||
/**
|
||||
* This method is used to replace existing documents in the search engine of an index with new documents.
|
||||
*
|
||||
* @param {string} indexName - The name of the index that the documents belong to.
|
||||
* @param {unknown} documents - The list of documents to index. For example, it can be an array of {@link entities!Product | products}.
|
||||
* Based on your search engine implementation, the documents should include an identification key that allows replacing the existing documents.
|
||||
* @param {string} type - The type of documents being replaced. For example, `products`.
|
||||
* @returns {unknown} The response of replacing the documents in the search engine, but there’s no required format of the response.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* async replaceDocuments(
|
||||
* indexName: string,
|
||||
* documents: Record<string, any>[],
|
||||
* type: string
|
||||
* ) {
|
||||
* await this.client_
|
||||
* .removeDocuments(indexName)
|
||||
* return await this.client_
|
||||
* .addDocuments(indexName, documents)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract replaceDocuments(
|
||||
indexName: string,
|
||||
documents: unknown,
|
||||
type: string
|
||||
): unknown
|
||||
|
||||
/**
|
||||
* This method is used to delete a document from an index.
|
||||
*
|
||||
* When a product is deleted in the Medusa backend, this method is used to delete the product from the search engine’s index.
|
||||
*
|
||||
* @param {string} indexName - The name of the index that the document belongs to.
|
||||
* @param {string | number} document_id - The ID of the item indexed. For example, if the deleted item is a product, then this is
|
||||
* the ID of the product.
|
||||
* @returns {unknown} The response of deleting the document in the search engine, but there’s no required format of the response.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* async deleteDocument(
|
||||
* indexName: string,
|
||||
* document_id: string | number
|
||||
* ) {
|
||||
* return await this.client_
|
||||
* .deleteDocument(indexName, document_id)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract deleteDocument(
|
||||
indexName: string,
|
||||
document_id: string | number
|
||||
): unknown
|
||||
|
||||
/**
|
||||
* This method is used to delete all documents from an index.
|
||||
*
|
||||
* @param {string} indexName - The index's name.
|
||||
* @returns {unknown} The response of deleting the documents of that index in the search engine, but there’s no required format of the response.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* async deleteAllDocuments(indexName: string) {
|
||||
* return await this.client_
|
||||
* .deleteDocuments(indexName)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract deleteAllDocuments(indexName: string): unknown
|
||||
|
||||
/**
|
||||
* This method is used to search through an index by a query.
|
||||
*
|
||||
* In the Medusa backend, this method is used within the [Search Products API Route](https://docs.medusajs.com/api/store#products_postproductssearch)
|
||||
* to retrieve the search results. The API route's response type is an array of items, though the item's format is not defined as it depends on the
|
||||
* data returned by this method.
|
||||
*
|
||||
* @param {string} indexName - The index's name. In the case of the Search Products API Routes, its value is `products`.
|
||||
* @param {string | null} query - The search query to retrieve results for.
|
||||
* @param {unknown} options -
|
||||
* Options that can configure the search process. The Search Products API route passes an object having the properties:
|
||||
*
|
||||
* - `paginationOptions`: An object having an `offset` and `limit` properties, which are passed in the API Route's body.
|
||||
* - `filter`: Filters that are passed in the API Route's request body. Its format is unknown, so you can pass filters based on your search service.
|
||||
* - `additionalOptions`: Any other parameters that may be passed in the request's body.
|
||||
*
|
||||
* @returns {unknown} The list of results. For example, an array of products.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* async search(
|
||||
* indexName: string,
|
||||
* query: string,
|
||||
* options: Record<string, any>
|
||||
* ) {
|
||||
* const hits = await this.client_
|
||||
* .search(indexName, query)
|
||||
* return {
|
||||
* hits,
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract search(
|
||||
indexName: string,
|
||||
query: string | null,
|
||||
options: unknown
|
||||
): unknown
|
||||
|
||||
/**
|
||||
* This method is used to update the settings of an index within the search service. This is useful if you want to update the index settings when the plugin options change.
|
||||
*
|
||||
* For example, in the Algolia plugin, a loader, which runs when the Medusa backend loads, is used to update the settings of indices based on the plugin options.
|
||||
* The loader uses this method to update the settings.
|
||||
*
|
||||
* @param {string} indexName - The index's name to update its settings.
|
||||
* @param {unknown} settings - The settings to update. Its format depends on your use case.
|
||||
* @returns {unknown} The response of updating the index in the search engine, but there’s no required format of the response.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* async updateSettings(
|
||||
* indexName: string,
|
||||
* settings: Record<string, any>
|
||||
* ) {
|
||||
* return await this.client_
|
||||
* .updateSettings(indexName, settings)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract updateSettings(indexName: string, settings: unknown): unknown
|
||||
}
|
||||
@@ -246,5 +246,5 @@ medusa telemetry
|
||||
|
||||
## See Also
|
||||
|
||||
- [Configure Medusa](../development/backend/configurations.md)
|
||||
- [Configure Medusa](../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx)
|
||||
- [Set up your development environment](../development/backend/prepare-environment.mdx)
|
||||
@@ -15,7 +15,7 @@ DigitalOcean is a reliable hosting provider that provides different ways to host
|
||||
|
||||
It is assumed that you already have a Medusa backend installed locally. If you don’t, please follow the [quickstart guide](../../create-medusa-app.mdx).
|
||||
|
||||
Furthermore, your Medusa backend should be configured to work with PostgreSQL and Redis. You can follow the [Configure your Backend documentation](../../development/backend/configurations.md) to learn how to do that.
|
||||
Furthermore, your Medusa backend should be configured to work with PostgreSQL and Redis. You can follow the [Configure your Backend documentation](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx) to learn how to do that.
|
||||
|
||||
### Needed Accounts
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Alternatively, you can use this button to deploy the Medusa backend to Heroku di
|
||||
|
||||
It is assumed that you already have a Medusa backend installed locally. If you don’t, please follow the [quickstart guide](../../development/backend/install.mdx).
|
||||
|
||||
Furthermore, your Medusa backend should be configured to work with PostgreSQL and Redis. You can follow the [Configure your Backend documentation](../../development/backend/configurations.md) to learn how to do that.
|
||||
Furthermore, your Medusa backend should be configured to work with PostgreSQL and Redis. You can follow the [Configure your Backend documentation](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx) to learn how to do that.
|
||||
|
||||
### Needed Accounts
|
||||
|
||||
@@ -87,7 +87,7 @@ This uses the lowest plan in Stackhero Redis. You can check out [the plans and p
|
||||
|
||||
### 4. Configure Environment Variables on Heroku
|
||||
|
||||
Medusa requires a set of environment variables to be configured. You can learn more about Medusa's configurations in the [Configure your Medusa backend](../../development/backend/configurations.md) document.
|
||||
Medusa requires a set of environment variables to be configured. You can learn more about Medusa's configurations in the [Configure your Medusa backend](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx) document.
|
||||
|
||||
Run the following commands in the root directory of your Medusa backend to set some environment variables:
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ Since Microtica deploys on your cloud account, here are the resources that the p
|
||||
|
||||
It is assumed that you already have a Medusa backend installed locally. If you don’t, please follow the [quickstart guide](../../development/backend/install.mdx).
|
||||
|
||||
Furthermore, your Medusa backend should be configured to work with PostgreSQL and Redis. You can follow the [Configure your Backend documentation](../../development/backend/configurations.md) to learn how to do that.
|
||||
Furthermore, your Medusa backend should be configured to work with PostgreSQL and Redis. You can follow the [Configure your Backend documentation](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx) to learn how to do that.
|
||||
|
||||
### Needed Accounts
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ If you also don't have a Medusa project, you can deploy to Railway instantly wit
|
||||
|
||||
It is assumed that you already have a Medusa backend installed locally. If you don’t, please follow the [quickstart guide](../../development/backend/install.mdx).
|
||||
|
||||
Furthermore, your Medusa backend should be configured to work with PostgreSQL and Redis. You can follow the [Configure your Backend documentation](./../../development/backend/configurations.md) to learn how to do that.
|
||||
Furthermore, your Medusa backend should be configured to work with PostgreSQL and Redis. You can follow the [Configure your Backend documentation](./../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx) to learn how to do that.
|
||||
|
||||
### Production Modules
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ Make sure the `start` script in your `package.json` runs migrations, the `build`
|
||||
|
||||
## Step 3: Set ssl Database Option
|
||||
|
||||
In production, it’s recommended to set the [database_extra option](../../development/backend/configurations.md#database_extra) in `medusa-config.js` to disable the `ssl.rejectUnauthorized` option:
|
||||
In production, it’s recommended to set the [database_extra option](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#database_extra) in `medusa-config.js` to disable the `ssl.rejectUnauthorized` option:
|
||||
|
||||
```jsx title="medusa-config.js"
|
||||
module.exports = {
|
||||
@@ -98,7 +98,7 @@ After you’ve deployed your backend, you can test it out in different ways:
|
||||
|
||||
## Set Up CORS Configuration
|
||||
|
||||
To connect your storefront and, if deployed separately, your admin dashboard to your deployed Medusa backend, make sure to set up the [admin_cors and store_cors configuration](../../development/backend/configurations.md#admin_cors-and-store_cors) in `medusa-config.js` accordingly.
|
||||
To connect your storefront and, if deployed separately, your admin dashboard to your deployed Medusa backend, make sure to set up the [admin_cors and store_cors configuration](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#admin_cors) in `medusa-config.js` accordingly.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ For example, if your API route accepts an author ID and a post ID, the path to y
|
||||
|
||||
## CORS Configuration
|
||||
|
||||
The `cors` middleware, which enables Cross-Origin Resource Sharing (CORS), is automatically applied on custom API Routes defined under the `/store` or `/admin` path prefixes based on the respective [store_cors and admin_cors configurations](../backend/configurations.md#admin_cors-and-store_cors).
|
||||
The `cors` middleware, which enables Cross-Origin Resource Sharing (CORS), is automatically applied on custom API Routes defined under the `/store` or `/admin` path prefixes based on the respective [store_cors and admin_cors configurations](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#admin_cors).
|
||||
|
||||
To add CORS configurations to custom API routes under other path prefixes, or override the CORS configurations added by default, define a [middleware](./add-middleware.mdx) on your API routes and pass it the `cors` middleware. For example:
|
||||
|
||||
|
||||
@@ -1,476 +0,0 @@
|
||||
---
|
||||
description: 'Learn about the different configurations available in a Medusa backend. This includes configurations related to the database, CORS, plugins, and more.'
|
||||
---
|
||||
|
||||
# Configure Medusa Backend
|
||||
|
||||
This document provides a reference of all accepted Medusa configurations in `medusa-config.js`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This document assumes you already followed along with the [Prepare Environment documentation](./prepare-environment.mdx) and have [installed a Medusa backend](./install.mdx#create-a-medusa-backend).
|
||||
|
||||
---
|
||||
|
||||
## Medusa Configurations File
|
||||
|
||||
The configurations for your Medusa backend are in `medusa-config.js` located in the root of your Medusa project. The configurations include database, modules, and plugin configurations, among other configurations.
|
||||
|
||||
`medusa-config.js` exports an object having the following properties:
|
||||
|
||||
| Property Name | Description | Required |
|
||||
| --- | --- | --- |
|
||||
| [projectConfig](#projectconfig) | An object that holds general configurations related to the Medusa backend, such as database or CORS configurations. | Yes |
|
||||
| [plugins](#plugins) | An array of plugin configurations that defines what plugins are installed and optionally specifies each of their configurations. | No |
|
||||
| [modules](#modules) | An object that defines what modules are installed and optionally specifies each of their configurations. | No |
|
||||
| [featureFlags](#featureflags) | An object that enables or disables features guarded by a feature flag. | No |
|
||||
|
||||
For example:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig,
|
||||
plugins,
|
||||
modules,
|
||||
featureFlags,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Many of the configurations mentioned in this guide are recommended to have their values set in environment variables and referenced within `medusa-config.js`.
|
||||
|
||||
During development, you can set your environment variables in the `.env` file at the root of your Medusa backend project. In production, setting the environment variables depends on the hosting provider.
|
||||
|
||||
---
|
||||
|
||||
## projectConfig
|
||||
|
||||
This section includes all configurations that belong to the `projectConfig` property in the configuration object exported by `medusa-config.js`. The `projectConfig` property is an object, and each of the configurations in this section is added to this object as key-value pairs.
|
||||
|
||||
### admin_cors and store_cors
|
||||
|
||||
The Medusa backend’s API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes.
|
||||
|
||||
`admin_cors` is used to specify the accepted URLs or patterns for admin API Routes, and `store_cors` is used to specify the accepted URLs or patterns for store API Routes.
|
||||
|
||||
For both the `admin_cors` and `store_cors`, the value is expected to be a string. This string can be a comma-separated list of accepted origins. Every origin in that list can be of the following types:
|
||||
|
||||
1. A URL. For example, `http://localhost:8000`. The URL shouldn’t end with a backslash.
|
||||
2. A regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`.
|
||||
|
||||
Here are some examples of common use cases:
|
||||
|
||||
```bash
|
||||
# Allow different ports locally starting with 700
|
||||
ADMIN_CORS=/http:\/\/localhost:700\d+$/
|
||||
|
||||
# Allow any origin ending with vercel.app. For example, storefront.vercel.app
|
||||
STORE_CORS=/vercel\.app$/
|
||||
|
||||
# Allow all HTTP requests
|
||||
ADMIN_CORS=/http:\/\/*/
|
||||
```
|
||||
|
||||
Typically, the value of these configurations would be set in an environment variable and referenced in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
admin_cors: process.env.ADMIN_CORS,
|
||||
store_cors: process.env.STORE_CORS,
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
If you’re adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
admin_cors: "/http:\\/\\/localhost:700\\d+$/",
|
||||
store_cors: "/vercel\\.app$/",
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### cookie_secret
|
||||
|
||||
A string that is used to create cookie tokens. Although this configuration option is not required, it’s highly recommended to set it for better security. It’s also recommended to generate a random string.
|
||||
|
||||
In a development environment, if this option is not set the default secret is `supersecret` However, in production, if this configuration is not set an error will be thrown and your backend will crash.
|
||||
|
||||
Typically, the value of this configuration would be set in an environment variable and referenced in `medusa-config.js`.
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
cookie_secret: process.env.COOKIE_SECRET,
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### http_compression
|
||||
|
||||
This configuration enables HTTP compression from the application layer. If you have access to the HTTP server, the recommended approach would be to enable it there. However, some platforms don't offer access to the HTTP layer and in those cases, this is a good alternative.
|
||||
|
||||
Its value is an object that has the following properties:
|
||||
|
||||
- `enabled`: A boolean flag that indicates whether HTTP compression is enabled. It is disabled by default.
|
||||
- `level`: A `number` value that indicates the level of zlib compression to apply to responses. A higher level will result in better compression but will take longer to complete. A lower level will result in less compression but will be much faster. The default value is 6.
|
||||
- `memLevel`: A `number` value that specifies how much memory should be allocated to the internal compression state. It's an integer in the range of 1 (minimum level) and 9 (maximum level). The default value is `8`.
|
||||
- `threshold`: A `number` or a `string` value in bytes that specifies the minimum response body size that compression is applied on. This is the number of bytes or any string accepted by the bytes module. The default value is `1024`.
|
||||
|
||||
If you enable HTTP compression and you want to disable it for specific API Routes, you can pass in the request header `"x-no-compression": true`.
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
http_compression: {
|
||||
enabled: true,
|
||||
level: 6,
|
||||
memLevel: 8,
|
||||
threshold: 1024,
|
||||
},
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### jwt_secret
|
||||
|
||||
A string that is used to create authentication tokens. Although this configuration option is not required, it’s highly recommended to set it for better security. It’s also recommended to generate a random string.
|
||||
|
||||
In a development environment, if this option is not set the default secret is `supersecret` However, in production, if this configuration is not set an error will be thrown and your backend will crash.
|
||||
|
||||
Typically, the value of this configuration would be set in an environment variable and referenced in `medusa-config.js`.
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
jwt_secret: process.env.JWT_SECRET,
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### database_database
|
||||
|
||||
The name of the database to connect to. If provided in `database_url`, then it’s not necessary to include it.
|
||||
|
||||
Make sure to create the PostgreSQL database before using it. You can check how to create a database in [PostgreSQL's documentation](https://www.postgresql.org/docs/current/sql-createdatabase.html).
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
database_database: "medusa-store",
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### database_extra
|
||||
|
||||
An object that includes additional configurations to pass to the database connection. You can pass any configuration. One defined configuration to pass is `ssl` which enables support for TLS/SSL connections.
|
||||
|
||||
This is useful for production databases, which can be supported by setting the `rejectUnauthorized` attribute of `ssl` object to `false`. During development, it’s recommended not to pass this option.
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
database_extra:
|
||||
process.env.NODE_ENV !== "development"
|
||||
? { ssl: { rejectUnauthorized: false } }
|
||||
: {},
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### database_logging
|
||||
|
||||
This configuration specifies what messages to log. Its value can be one of the following:
|
||||
|
||||
- (default) A boolean value that indicates whether any messages should be logged or not. By default, if no value is provided, the value will be `false`.
|
||||
- The string value `all` that indicates all types of messages should be logged.
|
||||
- An array of log-level strings to indicate which type of messages to show in the logs. The strings can be `query`, `schema`, `error`, `warn`, `info`, `log`, or `migration`. Refer to [Typeorm’s documentation](https://typeorm.io/logging#logging-options) for more details on what each of these values means.
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
database_logging: [
|
||||
"query", "error",
|
||||
],
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### database_schema
|
||||
|
||||
A string indicating the database schema to connect to. This is not necessary to provide if you’re using the default schema, which is `public`.
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
database_schema: "custom",
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### database_type
|
||||
|
||||
A string indicating the type of database to connect to. At the moment, only `postgres` is accepted, which is also the default value.
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
database_type: "postgres",
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### database_url
|
||||
|
||||
A string indicating the connection URL of the database. Typically, the connection URL would be set in an environment variable, and the variable would be referenced in `medusa-config.js`.
|
||||
|
||||
The format of the connection URL for PostgreSQL is:
|
||||
|
||||
```bash
|
||||
postgres://[user][:password]@[host][:port]/[dbname]
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `[user]`: (required) your PostgreSQL username. If not specified, the system's username is used by default. The database user that you use must have create privileges. If you're using the `postgres` superuser, then it should have these privileges by default. Otherwise, make sure to grant your user create privileges. You can learn how to do that in [PostgreSQL's documentation](https://www.postgresql.org/docs/current/ddl-priv.html).
|
||||
- `[:password]`: an optional password for the user. When provided, make sure to put `:` before the password.
|
||||
- `[host]`: (required) your PostgreSQL host. When run locally, it should be `localhost`.
|
||||
- `[:post]`: an optional port that the PostgreSQL server is listening on. By default, it's `5432`. When provided, make sure to put `:` before the port.
|
||||
- `[dbname]`: (required) the name of the database.
|
||||
|
||||
For example, you can set the following database URL in your environment variables:
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgres://postgres@localhost/medusa-store
|
||||
```
|
||||
|
||||
You can learn more about the connection URL format in [PostgreSQL’s documentation](https://www.postgresql.org/docs/current/libpq-connect.html).
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
database_url: process.env.DATABASE_URL,
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### redis_url
|
||||
|
||||
This configuration is used to specify the URL to connect to Redis. This is only used for scheduled jobs. If you omit this configuration, scheduled jobs will not work.
|
||||
|
||||
:::note
|
||||
|
||||
You must first have Redis installed. You can refer to [Redis's installation guide](https://redis.io/docs/getting-started/installation/).
|
||||
|
||||
:::
|
||||
|
||||
The Redis connection URL has the following format:
|
||||
|
||||
```bash
|
||||
redis[s]://[[username][:password]@][host][:port][/db-number]
|
||||
```
|
||||
|
||||
For a local Redis installation, the connection URL should be `redis://localhost:6379` unless you’ve made any changes to the Redis configuration during installation.
|
||||
|
||||
Typically, the value would be added as an environment variable and referenced in `medusa-config.js`.
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
redis_url: process.env.REDIS_URL,
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### redis_prefix
|
||||
|
||||
The prefix set on all keys stored in Redis. The default value is `sess:`. If this configuration option is provided, it is prepended to `sess:`.
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
redis_prefix: "medusa:",
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### redis_options
|
||||
|
||||
An object of options to pass ioredis. You can refer to [ioredis’s RedisOptions documentation](https://redis.github.io/ioredis/index.html#RedisOptions) for the list of available options.
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
redis_options: {
|
||||
connectionName: "medusa",
|
||||
},
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### session_options
|
||||
|
||||
An object of options to pass to `express-session`. The object can have the following properties:
|
||||
|
||||
- `name`: A string indicating the name of the session ID cookie to set in the response (and read from in the request). The default value is `connect.sid`. Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#name) for more details.
|
||||
- `resave`: A boolean value that indicates whether the session should be saved back to the session store, even if the session was never modified during the request. The default value is `true`. Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#resave) for more details.
|
||||
- `rolling`: A boolean value that indicates whether the session identifier cookie should be force-set on every response. The default value is `false`. Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#rolling) for more details.
|
||||
- `saveUninitialized`: A boolean value that indicates whether a session that is "uninitialized" is forced to be saved to the store. The default value is `true`. Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#saveUninitialized) for more details.
|
||||
- `secret`: A string that indicates the secret to sign the session ID cookie. By default, the value of [cookie_secret](#cookie_secret) will be used. Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#secret) for details.
|
||||
- `ttl`: A number is used when calculating the `Expires` `Set-Cookie` attribute of cookies. By default, it’ll be `10 * 60 * 60 * 1000`. Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#cookiemaxage) for details.
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
session_options: {
|
||||
name: "custom",
|
||||
},
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## plugins
|
||||
|
||||
On your Medusa backend, you can use Plugins to add custom features or integrate third-party services. For example, installing a plugin to use Stripe as a payment processor.
|
||||
|
||||
Aside from installing the plugin with NPM, you need to pass the plugin you installed into the `plugins` array defined in `medusa-config.js`.
|
||||
|
||||
The items in the array can either be:
|
||||
|
||||
- A string, which is the name of the plugin to add. You can pass a plugin as a string if it doesn’t require any configurations.
|
||||
- An object having the following properties:
|
||||
- `resolve`: A string indicating the name of the plugin.
|
||||
- `options`: An object that includes the plugin’s options. These options vary for each plugin, and you should refer to the plugin’s documentation for details on them.
|
||||
|
||||
For example:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
plugins: [
|
||||
`medusa-my-plugin-1`,
|
||||
{
|
||||
resolve: `medusa-my-plugin`,
|
||||
options: {
|
||||
apiKey: `test`, // or use env variables
|
||||
},
|
||||
},
|
||||
// ...
|
||||
],
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
You can refer to the [Plugins Overview documentation](../../plugins/overview.mdx) for a list of available official plugins.
|
||||
|
||||
---
|
||||
|
||||
## modules
|
||||
|
||||
In Medusa, commerce and core logic are modularized to allow developers to extend or replace certain modules with custom implementations.
|
||||
|
||||
Aside from installing the module with NPM, you need to add it to the exported object in `medusa-config.js`.
|
||||
|
||||
The keys of the `modules` configuration object refer to the type of module. Its value can be one of the following:
|
||||
|
||||
1. A boolean value that indicates whether the module type is enabled.
|
||||
2. A string value that indicates the name of the module to be used for the module type. This can be used if the module does not require any options.
|
||||
3. An object having the following properties, but typically you would mainly use the `resolve` and `options` properties only:
|
||||
1. `resolve`: a string indicating the name of the module.
|
||||
2. `options`: an object indicating the options to pass to the module. These options vary for each module, and you should refer to the module’s documentation for details on them.
|
||||
3. `resources`: a string indicating whether the module shares the dependency container with the Medusa core. Its value can either be `shared` or `isolated`. Refer to the [Modules documentation](../modules/create.mdx#module-scope) for more details.
|
||||
4. `alias`: a string indicating a unique alias to register the module under. Other modules can’t use the same alias.
|
||||
5. `main`: a boolean value indicating whether this module is the main registered module. This is useful when an alias is used.
|
||||
|
||||
For example:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
modules: {
|
||||
eventBus: {
|
||||
resolve: "@medusajs/event-bus-local",
|
||||
},
|
||||
cacheService: {
|
||||
resolve: "@medusajs/cache-redis",
|
||||
options: {
|
||||
redisUrl: process.env.CACHE_REDIS_URL,
|
||||
ttl: 30,
|
||||
},
|
||||
},
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Learn more about [Modules and how to create and use them](../modules/overview.mdx).
|
||||
|
||||
---
|
||||
|
||||
## featureFlags
|
||||
|
||||
Some features in the Medusa backend are guarded by a feature flag. This ensures constant shipping of new features while maintaining the engine’s stability.
|
||||
|
||||
You can specify whether a feature should or shouldn’t be used in your backend by enabling its feature flag. Feature flags can be enabled through environment variables or through this configuration exported in `medusa-config.js`. If you want to use the environment variables method, learn more about it in the [Feature Flags documentation](../feature-flags/toggle.md#method-one-using-environment-variables).
|
||||
|
||||
The `featureFlags` configuration is an object. Its properties are the names of the different feature flags. Each property’s value is a boolean indicating whether the feature flag is enabled.
|
||||
|
||||
You can find available feature flags and their key name [here](https://github.com/medusajs/medusa/tree/master/packages/medusa/src/loaders/feature-flags).
|
||||
|
||||
For example:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
featureFlags: {
|
||||
product_categories: true,
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Learn more about [Feature flags and how to toggle them](../feature-flags/overview.mdx).
|
||||
|
||||
:::note
|
||||
|
||||
After enabling a feature flag, make sure to [run migrations](../entities/migrations/overview.mdx#migrate-command) as it may require making changes to the database.
|
||||
|
||||
:::
|
||||
@@ -40,7 +40,7 @@ Defines an entry file, which is useful when starting the Medusa backend with a p
|
||||
|
||||
Defines the Medusa backend’s configurations, including the database configurations, plugins used, modules used, and more.
|
||||
|
||||
**Read more:** [Medusa backend configurations](./configurations.md).
|
||||
**Read more:** [Medusa backend configurations](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx).
|
||||
|
||||
### package.json
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ If you run into any errors while installing the CLI tool, check out the [trouble
|
||||
|
||||
:::warning
|
||||
|
||||
If you choose "Skip database setup" you will need to [set the database configurations](./configurations.md#database-configuration) and [run migrations](../entities/migrations/overview.mdx#migrate-command) later.
|
||||
If you choose "Skip database setup" you will need to [set the database configurations](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx) and [run migrations](../entities/migrations/overview.mdx#migrate-command) later.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ The function accepts a parameter of type `SubscriberArgs`, which has the followi
|
||||
- `data`: The data payload of the emitted event. Its type is different for each event. So, make sure to check the [events reference](./events-list.md) for the expected payload of the events your subscriber listens to. You can then pass the expected payload type as a type parameter to `SubscriberArgs`, for example, `Record<string, string>`.
|
||||
- `eventName`: A string indicating the name of the event. This is useful if your subscriber listens to more than one event and you want to differentiate between them.
|
||||
- `container`: The [dependency container](../fundamentals/dependency-injection.md) that allows you to resolve Medusa resources, such as services.
|
||||
- `pluginOptions`: When the subscriber is created within a plugin, this object holds the plugin's options defined in the [Medusa configurations](../backend/configurations.md).
|
||||
- `pluginOptions`: When the subscriber is created within a plugin, this object holds the plugin's options defined in the [Medusa configurations](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,474 +0,0 @@
|
||||
---
|
||||
description: "Learn how to create a file service in a Medusa backend or a plugin."
|
||||
---
|
||||
|
||||
# How to Create a File Service
|
||||
|
||||
In this document, you’ll learn how to create a file service in Medusa.
|
||||
|
||||
## Overview
|
||||
|
||||
In this guide, you’ll learn about the steps to implement a file service and the methods you’re required to implement in a file service. You can implement the file service within the Medusa backend codebase or in a plugin.
|
||||
|
||||
The file service you’ll be creating in this guide will be a local file service that allows you to upload files into your Medusa backend’s codebase. This is to give you a realistic example of the implementation of a file service. You’re free to implement the file service as required for your case.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### (Optional) Multer Types Package
|
||||
|
||||
If you’re using TypeScript, as instructed by this guide, you should install the Multer types package to resolve errors within your file service types.
|
||||
|
||||
To do that, run the following command in the directory of your Medusa backend or plugin:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @types/multer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create the File Service Class
|
||||
|
||||
A file service class is defined in a TypeScript or JavaScript file that’s created in the `src/services` directory. The class must extend the `AbstractFileService` class imported from the `@medusajs/medusa` package.
|
||||
|
||||
Based on services’ naming conventions, the file’s name should be the slug version of the file service’s name without `service`, and the class’s name should be the pascal case of the file service’s name following by `Service`.
|
||||
|
||||
For example, if you’re creating a local file service, the file name would be `local-file.ts`, whereas the class name would be `LocalFileService`.
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn more about services and their naming convention in [this documentation](../services/overview.mdx).
|
||||
|
||||
:::
|
||||
|
||||
For example, create the file `src/services/local-file.ts` with the following content:
|
||||
|
||||
```ts title="src/services/local-file.ts"
|
||||
import { AbstractFileService } from "@medusajs/medusa"
|
||||
import {
|
||||
DeleteFileType,
|
||||
FileServiceGetUploadStreamResult,
|
||||
FileServiceUploadResult,
|
||||
GetUploadedFileType,
|
||||
UploadStreamDescriptorType,
|
||||
} from "@medusajs/types"
|
||||
|
||||
class LocalFileService extends AbstractFileService {
|
||||
async upload(
|
||||
fileData: Express.Multer.File
|
||||
): Promise<FileServiceUploadResult> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async uploadProtected(
|
||||
fileData: Express.Multer.File
|
||||
): Promise<FileServiceUploadResult> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async delete(fileData: DeleteFileType): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async getUploadStreamDescriptor(
|
||||
fileData: UploadStreamDescriptorType
|
||||
): Promise<FileServiceGetUploadStreamResult> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async getDownloadStream(
|
||||
fileData: GetUploadedFileType
|
||||
): Promise<NodeJS.ReadableStream> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async getPresignedDownloadUrl(
|
||||
fileData: GetUploadedFileType
|
||||
): Promise<string> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
export default LocalFileService
|
||||
```
|
||||
|
||||
This creates the service `LocalFileService` which, at the moment, adds a general implementation of the methods defined in the abstract class `AbstractFileService`. Notice that the methods' signatures require importing types from the `@medusajs/types` package.
|
||||
|
||||
:::tip
|
||||
|
||||
If you get errors regarding the types not being available in the `@medusajs/types` package, make sure that the latest version of the package is installed:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/types@latest
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Using a Constructor
|
||||
|
||||
You can use a constructor to access services and resources registered in the dependency container, to define any necessary clients if you’re integrating a third-party storage service, and to access plugin options if your file service is defined in a plugin.
|
||||
|
||||
For example, the local service’s constructor could be useful to prepare the local upload directory:
|
||||
|
||||
```ts title="src/services/local-file.ts"
|
||||
// ...
|
||||
import * as fs from "fs"
|
||||
|
||||
class LocalFileService extends AbstractFileService {
|
||||
// can also be replaced by an environment variable
|
||||
// or a plugin option
|
||||
protected serverUrl = "http://localhost:9000"
|
||||
protected publicPath = "uploads"
|
||||
protected protectedPath = "protected-uploads"
|
||||
|
||||
constructor(container) {
|
||||
super(container)
|
||||
|
||||
// for public uploads
|
||||
if (!fs.existsSync(this.publicPath)) {
|
||||
fs.mkdirSync(this.publicPath)
|
||||
}
|
||||
|
||||
// for protected uploads
|
||||
if (!fs.existsSync(this.protectedPath)) {
|
||||
fs.mkdirSync(this.protectedPath)
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Another example showcasing how to access resources using dependency injection:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts title="src/services/local-file.ts"
|
||||
type InjectedDependencies = {
|
||||
logger: Logger
|
||||
}
|
||||
|
||||
class LocalFileService extends AbstractFileService {
|
||||
// ...
|
||||
protected logger_: Logger
|
||||
|
||||
constructor({ logger }: InjectedDependencies) {
|
||||
super(...arguments)
|
||||
this.logger_ = logger
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
You can access the plugin options in the second parameter passed to the constructor:
|
||||
|
||||
```ts title="src/services/local-file.ts"
|
||||
class LocalFileService extends AbstractFileService {
|
||||
protected serverUrl = "http://localhost:9000"
|
||||
// ...
|
||||
|
||||
constructor(
|
||||
container,
|
||||
pluginOptions
|
||||
) {
|
||||
super(container)
|
||||
|
||||
if (pluginOptions?.serverUrl) {
|
||||
this.serverUrl = pluginOptions?.serverUrl
|
||||
}
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implement Required Methods
|
||||
|
||||
In this section, you’ll learn about the required methods to implement in the file service.
|
||||
|
||||
### upload
|
||||
|
||||
This method is used to upload a file to the Medusa backend. You must handle the upload logic within this method.
|
||||
|
||||
This method accepts one parameter, which is a [multer file object](http://expressjs.com/en/resources/middleware/multer.html#file-information). The file is uploaded to a temporary directory by default. Among the file’s details, you can access the file’s path in the `path` property of the file object.
|
||||
|
||||
So, for example, you can create a read stream to the file’s content if necessary using the `fs` library:
|
||||
|
||||
```ts
|
||||
fs.createReadStream(file.path)
|
||||
```
|
||||
|
||||
Where `file` is the parameter passed to the `upload` method.
|
||||
|
||||
The method is expected to return an object that has the following properties:
|
||||
|
||||
- `url`: a string indicating the full accessible URL to the file.
|
||||
- `key`: a string indicating the key used to reference the uploaded file. This is useful when retrieving the file later with other methods like the [getDownloadStream](#getdownloadstream).
|
||||
|
||||
An example implementation of this method for the local file service:
|
||||
|
||||
```ts title="src/services/local-file.ts"
|
||||
class LocalFileService extends AbstractFileService {
|
||||
|
||||
async upload(
|
||||
fileData: Express.Multer.File
|
||||
): Promise<FileServiceUploadResult> {
|
||||
const filePath =
|
||||
`${this.publicPath}/${fileData.originalname}`
|
||||
fs.copyFileSync(fileData.path, filePath)
|
||||
return {
|
||||
url: `${this.serverUrl}/${filePath}`,
|
||||
key: filePath,
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
This example does not account for duplicate names to maintain simplicity in this guide. So, an uploaded file can replace another existing file that has the same name.
|
||||
|
||||
:::
|
||||
|
||||
### uploadProtected
|
||||
|
||||
This method is used to upload a file to the Medusa backend, but to a protected storage. Typically, this would be used to store files that shouldn’t be accessible by using the file’s URL or should only be accessible by authenticated users.
|
||||
|
||||
You must handle the upload logic and the file permissions or private storage configuration within this method.
|
||||
|
||||
This method accepts one parameter, which is a [multer file object](http://expressjs.com/en/resources/middleware/multer.html#file-information). The file is uploaded to a temporary directory by default. Among the file’s details, you can access the file’s path in the `path` property of the file object.
|
||||
|
||||
So, for example, you can create a read stream to the file’s content if necessary using the `fs` library:
|
||||
|
||||
```ts
|
||||
fs.createReadStream(file.path)
|
||||
```
|
||||
|
||||
Where `file` is the parameter passed to the `uploadProtected` method.
|
||||
|
||||
The method is expected to return an object that has the following properties:
|
||||
|
||||
- `url`: a string indicating the full accessible URL to the file.
|
||||
- `key`: a string indicating the key used to reference the uploaded file. This is useful when retrieving the file later with other methods like the [getDownloadStream](#getdownloadstream).
|
||||
|
||||
An example implementation of this method for the local file service:
|
||||
|
||||
```ts title="src/services/local-file.ts"
|
||||
class LocalFileService extends AbstractFileService {
|
||||
|
||||
async uploadProtected(
|
||||
fileData: Express.Multer.File
|
||||
): Promise<FileServiceUploadResult> {
|
||||
const filePath =
|
||||
`${this.protectedPath}/${fileData.originalname}`
|
||||
fs.copyFileSync(fileData.path, filePath)
|
||||
return {
|
||||
url: `${this.serverUrl}/${filePath}`,
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### delete
|
||||
|
||||
This method is used to delete a file from storage. You must handle the delete logic within this method.
|
||||
|
||||
This method accepts one parameter, which is an object that holds a `fileKey` property. The value of this property is a string that acts as an identifier of the file to delete. Typically, this would be the returned `key` property by other methods, such as the `upload` method.
|
||||
|
||||
For example, for local file service, it could be the file name.
|
||||
|
||||
You can also pass custom properties to the object passed as a first parameter.
|
||||
|
||||
This method is not expected to return anything.
|
||||
|
||||
An example implementation of this method for the local file service:
|
||||
|
||||
```ts title="src/services/local-file.ts"
|
||||
class LocalFileService extends AbstractFileService {
|
||||
|
||||
async delete(
|
||||
fileData: DeleteFileType
|
||||
): Promise<void> {
|
||||
fs.rmSync(fileData.fileKey)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### getUploadStreamDescriptor
|
||||
|
||||
This method is used to upload a file using a write stream. This is useful if the file is being written through a stream rather than uploaded to the temporary directory.
|
||||
|
||||
The method accepts one parameter, which is an object that has the following properties:
|
||||
|
||||
- `name`: a string indicating the name of the file.
|
||||
- `ext`: an optional string indicating the extension of the file.
|
||||
- `isPrivate`: an optional boolean value indicating if the file should be uploaded to a private bucket or location. By convention, the default value of this property should be `true`
|
||||
|
||||
The method is expected to return an object having the following properties:
|
||||
|
||||
- `writeStream`: a write stream object.
|
||||
- `promise`: A promise that should resolved when the writing process is done to finish the upload. This depends on the type of file service you’re creating.
|
||||
- `url`: a string indicating the URL of the file once it’s uploaded.
|
||||
- `fileKey`: a string indicating the identifier of your file in the storage. For example, for a local file service this can be the file name.
|
||||
|
||||
You can also return custom properties within the object.
|
||||
|
||||
An example implementation of this method for the local file service:
|
||||
|
||||
```ts title="src/services/local-file.ts"
|
||||
class LocalFileService extends AbstractFileService {
|
||||
|
||||
async getUploadStreamDescriptor({
|
||||
name,
|
||||
ext,
|
||||
isPrivate = true,
|
||||
}: UploadStreamDescriptorType
|
||||
): Promise<FileServiceGetUploadStreamResult> {
|
||||
const filePath = `${isPrivate ?
|
||||
this.publicPath : this.protectedPath
|
||||
}/${name}.${ext}`
|
||||
const writeStream = fs.createWriteStream(filePath)
|
||||
|
||||
return {
|
||||
writeStream,
|
||||
promise: Promise.resolve(),
|
||||
url: `${this.serverUrl}/${filePath}`,
|
||||
fileKey: filePath,
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### getDownloadStream
|
||||
|
||||
This method is used to read a file using a read stream, typically for download.
|
||||
|
||||
The method accepts as a parameter an object having the following properties:
|
||||
|
||||
- `fileKey`: a string indicating the identifier of the file. This is typically the value returned by other methods like [upload](#upload).
|
||||
- `isPrivate`: an optional boolean value indicating whether the file should be downloaded from the private bucket or storage location. By convention, this should default to `true`.
|
||||
|
||||
The method is expected to return a readable stream.
|
||||
|
||||
An example implementation of this method for the local file service:
|
||||
|
||||
```ts title="src/services/local-file.ts"
|
||||
class LocalFileService extends AbstractFileService {
|
||||
|
||||
async getDownloadStream({
|
||||
fileKey,
|
||||
isPrivate = true,
|
||||
}: GetUploadedFileType
|
||||
): Promise<NodeJS.ReadableStream> {
|
||||
const filePath = `${isPrivate ?
|
||||
this.publicPath : this.protectedPath
|
||||
}/${fileKey}`
|
||||
const readStream = fs.createReadStream(filePath)
|
||||
|
||||
return readStream
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### getPresignedDownloadUrl
|
||||
|
||||
The `getPresignedDownloadUrl` method is used to retrieve a download URL of the file. For some file services, such as S3, a presigned URL indicates a temporary URL to get access to a file.
|
||||
|
||||
If your file service doesn’t perform or offer a similar functionality, you can just return the URL to download the file.
|
||||
|
||||
The method accepts as a parameter an object having the following properties:
|
||||
|
||||
- `fileKey`: a string indicating the identifier of the file. This is typically the value returned by other methods like [upload](#upload).
|
||||
- `isPrivate`: an optional boolean value indicating whether the file should be downloaded from the private bucket or storage location. By convention, this should default to `true`.
|
||||
|
||||
The method is expected to return a string, being the URL of the file.
|
||||
|
||||
An example implementation of this method for the local file service:
|
||||
|
||||
```ts title="src/services/local-file.ts"
|
||||
class LocalFileService extends AbstractFileService {
|
||||
|
||||
async getPresignedDownloadUrl({
|
||||
fileKey,
|
||||
isPrivate = true,
|
||||
}: GetUploadedFileType
|
||||
): Promise<string> {
|
||||
const filePath = `${isPrivate ?
|
||||
this.publicPath : this.protectedPath
|
||||
}/${fileKey}`
|
||||
return `${this.serverUrl}/${filePath}`
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Run Build Command
|
||||
|
||||
In the directory of the Medusa backend, run the `build` command to transpile the files in the `src` directory into the `dist` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
:::note
|
||||
|
||||
This section explains how to test out your implementation if the file service was created in the Medusa backend codebase. You can refer to the [plugin documentation](../plugins/create.mdx#test-your-plugin) on how to test a plugin.
|
||||
|
||||
:::
|
||||
|
||||
Run your backend to test it out:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
Then, try uploading a file, for example, using the [Upload File API Route](https://docs.medusajs.com/api/admin#uploads_postuploads). The file should be uploaded based on the logic you’ve implemented.
|
||||
|
||||
### (Optional) Accessing the File
|
||||
|
||||
:::note
|
||||
|
||||
This step is only useful if you're implementing a local file service.
|
||||
|
||||
:::
|
||||
|
||||
Since the file is uploaded to a local directory `uploads`, you need to configure a static route in express that allows accessing the files within the `uploads` directory.
|
||||
|
||||
To do that, create the file `src/api/index.ts` with the following content:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import type { MiddlewaresConfig } from "@medusajs/medusa"
|
||||
import express from "express"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
matcher: "/uploads",
|
||||
middlewares: [express.static(uploadDir)],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to create a plugin](../plugins/create.mdx)
|
||||
- [How to publish a plugin](../plugins/publish.mdx)
|
||||
@@ -1,340 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a notification provider in Medusa. This guide explains the different methods available in a Notification provider.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Create a Notification Provider
|
||||
|
||||
In this document, you’ll learn how to create a Notification Provider in Medusa.
|
||||
|
||||
:::note
|
||||
|
||||
If you’re unfamiliar with the Notification architecture in Medusa, it is recommended to check out the [architecture overview](./overview.mdx) first.
|
||||
|
||||
:::
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you start creating a Notification Provider, you need to either install a [Medusa backend](../backend/install.mdx), or create it in a [plugin](../plugins/overview.mdx). The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
|
||||
|
||||
---
|
||||
|
||||
## Create a Notification Provider
|
||||
|
||||
Creating a Notification Provider is as simple as creating a TypeScript or JavaScript file in `src/services`. The name of the file is the name of the provider (for example, `sendgrid.ts`). A Notification Provider is essentially a Service that extends the `AbstractNotificationService` from `@medusajs/medusa`.
|
||||
|
||||
For example, create the file `src/services/email-sender.ts` with the following content:
|
||||
|
||||
```ts title="src/services/email-sender.ts"
|
||||
import { AbstractNotificationService } from "@medusajs/medusa"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
class EmailSenderService extends AbstractNotificationService {
|
||||
protected manager_: EntityManager
|
||||
protected transactionManager_: EntityManager
|
||||
|
||||
sendNotification(
|
||||
event: string,
|
||||
data: unknown,
|
||||
attachmentGenerator: unknown
|
||||
): Promise<{
|
||||
to: string;
|
||||
status: string;
|
||||
data: Record<string, unknown>;
|
||||
}> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
resendNotification(
|
||||
notification: unknown,
|
||||
config: unknown,
|
||||
attachmentGenerator: unknown
|
||||
): Promise<{
|
||||
to: string;
|
||||
status: string;
|
||||
data: Record<string, unknown>;
|
||||
}> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default EmailSenderService
|
||||
```
|
||||
|
||||
Where `EmailSenderService` is the name of your Notification Provider Service.
|
||||
|
||||
Notification Providers must extend `NotificationService` from `medusa-interfaces`.
|
||||
|
||||
:::info
|
||||
|
||||
Following the naming convention of Services, the name of the file should be the slug name of the Notification Provider, and the name of the class should be the camel case name of the Notification Provider suffixed with “Service”. In the example above, the name of the file should be `email-sender.js`. You can learn more in the [service documentation](../services/create-service.mdx).
|
||||
|
||||
:::
|
||||
|
||||
### identifier
|
||||
|
||||
Notification Provider Services must have a static property `identifier`.
|
||||
|
||||
The `NotificationProvider` entity has 2 properties: `identifier` and `is_installed`. The value of the `identifier` property in the Service class is used when the Notification Provider is created in the database.
|
||||
|
||||
The value of this property is also used later when you want to subscribe the Notification Provider to events in a Loader.
|
||||
|
||||
For example, in the class you created in the previous code snippet you can add the following property:
|
||||
|
||||
```ts
|
||||
class EmailSenderService extends AbstractNotificationService {
|
||||
static identifier = "email-sender"
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### constructor
|
||||
|
||||
You can use the `constructor` of your Notification Provider to have access to different Services in Medusa through dependency injection.
|
||||
|
||||
You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the Service.
|
||||
|
||||
Additionally, if you’re creating your Notification Provider as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor. The options are passed as a second parameter.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about plugins and how to create them in the [Plugins](../plugins/overview.mdx) documentation.
|
||||
|
||||
:::
|
||||
|
||||
Continuing on with the previous example, if you want to use the [`OrderService`](../../references/services/classes/services.OrderService.mdx) later when sending notifications, you can inject it into the constructor:
|
||||
|
||||
```ts
|
||||
import {
|
||||
AbstractNotificationService,
|
||||
OrderService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class EmailSenderService extends AbstractNotificationService {
|
||||
protected manager_: EntityManager
|
||||
protected transactionManager_: EntityManager
|
||||
static identifier = "email-sender"
|
||||
protected orderService: OrderService
|
||||
|
||||
constructor(container, options) {
|
||||
super(container)
|
||||
// you can access options here in case you're
|
||||
// using a plugin
|
||||
|
||||
this.orderService = container.orderService
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### sendNotification
|
||||
|
||||
When an event is triggered that your Notification Provider is registered as a handler for, the [`NotificationService`](../../references/services/classes/services.NotificationService.mdx) in Medusa’s core will execute the `sendNotification` method of your Notification Provider.
|
||||
|
||||
In this method, you can perform the necessary operation to send the Notification. Following the example above, you can send an email to the customer when they place an order.
|
||||
|
||||
This method receives three parameters:
|
||||
|
||||
1. `eventName`: This is the name of the event that was triggered. For example, `order.placed`.
|
||||
2. `eventData`: This is the data payload of the event that was triggered. For example, if the `order.placed` event is triggered, the `eventData` object contains the property `id` which is the ID of the order that was placed.
|
||||
3. `attachmentGenerator`: If you’ve previously attached a generator to the `NotificationService` using the [`registerAttachmentGenerator`](../../references/services/classes/services.NotificationService.mdx#registerattachmentgenerator) method, you have access to it here. You can use the `attachmentGenerator` to generate on-demand invoices or other documents. The default value of this parameter is null.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about what events are triggered in Medusa and their data payload in the [Events List](../events/events-list.md) documentation.
|
||||
|
||||
:::
|
||||
|
||||
This method must return an object containing two properties:
|
||||
|
||||
1. `to`: a string that represents the receiver of the Notification. For example, if you sent an email to the customer then `to` is the email address of the customer. In other cases, it might be a phone number or a username.
|
||||
2. `data`: an object that contains the data used to send the Notification. For example, if you sent an order confirmation email to the customer, then the `data` object might include the order items or the subject of the email. This `data` is necessary if the notification is resent later as you can use the same data.
|
||||
|
||||
Continuing with the previous example you can have the following implementation of the `sendNotification` method:
|
||||
|
||||
```ts
|
||||
class EmailSenderService extends AbstractNotificationService {
|
||||
// ...
|
||||
async sendNotification(
|
||||
event: string,
|
||||
data: any,
|
||||
attachmentGenerator: unknown
|
||||
): Promise<{
|
||||
to: string;
|
||||
status: string;
|
||||
data: Record<string, unknown>;
|
||||
}> {
|
||||
if (event === "order.placed") {
|
||||
// retrieve order
|
||||
const order = await this.orderService.retrieve(data.id)
|
||||
// TODO send email
|
||||
|
||||
console.log("Notification sent")
|
||||
return {
|
||||
to: order.email,
|
||||
status: "done",
|
||||
data: {
|
||||
// any data necessary to send the email
|
||||
// for example:
|
||||
subject: "You placed a new order!",
|
||||
items: order.items,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this code snippet, you check first if the event is `order.placed`. This can be helpful if you’re handling multiple events using the same Notification Provider.
|
||||
|
||||
You then retrieve the order using the ID and send the email. Here, the logic related to sending the email is not implemented as it is generally specific to your Notification Provider.
|
||||
|
||||
Finally, you return an object with the `to` property set to the customer email and the `data` property is an object that contains data necessary to send the email such as a `subject` or `items`.
|
||||
|
||||
:::note
|
||||
|
||||
The `to` and `data` properties are used in the `NotificationService` in Medusa’s core to create a new `Notification` record in the database. You can learn more about the `Notification` entity in the [Architecture Overview](./overview.mdx#notification-entity-overview) documentation.
|
||||
|
||||
:::
|
||||
|
||||
### resendNotification
|
||||
|
||||
Using the [Resend Notification API Route](https://docs.medusajs.com/api/admin#notifications_postnotificationsnotificationresend), an admin user can resend a Notification to the customer. The [`NotificationService`](../../references/services/classes/services.NotificationService.mdx) in Medusa’s core then executes the `resendNotification` method in your Notification Provider.
|
||||
|
||||
This method receives three parameters:
|
||||
|
||||
1. `notification`: This is the original Notification record that was created after you sent the notification with `sendNotification`. You can get an overview of the entity and its attributes in the [architecture overview](./overview.mdx#notification-entity-overview), but most notably it includes the `to` and `data` attributes which are populated originally using the `to` and `data` properties of the object you return in `sendNotification`.
|
||||
2. `config`: In the Resend Notification API Route you may specify an alternative receiver of the notification using the `to` request body parameter. For example, you may want to resend the order confirmation email to a different email. If that’s the case, you have access to it in the `config` parameter object. Otherwise, `config` will be an empty object.
|
||||
3. `attachmentGenerator`: If you’ve previously attached a generator to the Notification Service using the [`registerAttachmentGenerator`](../../references/services/classes/services.NotificationService.mdx#registerattachmentgenerator) method, you have access to it here. You can use the `attachmentGenerator` to generate on-demand invoices or other documents. The default value of this parameter is null.
|
||||
|
||||
Similarly to the `sendNotification` method, this method must return an object containing two properties:
|
||||
|
||||
1. `to`: a string that represents the receiver of the Notification. You can either return the same `to` available in the `notification` parameter or the updated one in the `config` parameter.
|
||||
2. `data`: an object that contains the data used to send the Notification. You can either return the same `data` in the `notification` parameter or make any necessary updates to it.
|
||||
|
||||
Continuing with the previous example you can have the following implementation of the `resendNotification` method:
|
||||
|
||||
```ts
|
||||
class EmailSenderService extends AbstractNotificationService {
|
||||
// ...
|
||||
async resendNotification(
|
||||
notification: any,
|
||||
config: any,
|
||||
attachmentGenerator: unknown
|
||||
): Promise<{
|
||||
to: string;
|
||||
status: string;
|
||||
data: Record<string, unknown>;
|
||||
}> {
|
||||
// check if the receiver should be changed
|
||||
const to: string = config.to ? config.to : notification.to
|
||||
|
||||
// TODO resend the notification using the same data
|
||||
// that is saved under notification.data
|
||||
|
||||
console.log("Notification resent")
|
||||
return {
|
||||
to,
|
||||
status: "done",
|
||||
data: notification.data, // make changes to the data
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the above snippet, you check if the `to` should be changed by checking if the `config` parameter has a `to` property. Otherwise, you keep the same `to` address stored in the `notification` parameter.
|
||||
|
||||
You then resend the email. Here, the logic related to sending the email is not implemented as it is generally specific to your Notification Provider.
|
||||
|
||||
Finally, you return an object with the `to` property set to the email (either new or old) and the `data` property is the same data used before to send the original notification. you can alternatively make any changes to the data.
|
||||
|
||||
:::note
|
||||
|
||||
The `to` and `data` properties are used in the `NotificationService` in Medusa’s core to create a new `Notification` record in the database. No changes are made to the original `Notification` record created after the `sendNotification` method. This new record is associated with the original `Notification` record using the `parent_id` attribute in the entity. You can learn more about the `Notification` entity in the [Architecture Overview](./overview.mdx#notification-entity-overview) documentation.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Subscribe with Loaders
|
||||
|
||||
After creating your Notification Provider Service, you must create a [Loader](../loaders/overview.mdx) that registers this Service as a notification handler of events.
|
||||
|
||||
Following the previous example, to make sure the `email-sender` Notification Provider handles the `order.placed` event, create the file `src/loaders/notification.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/notification.ts"
|
||||
import {
|
||||
MedusaContainer,
|
||||
NotificationService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async (
|
||||
container: MedusaContainer
|
||||
): Promise<void> => {
|
||||
const notificationService = container.resolve<
|
||||
NotificationService
|
||||
>("notificationService")
|
||||
|
||||
notificationService.subscribe(
|
||||
"order.placed",
|
||||
"email-sender"
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
This loader accesses the `notificationService` through the [MedusaContainer](../fundamentals/dependency-injection.md). The `notificationService` has a `subscribe` method that accepts 2 parameters. The first one is the name of the event to subscribe to, and the second is the identifier of the Notification Provider that's subscribing to that event.
|
||||
|
||||
---
|
||||
|
||||
## Test Sending Notifications with your Notification Provider
|
||||
|
||||
Make sure you have an event bus module configured in your Medusa backend. You can learn more on how to do that in the [Configurations guide](../backend/configurations.md).
|
||||
|
||||
Then, run the build command in the root directory of your Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
Next, start your Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
Try now to place an order either using the [REST APIs](https://docs.medusajs.com/api/store) or using the storefront.
|
||||
|
||||
:::tip
|
||||
|
||||
If you don’t have a storefront installed you can get started with the [Next.js Starter Template](../../starters/nextjs-medusa-starter.mdx) in minutes.
|
||||
|
||||
:::
|
||||
|
||||
After placing an order, you can see in your console the message “Notification Sent”. If you added your own notification sending logic, you should receive an email or alternatively the type of notification you’ve set up.
|
||||
|
||||
---
|
||||
|
||||
## Test Resending Notifications with your Notification Provider
|
||||
|
||||
To test resending a notification, first, retrieve the ID of the notification you just sent using the [List Notifications API Route](https://docs.medusajs.com/api/admin#notifications_getnotifications). You can pass as a body parameter the `to` or `event_name` parameters to filter out the notification you just sent.
|
||||
|
||||
:::tip
|
||||
|
||||
You must be authenticated as an admin user before sending this request. You can use the [Authenticate a User API Route](https://docs.medusajs.com/api/admin#auth_postauth) to get authenticated.
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
Then, send a request to the [Resend Notification API Route](https://docs.medusajs.com/api/admin#notifications_postnotificationsnotificationresend) using the ID retrieved from the previous request. You can pass the `to` parameter in the body to change the receiver of the notification. You should see the message “Notification Resent” in your console and if you implemented your own logic for resending the notification it will be resent.
|
||||
|
||||

|
||||
|
||||
This request returns the same notification object as the List Notifications API Route, but it now has a new object in the `resends` array. This is the resent notification. If you supplied a `to` parameter in the request body, you should see its value in the `to` property of the resent notification object.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Create a Plugin](../plugins/create.mdx)
|
||||
@@ -23,7 +23,7 @@ This guide explains how to create a scheduled job on your Medusa backend. The sc
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To use scheduled jobs, you must configure Redis in your Medusa backend. Learn more in the [Configurations documentation](../backend/configurations.md#redis_url).
|
||||
To use scheduled jobs, you must configure Redis in your Medusa backend. Learn more in the [Configurations documentation](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#redis_url).
|
||||
|
||||
---
|
||||
|
||||
@@ -91,7 +91,7 @@ The function accepts a parameter of type `ScheduledJobArgs`, which has the follo
|
||||
|
||||
- `container`: The [dependency container](../fundamentals/dependency-injection.md) that allows you to resolve Medusa resources, such as services.
|
||||
- `data`: The data passed in the [configuration object](#scheduled-job-configuration).
|
||||
- `pluginOptions`: When the scheduled job is created within a plugin, this object holds the plugin's options defined in the [Medusa configurations](../backend/configurations.md).
|
||||
- `pluginOptions`: When the scheduled job is created within a plugin, this object holds the plugin's options defined in the [Medusa configurations](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Scheduled jobs (also known as cron jobs) are tasks performed at a specific time
|
||||
|
||||
For example, you can synchronize your inventory with an Enterprise Resource Planning (ERP) system once a day using a scheduled job.
|
||||
|
||||
In the Medusa Backend, the scheduled jobs queue is implemented using [Redis](https://redis.io/) and [Bull](https://www.npmjs.com/package/bull). So, for scheduled jobs to work, you must have [Redis installed and enabled](../../development/backend/configurations.md#redis).
|
||||
In the Medusa Backend, the scheduled jobs queue is implemented using [Redis](https://redis.io/) and [Bull](https://www.npmjs.com/package/bull). So, for scheduled jobs to work, you must have [Redis installed and enabled](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#redis_url).
|
||||
|
||||
:::tip
|
||||
|
||||
|
||||
@@ -1,416 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a search service in Medusa. You can create the search service directly in your Medusa backend codebase, in a plugin, or in a module.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Create a Search Service
|
||||
|
||||
In this document, you’ll learn how to create a search service in Medusa. You can create the search service directly in your Medusa backend codebase, in a plugin, or in a module.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Utils Package
|
||||
|
||||
A search service must extend the `AbstractSearchService` class imported from the `@medusajs/utils` package. If you don’t already have the package installed, run the following command to install it within your project:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/utils
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Search Service Class
|
||||
|
||||
A search service class should be defined in a TypeScript or JavaScript file created in the `src/services` directory. The class must extend the `AbstractSearchService` class imported from the `@medusajs/utils` package.
|
||||
|
||||
Based on services’ naming conventions, the file’s name should be the slug version of the search service’s name without `service`, and the class’s name should be the pascal case of the search service’s name following by `Service`.
|
||||
|
||||
For example, if you’re creating an algolia search service, the file name would be `algolia.ts`, whereas the class name would be `AlgoliaService`.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about services and their naming convention in [this documentation](../services/overview.mdx).
|
||||
|
||||
:::
|
||||
|
||||
For example, create the file `src/services/my-search.ts` with the following content:
|
||||
|
||||
```ts title="src/services/my-search.ts"
|
||||
import { AbstractSearchService } from "@medusajs/utils"
|
||||
|
||||
class MySearchService extends AbstractSearchService {
|
||||
isDefault = false
|
||||
|
||||
createIndex(indexName: string, options: Record<string, any>) {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
getIndex(indexName: string) {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
addDocuments(
|
||||
indexName: string,
|
||||
documents: Record<string, any>[],
|
||||
type: string
|
||||
) {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
replaceDocuments(
|
||||
indexName: string,
|
||||
documents: Record<string, any>[],
|
||||
type: string
|
||||
) {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
deleteDocument(
|
||||
indexName: string,
|
||||
document_id: string | number
|
||||
) {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
deleteAllDocuments(indexName: string) {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
search(
|
||||
indexName: string,
|
||||
query: string,
|
||||
options: Record<string, any>
|
||||
) {
|
||||
return {
|
||||
message: "test",
|
||||
}
|
||||
}
|
||||
updateSettings(
|
||||
indexName: string,
|
||||
settings: Record<string, any>
|
||||
) {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default MySearchService
|
||||
```
|
||||
|
||||
This creates the service `MySearchService` which, at the moment, adds a general implementation of the methods defined in the abstract class `AbstractSearchService`.
|
||||
|
||||
### Using a Constructor
|
||||
|
||||
You can use a constructor to access services and resources registered in the dependency container, to define any necessary clients if you’re integrating a third-party storage service, and to access plugin options if your search service is defined in a plugin.
|
||||
|
||||
For example:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts title="src/services/my-search.ts"
|
||||
// ...
|
||||
import { ProductService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productService: ProductService
|
||||
}
|
||||
|
||||
class MySearchService extends AbstractSearchService {
|
||||
// ...
|
||||
protected readonly productService_: ProductService
|
||||
|
||||
constructor({ productService }: InjectedDependencies) {
|
||||
// @ts-expect-error prefer-rest-params
|
||||
super(...arguments)
|
||||
this.productService_ = productService
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
You can access the plugin options in the second parameter passed to the constructor:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts title="src/services/my-search.ts"
|
||||
// ...
|
||||
|
||||
class MySearchService extends AbstractSearchService {
|
||||
// ...
|
||||
protected readonly pluginOptions: Record<string, any>
|
||||
|
||||
constructor({
|
||||
productService,
|
||||
}: InjectedDependencies, pluginOptions) {
|
||||
// @ts-expect-error prefer-rest-params
|
||||
super(...arguments)
|
||||
|
||||
// ...
|
||||
this.pluginOptions = pluginOptions
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### isDefault Property
|
||||
|
||||
The `isDefault` property is mainly used to pinpoint the default search service defined in the Medusa core. For custom search services, the `isDefault` property should be `false`.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implement Required Methods
|
||||
|
||||
In this section, you’ll learn about the required methods to implement in the search service.
|
||||
|
||||
:::note
|
||||
|
||||
The Medusa backend mainly uses the `addDocuments`, `deleteDocument`, and `search` methods in different scenarios that are explained for each of the methods. Other methods can be helpful based on the search engine you’re integrating.
|
||||
|
||||
:::
|
||||
|
||||
### createIndex
|
||||
|
||||
This method is used to create an index in the search engine.
|
||||
|
||||
The method accepts two parameters:
|
||||
|
||||
1. `indexName`: this is the first parameter, and it’s a string indicating the name of the index to create.
|
||||
2. `options`: this is the second parameter is typically an object, and it can be used to pass any necessary options to the method. This parameter does not have any defined format.
|
||||
|
||||
The method does not require any specific data type to be returned.
|
||||
|
||||
An example implementation, assuming `client_` would interact with a third-party service:
|
||||
|
||||
```ts title="src/services/my-search.ts"
|
||||
class MySearchService extends AbstractSearchService {
|
||||
// ...
|
||||
|
||||
createIndex(indexName: string, options: Record<string, any>) {
|
||||
return this.client_.initIndex(indexName)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### getIndex
|
||||
|
||||
This method is used to retrieve an index’s results from the search engine.
|
||||
|
||||
The method accepts one parameter, which is a string indicating the name of the index. The method does not require any specific data type to be returned.
|
||||
|
||||
An example implementation, assuming `client_` would interact with a third-party service:
|
||||
|
||||
```ts title="src/services/my-search.ts"
|
||||
class MySearchService extends AbstractSearchService {
|
||||
// ...
|
||||
|
||||
getIndex(indexName: string) {
|
||||
return this.client_.getIndex(indexName)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### addDocuments
|
||||
|
||||
This method is used to add a document to an index in the search engine.
|
||||
|
||||
This method is used when the Medusa backend loads, indexing all products available in the Medusa backend. It’s also used whenever a new product is added or a product is updated.
|
||||
|
||||
The method accepts the following parameters:
|
||||
|
||||
- `indexName`: the first parameter is a string indicating the name of the index to add the document to.
|
||||
- `documents`: the second parameter is typically an array of objects to index. For example, it can be an array of products to index.
|
||||
- `type`: the third parameter is a string indicating the type of object being indexed. For example, when indexing products, the type would be `products`.
|
||||
|
||||
The method should return the response of saving the documents in the search engine, but there’s no required format of the response.
|
||||
|
||||
An example implementation, assuming `client_` would interact with a third-party service:
|
||||
|
||||
```ts title="src/services/my-search.ts"
|
||||
class MySearchService extends AbstractSearchService {
|
||||
// ...
|
||||
|
||||
async addDocuments(
|
||||
indexName: string,
|
||||
documents: Record<string, any>[],
|
||||
type: string
|
||||
) {
|
||||
return await this.client_
|
||||
.addDocuments(indexName, documents)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### replaceDocuments
|
||||
|
||||
This method is used to replace the existing documents in the search engine of an index with new documents.
|
||||
|
||||
The method accepts the following parameters:
|
||||
|
||||
- `indexName`: the first parameter is a string indicating the name of the index to replace the documents in.
|
||||
- `documents`: the second parameter is typically an array of objects to index. For example, it can be an array of products to index. This would be the new documents to add to the index.
|
||||
- `type`: the third parameter is a string indicating the type of object being indexed. For example, when indexing products, the type would be `products`.
|
||||
|
||||
The method should return the response of saving the documents in the search engine, but there’s no required format of the response.
|
||||
|
||||
An example implementation, assuming `client_` would interact with a third-party service:
|
||||
|
||||
```ts title="src/services/my-search.ts"
|
||||
class MySearchService extends AbstractSearchService {
|
||||
// ...
|
||||
|
||||
async replaceDocuments(
|
||||
indexName: string,
|
||||
documents: Record<string, any>[],
|
||||
type: string
|
||||
) {
|
||||
await this.client_
|
||||
.removeDocuments(indexName)
|
||||
return await this.client_
|
||||
.addDocuments(indexName, documents)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### deleteDocument
|
||||
|
||||
This method is used to delete a document from an index.
|
||||
|
||||
When a product is deleted in the Medusa backend, this method is used to delete the product from the search engine’s index.
|
||||
|
||||
The method accepts the following parameters:
|
||||
|
||||
- `indexName`: the first parameter is a string indicating the name of the index this document belongs in.
|
||||
- `document_id`: the second parameter is a string or a number indicating the ID of the document to delete. When a product is deleted, the product’s ID is passed as the value of this parameter.
|
||||
|
||||
The method should return the response of deleting the document in the search engine, but there’s no required format of the response.
|
||||
|
||||
An example implementation, assuming `client_` would interact with a third-party service:
|
||||
|
||||
```ts title="src/services/my-search.ts"
|
||||
class MySearchService extends AbstractSearchService {
|
||||
// ...
|
||||
|
||||
async deleteDocument(
|
||||
indexName: string,
|
||||
document_id: string | number
|
||||
) {
|
||||
return await this.client_
|
||||
.deleteDocument(indexName, document_id)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### deleteAllDocuments
|
||||
|
||||
This method is used to delete all documents from an index.
|
||||
|
||||
The method accepts one parameter, which is a string indicating the name of the index to delete its documents.
|
||||
|
||||
The method should return the response of deleting the documents of that index in the search engine, but there’s no required format of the response.
|
||||
|
||||
An example implementation, assuming `client_` would interact with a third-party service:
|
||||
|
||||
```ts title="src/services/my-search.ts"
|
||||
class MySearchService extends AbstractSearchService {
|
||||
// ...
|
||||
|
||||
async deleteAllDocuments(indexName: string) {
|
||||
return await this.client_
|
||||
.deleteDocuments(indexName)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### search
|
||||
|
||||
This method is used to search through an index by a query.
|
||||
|
||||
In the Medusa backend, this method is used within the [Search Products API Route](https://docs.medusajs.com/api/store#products_postproductssearch) to retrieve the search results.
|
||||
|
||||
This method accepts the following parameters:
|
||||
|
||||
1. `indexName`: the first parameter is a string indicating the index to search through. When using the Search Products API Route, the index is the default index defined in the `IndexName` static property of the `ProductService`, which is `products`.
|
||||
2. `query`: the second parameter is a string indicating the query to use to search through the documents.
|
||||
3. `options`: the third parameter is typically an object that can be used to pass any necessary options to the search engine.
|
||||
|
||||
Although there’s no required data format or type to be returned to the method, it’s recommended to return an object having a property `hits` with its value being an array of results. Each result can be an object of any format. This is recommended as this is the convention followed within Medusa’s official search plugins.
|
||||
|
||||
An example implementation, assuming `client_` would interact with a third-party service:
|
||||
|
||||
```ts title="src/services/my-search.ts"
|
||||
class MySearchService extends AbstractSearchService {
|
||||
// ...
|
||||
|
||||
async search(
|
||||
indexName: string,
|
||||
query: string,
|
||||
options: Record<string, any>
|
||||
) {
|
||||
const hits = await this.client_
|
||||
.search(indexName, query)
|
||||
return {
|
||||
hits,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### updateSettings
|
||||
|
||||
This method is used to update the settings of an index within the search engine. This can be useful if you want to update the index settings when the plugin options change.
|
||||
|
||||
For example, in the Algolia plugin, a loader, which runs when the Medusa backend loads, is used to update the settings of indices based on the plugin options. The loader uses this method to update the settings.
|
||||
|
||||
The method accepts the following parameters:
|
||||
|
||||
1. `indexName`: the first parameter is a string indicating the index that should be updated.
|
||||
2. `settings`: the second parameter is typically an object that holds the settings of the index. There’s no defined format for this parameter.
|
||||
|
||||
The method should return the response of updating the index in the search engine, but there’s no required format of the response.
|
||||
|
||||
An example implementation, assuming `client_` would interact with a third-party service:
|
||||
|
||||
```ts title="src/services/my-search.ts"
|
||||
class MySearchService extends AbstractSearchService {
|
||||
// ...
|
||||
|
||||
async updateSettings(
|
||||
indexName: string,
|
||||
settings: Record<string, any>
|
||||
) {
|
||||
return await this.client_
|
||||
.updateSettings(indexName, settings)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Run Build Command
|
||||
|
||||
In the directory of the Medusa backend, run the build command to transpile the files in the `src` directory into the `dist` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
:::note
|
||||
|
||||
This section explains how to test out your implementation if the search service was created in the Medusa backend codebase. You can refer to [the plugin documentation](../plugins/create.mdx#test-your-plugin) on how to test a plugin.
|
||||
|
||||
:::
|
||||
|
||||
Run your backend to test it out:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
You can then send a request to the [Search Products API Route](https://docs.medusajs.com/api/store#products_postproductssearch) to see if your search service returns any results.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to create a plugin](../plugins/create.mdx)
|
||||
- [How to publish a plugin](../plugins/publish.mdx)
|
||||
@@ -36,8 +36,8 @@ This guide explains how to override a strategy in general. It doesn’t explain
|
||||
There are other resources that provide steps specific to a strategy type:
|
||||
|
||||
- [How to override the Cart Completion Strategy](../../modules/carts-and-checkout/backend/cart-completion-strategy.md)
|
||||
- [How to override the Tax Calculation Strategy](../../modules/taxes/backend/tax-calculation-strategy.md)
|
||||
- [How to override the Price Selection Strategy](../../modules/price-lists/backend/override-price-selection-strategy.mdx)
|
||||
- [How to override the Tax Calculation Strategy](../../references/tax_calculation/classes/tax_calculation.AbstractTaxCalculationStrategy.mdx)
|
||||
- [How to override the Price Selection Strategy](/modules/price-lists/price-selection-strategy)
|
||||
- [How to override a Batch Job Strategy](../batch-jobs/customize-import.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -29,7 +29,7 @@ Add the following environment variable to your application:
|
||||
POSTGRES_URL=<DATABASE_URL>
|
||||
```
|
||||
|
||||
Where `<DATABASE_URL>` is your database connection URL of the format `postgres://[user][:password]@[host][:port]/[dbname]`. You can learn more about the connection URL format in [this guide](../../development/backend/configurations.md#database_url).
|
||||
Where `<DATABASE_URL>` is your database connection URL of the format `postgres://[user][:password]@[host][:port]/[dbname]`. You can learn more about the connection URL format in [this guide](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#database_url).
|
||||
|
||||
You can also set the following optional environment variables:
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Add the following environment variable to your application:
|
||||
POSTGRES_URL=<DATABASE_URL>
|
||||
```
|
||||
|
||||
Where `<DATABASE_URL>` is your database connection URL of the format `postgres://[user][:password]@[host][:port]/[dbname]`. You can learn more about the connection URL format in [this guide](https://www.notion.so/development/backend/configurations.md#database_url).
|
||||
Where `<DATABASE_URL>` is your database connection URL of the format `postgres://[user][:password]@[host][:port]/[dbname]`. You can learn more about the connection URL format in [this guide](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#database_url).
|
||||
|
||||
You can also set the following optional environment variables:
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ The [Local Event Bus module](../../../development/events/modules/local.md) works
|
||||
|
||||
As mentioned in the overview, this guide illustrates how to send the email using SendGrid. If you intend to follow along, you must have the [SendGrid plugin](../../../plugins/notifications/sendgrid.mdx) installed and configured.
|
||||
|
||||
You can also find other available Notification provider plugins in the [Plugins directory](https://medusajs.com/plugins/), or [create your own](../../../development/notification/create-notification-provider.md).
|
||||
You can also find other available Notification provider plugins in the [Plugins directory](https://medusajs.com/plugins/), or [create your own](../../../references/notification/classes/notification.AbstractNotificationService.mdx).
|
||||
|
||||
---
|
||||
|
||||
@@ -141,6 +141,6 @@ Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notifica
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../references/notification/classes/notification.AbstractNotificationService.mdx).
|
||||
|
||||
:::
|
||||
@@ -31,7 +31,7 @@ It's assumed that you already have a Medusa backend installed and set up. If not
|
||||
|
||||
### Notification Provider
|
||||
|
||||
To send an email or another type of notification method, you must have a notification provider installed or configured. You can either install an [existing plugin](../../../plugins/notifications/index.mdx) or [create your own](../../../development/notification/create-notification-provider.md).
|
||||
To send an email or another type of notification method, you must have a notification provider installed or configured. You can either install an [existing plugin](../../../plugins/notifications/index.mdx) or [create your own](../../../references/notification/classes/notification.AbstractNotificationService.mdx).
|
||||
|
||||
---
|
||||
|
||||
@@ -149,6 +149,6 @@ Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notifica
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../references/notification/classes/notification.AbstractNotificationService.mdx).
|
||||
|
||||
:::
|
||||
@@ -27,7 +27,7 @@ It's assumed that you already have a Medusa backend installed and set up. If not
|
||||
|
||||
### Notification Provider
|
||||
|
||||
To send an email or another type of notification method, you must have a notification provider installed or configured. You can either install an existing plugin or [create your own](../../../development/notification/create-notification-provider.md).
|
||||
To send an email or another type of notification method, you must have a notification provider installed or configured. You can either install an existing plugin or [create your own](../../../references/notification/classes/notification.AbstractNotificationService.mdx).
|
||||
|
||||
This document has an example using the [SendGrid](../../../plugins/notifications/sendgrid.mdx) plugin.
|
||||
|
||||
@@ -153,7 +153,7 @@ Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notifica
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../references/notification/classes/notification.AbstractNotificationService.mdx).
|
||||
|
||||
:::
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ The [Local Event Bus module](../../../development/events/modules/local.md) works
|
||||
|
||||
As mentioned in the overview, this guide illustrates how to send the email using SendGrid. If you intend to follow along, you must have the [SendGrid plugin](../../../plugins/notifications/sendgrid.mdx) installed and configured.
|
||||
|
||||
You can also find other available Notification provider plugins in the [Plugins directory](https://medusajs.com/plugins/), or [create your own](../../../development/notification/create-notification-provider.md).
|
||||
You can also find other available Notification provider plugins in the [Plugins directory](https://medusajs.com/plugins/), or [create your own](../../../references/notification/classes/notification.AbstractNotificationService.mdx).
|
||||
|
||||
---
|
||||
|
||||
@@ -153,6 +153,6 @@ Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notifica
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../references/notification/classes/notification.AbstractNotificationService.mdx).
|
||||
|
||||
:::
|
||||
-295
@@ -1,295 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to override the price selection strategy. The price selection strategy is used to determine the best price based on a specific context.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import ParameterTypes from "@site/src/components/ParameterTypes"
|
||||
|
||||
# How to Override Price Selection Strategy
|
||||
|
||||
In this document, you’ll learn how to override Medusa’s price selection strategy to create a custom pricing strategy.
|
||||
|
||||
:::note
|
||||
|
||||
If you’re interested in learning what a price selection strategy is and how it works, check out [this documentation](../price-selection-strategy.md) instead.
|
||||
|
||||
:::
|
||||
|
||||
## 1. Create Class
|
||||
|
||||
Create a TypeScript or JavaScript file in `src/strategies` of your Medusa backend project with a class that extends the `AbstractPriceSelectionStrategy` class:
|
||||
|
||||
```ts title="src/strategies/price.ts"
|
||||
import {
|
||||
AbstractPriceSelectionStrategy,
|
||||
PriceSelectionContext,
|
||||
PriceSelectionResult,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
TaxServiceRate,
|
||||
} from "@medusajs/medusa/dist/types/tax-service"
|
||||
|
||||
export default class MyStrategy extends
|
||||
AbstractPriceSelectionStrategy {
|
||||
|
||||
async calculateVariantPrice(
|
||||
data: {
|
||||
variantId: string;
|
||||
taxRates: TaxServiceRate[];
|
||||
quantity?: number
|
||||
}[],
|
||||
context: PriceSelectionContext
|
||||
): Promise<Map<string, PriceSelectionResult>> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Resolve Resources
|
||||
|
||||
You can resolve resources like services or repositories using [dependency injection](../../../development/fundamentals/dependency-injection.md).
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/strategies/price.ts"
|
||||
import {
|
||||
AbstractPriceSelectionStrategy,
|
||||
CustomerService,
|
||||
PriceSelectionContext,
|
||||
PriceSelectionResult,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
TaxServiceRate,
|
||||
} from "@medusajs/medusa/dist/types/tax-service"
|
||||
|
||||
type InjectedDependencies = {
|
||||
customerService: CustomerService
|
||||
}
|
||||
|
||||
export default class MyStrategy extends
|
||||
AbstractPriceSelectionStrategy {
|
||||
|
||||
protected customerService_: CustomerService
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
super(container)
|
||||
this.customerService_ = container.customerService
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Implement calculateVariantPrice
|
||||
|
||||
In this method, you can implement your price selection strategy.
|
||||
|
||||
### Parameters
|
||||
|
||||
You can learn more about optional properties and the meaning behind every property [here](../price-selection-strategy.md#calculatevariantprice-method).
|
||||
|
||||
<ParameterTypes parameters={[
|
||||
{
|
||||
name: "data",
|
||||
type: "object[]",
|
||||
description: "Holds the data necessary to perform the price selection for each variant ID.",
|
||||
optional: false,
|
||||
children: [
|
||||
{
|
||||
name: "variantId",
|
||||
type: "string",
|
||||
description: "The ID of the variant to retrieve the prices for.",
|
||||
optional: false,
|
||||
},
|
||||
{
|
||||
name: "taxRates",
|
||||
type: "object[]",
|
||||
description: "The tax rates to be applied. This is only used for [Tax-Inclusive Pricing](../../taxes/inclusive-pricing.md).",
|
||||
optional: false,
|
||||
children: [
|
||||
{
|
||||
name: "rate",
|
||||
type: "number | null",
|
||||
description: "The tax rate",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
name: "name",
|
||||
type: "string",
|
||||
description: "The name of the tax rate",
|
||||
optional: false
|
||||
},
|
||||
{
|
||||
name: "code",
|
||||
type: "string | null",
|
||||
description: "The code of the tax rate",
|
||||
optional: false
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "quantity",
|
||||
type: "number",
|
||||
description: "The quantity of the variant",
|
||||
optional: true
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "context",
|
||||
type: "[PriceSelectionContext](../price-selection-strategy.md#context-object)",
|
||||
description: "The context of the price selection",
|
||||
optional: false,
|
||||
children: [
|
||||
{
|
||||
name: "cart_id",
|
||||
type: "string",
|
||||
description: "The ID of the customer’s cart. This is used when the prices are being retrieved for the variant of a line item, as it is used to determine the current region and currency code of the context.",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
name: "customer_id",
|
||||
type: "string",
|
||||
description: "The ID of the customer. This is used to filter out price lists for a customer group that this customer doesn’t belong to.",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
name: "region_id",
|
||||
type: "string",
|
||||
description: "The ID of the region the customer is using.",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
name: "quantity",
|
||||
type: "number",
|
||||
description: "The quantity of the item in the cart. This is used to filter out price lists that have `min_quantity` or `max_quantity` conditions set.",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
name: "currency_code",
|
||||
type: "string",
|
||||
description: "The currency code the customer is using.",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
name: "include_discount_prices",
|
||||
type: "boolean",
|
||||
description: "Whether the price list's prices should be retrieved or not.",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
name: "taxRates",
|
||||
type: "object[]",
|
||||
description: "The tax rates to be applied. This is only used for [Tax-Inclusive Pricing](../../taxes/inclusive-pricing.md).",
|
||||
optional: false,
|
||||
children: [
|
||||
{
|
||||
name: "rate",
|
||||
type: "number | null",
|
||||
description: "The tax rate",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
name: "name",
|
||||
type: "string",
|
||||
description: "The name of the tax rate",
|
||||
optional: false
|
||||
},
|
||||
{
|
||||
name: "code",
|
||||
type: "string | null",
|
||||
description: "The code of the tax rate",
|
||||
optional: false
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "ignore_cache",
|
||||
type: "boolean",
|
||||
description: "Whether to calculate the prices even if the value of an earlier price calculation is available in the cache.",
|
||||
optional: true
|
||||
},
|
||||
]
|
||||
}
|
||||
]} />
|
||||
|
||||
### Returns
|
||||
|
||||
<ParameterTypes parameters={[
|
||||
{
|
||||
name: "Promise",
|
||||
type: "Promise<Map<string, PriceSelectionResult>>",
|
||||
description: "A map, each key is an ID of a variant, and its value is an object holding the price selection result.",
|
||||
optional: false,
|
||||
children: [
|
||||
{
|
||||
name: "PriceSelectionResult",
|
||||
type: "PriceSelectionResult",
|
||||
description: "The price selection result of a variant.",
|
||||
optional: false,
|
||||
children: [
|
||||
{
|
||||
name: "originalPrice",
|
||||
type: "number | null",
|
||||
description: "The original price of the variant which depends on the selected region or currency code in the context object. If both region ID and currency code are available in the context object, the region has higher precedence.",
|
||||
optional: false
|
||||
},
|
||||
{
|
||||
name: "originalPriceIncludesTax",
|
||||
type: "boolean | null",
|
||||
description: "Whether the original price includes taxes or not. This is only available for [Tax-Inclusive Pricing](../../taxes/inclusive-pricing.md).",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
name: "calculatedPrice",
|
||||
type: "number | null",
|
||||
description: "The lowest price among the prices of the product variant retrieved using the context object.",
|
||||
optional: false
|
||||
},
|
||||
{
|
||||
name: "calculatedPriceIncludesTax",
|
||||
type: "boolean | null",
|
||||
description: "Whether the calculated price includes taxes or not. This is only available for [Tax-Inclusive Pricing](../../taxes/inclusive-pricing.md).",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
name: "calculatedPriceType",
|
||||
type: "enum",
|
||||
description: "Either `default` if the `calculatedPrice` is the original price, or the type of the price list applied, which can be `override` or `sale`.",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
name: "prices",
|
||||
type: "[MoneyAmount](../../../references/entities/classes/entities.MoneyAmount.mdx)[]",
|
||||
description: "The prices of the variant retrieved using the `context` object. It can include its original price and its price lists if there are any.",
|
||||
optional: false
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## 3. Run Build Command
|
||||
|
||||
In your terminal, run the build command to transpile the files in the `src` directory into the `dist` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
Run your backend to test it out:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
Then, try out your strategy using any of the [Products](https://docs.medusajs.com/api/store#products) or [Carts](https://docs.medusajs.com/api/store#carts) API Routes which include retrieving product variants and line items respectively. You should then see the prices in the response based on your implemented strategy.
|
||||
@@ -45,7 +45,7 @@ Developers can change the default logic behind how prices are selected to be sho
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/price-lists/backend/override-price-selection-strategy',
|
||||
href: '/modules/price-lists/price-selection-strategy',
|
||||
label: 'Backend: Override Price Selection',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
|
||||
@@ -45,11 +45,11 @@ You can use any of the following conditions:
|
||||
|
||||
## How are Price Lists Applied
|
||||
|
||||
When a product or a line item is retrieved or manipulated on the storefront, Medusa determines its price using a Price Selection Strategy. The price selection strategy determines the best price to apply in a given [context](./price-selection-strategy.md#context-object). Part of determining the price depends on the price list.
|
||||
When a product or a line item is retrieved or manipulated on the storefront, Medusa determines its price using a Price Selection Strategy. The price selection strategy determines the best price to apply in a given context. Part of determining the price depends on the price list.
|
||||
|
||||
:::info
|
||||
|
||||
This section explains how the price selection strategy uses price lists when it determines the price of a product variant. If you want full details on how the price selection strategy works, check out this documentation instead.
|
||||
This section explains how the [price selection strategy](../../references/price_selection/interfaces/price_selection.IPriceSelectionStrategy.mdx) uses price lists when it determines the price of a product variant. If you want full details on how the price selection strategy works, check out [this documentation](../../references/price_selection/interfaces/price_selection.IPriceSelectionStrategy.mdx) instead.
|
||||
|
||||
:::
|
||||
|
||||
@@ -59,7 +59,7 @@ When the strategy calculates the prices of a product variant, it retrieves both
|
||||
|
||||
The original price depends on the selected region or currency code in the current context, where the region has higher precedence.
|
||||
|
||||
The calculated price is the lowest price among all retrieved prices. Retrieved prices can include the original price and the price lists that can be applied. Prices are retrieved based on the [context](./price-selection-strategy.md#context-object).
|
||||
The calculated price is the lowest price among all retrieved prices. Retrieved prices can include the original price and the price lists that can be applied. Prices are retrieved based on the provided context, such as region ID or currency code.
|
||||
|
||||
In the [Get Product](https://docs.medusajs.com/api/store#products_getproductsproduct) and [List Product](https://docs.medusajs.com/api/store#products_getproducts) API Routes, you must pass either the `region_id` or `currency_code` to retrieve the correct prices, as they are part of the price selection strategy context.
|
||||
|
||||
@@ -87,5 +87,5 @@ Since the line item belongs to a cart, there’s no need to pass the `region_id`
|
||||
|
||||
## See Also
|
||||
|
||||
- [Price Selection Strategy Overview](./price-selection-strategy.md)
|
||||
- [Price Selection Strategy](../../references/price_selection/classes/price_selection.AbstractPriceSelectionStrategy.mdx)
|
||||
- [Manage price lists using the admin APIs](./admin/manage-price-lists.mdx)
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
description: 'Learn what the price selection strategy is in the Medusa backend. The price selection strategy retrieves the best price for a product variant for a specific context.'
|
||||
---
|
||||
|
||||
# Price Selection Strategy
|
||||
|
||||
In this document, you’ll learn what a price selection strategy is.
|
||||
|
||||
:::note
|
||||
|
||||
If you’re interested to learn how to override the price selection strategy, check out [this documentation](./backend/override-price-selection-strategy.mdx) instead.
|
||||
|
||||
:::
|
||||
|
||||
## What's a Price Selection Strategy
|
||||
|
||||
Medusa provides many features and different ways to control the price of a product variant. This includes price lists and their different conditions, products’ original prices, and taxes.
|
||||
|
||||
Medusa uses the `PriceSelectionStrategy` class to retrieve the best price for a product variant for a specific context. This strategy is used whenever products and line items are retrieved or manipulated on the storefront.
|
||||
|
||||
---
|
||||
|
||||
## PriceSelectionStrategy Overview
|
||||
|
||||
The `PriceSelectionStrategy` class extends the `AbstractPriceSelectionStrategy` class. Its main method is the `calculateVariantPrice`.
|
||||
|
||||
### calculateVariantPrice Method
|
||||
|
||||
Medusa uses this method to retrieve one or more product variants' prices. This method is used when retrieving product variants or their associated line items. It's also used when retrieving other entities that product variants and line items belong to, such as products and carts respectively.
|
||||
|
||||
This method accepts two parameters:
|
||||
|
||||
1. The first parameter is an array of objects, each object having the following properties:
|
||||
1. `variantId`: a string indicating the ID of the variant to calculate the price for.
|
||||
2. `quantity`: an optional number indicating the quantity of the variant.
|
||||
2. A [context](#context-object) object.
|
||||
|
||||
The method retrieves all the available prices of the variant based on the conditions in the context object.
|
||||
|
||||
It returns an object with the following properties:
|
||||
|
||||
1. `originalPrice`: The original price of the variant which depends on the selected region or currency code in the context object. If both region ID and currency code are available in the context object, the region has higher precedence.
|
||||
2. `originalPriceIncludesTax`: A boolean value indicating whether the original price includes taxes or not. This is only available for [Tax-Inclusive Pricing](../taxes/inclusive-pricing.md).
|
||||
3. `calculatedPrice`: The lowest price among the prices of the product variant retrieved using the context object.
|
||||
4. `calculatedPriceIncludesTax`: A boolean value indicating whether the calculated price includes taxes or not. This is only available for [Tax-Inclusive Pricing](../taxes/inclusive-pricing.md).
|
||||
5. `calculatedPriceType`: Either `default` if the `calculatedPrice` is the original price, or the type of the price list applied.
|
||||
6. `prices`: an array of all the prices of the variant retrieved using the context object. It can include its original price and its price lists if there are any.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about price lists and how they’re used in [this documentation](./price-lists.md).
|
||||
|
||||
:::
|
||||
|
||||
### Context Object
|
||||
|
||||
The context that is passed to the `calculateVariantPrice` method is an object that has the following optional properties:
|
||||
|
||||
- `cart_id`: A string indicating the ID of the customer’s cart. This is used when the prices are being retrieved for the variant of a line item, as it is used to determine the current region and currency code of the context.
|
||||
- `customer_id`: A string indicating the ID of the customer. This is used to filter out price lists for a customer group that this customer doesn’t belong to.
|
||||
- `quantity`: A number indicating the quantity of the item in the cart. This is used to filter out price lists that have `min_quantity` or `max_quantity` conditions set.
|
||||
- `region_id`: A string indicating the ID of the region the customer is using.
|
||||
- `currency_code`: A string indicating the currency code the customer is using.
|
||||
- `include_discount_prices`: A boolean value indicating whether price list prices should be retrieved or not.
|
||||
- `tax_rates`: An array of objects indicating the tax rates to be applied. This is only used for [Tax-Inclusive Pricing](../taxes/inclusive-pricing.md).
|
||||
- `ignore_cache`: a boolean value indicating whether to calculate the prices even if the value of an earlier price calculation is available in the cache.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Override the Price Selection Strategy](./backend/override-price-selection-strategy.mdx)
|
||||
@@ -86,5 +86,5 @@ The relation is available on the `PaymentCollection` entity by expanding the `cu
|
||||
|
||||
## See Also
|
||||
|
||||
- [Price Selection Strategy](../price-lists/price-selection-strategy.md)
|
||||
- [Price Selection Strategy](../../references/price_selection/classes/price_selection.AbstractPriceSelectionStrategy.mdx)
|
||||
- [Tax-Inclusive Pricing](../taxes/inclusive-pricing.md)
|
||||
@@ -1,224 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a tax provider. You can create a tax provider in a Medusa backend or a plugin.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Create a Tax Provider
|
||||
|
||||
In this document, you’ll learn how to create a tax provider.
|
||||
|
||||
## Overview
|
||||
|
||||
A tax provider is used to retrieve the tax lines in a cart. The Medusa backend provides a default `system` provider. You can create your own tax provider, either in a plugin or directly in your Medusa backend, then use it in any region.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Tax Provider Class
|
||||
|
||||
A tax provider class should be defined in a TypeScript or JavaScript file under `src/services` and the class should extend `AbstractTaxService` imported from `@medusajs/medusa`.
|
||||
|
||||
For example, you can create the file `src/services/my-tax.ts` with the following content:
|
||||
|
||||
```ts title="src/services/my-tax.ts"
|
||||
import {
|
||||
AbstractTaxService,
|
||||
ItemTaxCalculationLine,
|
||||
ShippingTaxCalculationLine,
|
||||
TaxCalculationContext,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
ProviderTaxLine,
|
||||
} from "@medusajs/medusa/dist/types/tax-service"
|
||||
|
||||
class MyTaxService extends AbstractTaxService {
|
||||
async getTaxLines(
|
||||
itemLines: ItemTaxCalculationLine[],
|
||||
shippingLines: ShippingTaxCalculationLine[],
|
||||
context: TaxCalculationContext):
|
||||
Promise<ProviderTaxLine[]> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
export default MyTaxService
|
||||
```
|
||||
|
||||
Since the class extends `AbstractTaxService`, it must implement its abstract method `getTaxLines`, which is explained later in this guide.
|
||||
|
||||
### Using a Constructor
|
||||
|
||||
You can use a constructor to access services and resources registered in the dependency container using dependency injection. For example:
|
||||
|
||||
```ts title="src/services/my-tax.ts"
|
||||
// ...
|
||||
import { LineItemService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
lineItemService: LineItemService
|
||||
}
|
||||
|
||||
class MyTaxService extends AbstractTaxService {
|
||||
protected readonly lineItemService_: LineItemService
|
||||
|
||||
constructor({ lineItemService }: InjectedDependencies) {
|
||||
super()
|
||||
this.lineItemService_ = lineItemService
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default MyTaxService
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Define Identifier
|
||||
|
||||
Every tax provider must have a unique identifier. The identifier is defined as a static property in the class, and its value is used when registering the tax provider in the database and in the dependency container.
|
||||
|
||||
Add the static property `identifier` in your tax provider class:
|
||||
|
||||
```ts title="src/services/my-tax.ts"
|
||||
class MyTaxService extends AbstractTaxService {
|
||||
static identifier = "my-tax"
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to change `my-tax` to the name of your tax provider.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Implement getTaxLines Method
|
||||
|
||||
The `getTaxLines` method is the only required method in a tax provider. It’s used when retrieving the tax lines for line items and shipping methods, typically during checkout or when calculating totals, for example, for orders, swaps, or returns.
|
||||
|
||||
The method accepts three parameters. The first parameter is an array of tax calculation objects for line items. Each object having the following properties:
|
||||
|
||||
- `item`: a line item object.
|
||||
- `rates`: an array of objects, each object having the following properties:
|
||||
- `rate`: an optional number indicating the tax rate.
|
||||
- `name`: a string indicating the name of the tax rate.
|
||||
- `code`: an optional string indicating the tax code.
|
||||
|
||||
The second parameter is an array of tax calculation objects for shipping methods. Each object having the following properties:
|
||||
|
||||
- `shipping_method`: a shipping method object.
|
||||
- `rates`: an array of objects, each object having the following properties:
|
||||
- `rate`: an optional number indicating the tax rate.
|
||||
- `name`: a string indicating the name of the tax rate.
|
||||
- `code`: an optional string indicating the tax code.
|
||||
|
||||
The third parameter is a context object that can be helpful for the tax calculation. The object can have the following properties:
|
||||
|
||||
- `shipping_address`: an optional address object used for shipping.
|
||||
- `customer`: an optional customer object.
|
||||
- `region`: an optional region object.
|
||||
- `is_return`: a boolean value that determines whether the taxes are being calculated for a return flow.
|
||||
- `shipping_methods`: an array of shipping methods being used in the current context.
|
||||
- `allocation_map`: an object that indicates the gift cards and discounts applied on line items. Each object key or property is an ID of a line item, and the value is an object having the following properties:
|
||||
- `gift_card`: an optional object indicating the gift card applied on the line item.
|
||||
- `discount`: an optional object indicating the discount applied on the line item.
|
||||
|
||||
This method is expected to return an array of line item tax line or shipping method tax line objects.
|
||||
|
||||
The line item tax line object has the following properties:
|
||||
|
||||
- `rate`: a number indicating the tax rate.
|
||||
- `name`: a string indicating the name of the tax rate.
|
||||
- `code`: a string indicating the tax code.
|
||||
- `item_id`: the ID of the line item.
|
||||
- `metadata`: an optional object that can hold any necessary additional data to be added to the line item tax lines.
|
||||
|
||||
:::note
|
||||
|
||||
Tax lines for line item must have a unique `code` and `item_id` combination. Otherwise, the tax lines will be applied multiple times.
|
||||
|
||||
:::
|
||||
|
||||
The shipping method tax line object has the following properties:
|
||||
|
||||
- `rate`: a number indicating the tax rate.
|
||||
- `name`: a string indicating the name of the tax rate.
|
||||
- `code`: a string indicating the tax code.
|
||||
- `shipping_method_id`: the ID of the shipping method.
|
||||
- `metadata`: an optional object that can hold any necessary additional data to be added to the shipping method tax lines.
|
||||
|
||||
:::note
|
||||
|
||||
Tax lines for a shipping method must have a unique `code` and `shipping_method_id` combination. Otherwise, the tax lines will be applied multiple times.
|
||||
|
||||
:::
|
||||
|
||||
The returned array would be a combination of both the line item tax lines and shipping method tax lines.
|
||||
|
||||
:::note
|
||||
|
||||
The Medusa backend determines whether an object in the returned array is a shipping method tax line item, depending on the availability of the `shipping_method_id` attribute. For line items, it depends on the availability of the `item_id` attribute.
|
||||
|
||||
:::
|
||||
|
||||
For example, the `system` tax provider returns the tax calculation line items in the first parameter and the tax calculation shipping methods in the second parameter as is:
|
||||
|
||||
```ts title="src/services/my-tax.ts"
|
||||
// ...
|
||||
|
||||
class SystemTaxService extends AbstractTaxService {
|
||||
// ...
|
||||
|
||||
async getTaxLines(
|
||||
itemLines: ItemTaxCalculationLine[],
|
||||
shippingLines: ShippingTaxCalculationLine[],
|
||||
context: TaxCalculationContext
|
||||
): Promise<ProviderTaxLine[]> {
|
||||
let taxLines: ProviderTaxLine[] = itemLines.flatMap((l) => {
|
||||
return l.rates.map((r) => ({
|
||||
rate: r.rate || 0,
|
||||
name: r.name,
|
||||
code: r.code,
|
||||
item_id: l.item.id,
|
||||
}))
|
||||
})
|
||||
|
||||
taxLines = taxLines.concat(
|
||||
shippingLines.flatMap((l) => {
|
||||
return l.rates.map((r) => ({
|
||||
rate: r.rate || 0,
|
||||
name: r.name,
|
||||
code: r.code,
|
||||
shipping_method_id: l.shipping_method.id,
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
return taxLines
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Run the Build Command
|
||||
|
||||
In the directory of the Medusa backend, run the `build` command to transpile the files in the `src` directory into the `dist` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
Run your backend to test it out:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
Before you can test out your tax provider, you must enable it in a region. You can do that either using the [Medusa Admin dashboard](../../../user-guide/taxes/manage.md#change-tax-provider) or using the [Update Region admin API Route](../admin/manage-tax-settings.mdx#change-tax-provider-of-a-region).
|
||||
|
||||
Then, you can test out the tax provider by simulating a checkout process in that region. You should see the line item tax lines in the cart’s `items`, as each item object has a `tax_lines` array which are the tax lines that you return in the `getTaxLines` method for line items.
|
||||
|
||||
Similarly, you should see the shipping method tax lines in the cart’s `shipping_methods`, as each shipping method object has a `tax_lines` array which are the tax lines that you return in the `getTaxLines` method for shipping methods.
|
||||
@@ -1,132 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a tax provider. You can create a tax provider in a Medusa backend or a plugin.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Override a Tax Calculation Strategy
|
||||
|
||||
In this document, you’ll learn how to override a tax calculation strategy.
|
||||
|
||||
## Overview
|
||||
|
||||
A tax calculation strategy is used to calculate taxes when calculating totals. The Medusa backend provides a tax calculation strategy that handles calculating the taxes accounting for defined tax rates, and settings such as whether tax-inclusive pricing is enabled.
|
||||
|
||||
You can override the tax calculation strategy to implement different calculation logic or to integrate a third-party service that handles the tax calculation. You can override it either in a Medusa backend setup or in a plugin.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Strategy Class
|
||||
|
||||
A tax calculation strategy should be defined in a TypeScript or JavaScript file created under the `src/strategies` directory. The class must also implement the `ITaxCalculationStrategy` interface imported from the `@medusajs/medusa` package.
|
||||
|
||||
For example, you can create the file `src/strategies/tax-calculation.ts` with the following content:
|
||||
|
||||
```ts title="src/strategies/tax-calculation.ts"
|
||||
import {
|
||||
ITaxCalculationStrategy,
|
||||
LineItem,
|
||||
LineItemTaxLine,
|
||||
ShippingMethodTaxLine,
|
||||
TaxCalculationContext,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class TaxCalculationStrategy
|
||||
implements ITaxCalculationStrategy {
|
||||
|
||||
async calculate(
|
||||
items: LineItem[],
|
||||
taxLines: (ShippingMethodTaxLine | LineItemTaxLine)[],
|
||||
calculationContext: TaxCalculationContext
|
||||
): Promise<number> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default TaxCalculationStrategy
|
||||
```
|
||||
|
||||
Note that you add a basic implementation of the `calculate` method because it’s required by the `ITaxCalculationStrategy` interface. You’ll learn about the method later in the guide.
|
||||
|
||||
### Using a Constructor
|
||||
|
||||
You can use a constructor to access services and resources registered in the dependency container using dependency injection. For example:
|
||||
|
||||
```ts title="src/strategies/tax-calculation.ts"
|
||||
// ...
|
||||
import {
|
||||
LineItemService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
lineItemService: LineItemService
|
||||
}
|
||||
|
||||
class TaxCalculationStrategy
|
||||
implements ITaxCalculationStrategy {
|
||||
|
||||
protected readonly lineItemService_: LineItemService
|
||||
|
||||
constructor({ lineItemService }: InjectedDependencies) {
|
||||
this.lineItemService_ = lineItemService
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implement the calculate Method
|
||||
|
||||
A tax calculation strategy is only required to implement the `calculate` method. This method is used whenever the totals are calculated.
|
||||
|
||||
:::tip
|
||||
|
||||
If automatic tax calculation is disabled, then the tax calculation strategy will only be used when taxes are calculated manually as explain [this guide](../storefront/manual-calculation.md).
|
||||
|
||||
:::
|
||||
|
||||
The `calculate` method expects three parameters:
|
||||
|
||||
- `items`: the first parameter is an array of [line item](../../../references/entities/classes/entities.LineItem.mdx) objects.
|
||||
- `taxLines`: the second parameter is an array of either [shipping method tax line](../../../references/entities/classes/entities.ShippingMethodTaxLine.mdx) or [line item tax line](../../../references/entities/classes/entities.LineItemTaxLine.mdx) objects.
|
||||
- `calculationContext`: an object holding the context of the tax calculation. The object has the following properties:
|
||||
- `shipping_address`: an optional address object used for shipping.
|
||||
- `customer`: an optional customer object.
|
||||
- `region`: an optional region object.
|
||||
- `is_return`: a boolean value that determines whether the taxes are being calculated for a return flow.
|
||||
- `shipping_methods`: an array of shipping methods being used in the current context.
|
||||
- `allocation_map`: an object that indicates the gift cards and discounts applied on line items. Each object key or property is an ID of a line item, and the value is an object having the following properties:
|
||||
- `gift_card`: an optional object indicating the gift card applied on the line item.
|
||||
- `discount`: an optional object indicating the discount applied on the line item.
|
||||
|
||||
The method returns a number, being the tax amount for the line items, tax lines, and context provided.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Run Build Command
|
||||
|
||||
In the directory of the Medusa backend, run the `build` command to transpile the files in the `src` directory into the `dist` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
Run your backend to test it out:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
To test it out, you can simulate a checkout flow and check the calculated taxes to see if it matches the logic you implemented in the `calculate` method.
|
||||
|
||||
:::tip
|
||||
|
||||
As mentioned earlier, if automatic calculation of taxes is disabled in the region, you have to manually trigger tax calculation as explained in [this guide](../storefront/manual-calculation.md).
|
||||
|
||||
:::
|
||||
@@ -27,7 +27,7 @@ The [Local Event Bus module](../../../development/events/modules/local.md) works
|
||||
|
||||
As mentioned in the overview, this guide illustrates how to send the email using SendGrid. If you intend to follow along, you must have the [SendGrid plugin](../../../plugins/notifications/sendgrid.mdx) installed and configured.
|
||||
|
||||
You can also find other available Notification provider plugins in the [Plugins directory](https://medusajs.com/plugins/), or [create your own](../../../development/notification/create-notification-provider.md).
|
||||
You can also find other available Notification provider plugins in the [Plugins directory](https://medusajs.com/plugins/), or [create your own](../../../references/notification/classes/notification.AbstractNotificationService.mdx).
|
||||
|
||||
---
|
||||
|
||||
@@ -147,6 +147,6 @@ Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notifica
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
|
||||
You can learn more about handling events with the Notification Service using [this documentation](../../../references/notification/classes/notification.AbstractNotificationService.mdx).
|
||||
|
||||
:::
|
||||
@@ -20,7 +20,7 @@ By integrating Contentful to Medusa, you can benefit from powerful features in y
|
||||
|
||||
This guide assumes you already have a Medusa backend installed. If not, you can learn how to install it [here](../../create-medusa-app.mdx).
|
||||
|
||||
Redis must also be configured in your Medusa backend if you want to sync data from Medusa to Contentful. Learn how to configure it [here](../../development/backend/configurations.md#redis_url).
|
||||
Redis must also be configured in your Medusa backend if you want to sync data from Medusa to Contentful. Learn how to configure it [here](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#redis_url).
|
||||
|
||||
### Needed Account
|
||||
|
||||
|
||||
@@ -99,8 +99,8 @@ cd medusa-backend
|
||||
|
||||
You must then configure your backend to:
|
||||
|
||||
- Connect to a PostgreSQL database, as explained [here](../../development/backend/configurations.md#database-configuration)
|
||||
- Install and configure an event-bus module, as explained [here](../../development/backend/configurations.md#recommended-event-bus-modules)
|
||||
- Connect to a PostgreSQL database, as explained [here](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#database-configuration)
|
||||
- Install and configure an event-bus module, as explained [here](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#recommended-event-bus-modules)
|
||||
|
||||
### Run Migrations
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@ import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# File Service Plugins
|
||||
|
||||
If you can't find your file service or storage provider, try checking the [Community Plugins Library](https://medusajs.com/plugins/?filters=Storage&categories=Storage). You can also [create your own file service](../../development/file-service/create-file-service.md).
|
||||
If you can't find your file service or storage provider, try checking the [Community Plugins Library](https://medusajs.com/plugins/?filters=Storage&categories=Storage). You can also [create your own file service](../../references/file/classes/file.AbstractFileService.mdx).
|
||||
|
||||
<DocCardList />
|
||||
@@ -2,6 +2,6 @@ import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Notifications Plugins
|
||||
|
||||
If you can't find your notification provider, try checking the [Community Plugins Library](https://medusajs.com/plugins/?filters=Notification&categories=Notification). You can also [create your own notification service](../../development/notification/create-notification-provider.md).
|
||||
If you can't find your notification provider, try checking the [Community Plugins Library](https://medusajs.com/plugins/?filters=Notification&categories=Notification). You can also [create your own notification service](../../references/notification/classes/notification.AbstractNotificationService.mdx).
|
||||
|
||||
<DocCardList />
|
||||
@@ -2,6 +2,6 @@ import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Search Plugins
|
||||
|
||||
If you can't find your search provider, try checking the [Community Plugins Library](https://medusajs.com/plugins/?filters=Search&categories=Search). You can also [create your own search service](../../development/search/create.md).
|
||||
If you can't find your search provider, try checking the [Community Plugins Library](https://medusajs.com/plugins/?filters=Search&categories=Search). You can also [create your own search service](../../references/search/classes/search.AbstractSearchService.mdx).
|
||||
|
||||
<DocCardList />
|
||||
@@ -11,4 +11,4 @@ A Batch Job indicates an asynchronus task stored in the Medusa backend. Its stat
|
||||
|
||||
## Properties
|
||||
|
||||
<ParameterTypes parameters={[{"name":"context","type":"`Record<string, unknown>`","description":"The context of the batch job, the type of the batch job determines what the context should contain.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"created_at","type":"`Date`","description":"The date with timezone at which the resource was created.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"created_by","type":"`null` \\| `string`","description":"The unique identifier of the user that created the batch job.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"created_by_user","type":"[User](entities.User.mdx)","description":"The details of the user that created the batch job.","optional":false,"defaultValue":"","expandable":true,"children":[{"name":"api_token","type":"`string`","description":"An API token associated with the user.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"created_at","type":"`Date`","description":"The date with timezone at which the resource was created.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"deleted_at","type":"`null` \\| `Date`","description":"The date with timezone at which the resource was deleted.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"email","type":"`string`","description":"The email of the User","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"first_name","type":"`string`","description":"The first name of the User","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"id","type":"`string`","description":"The user's ID","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"last_name","type":"`string`","description":"The last name of the User","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"metadata","type":"`Record<string, unknown>`","description":"An optional key-value map with additional details","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"password_hash","type":"`string`","description":"","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"role","type":"[UserRoles](../enums/entities.UserRoles.mdx)","description":"The user's role. These roles don't provide any different privileges.","optional":false,"defaultValue":"member","expandable":false,"children":[]},{"name":"updated_at","type":"`Date`","description":"The date with timezone at which the resource was updated.","optional":false,"defaultValue":"","expandable":false,"children":[]}]},{"name":"deleted_at","type":"`null` \\| `Date`","description":"The date with timezone at which the resource was deleted.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"dry_run","type":"`boolean`","description":"Specify if the job must apply the modifications or not.","optional":false,"defaultValue":"false","expandable":false,"children":[]},{"name":"id","type":"`string`","description":"The unique identifier for the batch job.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"result","type":"`object` & `Record<string, unknown>`","description":"The result of the batch job.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"advancement_count","type":"`number`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"count","type":"`number`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"errors","type":"(`string` \\| [BatchJobResultError](../../medusa/types/medusa.BatchJobResultError.mdx))[]","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"file_key","type":"`string`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"file_size","type":"`number`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"progress","type":"`number`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"stat_descriptors","type":"[BatchJobResultStatDescriptor](../../medusa/types/medusa.BatchJobResultStatDescriptor.mdx)[]","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]}]},{"name":"status","type":"[BatchJobStatus](../../medusa/enums/medusa.BatchJobStatus.mdx)","description":"The status of the batch job.","optional":false,"defaultValue":"created","expandable":false,"children":[{"name":"CANCELED","type":"`\"canceled\"`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"COMPLETED","type":"`\"completed\"`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"CONFIRMED","type":"`\"confirmed\"`","description":"","optional":true,"defaultValue":"","expandable":false,"children"Line truncated
|
||||
<ParameterTypes parameters={[{"name":"context","type":"`Record<string, unknown>`","description":"The context of the batch job, the type of the batch job determines what the context should contain.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"created_at","type":"`Date`","description":"The date with timezone at which the resource was created.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"created_by","type":"`null` \\| `string`","description":"The unique identifier of the user that created the batch job.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"created_by_user","type":"[User](entities.User.mdx)","description":"The details of the user that created the batch job.","optional":false,"defaultValue":"","expandable":true,"children":[{"name":"api_token","type":"`string`","description":"An API token associated with the user.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"created_at","type":"`Date`","description":"The date with timezone at which the resource was created.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"deleted_at","type":"`null` \\| `Date`","description":"The date with timezone at which the resource was deleted.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"email","type":"`string`","description":"The email of the User","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"first_name","type":"`string`","description":"The first name of the User","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"id","type":"`string`","description":"The user's ID","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"last_name","type":"`string`","description":"The last name of the User","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"metadata","type":"`Record<string, unknown>`","description":"An optional key-value map with additional details","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"password_hash","type":"`string`","description":"","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"role","type":"[UserRoles](../enums/entities.UserRoles.mdx)","description":"The user's role. These roles don't provide any different privileges.","optional":false,"defaultValue":"member","expandable":false,"children":[]},{"name":"updated_at","type":"`Date`","description":"The date with timezone at which the resource was updated.","optional":false,"defaultValue":"","expandable":false,"children":[]}]},{"name":"deleted_at","type":"`null` \\| `Date`","description":"The date with timezone at which the resource was deleted.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"dry_run","type":"`boolean`","description":"Specify if the job must apply the modifications or not.","optional":false,"defaultValue":"false","expandable":false,"children":[]},{"name":"id","type":"`string`","description":"The unique identifier for the batch job.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"result","type":"`object` & `Record<string, unknown>`","description":"The result of the batch job.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"advancement_count","type":"`number`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"count","type":"`number`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"errors","type":"(`string` \\| [BatchJobResultError](../../medusa/types/medusa.BatchJobResultError.mdx))[]","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"file_key","type":"`string`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"file_size","type":"`number`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"progress","type":"`number`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"stat_descriptors","type":"[BatchJobResultStatDescriptor](../../medusa/types/medusa.BatchJobResultStatDescriptor.mdx)[]","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]}]},{"name":"status","type":"[BatchJobStatus](../../medusa/enums/medusa.BatchJobStatus.mdx)","description":"The status of the batch job.","optional":false,"defaultValue":"created","expandable":false,"children":[{"name":"CREATED","type":"`\"created\"`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"PRE_PROCESSED","type":"`\"pre_processed\"`","description":"","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"CONFIRMED","type":"`\"confirmed\"`","description":"","optional":true,"defaultValue":"","expandable":false,"chiLine truncated
|
||||
@@ -11,4 +11,4 @@ A Price List represents a set of prices that override the default price for one
|
||||
|
||||
## Properties
|
||||
|
||||
<ParameterTypes parameters={[{"name":"created_at","type":"`Date`","description":"The date with timezone at which the resource was created.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"customer_groups","type":"[CustomerGroup](entities.CustomerGroup.mdx)[]","description":"The details of the customer groups that the Price List can apply to.","optional":false,"defaultValue":"","expandable":true,"children":[{"name":"created_at","type":"`Date`","description":"The date with timezone at which the resource was created.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"customers","type":"[Customer](entities.Customer.mdx)[]","description":"The details of the customers that belong to the customer group.","optional":false,"defaultValue":"","expandable":true,"children":[]},{"name":"deleted_at","type":"`null` \\| `Date`","description":"The date with timezone at which the resource was deleted.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"id","type":"`string`","description":"The customer group's ID","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"metadata","type":"`Record<string, unknown>`","description":"An optional key-value map with additional details","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"name","type":"`string`","description":"The name of the customer group","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"price_lists","type":"[PriceList](entities.PriceList.mdx)[]","description":"The price lists that are associated with the customer group.","optional":false,"defaultValue":"","expandable":true,"children":[]},{"name":"updated_at","type":"`Date`","description":"The date with timezone at which the resource was updated.","optional":false,"defaultValue":"","expandable":false,"children":[]}]},{"name":"deleted_at","type":"`null` \\| `Date`","description":"The date with timezone at which the resource was deleted.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"description","type":"`string`","description":"The price list's description","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"ends_at","type":"`null` \\| `Date`","description":"The date with timezone that the Price List stops being valid.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"id","type":"`string`","description":"The price list's ID","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"includes_tax","type":"`boolean`","description":"Whether the price list prices include tax","optional":false,"defaultValue":"false","expandable":false,"featureFlag":"tax_inclusive_pricing","children":[]},{"name":"name","type":"`string`","description":"The price list's name","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"prices","type":"[MoneyAmount](entities.MoneyAmount.mdx)[]","description":"The prices that belong to the price list, represented as a Money Amount.","optional":false,"defaultValue":"","expandable":true,"children":[{"name":"amount","type":"`number`","description":"The amount in the smallest currecny unit (e.g. cents 100 cents to charge $1) that the Product Variant will cost.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"created_at","type":"`Date`","description":"The date with timezone at which the resource was created.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"currency_code","type":"`string`","description":"The 3 character currency code that the money amount may belong to.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"deleted_at","type":"`null` \\| `Date`","description":"The date with timezone at which the resource was deleted.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"id","type":"`string`","description":"The money amount's ID","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"max_quantity","type":"`null` \\| `number`","description":"The maximum quantity that the Money Amount applies to. If this value is not set, the Money Amount applies to all quantities.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"min_quantity","type":"`null` \\| `number`","description":"The minimum quantity that the Money Amount applies to. If this value is not set, the Money Amount applies to all quantities.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"price_list","type":"`null` \\| [PriceList](entities.PriceList.mdx)","description":"The details of the price list that the money amount may belong to.","optional":false,"defaultValue":"","expandable":true,"children":[]},{"name":"price_list_id","type":"`null` \\| `string`","description":"The ID of the price list that the money amount may belong to.","optional":false,"defaultValue":"",Line truncated
|
||||
<ParameterTypes parameters={[{"name":"created_at","type":"`Date`","description":"The date with timezone at which the resource was created.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"customer_groups","type":"[CustomerGroup](entities.CustomerGroup.mdx)[]","description":"The details of the customer groups that the Price List can apply to.","optional":false,"defaultValue":"","expandable":true,"children":[{"name":"created_at","type":"`Date`","description":"The date with timezone at which the resource was created.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"customers","type":"[Customer](entities.Customer.mdx)[]","description":"The details of the customers that belong to the customer group.","optional":false,"defaultValue":"","expandable":true,"children":[]},{"name":"deleted_at","type":"`null` \\| `Date`","description":"The date with timezone at which the resource was deleted.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"id","type":"`string`","description":"The customer group's ID","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"metadata","type":"`Record<string, unknown>`","description":"An optional key-value map with additional details","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"name","type":"`string`","description":"The name of the customer group","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"price_lists","type":"[PriceList](entities.PriceList.mdx)[]","description":"The price lists that are associated with the customer group.","optional":false,"defaultValue":"","expandable":true,"children":[]},{"name":"updated_at","type":"`Date`","description":"The date with timezone at which the resource was updated.","optional":false,"defaultValue":"","expandable":false,"children":[]}]},{"name":"deleted_at","type":"`null` \\| `Date`","description":"The date with timezone at which the resource was deleted.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"description","type":"`string`","description":"The price list's description","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"ends_at","type":"`null` \\| `Date`","description":"The date with timezone that the Price List stops being valid.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"id","type":"`string`","description":"The price list's ID","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"includes_tax","type":"`boolean`","description":"Whether the price list prices include tax","optional":false,"defaultValue":"false","expandable":false,"featureFlag":"tax_inclusive_pricing","children":[]},{"name":"name","type":"`string`","description":"The price list's name","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"prices","type":"[MoneyAmount](entities.MoneyAmount.mdx)[]","description":"The prices that belong to the price list, represented as a Money Amount.","optional":false,"defaultValue":"","expandable":true,"children":[{"name":"amount","type":"`number`","description":"The amount in the smallest currecny unit (e.g. cents 100 cents to charge $1) that the Product Variant will cost.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"created_at","type":"`Date`","description":"The date with timezone at which the resource was created.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"currency_code","type":"`string`","description":"The 3 character currency code that the money amount may belong to.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"deleted_at","type":"`null` \\| `Date`","description":"The date with timezone at which the resource was deleted.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"id","type":"`string`","description":"The money amount's ID","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"max_quantity","type":"`null` \\| `number`","description":"The maximum quantity that the Money Amount applies to. If this value is not set, the Money Amount applies to all quantities.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"min_quantity","type":"`null` \\| `number`","description":"The minimum quantity that the Money Amount applies to. If this value is not set, the Money Amount applies to all quantities.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"price_list","type":"`null` \\| [PriceList](entities.PriceList.mdx)","description":"The details of the price list that the money amount may belong to.","optional":false,"defaultValue":"","expandable":true,"children":[]},{"name":"price_list_id","type":"`null` \\| `string`","description":"The ID of the price list that the money amount may belong to.","optional":false,"defaultValue":"",Line truncated
|
||||
@@ -16,8 +16,6 @@ The scope that the discount should apply to.
|
||||
|
||||
The discount should be applied to applicable items in the cart.
|
||||
|
||||
___
|
||||
|
||||
### TOTAL
|
||||
|
||||
**TOTAL** = `"total"`
|
||||
|
||||
@@ -12,26 +12,18 @@ import ParameterTypes from "@site/src/components/ParameterTypes"
|
||||
|
||||
**CLAIM** = `"claim"`
|
||||
|
||||
___
|
||||
|
||||
### DEFAULT
|
||||
|
||||
**DEFAULT** = `"default"`
|
||||
|
||||
___
|
||||
|
||||
### DRAFT\_ORDER
|
||||
|
||||
**DRAFT\_ORDER** = `"draft_order"`
|
||||
|
||||
___
|
||||
|
||||
### PAYMENT\_LINK
|
||||
|
||||
**PAYMENT\_LINK** = `"payment_link"`
|
||||
|
||||
___
|
||||
|
||||
### SWAP
|
||||
|
||||
**SWAP** = `"swap"`
|
||||
@@ -16,64 +16,48 @@ The claim's fulfillment status.
|
||||
|
||||
The claim's fulfillments are canceled.
|
||||
|
||||
___
|
||||
|
||||
### FULFILLED
|
||||
|
||||
**FULFILLED** = `"fulfilled"`
|
||||
|
||||
The claim's replacement items are fulfilled.
|
||||
|
||||
___
|
||||
|
||||
### NOT\_FULFILLED
|
||||
|
||||
**NOT\_FULFILLED** = `"not_fulfilled"`
|
||||
|
||||
The claim's replacement items are not fulfilled.
|
||||
|
||||
___
|
||||
|
||||
### PARTIALLY\_FULFILLED
|
||||
|
||||
**PARTIALLY\_FULFILLED** = `"partially_fulfilled"`
|
||||
|
||||
Some of the claim's replacement items, but not all, are fulfilled.
|
||||
|
||||
___
|
||||
|
||||
### PARTIALLY\_RETURNED
|
||||
|
||||
**PARTIALLY\_RETURNED** = `"partially_returned"`
|
||||
|
||||
Some of the claim's items, but not all, are returned.
|
||||
|
||||
___
|
||||
|
||||
### PARTIALLY\_SHIPPED
|
||||
|
||||
**PARTIALLY\_SHIPPED** = `"partially_shipped"`
|
||||
|
||||
Some of the claim's replacement items, but not all, are shipped.
|
||||
|
||||
___
|
||||
|
||||
### REQUIRES\_ACTION
|
||||
|
||||
**REQUIRES\_ACTION** = `"requires_action"`
|
||||
|
||||
The claim's fulfillment requires action.
|
||||
|
||||
___
|
||||
|
||||
### RETURNED
|
||||
|
||||
**RETURNED** = `"returned"`
|
||||
|
||||
The claim's items are returned.
|
||||
|
||||
___
|
||||
|
||||
### SHIPPED
|
||||
|
||||
**SHIPPED** = `"shipped"`
|
||||
|
||||
@@ -16,16 +16,12 @@ The claim's payment status
|
||||
|
||||
The payment status isn't set, which is typically used when the claim's type is `replace`.
|
||||
|
||||
___
|
||||
|
||||
### NOT\_REFUNDED
|
||||
|
||||
**NOT\_REFUNDED** = `"not_refunded"`
|
||||
|
||||
The payment isn't refunded.
|
||||
|
||||
___
|
||||
|
||||
### REFUNDED
|
||||
|
||||
**REFUNDED** = `"refunded"`
|
||||
|
||||
@@ -12,20 +12,14 @@ import ParameterTypes from "@site/src/components/ParameterTypes"
|
||||
|
||||
**MISSING\_ITEM** = `"missing_item"`
|
||||
|
||||
___
|
||||
|
||||
### OTHER
|
||||
|
||||
**OTHER** = `"other"`
|
||||
|
||||
___
|
||||
|
||||
### PRODUCTION\_FAILURE
|
||||
|
||||
**PRODUCTION\_FAILURE** = `"production_failure"`
|
||||
|
||||
___
|
||||
|
||||
### WRONG\_ITEM
|
||||
|
||||
**WRONG\_ITEM** = `"wrong_item"`
|
||||
@@ -16,8 +16,6 @@ The claim's type.
|
||||
|
||||
The claim refunds an amount to the customer.
|
||||
|
||||
___
|
||||
|
||||
### REPLACE
|
||||
|
||||
**REPLACE** = `"replace"`
|
||||
|
||||
Loaded 100 of 980 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user