chore(docs): added eslint to lint documentation code blocks (#2920)

* docs: added rule for code length

* chore: fixes based on vale errors

* changed to use eslint

* fixes using eslint

* added github action for documentation eslint

* changed allowed max-length

* fixed incorrect heading level

* removed comment
This commit is contained in:
Shahed Nasser authored and GitHub committed 2022-12-30 18:44:46 +02:00
1 parent 99add15fc3
commit d1b4b11ff6
67 files changed
+2611 -1735

No files matched your search

+22 -1
View File
@@ -63,4 +63,25 @@ jobs:
fail_on_error: true fail_on_error: true
vale_flags: '--minAlertLevel=error' vale_flags: '--minAlertLevel=error'
env: env:
GITHUB_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ github.token }}
eslint:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}
- name: Checkout
uses: actions/checkout@v2.3.5
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/cache-deps
with:
extension: eslint-docs
- name: Run Eslint
run: yarn lint:docs
+5
View File
@@ -0,0 +1,5 @@
{
"presets": [
"@babel/preset-react"
]
}
+1
View File
@@ -0,0 +1 @@
!docs/content
+121
View File
@@ -0,0 +1,121 @@
module.exports = {
root: true,
parser: "@babel/eslint-parser",
parserOptions: {
requireConfigFile: false,
ecmaFeatures: {
experimentalDecorators: true,
},
},
plugins: ["prettier", "markdown"],
extends: [
"eslint:recommended",
"google",
"plugin:prettier/recommended",
"plugin:markdown/recommended",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
],
settings: {
react: {
version: "detect"
}
},
rules: {
"no-undef": "off",
"no-unused-expressions": "off",
"no-unused-vars": "off",
"no-unused-labels": "off",
"no-console": "off",
curly: ["error", "all"],
"new-cap": "off",
"require-jsdoc": "off",
"no-unused-expressions": "off",
"no-unused-vars": "off",
camelcase: "off",
"no-invalid-this": "off",
"max-len": [
"error",
{
code: 75,
},
],
semi: ["error", "never"],
quotes: [
"error",
"double",
{
allowTemplateLiterals: true,
},
],
"comma-dangle": [
"error",
{
arrays: "always-multiline",
objects: "always-multiline",
imports: "always-multiline",
exports: "always-multiline",
functions: "never",
},
],
"object-curly-spacing": ["error", "always"],
"arrow-parens": ["error", "always"],
"linebreak-style": 0,
"no-confusing-arrow": [
"error",
{
allowParens: false,
},
],
"space-before-function-paren": [
"error",
{
anonymous: "always",
named: "never",
asyncArrow: "always",
},
],
"space-infix-ops": "off",
"eol-last": ["error", "always"],
"react/prop-types": "off",
"react/jsx-no-undef": "off"
},
env: {
es6: true,
node: true,
},
ignorePatterns: [
'docs/content/references/**',
'docs/content/advanced/backend/subscribers/events-list.md'
],
overrides: [
{
files: ["docs/content/**/*.md", "docs/content/**/*.mdx"],
processor: "markdown/markdown",
},
{
files: [
"docs/content/**/*.md/*.js",
"docs/content/**/*.mdx/*.js",
"docs/content/**/*.md/*.jsx",
"docs/content/**/*.mdx/*.jsx",
],
},
{
files: [
"docs/content/**/*.md/*.ts",
"docs/content/**/*.mdx/*.ts",
"docs/content/**/*.md/*.tsx",
"docs/content/**/*.mdx/*.tsx",
],
plugins: ["@typescript-eslint/eslint-plugin"],
extends: ["plugin:@typescript-eslint/recommended"],
parser: "@typescript-eslint/parser",
rules: {
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-explicit-any": "off",
},
},
],
}
+32 -23
View File
@@ -97,7 +97,7 @@ Finally, in `medusa-config.js` add the following item into the `plugins` arr
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
//... // ...
{ {
resolve: `medusa-plugin-algolia`, resolve: `medusa-plugin-algolia`,
options: { options: {
@@ -123,7 +123,7 @@ const plugins = [
}, },
}, },
}, },
]; ]
``` ```
The `searchableAttributes` are the attributes in a product that are searchable, and `attributesToRetrieve` are the attributes to retrieve for each product result. You’re free to make changes to these attributes as you see fit, but these are the recommended attributes. The `searchableAttributes` are the attributes in a product that are searchable, and `attributesToRetrieve` are the attributes to retrieve for each product result. You’re free to make changes to these attributes as you see fit, but these are the recommended attributes.
@@ -207,7 +207,7 @@ Finally, change the code in `src/lib/search-client.ts` to the following:
```jsx title=src/lib/search-client.ts ```jsx title=src/lib/search-client.ts
import algoliasearch from "algoliasearch/lite" import algoliasearch from "algoliasearch/lite"
const appId = process.env.NEXT_PUBLIC_SEARCH_APP_ID || "" // You should add this to your environment variables const appId = process.env.NEXT_PUBLIC_SEARCH_APP_ID || ""
const apiKey = process.env.NEXT_PUBLIC_SEARCH_API_KEY || "test_key" const apiKey = process.env.NEXT_PUBLIC_SEARCH_API_KEY || "test_key"
@@ -227,9 +227,7 @@ To make sure the Next.js storefront properly displays the products in the search
![Search pop up in the Next.js storefront](https://res.cloudinary.com/dza7lstvk/image/upload/v1668000082/Medusa%20Docs/Algolia/1f9qqK6_c0z8zi.png) ![Search pop up in the Next.js storefront](https://res.cloudinary.com/dza7lstvk/image/upload/v1668000082/Medusa%20Docs/Algolia/1f9qqK6_c0z8zi.png)
--- ### Add to Gatsby and React-Based Storefronts
## Add to Gatsby and React-Based Storefronts
This section covers adding the search UI to React-based storefronts. It uses the Gatsby storefront as an example, but you can use the same steps on any React-based framework. This section covers adding the search UI to React-based storefronts. It uses the Gatsby storefront as an example, but you can use the same steps on any React-based framework.
@@ -269,11 +267,11 @@ import {
Hits, Hits,
InstantSearch, InstantSearch,
SearchBox, SearchBox,
connectStateResults connectStateResults,
} from "react-instantsearch-dom" } from "react-instantsearch-dom"
import React from "react" import React from "react"
import algoliasearch from 'algoliasearch/lite'; import algoliasearch from "algoliasearch/lite"
const searchClient = algoliasearch( const searchClient = algoliasearch(
process.env.GATSBY_ALGOLIA_APP_ID, process.env.GATSBY_ALGOLIA_APP_ID,
@@ -281,19 +279,28 @@ const searchClient = algoliasearch(
) )
const Search = () => { const Search = () => {
const Results = connectStateResults(({ searchState, searchResults, children }) => const Results = connectStateResults(
searchState && searchState.query && searchResults && searchResults.nbHits !== 0 ? ( ({ searchState, searchResults, children }) => {
<div className="absolute top-full w-full p-2 bg-gray-200 shadow-md"> return (
{children} searchState && searchState.query &&
</div> searchResults && searchResults.nbHits !== 0 ?
) : ( (
<div></div> <div
) className="absolute top-full w-full p-2 bg-gray-200 shadow-md">
); {children}
</div>
) : (
<div></div>
)
)
}
)
return ( return (
<div className="relative"> <div className="relative">
<InstantSearch indexName={process.env.GATSBY_SEARCH_INDEX_NAME} searchClient={searchClient}> <InstantSearch
indexName={process.env.GATSBY_SEARCH_INDEX_NAME}
searchClient={searchClient}>
<SearchBox submit={null} reset={null} /> <SearchBox submit={null} reset={null} />
<Results> <Results>
<Hits hitComponent={Hit} /> <Hits hitComponent={Hit} />
@@ -313,7 +320,7 @@ const Hit = ({ hit }) => {
) )
} }
export default Search; export default Search
``` ```
This file uses the dependencies you installed to show the search results. It also initializes Algolia using the environment variables you added. This file uses the dependencies you installed to show the search results. It also initializes Algolia using the environment variables you added.
@@ -333,10 +340,12 @@ import Search from "./search"
And add the `Search` component in the returned JSX before `RegionPopover`: And add the `Search` component in the returned JSX before `RegionPopover`:
```jsx title=src/components/header/index.jsx ```jsx title=src/components/header/index.jsx
//... // ...
<Search /> <div className="...">
<RegionPopover regions={mockData.regions} /> <Search />
//... <RegionPopover regions={mockData.regions} />
</div>
// ...
``` ```
If you run your Gatsby storefront while the Medusa server is running, you should find a search bar in the header of the page. Try entering a query to search through the products in your store. If you run your Gatsby storefront while the Medusa server is running, you should find a search bar in the header of the page. Try entering a query to search through the products in your store.
@@ -29,32 +29,32 @@ Here’s an example of a migration created in a new file `contentful-migrations/
```jsx title=contentful-migrations/rich-text.js ```jsx title=contentful-migrations/rich-text.js
#! /usr/bin/env node #! /usr/bin/env node
require("dotenv").config(); require("dotenv").config()
const { runMigration } = require("contentful-migration"); const { runMigration } = require("contentful-migration")
const options = { const options = {
spaceId: process.env.CONTENTFUL_SPACE_ID, spaceId: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN, accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
environment: process.env.CONTENTFUL_ENVIRONMENT, environment: process.env.CONTENTFUL_ENVIRONMENT,
yes: true, yes: true,
}; }
const migration = async () => { const migration = async () => {
await runMigration({ await runMigration({
...options, ...options,
migrationFunction: function (migration, context) { migrationFunction: function (migration, context) {
//create Rich Text content model // create Rich Text content model
const richText = migration const richText = migration
.createContentType("richText") .createContentType("richText")
.name("Rich Text") .name("Rich Text")
.displayField("title"); .displayField("title")
richText.createField("title").name("Title (Internal)").type("Symbol"); richText.createField("title").name("Title (Internal)").type("Symbol")
richText.createField("body").name("Body").type("RichText"); richText.createField("body").name("Body").type("RichText")
//edit Page content model // edit Page content model
const page = migration.editContentType("page") const page = migration.editContentType("page")
page.editField("contentModules").items({ page.editField("contentModules").items({
@@ -67,7 +67,7 @@ const migration = async () => {
], ],
}) })
//edit Product content model // edit Product content model
const product = migration.editContentType("product") const product = migration.editContentType("product")
product product
@@ -83,11 +83,11 @@ const migration = async () => {
}, },
], ],
}) })
} },
}); })
} }
migration(); migration()
``` ```
This example creates a new content model Rich Text that has two fields: title and body. It also edits the Page content model to allow using Rich Text content models on the page. This example creates a new content model Rich Text that has two fields: title and body. It also edits the Page content model to allow using Rich Text content models on the page.
@@ -97,7 +97,19 @@ In addition, it edits the Product content model by adding a new field `contentMo
You can also add other types of content models the `contentModules` should accept. For example, to accept `tileSection` add it to the `linkContentType` option: You can also add other types of content models the `contentModules` should accept. For example, to accept `tileSection` add it to the `linkContentType` option:
```jsx ```jsx
linkContentType: ["tileSection", "richText"], product
.createField("contentModules")
.name("Content Modules")
.type("Array")
.items({
type: "Link",
linkType: "Entry",
validations: [
{
linkContentType: ["richText", "tileSection"],
},
],
})
``` ```
### Run a Contentful Migration ### Run a Contentful Migration
@@ -165,10 +177,10 @@ import { renderRichText } from "gatsby-source-contentful/rich-text"
const RichText = ({ data }) => { const RichText = ({ data }) => {
return ( return (
<div style={{ <div style={{
maxWidth: '870px', maxWidth: "870px",
margin: '0 auto', margin: "0 auto",
paddingTop: '20px', paddingTop: "20px",
paddingBottom: '20px' paddingBottom: "20px",
}}> }}>
{data.body ? renderRichText(data.body) : ""} {data.body ? renderRichText(data.body) : ""}
</div> </div>
@@ -194,7 +206,7 @@ Then, in the returned JSX add a new case to the switch statement:
```jsx title=src/pages/{ContentfulPage.slug}.js ```jsx title=src/pages/{ContentfulPage.slug}.js
switch (cm.internal.type) { switch (cm.internal.type) {
//... // ...
case "ContentfulRichText": case "ContentfulRichText":
return <RichText key={cm.id} data={cm} /> return <RichText key={cm.id} data={cm} />
default: default:
+3 -3
View File
@@ -123,14 +123,14 @@ Then, in `medusa-config.js` in the exported object, comment out or remove the SQ
```jsx title=medusa-config.js ```jsx title=medusa-config.js
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//... // ...
database_url: DATABASE_URL, database_url: DATABASE_URL,
database_type: "postgres", database_type: "postgres",
//REMOVE OR COMMENT OUT THE BELOW: // REMOVE OR COMMENT OUT THE BELOW:
// database_database: "./medusa-db.sql", // database_database: "./medusa-db.sql",
// database_type: "sqlite", // database_type: "sqlite",
}, },
}; }
``` ```
### Migrate Content Types to Contentful ### Migrate Content Types to Contentful
+7 -7
View File
@@ -55,22 +55,22 @@ Finally, in `medusa-config.js`, add the Klarna plugin to the `plugins` array wit
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
//other plugins... // other plugins...
{ {
resolve: `medusa-payment-klarnal`, resolve: `medusa-payment-klarnal`,
options: { options: {
backend_url: process.env.KLARNA_BACKEND_URL backend_url: process.env.KLARNA_BACKEND_URL,
url: process.env.KLARNA_URL, url: process.env.KLARNA_URL,
user: process.env.KLARNA_USER, user: process.env.KLARNA_USER,
password: process.env.KLARNA_PASSWORD, password: process.env.KLARNA_PASSWORD,
merchant_urls: { merchant_urls: {
terms: process.env.KLARNA_TERMS_URL, terms: process.env.KLARNA_TERMS_URL,
checkout: process.env.KLARNA_CHECKOUT_URL, checkout: process.env.KLARNA_CHECKOUT_URL,
confirmation: process.env.KLARNA_CONFIRMATION_URL confirmation: process.env.KLARNA_CONFIRMATION_URL,
} },
} },
} },
]; ]
``` ```
--- ---
+16 -14
View File
@@ -59,15 +59,15 @@ Open `medusa-config.js` and add the new plugin into the `plugins` array:
```js title=medusa-config.js ```js title=medusa-config.js
const plugins = [ const plugins = [
..., // ...,
{ {
resolve: `medusa-plugin-mailchimp`, resolve: `medusa-plugin-mailchimp`,
options: { options: {
api_key: process.env.MAILCHIMP_API_KEY, api_key: process.env.MAILCHIMP_API_KEY,
newsletter_list_id: process.env.MAILCHIMP_NEWSLETTER_LIST_ID newsletter_list_id: process.env.MAILCHIMP_NEWSLETTER_LIST_ID,
} },
} },
]; ]
``` ```
--- ---
@@ -147,35 +147,37 @@ npm install axios
Then, in the component you want to add the subscription form add the following code: Then, in the component you want to add the subscription form add the following code:
```jsx ```jsx
import axios from 'axios' import axios from "axios"
import { useState } from "react"; import { useState } from "react"
export default function NewsletterForm() { export default function NewsletterForm() {
const [email, setEmail] = useState("") const [email, setEmail] = useState("")
function subscribe(e) { function subscribe(e) {
e.preventDefault(); e.preventDefault()
if (!email) { if (!email) {
return; return
} }
axios.post('http://localhost:9000/mailchimp/subscribe', { axios.post("http://localhost:9000/mailchimp/subscribe", {
email email,
}) })
.then((e) => { .then((e) => {
alert("Subscribed sucessfully!") alert("Subscribed sucessfully!")
setEmail("") setEmail("")
}) })
.catch((e) => { .catch((e) => {
console.error(e); console.error(e)
alert("An error occurred"); alert("An error occurred")
}) })
} }
return ( return (
<form onSubmit={subscribe}> <form onSubmit={subscribe}>
<h2>Sign Up for our newsletter</h2> <h2>Sign Up for our newsletter</h2>
<input type="email" name="email" id="email" placeholder="example@gmail.com" <input
type="email" name="email" id="email"
placeholder="example@gmail.com"
value={email} onChange={(e) => setEmail(e.target.value)} /> value={email} onChange={(e) => setEmail(e.target.value)} />
<button type="submit">Subscribe</button> <button type="submit">Subscribe</button>
</form> </form>
+38 -20
View File
@@ -57,11 +57,12 @@ Finally, in `medusa-config.js` add the following item into the `plugins` array:
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
//... // ...
{ {
resolve: `medusa-plugin-meilisearch`, resolve: `medusa-plugin-meilisearch`,
options: { options: {
// config object passed when creating an instance of the MeiliSearch client // config object passed when creating an instance
// of the MeiliSearch client
config: { config: {
host: process.env.MEILISEARCH_HOST, host: process.env.MEILISEARCH_HOST,
apiKey: process.env.MEILISEARCH_API_KEY, apiKey: process.env.MEILISEARCH_API_KEY,
@@ -71,12 +72,18 @@ const plugins = [
products: { products: {
// MeiliSearch's setting options to be set on a particular index // MeiliSearch's setting options to be set on a particular index
searchableAttributes: ["title", "description", "variant_sku"], searchableAttributes: ["title", "description", "variant_sku"],
displayedAttributes: ["title", "description", "variant_sku", "thumbnail", "handle"], displayedAttributes: [
"title",
"description",
"variant_sku",
"thumbnail",
"handle",
],
}, },
}, },
}, },
}, },
]; ]
``` ```
You can change the `searchableAttributes` and `displayedAttributes` as you see fit. However, the attributes included are the recommended attributes. You can change the `searchableAttributes` and `displayedAttributes` as you see fit. However, the attributes included are the recommended attributes.
@@ -220,7 +227,7 @@ import {
Hits, Hits,
InstantSearch, InstantSearch,
SearchBox, SearchBox,
connectStateResults connectStateResults,
} from "react-instantsearch-dom" } from "react-instantsearch-dom"
import React from "react" import React from "react"
@@ -232,19 +239,28 @@ const searchClient = instantMeiliSearch(
) )
const Search = () => { const Search = () => {
const Results = connectStateResults(({ searchState, searchResults, children }) => const Results = connectStateResults(
searchState && searchState.query && searchResults && searchResults.nbHits !== 0 ? ( ({ searchState, searchResults, children }) => {
<div className="absolute top-full w-full p-2 bg-gray-200 shadow-md"> return (
{children} searchState && searchState.query &&
</div> searchResults && searchResults.nbHits !== 0 ?
) : ( (
<div></div> <div
) className="absolute top-full w-full p-2 bg-gray-200 shadow-md">
); {children}
</div>
) : (
<div></div>
)
)
}
)
return ( return (
<div className="relative"> <div className="relative">
<InstantSearch indexName={process.env.GATSBY_SEARCH_INDEX_NAME} searchClient={searchClient}> <InstantSearch
indexName={process.env.GATSBY_SEARCH_INDEX_NAME}
searchClient={searchClient}>
<SearchBox submit={null} reset={null} /> <SearchBox submit={null} reset={null} />
<Results> <Results>
<Hits hitComponent={Hit} /> <Hits hitComponent={Hit} />
@@ -264,7 +280,7 @@ const Hit = ({ hit }) => {
) )
} }
export default Search; export default Search
``` ```
This file uses the dependencies you installed to show the search results. It also initializes MeiliSearch using the environment variables you added. This file uses the dependencies you installed to show the search results. It also initializes MeiliSearch using the environment variables you added.
@@ -284,10 +300,12 @@ import Search from "./search"
And add the `Search` component in the returned JSX before `RegionPopover`: And add the `Search` component in the returned JSX before `RegionPopover`:
```jsx title=src/components/header/index.jsx ```jsx title=src/components/header/index.jsx
//... // ...
<Search /> <div className="...">
<RegionPopover regions={mockData.regions} /> <Search />
//... <RegionPopover regions={mockData.regions} />
</div>
// ...
``` ```
If you run your Gatsby storefront while the Medusa server and the MeiliSearch instance are running, you should find a search bar in the header of the page. Try entering a query to search through the products in your store. If you run your Gatsby storefront while the Medusa server and the MeiliSearch instance are running, you should find a search bar in the header of the page. Try entering a query to search through the products in your store.
+22 -13
View File
@@ -104,7 +104,9 @@ Where `<ENDPOINT>` is the URL of your MinIO server, `<BUCKET>` is the name of th
Finally, configure your `medusa-config.js` to include the plugin with the required options: Finally, configure your `medusa-config.js` to include the plugin with the required options:
```js title=medusa-config.js ```js title=medusa-config.js
{ const plugins = [
// ...
{
resolve: `medusa-file-minio`, resolve: `medusa-file-minio`,
options: { options: {
endpoint: process.env.MINIO_ENDPOINT, endpoint: process.env.MINIO_ENDPOINT,
@@ -112,7 +114,8 @@ Finally, configure your `medusa-config.js` to include the plugin with the requ
access_key_id: process.env.MINIO_ACCESS_KEY, access_key_id: process.env.MINIO_ACCESS_KEY,
secret_access_key: process.env.MINIO_SECRET_KEY, secret_access_key: process.env.MINIO_SECRET_KEY,
}, },
}, },
]
``` ```
:::caution :::caution
@@ -156,13 +159,16 @@ MINIO_PRIVATE_BUCKET=exports
Then, add a new option to the plugin’s options in `medusa-config.js`: Then, add a new option to the plugin’s options in `medusa-config.js`:
```jsx title=medusa-config.js ```jsx title=medusa-config.js
{ const plugins = [
// ...
{
resolve: `medusa-file-minio`, resolve: `medusa-file-minio`,
options: { options: {
//... // ...
private_bucket: process.env.MINIO_PRIVATE_BUCKET private_bucket: process.env.MINIO_PRIVATE_BUCKET,
}, },
}, },
]
``` ```
### Use Different Secret and Access Keys ### Use Different Secret and Access Keys
@@ -181,14 +187,17 @@ Where `<YOUR_PRIVATE_ACCESS_KEY>` and `<YOUR_PRIVATE_SECRET_KEY>` are the access
Then, add two new options to the plugin’s options in `medusa-config.js`: Then, add two new options to the plugin’s options in `medusa-config.js`:
```jsx title=medusa-config.js ```jsx title=medusa-config.js
{ const plugins = [
// ...
{
resolve: `medusa-file-minio`, resolve: `medusa-file-minio`,
options: { options: {
//... // ...
private_access_key_id: process.env.MINIO_PRIVATE_ACCESS_KEY, private_access_key_id: process.env.MINIO_PRIVATE_ACCESS_KEY,
private_secret_access_key: process.env.MINIO_PRIVATE_SECRET_KEY private_secret_access_key: process.env.MINIO_PRIVATE_SECRET_KEY,
}, },
}, },
]
``` ```
--- ---
@@ -204,13 +213,13 @@ In `next.config.js` add the following option in the exported object:
```jsx title=next.config.js ```jsx title=next.config.js
const { withStoreConfig } = require("./store-config") const { withStoreConfig } = require("./store-config")
//... // ...
module.exports = withStoreConfig({ module.exports = withStoreConfig({
//... // ...
images: { images: {
domains: [ domains: [
//... // ...
"127.0.0.1", "127.0.0.1",
], ],
}, },
+53 -41
View File
@@ -57,17 +57,17 @@ Then, in `medusa-config.js`, add the PayPal plugin to the `plugins` array with t
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
//other plugins... // other plugins...
{ {
resolve: `medusa-payment-paypal`, resolve: `medusa-payment-paypal`,
options: { options: {
sandbox: process.env.PAYPAL_SANDBOX, sandbox: process.env.PAYPAL_SANDBOX,
client_id: process.env.PAYPAL_CLIENT_ID, client_id: process.env.PAYPAL_CLIENT_ID,
client_secret: process.env.PAYPAL_CLIENT_SECRET, client_secret: process.env.PAYPAL_CLIENT_SECRET,
auth_webhook_id: process.env.PAYPAL_AUTH_WEBHOOK_ID auth_webhook_id: process.env.PAYPAL_AUTH_WEBHOOK_ID,
} },
} },
]; ]
``` ```
That’s all you need to install PayPal on your Medusa server! That’s all you need to install PayPal on your Medusa server!
@@ -158,12 +158,15 @@ npm install @paypal/react-paypal-js
Next, create a new file `src/components/payment/paypal-payment/index.jsx` with the following content: Next, create a new file `src/components/payment/paypal-payment/index.jsx` with the following content:
```jsx title=src/components/payment/paypal-payment/index.jsx ```jsx title=src/components/payment/paypal-payment/index.jsx
import { PayPalButtons, PayPalScriptProvider } from "@paypal/react-paypal-js"; import {
import React, { useMemo, useState } from "react"; PayPalButtons,
PayPalScriptProvider,
} from "@paypal/react-paypal-js"
import React, { useMemo, useState } from "react"
import { navigate } from "gatsby" import { navigate } from "gatsby"
import { useCart } from "../../../hooks/use-cart" import { useCart } from "../../../hooks/use-cart"
import { useMedusa } from "../../../hooks/use-medusa"; import { useMedusa } from "../../../hooks/use-medusa"
const paypalClientId = process.env.GATSBY_PAYPAL_CLIENT_ID || "" const paypalClientId = process.env.GATSBY_PAYPAL_CLIENT_ID || ""
@@ -179,7 +182,7 @@ const PaypalPayment = () => {
const paypalSession = useMemo(() => { const paypalSession = useMemo(() => {
if (cart.payment_sessions) { if (cart.payment_sessions) {
return cart.payment_sessions.find(s => s.provider_id === "paypal") return cart.payment_sessions.find((s) => s.provider_id === "paypal")
} }
return null return null
@@ -200,10 +203,10 @@ const PaypalPayment = () => {
await client.carts.updatePaymentSession(cart.id, "paypal", { await client.carts.updatePaymentSession(cart.id, "paypal", {
data: { data: {
data: { data: {
...authorizationOrder ...authorizationOrder,
} },
} },
}); })
const order = await completeCart(cart.id) const order = await completeCart(cart.id)
@@ -218,10 +221,12 @@ const PaypalPayment = () => {
const handlePayment = (data, actions) => { const handlePayment = (data, actions) => {
actions.order.authorize().then((authorization) => { actions.order.authorize().then((authorization) => {
if (authorization.status !== 'COMPLETED') { if (authorization.status !== "COMPLETED") {
setErrorMessage(`An error occurred, status: ${authorization.status}`); setErrorMessage(
setProcessing(false); `An error occurred, status: ${authorization.status}`
return; )
setProcessing(false)
return
} }
completeOrder(authorization) completeOrder(authorization)
@@ -232,7 +237,7 @@ const PaypalPayment = () => {
<PayPalScriptProvider options={{ <PayPalScriptProvider options={{
"client-id": paypalClientId, "client-id": paypalClientId,
"currency": cart.region.currency_code.toUpperCase(), "currency": cart.region.currency_code.toUpperCase(),
"intent": "authorize" "intent": "authorize",
}}> }}>
{errorMessage && ( {errorMessage && (
<span className="text-rose-500 mt-4">{errorMessage}</span> <span className="text-rose-500 mt-4">{errorMessage}</span>
@@ -246,7 +251,7 @@ const PaypalPayment = () => {
) )
} }
export default PaypalPayment; export default PaypalPayment
``` ```
Here’s briefly what this code snippet does: Here’s briefly what this code snippet does:
@@ -264,9 +269,11 @@ In `src/components/payment/index.js` you’ll find in the return statement a swi
```jsx title=src/components/payment/index.js ```jsx title=src/components/payment/index.js
switch (ps.provider_id) { switch (ps.provider_id) {
case "stripe": case "stripe":
//... // ...
break
case "manual": case "manual":
//... // ...
break
case "paypal": case "paypal":
return <PaypalPayment /> return <PaypalPayment />
default: default:
@@ -303,28 +310,33 @@ Then, add the Client ID as an environment variable based on the framework you’
Next, create the file that will hold the PayPal component with the following content: Next, create the file that will hold the PayPal component with the following content:
```jsx ```jsx
import { PayPalButtons, PayPalScriptProvider } from "@paypal/react-paypal-js"; import {
PayPalButtons,
PayPalScriptProvider,
} from "@paypal/react-paypal-js"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import Medusa from "@medusajs/medusa-js" import Medusa from "@medusajs/medusa-js"
function Paypal() { function Paypal() {
const client = new Medusa(); const client = new Medusa()
const [errorMessage, setErrorMessage] = useState(undefined) const [errorMessage, setErrorMessage] = useState(undefined)
const [processing, setProcessing] = useState(false) const [processing, setProcessing] = useState(false)
const cart //TODO retrieve the cart here const cart = "..." // TODO retrieve the cart here
const handlePayment = (data, actions) => { const handlePayment = (data, actions) => {
actions.order.authorize().then(async (authorization) => { actions.order.authorize().then(async (authorization) => {
if (authorization.status !== 'COMPLETED') { if (authorization.status !== "COMPLETED") {
setErrorMessage(`An error occurred, status: ${authorization.status}`); setErrorMessage(
setProcessing(false); `An error occurred, status: ${authorization.status}`
return; )
setProcessing(false)
return
} }
const response = await client.carts.setPaymentSession(cart.id, { const response = await client.carts.setPaymentSession(cart.id, {
"provider_id": "paypal" "provider_id": "paypal",
}); })
if (!response.cart) { if (!response.cart) {
setProcessing(false) setProcessing(false)
@@ -334,30 +346,30 @@ function Paypal() {
await client.carts.updatePaymentSession(cart.id, "paypal", { await client.carts.updatePaymentSession(cart.id, "paypal", {
data: { data: {
data: { data: {
...authorization ...authorization,
} },
} },
}); })
const {data} = await client.carts.complete(cart.id) const { data } = await client.carts.complete(cart.id)
if (!data || data.object !== "order") { if (!data || data.object !== "order") {
setProcessing(false) setProcessing(false)
return return
} }
//order successful // order successful
alert("success") alert("success")
}) })
} }
return ( return (
<div style={{marginTop: "10px", marginLeft: "10px"}}> <div style={{ marginTop: "10px", marginLeft: "10px" }}>
{cart !== undefined && ( {cart !== undefined && (
<PayPalScriptProvider options={{ <PayPalScriptProvider options={{
"client-id": <CLIENT_ID>, "client-id": "<CLIENT_ID>",
"currency": "EUR", "currency": "EUR",
"intent": "authorize" "intent": "authorize",
}}> }}>
{errorMessage && ( {errorMessage && (
<span className="text-rose-500 mt-4">{errorMessage}</span> <span className="text-rose-500 mt-4">{errorMessage}</span>
@@ -370,10 +382,10 @@ function Paypal() {
</PayPalScriptProvider> </PayPalScriptProvider>
)} )}
</div> </div>
); )
} }
export default Paypal; export default Paypal
``` ```
Here’s briefly what this code snippet does: Here’s briefly what this code snippet does:
+9 -9
View File
@@ -118,7 +118,7 @@ Finally, in `medusa-config.js`, add to the `plugins` array the following new ite
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
//... // ...
{ {
resolve: `medusa-file-s3`, resolve: `medusa-file-s3`,
options: { options: {
@@ -129,7 +129,7 @@ const plugins = [
secret_access_key: process.env.S3_SECRET_ACCESS_KEY, secret_access_key: process.env.S3_SECRET_ACCESS_KEY,
}, },
}, },
]; ]
``` ```
### Add AWS Configurations ### Add AWS Configurations
@@ -140,7 +140,7 @@ For example, you can pass the `region`, `access_key_id` and `secret_access_key`
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
//... // ...
{ {
resolve: `medusa-file-s3`, resolve: `medusa-file-s3`,
options: { options: {
@@ -150,10 +150,10 @@ const plugins = [
region: process.env.S3_REGION, region: process.env.S3_REGION,
access_key_id: process.env.S3_ACCESS_KEY_ID, access_key_id: process.env.S3_ACCESS_KEY_ID,
secret_access_key: process.env.S3_SECRET_ACCESS_KEY, secret_access_key: process.env.S3_SECRET_ACCESS_KEY,
} },
}, },
}, },
]; ]
``` ```
Make sure to define `S3_REGION`, `S3_ACCESS_KEY_ID`, and `S3_SECRET_ACCESS_KEY` in your environment variables first. Make sure to define `S3_REGION`, `S3_ACCESS_KEY_ID`, and `S3_SECRET_ACCESS_KEY` in your environment variables first.
@@ -198,14 +198,14 @@ In `next.config.js` add the following option in the exported object:
```jsx title=next.config.js ```jsx title=next.config.js
const { withStoreConfig } = require("./store-config") const { withStoreConfig } = require("./store-config")
//... // ...
module.exports = withStoreConfig({ module.exports = withStoreConfig({
//... // ...
images: { images: {
domains: [ domains: [
//... // ...
"<BUCKET_NAME>.s3.amazonaws.com" "<BUCKET_NAME>.s3.amazonaws.com",
], ],
}, },
}) })
+12 -12
View File
@@ -110,14 +110,14 @@ Finally, in `medusa-config.js`, add the following new item to the `plugins` arra
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
//... // ...
{ {
resolve: `medusa-plugin-segment`, resolve: `medusa-plugin-segment`,
options: { options: {
write_key: process.env.SEGMENT_WRITE_KEY, write_key: process.env.SEGMENT_WRITE_KEY,
} },
} },
]; ]
``` ```
--- ---
@@ -155,24 +155,24 @@ For example, you can add the following subscriber to listen to the `customer.cre
```jsx title=src/subscribers/customer.ts ```jsx title=src/subscribers/customer.ts
class CustomerSubscriber { class CustomerSubscriber {
constructor({ segmentService, eventBusService }) { constructor({ segmentService, eventBusService }) {
this.segmentService = segmentService; this.segmentService = segmentService
eventBusService.subscribe("customer.created", this.handleCustomer); eventBusService.subscribe("customer.created", this.handleCustomer)
} }
handleCustomer = async (data) => { handleCustomer = async (data) => {
const customerData = data; const customerData = data
delete customerData['password_hash']; delete customerData["password_hash"]
this.segmentService.track({ this.segmentService.track({
event: 'Customer Created', event: "Customer Created",
userId: data.id, userId: data.id,
properties: customerData properties: customerData,
}) })
}; }
} }
export default CustomerSubscriber; export default CustomerSubscriber
``` ```
You resolve the `SegmentService` using dependency injection. Then, when the `customer.created` event is triggered, you use the `track` method available in the `SegmentService` to send tracking data to Segment. You resolve the `SegmentService` using dependency injection. Then, when the `customer.created` event is triggered, you use the `track` method available in the `SegmentService` to send tracking data to Segment.
+8 -7
View File
@@ -3928,7 +3928,7 @@ Finally, in your `medusa-config.js` file, add the SendGrid plugin into the array
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
..., // ...,
{ {
resolve: `medusa-plugin-sendgrid`, resolve: `medusa-plugin-sendgrid`,
options: { options: {
@@ -3937,12 +3937,13 @@ const plugins = [
order_placed_template: process.env.SENDGRID_ORDER_PLACED_ID, order_placed_template: process.env.SENDGRID_ORDER_PLACED_ID,
localization: { localization: {
"de-DE": { // locale key "de-DE": { // locale key
order_placed_template: process.env.SENDGRID_ORDER_PLACED_ID_LOCALIZED, order_placed_template:
} process.env.SENDGRID_ORDER_PLACED_ID_LOCALIZED,
} },
} },
} },
]; },
]
``` ```
The `api_key` and `from` options are required. Then, use the key of each template you create (from the [reference](#template-reference)) as the option name with the template ID as the value. The `api_key` and `from` options are required. Then, use the key of each template you create (from the [reference](#template-reference)) as the option name with the template ID as the value.
+5 -5
View File
@@ -83,16 +83,16 @@ After that, open `medusa-config.js` and add the new plugin with its configuratio
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
..., // ...,
{ {
resolve: `medusa-plugin-slack-notification`, resolve: `medusa-plugin-slack-notification`,
options: { options: {
show_discount_code: false, show_discount_code: false,
slack_url: `<WEBHOOK_URL>`, slack_url: `<WEBHOOK_URL>`,
admin_orders_url: `http://localhost:7001/a/orders` admin_orders_url: `http://localhost:7001/a/orders`,
} },
} },
]; ]
``` ```
- Make sure to change `<WEBHOOK_URL>` with the Webhook URL you copied after creating the Slack app. - Make sure to change `<WEBHOOK_URL>` with the Webhook URL you copied after creating the Slack app.
+6 -6
View File
@@ -105,7 +105,7 @@ Finally, in `medusa-config.js` add a new item to the `plugins` array:
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
//... // ...
{ {
resolve: `medusa-file-spaces`, resolve: `medusa-file-spaces`,
options: { options: {
@@ -116,7 +116,7 @@ const plugins = [
secret_access_key: process.env.SPACE_SECRET_ACCESS_KEY, secret_access_key: process.env.SPACE_SECRET_ACCESS_KEY,
}, },
}, },
]; ]
``` ```
:::caution :::caution
@@ -158,14 +158,14 @@ In `next.config.js` add the following option in the exported object:
```jsx title=next.config.js ```jsx title=next.config.js
const { withStoreConfig } = require("./store-config") const { withStoreConfig } = require("./store-config")
//... // ...
module.exports = withStoreConfig({ module.exports = withStoreConfig({
//... // ...
images: { images: {
domains: [ domains: [
//... // ...
"<YOUR_SPACE_DOMAIN>" "<YOUR_SPACE_DOMAIN>",
], ],
}, },
}) })
+11 -11
View File
@@ -94,10 +94,10 @@ Once the command is done executing, change to the newly created `medusa-server`
module.exports = { module.exports = {
projectConfig: { projectConfig: {
redis_url: REDIS_URL, redis_url: REDIS_URL,
//... // ...
} },
//... // ...
}; }
``` ```
This uses the default Redis configurations. If you want to learn more about configuring Redis, [check out this documentation](../usage/configurations.md#redis). This uses the default Redis configurations. If you want to learn more about configuring Redis, [check out this documentation](../usage/configurations.md#redis).
@@ -140,18 +140,18 @@ Finally, open `medusa-config.js` and add the following new item to the `plugins`
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
//... // ...
{ {
resolve: `medusa-plugin-strapi`, resolve: `medusa-plugin-strapi`,
options: { options: {
strapi_medusa_user: process.env.STRAPI_USER, strapi_medusa_user: process.env.STRAPI_USER,
strapi_medusa_password: process.env.STRAPI_PASSWORD, strapi_medusa_password: process.env.STRAPI_PASSWORD,
strapi_url: process.env.STRAPI_URL, //optional strapi_url: process.env.STRAPI_URL, // optional
strapi_port: process.env.STRAPI_PORT, //optional strapi_port: process.env.STRAPI_PORT, // optional
strapi_protocol: process.env.STRAPI_PROTOCOL //optional strapi_protocol: process.env.STRAPI_PROTOCOL, // optional
} },
} },
]; ]
``` ```
--- ---
+42 -36
View File
@@ -48,7 +48,7 @@ In `medusa-config.js` add the following at the end of the `plugins` array:
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
..., // ...
{ {
resolve: `medusa-payment-stripe`, resolve: `medusa-payment-stripe`,
options: { options: {
@@ -56,7 +56,7 @@ const plugins = [
webhook_secret: process.env.STRIPE_WEBHOOK_SECRET, webhook_secret: process.env.STRIPE_WEBHOOK_SECRET,
}, },
}, },
]; ]
``` ```
:::note :::note
@@ -75,7 +75,7 @@ You’ll first retrieve the API key. You can find it by choosing API Keys from t
Next, you need to add the key to your environment variables. In your Medusa server, create `.env` if it doesn’t already exist and add the Stripe key: Next, you need to add the key to your environment variables. In your Medusa server, create `.env` if it doesn’t already exist and add the Stripe key:
```jsx ```bash
STRIPE_API_KEY=sk_... STRIPE_API_KEY=sk_...
``` ```
@@ -95,7 +95,7 @@ Then, you can add a description. You must select at least one event to listen to
After the Webhook is created, you’ll see "Signing secret" in the Webhook details. Click on "Reveal" to reveal the secret key. Copy that key and in your Medusa server add the Webhook secret environment variable: After the Webhook is created, you’ll see "Signing secret" in the Webhook details. Click on "Reveal" to reveal the secret key. Copy that key and in your Medusa server add the Webhook secret environment variable:
```jsx ```bash
STRIPE_WEBHOOK_SECRET=whsec_... STRIPE_WEBHOOK_SECRET=whsec_...
``` ```
@@ -195,30 +195,30 @@ In this section, you’ll initialize Stripe without Medusa’s checkout workflow
Create a container component that will hold the payment card component: Create a container component that will hold the payment card component:
```jsx ```jsx
import { useState } from 'react'; import { useState } from "react"
import {Elements} from '@stripe/react-stripe-js'; import { Elements } from "@stripe/react-stripe-js"
import Form from './Form'; import Form from "./Form"
import {loadStripe} from '@stripe/stripe-js'; import { loadStripe } from "@stripe/stripe-js"
const stripePromise = loadStripe('pk_...'); const stripePromise = loadStripe("pk_...")
export default function Container() { export default function Container() {
const [clientSecret, setClientSecret] = useState() const [clientSecret, setClientSecret] = useState()
//TODO set clientSecret // TODO set clientSecret
return ( return (
<div> <div>
{clientSecret && ( {clientSecret && (
<Elements stripe={stripePromise} options={{ <Elements stripe={stripePromise} options={{
clientSecret clientSecret,
}}> }}>
<Form clientSecret={clientSecret} cartId={cartId} /> <Form clientSecret={clientSecret} cartId={cartId} />
</Elements> </Elements>
)} )}
</div> </div>
); )
}; };
``` ```
@@ -237,15 +237,19 @@ Once the clientSecret is set, the `Elements` Stripe component will wrap a `Form`
Create a new file for the `Form` component with the following content: Create a new file for the `Form` component with the following content:
```jsx ```jsx
import {CardElement, useElements, useStripe} from '@stripe/react-stripe-js'; import {
CardElement,
useElements,
useStripe,
} from "@stripe/react-stripe-js"
export default function Form({clientSecret, cartId}) { export default function Form({ clientSecret, cartId }) {
const stripe = useStripe(); const stripe = useStripe()
const elements = useElements(); const elements = useElements()
async function handlePayment(e) { async function handlePayment(e) {
e.preventDefault() e.preventDefault()
//TODO handle payment // TODO handle payment
} }
return ( return (
@@ -253,7 +257,7 @@ export default function Form({clientSecret, cartId}) {
<CardElement /> <CardElement />
<button onClick={handlePayment}>Submit</button> <button onClick={handlePayment}>Submit</button>
</form> </form>
); )
}; };
``` ```
@@ -267,8 +271,8 @@ You’ll now implement the workflow explained earlier. You’ll use Medusa’s J
import Medusa from "@medusajs/medusa-js" import Medusa from "@medusajs/medusa-js"
export default function Container() { export default function Container() {
const client = new Medusa(); const client = new Medusa()
... // ...
} }
``` ```
@@ -282,19 +286,21 @@ Then, in the place of the `//TODO` inside the `Container` element, initialize th
```jsx ```jsx
client.carts.createPaymentSessions(cart.id) client.carts.createPaymentSessions(cart.id)
.then(({cart}) => { .then(({ cart }) => {
//check if stripe is selected // check if stripe is selected
const isStripeAvailable = cart.payment_sessions?.some((session) => session.provider_id === 'stripe'); const isStripeAvailable = cart.payment_sessions?.some((session) => (
session.provider_id === "stripe"
))
if (!isStripeAvailable) { if (!isStripeAvailable) {
return; return
} }
//select stripe payment session // select stripe payment session
client.carts.setPaymentSession(cart.id, { client.carts.setPaymentSession(cart.id, {
provider_id: 'stripe' provider_id: "stripe",
}).then(({cart}) => { }).then(({ cart }) => {
setClientSecret(cart.payment_session.data.client_secret); setClientSecret(cart.payment_session.data.client_secret)
}); })
}) })
``` ```
@@ -314,8 +320,8 @@ As you’ll use Medusa’s client again make sure to import it and initialize it
import Medusa from "@medusajs/medusa-js" import Medusa from "@medusajs/medusa-js"
export default function Form() { export default function Form() {
const client = new Medusa(); const client = new Medusa()
... // ...
} }
``` ```
@@ -335,12 +341,12 @@ return stripe.confirmCardPayment(clientSecret, {
line1, line1,
line2, line2,
postal_code, postal_code,
} },
} },
} },
}).then(({ error, paymentIntent }) => { }).then(({ error, paymentIntent }) => {
//TODO handle errors // TODO handle errors
client.carts.complete(cartId).then(resp => console.log(resp)) client.carts.complete(cartId).then((resp) => console.log(resp))
}) })
``` ```
+13 -13
View File
@@ -52,16 +52,16 @@ Finally, add the plugin and its options in the `medusa-config.js` file to the `p
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
..., // ...
{ {
resolve: `medusa-plugin-twilio-sms`, resolve: `medusa-plugin-twilio-sms`,
options: { options: {
account_sid: process.env.TWILIO_SMS_ACCOUNT_SID, account_sid: process.env.TWILIO_SMS_ACCOUNT_SID,
auth_token: process.env.TWILIO_SMS_AUTH_TOKEN, auth_token: process.env.TWILIO_SMS_AUTH_TOKEN,
from_number: process.env.TWILIO_SMS_FROM_NUMBER from_number: process.env.TWILIO_SMS_FROM_NUMBER,
} },
} },
]; ]
``` ```
--- ---
@@ -83,27 +83,27 @@ Create the file `src/services/sms.js` in your Medusa server with the following c
```jsx title=src/services/sms.js ```jsx title=src/services/sms.js
class SmsSubscriber { class SmsSubscriber {
constructor({ twilioSmsService, orderService, eventBusService }) { constructor({ twilioSmsService, orderService, eventBusService }) {
this.twilioSmsService_ = twilioSmsService; this.twilioSmsService_ = twilioSmsService
this.orderService = orderService; this.orderService = orderService
eventBusService.subscribe("order.placed", this.sendSMS); eventBusService.subscribe("order.placed", this.sendSMS)
} }
sendSMS = async (data) => { sendSMS = async (data) => {
const order = await this.orderService.retrieve(data.id, { const order = await this.orderService.retrieve(data.id, {
relations: ['shipping_address'] relations: ["shipping_address"],
}); })
if (order.shipping_address.phone) { if (order.shipping_address.phone) {
this.twilioSmsService_.sendSms({ this.twilioSmsService_.sendSms({
to: order.shipping_address.phone, to: order.shipping_address.phone,
body: 'We have received your order #' + data.id, body: "We have received your order #" + data.id,
}) })
} }
}; }
} }
export default SmsSubscriber; export default SmsSubscriber
``` ```
In the `constructor`, you resolve the `twilioSmsService` and `orderService` using dependency injection to use it later in the `sendSMS` method. In the `constructor`, you resolve the `twilioSmsService` and `orderService` using dependency injection to use it later in the `sendSMS` method.
+39 -39
View File
@@ -68,31 +68,31 @@ You can do that by sending the following request to the [Upload Files](https://d
<TabItem value="client" label="Medusa JS Client" default> <TabItem value="client" label="Medusa JS Client" default>
```jsx ```jsx
medusa.admin.uploads.create(file) //file is an instance of File medusa.admin.uploads.create(file) // file is an instance of File
.then(({ uploads }) => { .then(({ uploads }) => {
const key = uploads[0].key; const key = uploads[0].key
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```jsx ```jsx
const formData = new FormData(); const formData = new FormData()
formData.append('files', file); //file is an instance of File formData.append("files", file) // file is an instance of File
fetch(`<YOUR_SERVER>/admin/uploads`, { fetch(`<YOUR_SERVER>/admin/uploads`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'multipart/form-data', "Content-Type": "multipart/form-data",
}, },
body: formData body: formData,
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ uploads }) => { .then(({ uploads }) => {
const key = uploads[0].key; const key = uploads[0].key
}); })
``` ```
</TabItem> </TabItem>
@@ -123,16 +123,16 @@ You can do that by sending the following request to the [Create a Batch Job](htt
```jsx ```jsx
medusa.admin.batchJobs.create({ medusa.admin.batchJobs.create({
type: 'price-list-import', type: "price-list-import",
context: { context: {
fileKey: key, //obtained from previous step fileKey: key, // obtained from previous step
price_list_id price_list_id,
}, },
dry_run: true dry_run: true,
}) })
.then(( batch_job ) => { .then(( batch_job ) => {
console.log(batch_job.status); console.log(batch_job.status)
}); })
``` ```
</TabItem> </TabItem>
@@ -140,24 +140,24 @@ medusa.admin.batchJobs.create({
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/batch-jobs`, { fetch(`<YOUR_SERVER>/admin/batch-jobs`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
type: 'price-list-import', type: "price-list-import",
context: { context: {
fileKey: key, //obtained from previous step fileKey: key, // obtained from previous step
price_list_id price_list_id,
}, },
dry_run: true dry_run: true,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ batch_job }) => { .then(({ batch_job }) => {
console.log(batch_job.status); console.log(batch_job.status)
}); })
``` ```
</TabItem> </TabItem>
@@ -212,8 +212,8 @@ You can retrieve all the details of the batch job, including its status and the
```jsx ```jsx
medusa.admin.batchJobs.retrieve(batchJobId) medusa.admin.batchJobs.retrieve(batchJobId)
.then(( batch_job ) => { .then(( batch_job ) => {
console.log(batch_job.status, batch_job.result); console.log(batch_job.status, batch_job.result)
}); })
``` ```
</TabItem> </TabItem>
@@ -221,12 +221,12 @@ medusa.admin.batchJobs.retrieve(batchJobId)
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}`, { fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ batch_job }) => { .then(({ batch_job }) => {
console.log(batch_job.status, batch_job.result); console.log(batch_job.status, batch_job.result)
}); })
``` ```
</TabItem> </TabItem>
@@ -257,7 +257,7 @@ Here’s an example of the `result` property:
"message": "5 prices will be added" "message": "5 prices will be added"
} }
], ],
"advancement_count": 0 //number of prices processed so far. Will be 0 before the import is confirmed. "advancement_count": 0 //number of prices processed so far.
}, },
``` ```
@@ -275,8 +275,8 @@ To confirm a batch job send the following request:
```jsx ```jsx
medusa.admin.batchJobs.confirm(batchJobId) medusa.admin.batchJobs.confirm(batchJobId)
.then(( batch_job ) => { .then(( batch_job ) => {
console.log(batch_job.status); console.log(batch_job.status)
}); })
``` ```
</TabItem> </TabItem>
@@ -284,13 +284,13 @@ medusa.admin.batchJobs.confirm(batchJobId)
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}/confirm`, { fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}/confirm`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ batch_job }) => { .then(({ batch_job }) => {
console.log(batch_job.status); console.log(batch_job.status)
}); })
``` ```
</TabItem> </TabItem>
+38 -38
View File
@@ -62,31 +62,31 @@ You can do that by sending the following request to the [Upload Files](https://d
<TabItem value="client" label="Medusa JS Client" default> <TabItem value="client" label="Medusa JS Client" default>
```jsx ```jsx
medusa.admin.uploads.create(file) //file is an instance of File medusa.admin.uploads.create(file) // file is an instance of File
.then(({ uploads }) => { .then(({ uploads }) => {
const key = uploads[0].key; const key = uploads[0].key
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```jsx ```jsx
const formData = new FormData(); const formData = new FormData()
formData.append('files', file); //file is an instance of File formData.append("files", file) // file is an instance of File
fetch(`<YOUR_SERVER>/admin/uploads`, { fetch(`<YOUR_SERVER>/admin/uploads`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'multipart/form-data', "Content-Type": "multipart/form-data",
}, },
body: formData body: formData,
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ uploads }) => { .then(({ uploads }) => {
const key = uploads[0].key; const key = uploads[0].key
}); })
``` ```
</TabItem> </TabItem>
@@ -117,15 +117,15 @@ You can do that by sending the following request to the [Create a Batch Job](htt
```jsx ```jsx
medusa.admin.batchJobs.create({ medusa.admin.batchJobs.create({
type: 'product-import', type: "product-import",
context: { context: {
fileKey: key //obtained from previous step fileKey: key, // obtained from previous step
}, },
dry_run: true dry_run: true,
}) })
.then(( batch_job ) => { .then(( batch_job ) => {
console.log(batch_job.status); console.log(batch_job.status)
}); })
``` ```
</TabItem> </TabItem>
@@ -133,23 +133,23 @@ medusa.admin.batchJobs.create({
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/batch-jobs`, { fetch(`<YOUR_SERVER>/admin/batch-jobs`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
type: 'product-import', type: "product-import",
context: { context: {
fileKey: key //obtained from previous step fileKey: key, // obtained from previous step
}, },
dry_run: true dry_run: true,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ batch_job }) => { .then(({ batch_job }) => {
console.log(batch_job.status); console.log(batch_job.status)
}); })
``` ```
</TabItem> </TabItem>
@@ -200,8 +200,8 @@ You can retrieve all the details of the batch job, including its status and the
```jsx ```jsx
medusa.admin.batchJobs.retrieve(batchJobId) medusa.admin.batchJobs.retrieve(batchJobId)
.then(( batch_job ) => { .then(( batch_job ) => {
console.log(batch_job.status, batch_job.result); console.log(batch_job.status, batch_job.result)
}); })
``` ```
</TabItem> </TabItem>
@@ -209,12 +209,12 @@ medusa.admin.batchJobs.retrieve(batchJobId)
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}`, { fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ batch_job }) => { .then(({ batch_job }) => {
console.log(batch_job.status, batch_job.result); console.log(batch_job.status, batch_job.result)
}); })
``` ```
</TabItem> </TabItem>
@@ -242,10 +242,10 @@ Here’s an example of the `result` property:
{ {
"key": "product-import-count", "key": "product-import-count",
"name": "Products/variants to import", "name": "Products/variants to import",
"message": "There will be 2 products created (0 updated).\n 3 variants will be created and 0 updated" "message": "There will be 2 products created..."
} }
], ],
"advancement_count": 0 //number of products processed so far. Will be 0 before the import is confirmed. "advancement_count": 0 //number of products processed so far.
}, },
``` ```
@@ -263,8 +263,8 @@ To confirm a batch job send the following request:
```jsx ```jsx
medusa.admin.batchJobs.confirm(batchJobId) medusa.admin.batchJobs.confirm(batchJobId)
.then(( batch_job ) => { .then(( batch_job ) => {
console.log(batch_job.status); console.log(batch_job.status)
}); })
``` ```
</TabItem> </TabItem>
@@ -272,13 +272,13 @@ medusa.admin.batchJobs.confirm(batchJobId)
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}/confirm`, { fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}/confirm`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ batch_job }) => { .then(({ batch_job }) => {
console.log(batch_job.status); console.log(batch_job.status)
}); })
``` ```
</TabItem> </TabItem>
@@ -49,8 +49,8 @@ You can show a list of customers by sending a request to the [List Customers](/a
```ts ```ts
medusa.admin.customers.list() medusa.admin.customers.list()
.then(({ customers, limit, offset, count }) => { .then(({ customers, limit, offset, count }) => {
console.log(customers.length); console.log(customers.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -58,12 +58,12 @@ medusa.admin.customers.list()
```ts ```ts
fetch(`<SERVER_URL>/admin/customers`, { fetch(`<SERVER_URL>/admin/customers`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customers, limit, offset, count }) => { .then(({ customers, limit, offset, count }) => {
console.log(customers.length); console.log(customers.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -108,11 +108,11 @@ medusa.admin.customers.create({
email, email,
password, password,
first_name, first_name,
last_name last_name,
}) })
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -120,19 +120,19 @@ medusa.admin.customers.create({
```ts ```ts
fetch(`<SERVER_URL>/admin/customers`, { fetch(`<SERVER_URL>/admin/customers`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
email, email,
password, password,
first_name, first_name,
last_name last_name,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -177,11 +177,11 @@ You can edit a customer’s information by sending a request to the [Update a Cu
```ts ```ts
medusa.admin.customers.update(customerId, { medusa.admin.customers.update(customerId, {
first_name first_name,
}) })
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -189,16 +189,16 @@ medusa.admin.customers.update(customerId, {
```ts ```ts
fetch(`<SERVER_URL>/admin/customers/${customerId}`, { fetch(`<SERVER_URL>/admin/customers/${customerId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
first_name first_name,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -60,21 +60,21 @@ You can create a discount by sending a request to the [Create Discount](/api/adm
```jsx ```jsx
import { AllocationType, DiscountRuleType } from "@medusajs/medusa" import { AllocationType, DiscountRuleType } from "@medusajs/medusa"
//... // ...
medusa.admin.discounts.create({ medusa.admin.discounts.create({
code, code,
rule: { rule: {
type: DiscountRuleType.FIXED, type: DiscountRuleType.FIXED,
value: 10, value: 10,
allocation: AllocationType.ITEM allocation: AllocationType.ITEM,
}, },
regions: [ regions: [
regionId regionId,
] ],
}) })
.then(({ discount }) => { .then(({ discount }) => {
console.log(discount.id); console.log(discount.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -82,27 +82,27 @@ medusa.admin.discounts.create({
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/discounts`, { fetch(`<YOUR_SERVER>/admin/discounts`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
code, code,
rule: { rule: {
type: 'fixed', type: "fixed",
value: 10, value: 10,
allocation: 'item' allocation: "item",
}, },
regions: [ regions: [
regionId regionId,
] ],
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ discount }) => { .then(({ discount }) => {
console.log(discount.id); console.log(discount.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -153,11 +153,11 @@ For example, you can update the discount’s description and status by sending t
```jsx ```jsx
medusa.admin.discounts.update(discountId, { medusa.admin.discounts.update(discountId, {
description: "New description", description: "New description",
is_disabled: true is_disabled: true,
}) })
.then(({ discount }) => { .then(({ discount }) => {
console.log(discount.id); console.log(discount.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -165,20 +165,20 @@ medusa.admin.discounts.update(discountId, {
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/discounts/${discountId}`, { fetch(`<YOUR_SERVER>/admin/discounts/${discountId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
description: "New description", description: "New description",
is_disabled: true is_disabled: true,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ discount }) => { .then(({ discount }) => {
console.log(discount.id); console.log(discount.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -222,16 +222,16 @@ You can send a request to the [Create Condition](/api/admin/#tag/Discount-Condit
```jsx ```jsx
import { DiscountConditionOperator } from "@medusajs/medusa" import { DiscountConditionOperator } from "@medusajs/medusa"
//... // ...
medusa.admin.discounts.createCondition(discount_id, { medusa.admin.discounts.createCondition(discount_id, {
operator: DiscountConditionOperator.IN, operator: DiscountConditionOperator.IN,
products: [ products: [
productId productId,
] ],
}) })
.then(({ discount }) => { .then(({ discount }) => {
console.log(discount.id); console.log(discount.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -239,22 +239,22 @@ medusa.admin.discounts.createCondition(discount_id, {
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/discounts/${discountId}/conditions`, { fetch(`<YOUR_SERVER>/admin/discounts/${discountId}/conditions`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
operator: 'in', operator: "in",
products: [ products: [
productId productId,
] ],
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ discount }) => { .then(({ discount }) => {
console.log(discount.id); console.log(discount.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -301,24 +301,28 @@ You can retrieve a condition and its resources by sending a request to the [Get
```jsx ```jsx
medusa.admin.discounts.getCondition(discountId, conditionId, { medusa.admin.discounts.getCondition(discountId, conditionId, {
expand: 'products' expand: "products",
}) })
.then(({ discount_condition }) => { .then(({ discount_condition }) => {
console.log(discount_condition.id, discount_condition.products); console.log(discount_condition.id, discount_condition.products)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/discounts/${discountId}/conditions/${conditionId}&expand=products`, { fetch(
credentials: 'include' `<YOUR_SERVER>/admin/discounts/${discountId}` +
}) `/conditions/${conditionId}&expand=products`,
{
credentials: "include",
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ discount_condition }) => { .then(({ discount_condition }) => {
console.log(discount_condition.id, discount_condition.products); console.log(discount_condition.id, discount_condition.products)
}); })
``` ```
</TabItem> </TabItem>
@@ -351,35 +355,38 @@ For example, to update the products in a condition:
medusa.admin.discounts.updateCondition(discountId, conditionId, { medusa.admin.discounts.updateCondition(discountId, conditionId, {
products: [ products: [
productId1, productId1,
productId2 productId2,
] ],
}) })
.then(({ discount }) => { .then(({ discount }) => {
console.log(discount.id); console.log(discount.id)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/discounts/${discountId}/conditions/${conditionId}`, { fetch(
method: 'POST', `<YOUR_SERVER>/admin/discounts/${discountId}/conditions/${conditionId}`,
credentials: 'include', {
headers: { method: "POST",
'Content-Type': 'application/json' credentials: "include",
}, headers: {
body: JSON.stringify({ "Content-Type": "application/json",
products: [ },
productId1, body: JSON.stringify({
productId2 products: [
] productId1,
}) productId2,
}) ],
}),
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ discount }) => { .then(({ discount }) => {
console.log(discount.id); console.log(discount.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -416,22 +423,25 @@ You can delete a condition by sending a request to the [Delete Condition](/api/a
```jsx ```jsx
medusa.admin.discounts.deleteCondition(discountId, conditionId) medusa.admin.discounts.deleteCondition(discountId, conditionId)
.then(({ discount }) => { .then(({ discount }) => {
console.log(discount); console.log(discount)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/discounts/${discountId}/conditions/${conditionId}`, { fetch(
method: 'DELETE', `<YOUR_SERVER>/admin/discounts/${discountId}/conditions/${conditionId}`,
credentials: 'include' {
}) method: "DELETE",
credentials: "include",
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ discount }) => { .then(({ discount }) => {
console.log(discount.id); console.log(discount.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -461,8 +471,8 @@ You can delete a discount by sending a request to the [Delete Discount](/api/adm
```jsx ```jsx
medusa.admin.discounts.delete(discount_id) medusa.admin.discounts.delete(discount_id)
.then(({ id, object, deleted }) => { .then(({ id, object, deleted }) => {
console.log(id); console.log(id)
}); })
``` ```
</TabItem> </TabItem>
@@ -470,13 +480,13 @@ medusa.admin.discounts.delete(discount_id)
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/discounts/${discountId}`, { fetch(`<YOUR_SERVER>/admin/discounts/${discountId}`, {
method: 'DELETE', method: "DELETE",
credentials: 'include' credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ id, object, deleted }) => { .then(({ id, object, deleted }) => {
console.log(id); console.log(id)
}); })
``` ```
</TabItem> </TabItem>
+60 -60
View File
@@ -57,9 +57,9 @@ You can retrieve regions available on your server using the [List Regions](/api/
```tsx ```tsx
medusa.admin.regions.list() medusa.admin.regions.list()
.then(({ regions, limit, offset, count }) => { .then(({ regions, limit, offset, count }) => {
console.log(regions.length); console.log(regions.length)
//display regions // display regions
}); })
``` ```
</TabItem> </TabItem>
@@ -67,13 +67,13 @@ medusa.admin.regions.list()
```tsx ```tsx
fetch(`<SERVER_URL>/admin/regions`, { fetch(`<SERVER_URL>/admin/regions`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ regions, limit, offset, count }) => { .then(({ regions, limit, offset, count }) => {
console.log(regions.length); console.log(regions.length)
//display regions // display regions
}); })
``` ```
</TabItem> </TabItem>
@@ -102,22 +102,22 @@ You can create a region by sending a request to the [Create a Region](/api/admin
```tsx ```tsx
medusa.admin.regions.create({ medusa.admin.regions.create({
name: 'Europe', name: "Europe",
currency_code: 'eur', currency_code: "eur",
tax_rate: 0, tax_rate: 0,
payment_providers: [ payment_providers: [
'manual' "manual",
], ],
fulfillment_providers: [ fulfillment_providers: [
'manual' "manual",
], ],
countries: [ countries: [
'DK' "DK",
] ],
}) })
.then(({ region }) => { .then(({ region }) => {
console.log(region.id); console.log(region.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -125,27 +125,27 @@ medusa.admin.regions.create({
```tsx ```tsx
fetch(`<SERVER_URL>/admin/regions`, { fetch(`<SERVER_URL>/admin/regions`, {
credentials: 'include', credentials: "include",
method: 'POST', method: "POST",
body: JSON.stringify({ body: JSON.stringify({
name: 'Europe', name: "Europe",
currency_code: 'eur', currency_code: "eur",
tax_rate: 0, tax_rate: 0,
payment_providers: [ payment_providers: [
'manual' "manual",
], ],
fulfillment_providers: [ fulfillment_providers: [
'manual' "manual",
], ],
countries: [ countries: [
'DK' "DK",
] ],
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ region }) => { .then(({ region }) => {
console.log(region.id); console.log(region.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -202,12 +202,12 @@ Alternatively, you can update the details of a region using the [Update a Region
medusa.admin.regions.update(regionId, { medusa.admin.regions.update(regionId, {
countries: [ countries: [
"DK", "DK",
"DE" "DE",
] ],
}) })
.then(({ region }) => { .then(({ region }) => {
console.log(region.id); console.log(region.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -215,25 +215,25 @@ medusa.admin.regions.update(regionId, {
```tsx ```tsx
fetch(`<SERVER_URL>/admin/regions/${regionId}`, { fetch(`<SERVER_URL>/admin/regions/${regionId}`, {
credentials: 'include', credentials: "include",
method: 'POST', method: "POST",
body: JSON.stringify({ body: JSON.stringify({
countries: [ countries: [
'DK', "DK",
'DE' "DE",
] ],
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ region }) => { .then(({ region }) => {
console.log(region.id); console.log(region.id)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="curl" label="cURL"> <TabItem value="curl" label="cURL">
```tsx ```bash
curl -L -X POST '<SERVER_URL>/admin/regions/<REGION_ID>' \ curl -L -X POST '<SERVER_URL>/admin/regions/<REGION_ID>' \
-H 'Authorization: Bearer <API_TOKEN>' \ -H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \ -H 'Content-Type: application/json' \
@@ -271,17 +271,17 @@ You can add a shipping option to a region by sending a request to the [Create Sh
```tsx ```tsx
medusa.admin.shippingOptions.create({ medusa.admin.shippingOptions.create({
name: 'PostFake', name: "PostFake",
region_id: regionId, region_id: regionId,
provider_id: "manual", provider_id: "manual",
data: { data: {
}, },
price_type: 'flat_rate', price_type: "flat_rate",
amount: 1000 amount: 1000,
}) })
.then(({ shipping_option }) => { .then(({ shipping_option }) => {
console.log(shipping_option.id); console.log(shipping_option.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -289,28 +289,28 @@ medusa.admin.shippingOptions.create({
```tsx ```tsx
fetch(`<SERVER_URL>/admin/shipping-options`, { fetch(`<SERVER_URL>/admin/shipping-options`, {
credentials: 'include', credentials: "include",
method: 'POST', method: "POST",
body: JSON.stringify({ body: JSON.stringify({
name: 'PostFake', name: "PostFake",
region_id: regionId, region_id: regionId,
provider_id: "manual", provider_id: "manual",
price_type: 'flat_rate', price_type: "flat_rate",
data: { data: {
}, },
amount: 1000 amount: 1000,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ shipping_option }) => { .then(({ shipping_option }) => {
console.log(shipping_option.id); console.log(shipping_option.id)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="curl" label="cURL"> <TabItem value="curl" label="cURL">
```tsx ```bash
curl -L -X POST '<SERVER_URL>/admin/shipping-options' \ curl -L -X POST '<SERVER_URL>/admin/shipping-options' \
-H 'Authorization: Bearer <API_TOKEN>' \ -H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \ -H 'Content-Type: application/json' \
@@ -360,8 +360,8 @@ You can delete a region by sending a request to the [Delete a Region](/api/admin
```tsx ```tsx
medusa.admin.regions.delete(regionId) medusa.admin.regions.delete(regionId)
.then(({ id, object, deleted }) => { .then(({ id, object, deleted }) => {
console.log(id); console.log(id)
}); })
``` ```
</TabItem> </TabItem>
@@ -369,19 +369,19 @@ medusa.admin.regions.delete(regionId)
```tsx ```tsx
fetch(`<SERVER_URL>/admin/regions/${regionId}`, { fetch(`<SERVER_URL>/admin/regions/${regionId}`, {
credentials: 'include', credentials: "include",
method: 'DELETE' method: "DELETE",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ id, object, deleted }) => { .then(({ id, object, deleted }) => {
console.log(id); console.log(id)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="curl" label="cURL"> <TabItem value="curl" label="cURL">
```tsx ```bash
curl -L -X DELETE '<SERVER_URL>/admin/regions/<REGION_ID>' \ curl -L -X DELETE '<SERVER_URL>/admin/regions/<REGION_ID>' \
-H 'Authorization: Bearer <API_TOKEN>' -H 'Authorization: Bearer <API_TOKEN>'
``` ```
+66 -63
View File
@@ -87,11 +87,11 @@ To do that, send a request to the [Create an OrderEdit](/api/admin/#tag/OrderEdi
```ts ```ts
medusa.admin.orderEdits.create({ medusa.admin.orderEdits.create({
order_id, //required order_id, // required
}) })
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.id); console.log(order_edit.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -99,19 +99,19 @@ medusa.admin.orderEdits.create({
```ts ```ts
fetch(`<YOUR_SERVER>/admin/order-edits`, { fetch(`<YOUR_SERVER>/admin/order-edits`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
order_id order_id,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.id); console.log(order_edit.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -161,11 +161,11 @@ To add a new item to the original order, send a request to the [Add Line Item](/
```ts ```ts
medusa.admin.orderEdits.addLineItem(orderEditId, { medusa.admin.orderEdits.addLineItem(orderEditId, {
quantity: 1, quantity: 1,
variant_id variant_id,
}) })
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.changes); console.log(order_edit.changes)
}); })
``` ```
</TabItem> </TabItem>
@@ -173,20 +173,20 @@ medusa.admin.orderEdits.addLineItem(orderEditId, {
```ts ```ts
fetch(`<YOUR_SERVER>/admin/order-edits/${orderEditId}/items`, { fetch(`<YOUR_SERVER>/admin/order-edits/${orderEditId}/items`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
quantity: 1, quantity: 1,
variant_id variant_id,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.changes); console.log(order_edit.changes)
}); })
``` ```
</TabItem> </TabItem>
@@ -225,8 +225,8 @@ medusa.admin.orderEdits.updateLineItem(orderEditId, itemId, {
quantity: 2, quantity: 2,
}) })
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.changes); console.log(order_edit.changes)
}); })
``` ```
</TabItem> </TabItem>
@@ -234,19 +234,19 @@ medusa.admin.orderEdits.updateLineItem(orderEditId, itemId, {
```ts ```ts
fetch(`<YOUR_SERVER>/admin/order-edits/${orderEditId}/items/${itemId}`, { fetch(`<YOUR_SERVER>/admin/order-edits/${orderEditId}/items/${itemId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
quantity: 2 quantity: 2,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.changes); console.log(order_edit.changes)
}); })
``` ```
</TabItem> </TabItem>
@@ -280,8 +280,8 @@ You can remove an item from the original order by sending a request to the [Remo
```ts ```ts
medusa.admin.orderEdits.removeLineItem(orderEditId, itemId) medusa.admin.orderEdits.removeLineItem(orderEditId, itemId)
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.changes); console.log(order_edit.changes)
}); })
``` ```
</TabItem> </TabItem>
@@ -289,13 +289,13 @@ medusa.admin.orderEdits.removeLineItem(orderEditId, itemId)
```ts ```ts
fetch(`<YOUR_SERVER>/admin/order-edits/${orderEditId}/items/${itemId}`, { fetch(`<YOUR_SERVER>/admin/order-edits/${orderEditId}/items/${itemId}`, {
method: 'DELETE', method: "DELETE",
credentials: 'include' credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.changes); console.log(order_edit.changes)
}); })
``` ```
</TabItem> </TabItem>
@@ -327,22 +327,25 @@ To revert an item change, send a request to the [Delete Item Change](/api/admin/
```ts ```ts
medusa.admin.orderEdits.deleteItemChange(orderEditId, changeId) medusa.admin.orderEdits.deleteItemChange(orderEditId, changeId)
.then(({ id, object, deleted }) => { .then(({ id, object, deleted }) => {
console.log(id); console.log(id)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```ts ```ts
fetch(`<YOUR_SERVER>/admin/order-edits/${orderEditId}/changes/${changeId}`, { fetch(
method: 'DELETE', `<YOUR_SERVER>/admin/order-edits/${orderEditId}/changes/${changeId}`,
credentials: 'include' {
}) method: "DELETE",
credentials: "include",
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ id, object, deleted }) => { .then(({ id, object, deleted }) => {
console.log(id, object, deleted); console.log(id, object, deleted)
}); })
``` ```
</TabItem> </TabItem>
@@ -378,7 +381,7 @@ To move an Order Edit into the request state, send a request to the [Request Con
```ts ```ts
medusa.admin.orderEdits.requestConfirmation(orderEditId) medusa.admin.orderEdits.requestConfirmation(orderEditId)
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.requested_at, order_edit.requested_by); console.log(order_edit.requested_at, order_edit.requested_by)
}) })
``` ```
@@ -387,12 +390,12 @@ medusa.admin.orderEdits.requestConfirmation(orderEditId)
```ts ```ts
fetch(`<YOUR_SERVER>/admin/order-edits/${orderEditId}/request`, { fetch(`<YOUR_SERVER>/admin/order-edits/${orderEditId}/request`, {
method: 'POST', method: "POST",
credentials: 'include' credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.requested_at, order_edit.requested_by); console.log(order_edit.requested_at, order_edit.requested_by)
}) })
``` ```
@@ -445,7 +448,7 @@ To confirm an Order Edit, send a request to the [Confirm Order Edit](/api/admin/
```ts ```ts
medusa.admin.orderEdits.confirm(orderEditId) medusa.admin.orderEdits.confirm(orderEditId)
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.confirmed_at, order_edit.confirmed_by); console.log(order_edit.confirmed_at, order_edit.confirmed_by)
}) })
``` ```
@@ -454,12 +457,12 @@ medusa.admin.orderEdits.confirm(orderEditId)
```ts ```ts
fetch(`<YOUR_SERVER>/admin/order-edits/${orderEditId}/confirm`, { fetch(`<YOUR_SERVER>/admin/order-edits/${orderEditId}/confirm`, {
method: 'POST', method: "POST",
credentials: 'include' credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.confirmed_at, order_edit.confirmed_by); console.log(order_edit.confirmed_at, order_edit.confirmed_by)
}) })
``` ```
@@ -507,7 +510,7 @@ If the payment is authorized by the customer, it can be captured by sending a re
```ts ```ts
medusa.admin.payments.capturePayment(paymentId) medusa.admin.payments.capturePayment(paymentId)
.then(({ payment }) => { .then(({ payment }) => {
console.log(payment.captured_at); console.log(payment.captured_at)
}) })
``` ```
@@ -516,12 +519,12 @@ medusa.admin.payments.capturePayment(paymentId)
```ts ```ts
fetch(`<YOUR_SERVER>/admin/payments/${paymentId}/capture`, { fetch(`<YOUR_SERVER>/admin/payments/${paymentId}/capture`, {
method: 'POST', method: "POST",
credentials: 'include' credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ payment }) => { .then(({ payment }) => {
console.log(payment.captured_at); console.log(payment.captured_at)
}) })
``` ```
@@ -551,10 +554,10 @@ To refund the difference to the customer, send a request to the [Refund Payment]
```ts ```ts
medusa.admin.payments.refundPayment(paymentId, { medusa.admin.payments.refundPayment(paymentId, {
amount amount,
}) })
.then(({ refund }) => { .then(({ refund }) => {
console.log(refund.id); console.log(refund.id)
}) })
``` ```
@@ -563,18 +566,18 @@ medusa.admin.payments.refundPayment(paymentId, {
```ts ```ts
fetch(`<YOUR_SERVER>/admin/payments/${paymentId}/refund`, { fetch(`<YOUR_SERVER>/admin/payments/${paymentId}/refund`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
amount amount,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ refund }) => { .then(({ refund }) => {
console.log(refund.id); console.log(refund.id)
}) })
``` ```
@@ -44,11 +44,11 @@ You can create a customer group by sending a request to the Create Customer Grou
```jsx ```jsx
medusa.admin.customerGroups.create({ medusa.admin.customerGroups.create({
name: 'VIP' name: "VIP",
}) })
.then(({ customer_group }) => { .then(({ customer_group }) => {
console.log(customer_group.id); console.log(customer_group.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -56,19 +56,19 @@ medusa.admin.customerGroups.create({
```jsx ```jsx
fetch(`<SERVER_URL>/admin/customer-groups`, { fetch(`<SERVER_URL>/admin/customer-groups`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
name: 'VIP' name: "VIP",
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer_group }) => { .then(({ customer_group }) => {
console.log(customer_group.id) console.log(customer_group.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -100,8 +100,8 @@ You can get a list of all customer groups by sending a request to the List Custo
```jsx ```jsx
medusa.admin.customerGroups.list() medusa.admin.customerGroups.list()
.then(({ customer_groups, limit, offset, count }) => { .then(({ customer_groups, limit, offset, count }) => {
console.log(customer_groups.length); console.log(customer_groups.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -109,12 +109,12 @@ medusa.admin.customerGroups.list()
```jsx ```jsx
fetch(`<SERVER_URL>/admin/customer-groups`, { fetch(`<SERVER_URL>/admin/customer-groups`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer_groups, limit, offset, count }) => { .then(({ customer_groups, limit, offset, count }) => {
console.log(customer_groups.length) console.log(customer_groups.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -144,8 +144,8 @@ You can retrieve a single customer group by sending a request to the Get a Custo
```jsx ```jsx
medusa.admin.customerGroups.retrieve(customerGroupId) medusa.admin.customerGroups.retrieve(customerGroupId)
.then(({ customer_group }) => { .then(({ customer_group }) => {
console.log(customer_group.id); console.log(customer_group.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -153,12 +153,12 @@ medusa.admin.customerGroups.retrieve(customerGroupId)
```jsx ```jsx
fetch(`<SERVER_URL>/admin/customer-groups/${customerGroupId}`, { fetch(`<SERVER_URL>/admin/customer-groups/${customerGroupId}`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer_group }) => { .then(({ customer_group }) => {
console.log(customer_group.id) console.log(customer_group.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -186,12 +186,12 @@ You can update a customer group’s data by sending a request to the Update Cust
```jsx ```jsx
medusa.admin.customerGroups.update(customerGroupId, { medusa.admin.customerGroups.update(customerGroupId, {
metadata: { metadata: {
is_seller: true is_seller: true,
} },
}) })
.then(({ customer_group }) => { .then(({ customer_group }) => {
console.log(customer_group.id); console.log(customer_group.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -199,21 +199,21 @@ medusa.admin.customerGroups.update(customerGroupId, {
```jsx ```jsx
fetch(`<SERVER_URL>/admin/customer-groups/${customerGroupId}`, { fetch(`<SERVER_URL>/admin/customer-groups/${customerGroupId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
metadata: { metadata: {
is_seller: true is_seller: true,
} },
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer_group }) => { .then(({ customer_group }) => {
console.log(customer_group.id) console.log(customer_group.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -247,8 +247,8 @@ You can delete a customer group by sending a request to the Delete a Customer Gr
```jsx ```jsx
medusa.admin.customerGroups.delete(customerGroupId) medusa.admin.customerGroups.delete(customerGroupId)
.then(({ id, object, deleted }) => { .then(({ id, object, deleted }) => {
console.log(id); console.log(id)
}); })
``` ```
</TabItem> </TabItem>
@@ -256,13 +256,13 @@ medusa.admin.customerGroups.delete(customerGroupId)
```jsx ```jsx
fetch(`<SERVER_URL>/admin/customer-groups/${customerGroupId}`, { fetch(`<SERVER_URL>/admin/customer-groups/${customerGroupId}`, {
method: 'DELETE', method: "DELETE",
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ id, object, deleted }) => { .then(({ id, object, deleted }) => {
console.log(id) console.log(id)
}); })
``` ```
</TabItem> </TabItem>
@@ -293,37 +293,40 @@ You can add a customer to a group by sending a request to the Customer Group’s
medusa.admin.customerGroups.addCustomers(customerGroupId, { medusa.admin.customerGroups.addCustomers(customerGroupId, {
customer_ids: [ customer_ids: [
{ {
id: customerId id: customerId,
} },
] ],
}) })
.then(({ customer_group }) => { .then(({ customer_group }) => {
console.log(customer_group.id); console.log(customer_group.id)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```jsx ```jsx
fetch(`<SERVER_URL>/admin/customer-groups/${customerGroupId}/customers/batch`, { fetch(
method: 'POST', `<SERVER_URL>/admin/customer-groups/${customerGroupId}/customers/batch`,
credentials: 'include', {
headers: { method: "POST",
'Content-Type': 'application/json' credentials: "include",
}, headers: {
body: JSON.stringify({ "Content-Type": "application/json",
customer_ids: [ },
{ body: JSON.stringify({
id: customerId customer_ids: [
} {
] id: customerId,
}) },
}) ],
}),
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer_group }) => { .then(({ customer_group }) => {
console.log(customer_group.id) console.log(customer_group.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -357,8 +360,8 @@ You can retrieve a list of all customers in a customer group using the List Cust
```jsx ```jsx
medusa.admin.customerGroups.listCustomers(customerGroupId) medusa.admin.customerGroups.listCustomers(customerGroupId)
.then(({ customers, count, offset, limit }) => { .then(({ customers, count, offset, limit }) => {
console.log(customers.length); console.log(customers.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -366,12 +369,12 @@ medusa.admin.customerGroups.listCustomers(customerGroupId)
```jsx ```jsx
fetch(`<SERVER_URL>/admin/customer-groups/${customerGroupId}/customers`, { fetch(`<SERVER_URL>/admin/customer-groups/${customerGroupId}/customers`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customers, count, offset, limit }) => { .then(({ customers, count, offset, limit }) => {
console.log(customers.length) console.log(customers.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -404,37 +407,40 @@ You can remove customers from a customer group by sending a request to the Remov
medusa.admin.customerGroups.removeCustomers(customer_group_id, { medusa.admin.customerGroups.removeCustomers(customer_group_id, {
customer_ids: [ customer_ids: [
{ {
id: customer_id id: customer_id,
} },
] ],
}) })
.then(({ customer_group }) => { .then(({ customer_group }) => {
console.log(customer_group.id); console.log(customer_group.id)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```jsx ```jsx
fetch(`<SERVER_URL>/admin/customer-groups/${customerGroupId}/customers/batch`, { fetch(
method: 'DELETE', `<SERVER_URL>/admin/customer-groups/${customerGroupId}/customers/batch`,
credentials: 'include', {
headers: { method: "DELETE",
'Content-Type': 'application/json' credentials: "include",
}, headers: {
body: JSON.stringify({ "Content-Type": "application/json",
customer_ids: [ },
{ body: JSON.stringify({
id: customerId customer_ids: [
} {
] id: customerId,
}) },
}) ],
}),
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer_group }) => { .then(({ customer_group }) => {
console.log(customer_group.id) console.log(customer_group.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -49,17 +49,20 @@ Batch job strategies must extend the abstract class `AbstractBatchJobStrategy` a
Add the following content to the file you created: Add the following content to the file you created:
```tsx title=src/strategies/publish.ts ```ts title=src/strategies/publish.ts
import { AbstractBatchJobStrategy, BatchJobService } from '@medusajs/medusa' import {
import { EntityManager } from 'typeorm' AbstractBatchJobStrategy,
BatchJobService,
} from "@medusajs/medusa"
import { EntityManager } from "typeorm"
class PublishStrategy extends AbstractBatchJobStrategy { class PublishStrategy extends AbstractBatchJobStrategy {
protected batchJobService_: BatchJobService protected batchJobService_: BatchJobService
processJob(batchJobId: string): Promise<void> { processJob(batchJobId: string): Promise<void> {
throw new Error('Method not implemented.') throw new Error("Method not implemented.")
} }
buildTemplate(): Promise<string> { buildTemplate(): Promise<string> {
throw new Error('Method not implemented.') throw new Error("Method not implemented.")
} }
protected manager_: EntityManager protected manager_: EntityManager
protected transactionManager_: EntityManager protected transactionManager_: EntityManager
@@ -79,12 +82,12 @@ You will use the `batchType` later when you [interact with the Batch Job APIs](#
Following the same example, add the following properties to the `PublishStrategy` class: Following the same example, add the following properties to the `PublishStrategy` class:
```tsx ```ts
class PublishStrategy extends AbstractBatchJobStrategy { class PublishStrategy extends AbstractBatchJobStrategy {
static identifier = 'publish-products-strategy' static identifier = "publish-products-strategy"
static batchType = 'publish-products' static batchType = "publish-products"
//... // ...
} }
``` ```
@@ -98,10 +101,16 @@ Medusa runs this method before it creates the batch job to prepare the content o
Implementing this method is optional. For example: Implementing this method is optional. For example:
```tsx ```ts
async prepareBatchJobForProcessing(batchJob: CreateBatchJobInput, req: Express.Request): Promise<CreateBatchJobInput> { class PublishStrategy extends AbstractBatchJobStrategy {
//make changes to the batch job's fields... // ...
return batchJob async prepareBatchJobForProcessing(
batchJob: CreateBatchJobInput,
req: Express.Request
): Promise<CreateBatchJobInput> {
// make changes to the batch job's fields...
return batchJob
}
} }
``` ```
@@ -111,35 +120,38 @@ Medusa runs this method after it creates the batch job, but before it is confirm
For example, this implementation of the `preProcessBatchJob` method calculates how many draft products it will published and adds it to the `result` attribute of the batch job: For example, this implementation of the `preProcessBatchJob` method calculates how many draft products it will published and adds it to the `result` attribute of the batch job:
```tsx ```ts
async preProcessBatchJob(batchJobId: string): Promise<void> { class PublishStrategy extends AbstractBatchJobStrategy {
return await this.atomicPhase_(async (transactionManager) => { // ...
const batchJob = (await this.batchJobService_ async preProcessBatchJob(batchJobId: string): Promise<void> {
.withTransaction(transactionManager) return await this.atomicPhase_(async (transactionManager) => {
.retrieve(batchJobId)) const batchJob = (await this.batchJobService_
.withTransaction(transactionManager)
const count = await this.productService_ .retrieve(batchJobId))
.withTransaction(transactionManager)
.count({ const count = await this.productService_
status: ProductStatus.DRAFT .withTransaction(transactionManager)
}); .count({
status: ProductStatus.DRAFT,
})
await this.batchJobService_ await this.batchJobService_
.withTransaction(transactionManager) .withTransaction(transactionManager)
.update(batchJob, { .update(batchJob, {
result: { result: {
advancement_count: 0, advancement_count: 0,
count, count,
stat_descriptors: [ stat_descriptors: [
{ {
key: 'product-publish-count', key: "product-publish-count",
name: 'Number of products to publish', name: "Number of products to publish",
message: `${count} product(s) will be published.` message: `${count} product(s) will be published.`,
} },
] ],
} },
}) })
}) })
}
} }
``` ```
@@ -155,34 +167,37 @@ Medusa runs this method to process the batch job once it is confirmed.
For example, this implementation of the `processJob` method retrieves all draft products and changes their status to published: For example, this implementation of the `processJob` method retrieves all draft products and changes their status to published:
```tsx ```ts
async processJob(batchJobId: string): Promise<void> { class PublishStrategy extends AbstractBatchJobStrategy {
return await this.atomicPhase_( // ...
async (transactionManager) => { async processJob(batchJobId: string): Promise<void> {
const productServiceTx = this.productService_ return await this.atomicPhase_(
.withTransaction(transactionManager) async (transactionManager) => {
const productServiceTx = this.productService_
.withTransaction(transactionManager)
const productList = await productServiceTx const productList = await productServiceTx
.list({ .list({
status: [ProductStatus.DRAFT] status: [ProductStatus.DRAFT],
})
productList.forEach(async (product: Product) => {
await productServiceTx
.update(product.id, {
status: ProductStatus.PUBLISHED
}) })
})
productList.forEach(async (product: Product) => {
await this.batchJobService_ await productServiceTx
.withTransaction(transactionManager) .update(product.id, {
.update(batchJobId, { status: ProductStatus.PUBLISHED,
result: { })
advancement_count: productList.length
}
}) })
}
) await this.batchJobService_
.withTransaction(transactionManager)
.update(batchJobId, {
result: {
advancement_count: productList.length,
},
})
}
)
}
} }
``` ```
@@ -198,9 +213,12 @@ This method can be used in cases where you provide a template file to download,
If not necessary to your use case, you can simply return an empty string: If not necessary to your use case, you can simply return an empty string:
```tsx ```ts
async buildTemplate(): Promise<string> { class PublishStrategy extends AbstractBatchJobStrategy {
return '' // ...
async buildTemplate(): Promise<string> {
return ""
}
} }
``` ```
@@ -212,9 +230,15 @@ By default, the `AbstractBatchJobStrategy` class implements this method and retu
If you would like to change that behavior, you can override this method to return a different value: If you would like to change that behavior, you can override this method to return a different value:
```tsx ```ts
protected async shouldRetryOnProcessingError(batchJob: BatchJob, err: unknown): Promise<boolean> { class PublishStrategy extends AbstractBatchJobStrategy {
return true // ...
protected async shouldRetryOnProcessingError(
batchJob: BatchJob,
err: unknown
): Promise<boolean> {
return true
}
} }
``` ```
@@ -226,9 +250,16 @@ You can use this method as implemented in `AbstractBatchJobStrategy` at any poin
You can also override this method in your batch job strategy and change how it works: You can also override this method in your batch job strategy and change how it works:
```tsx ```ts
protected async handleProcessingError<T>(batchJobId: string, err: unknown, result: T): Promise<void> { class PublishStrategy extends AbstractBatchJobStrategy {
//different implementation... // ...
protected async handleProcessingError<T>(
batchJobId: string,
err: unknown,
result: T
): Promise<void> {
// different implementation...
}
} }
``` ```
@@ -273,13 +304,13 @@ For example, this creates a batch job of the type `publish-products`:
```jsx ```jsx
medusa.admin.batchJobs.create({ medusa.admin.batchJobs.create({
type: 'publish-products', type: "publish-products",
context: { }, context: { },
dry_run: true dry_run: true,
}) })
.then(( batch_job ) => { .then(( batch_job ) => {
console.log(batch_job.status); console.log(batch_job.status)
}); })
``` ```
</TabItem> </TabItem>
@@ -287,21 +318,21 @@ medusa.admin.batchJobs.create({
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/batch-jobs`, { fetch(`<YOUR_SERVER>/admin/batch-jobs`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
type: 'publish-products', type: "publish-products",
context: { }, context: { },
dry_run: true dry_run: true,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ batch_job }) => { .then(({ batch_job }) => {
console.log(batch_job.status); console.log(batch_job.status)
}); })
``` ```
</TabItem> </TabItem>
@@ -335,8 +366,8 @@ You can retrieve the batch job afterward to get its status and view details abou
```jsx ```jsx
medusa.admin.batchJobs.retrieve(batchJobId) medusa.admin.batchJobs.retrieve(batchJobId)
.then(( batch_job ) => { .then(( batch_job ) => {
console.log(batch_job.status, batch_job.result); console.log(batch_job.status, batch_job.result)
}); })
``` ```
</TabItem> </TabItem>
@@ -344,12 +375,12 @@ medusa.admin.batchJobs.retrieve(batchJobId)
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}`, { fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ batch_job }) => { .then(({ batch_job }) => {
console.log(batch_job.status, batch_job.result); console.log(batch_job.status, batch_job.result)
}); })
``` ```
</TabItem> </TabItem>
@@ -390,8 +421,8 @@ To process the batch job, send a request to [confirm the batch job](https://docs
```jsx ```jsx
medusa.admin.batchJobs.confirm(batchJobId) medusa.admin.batchJobs.confirm(batchJobId)
.then(( batch_job ) => { .then(( batch_job ) => {
console.log(batch_job.status); console.log(batch_job.status)
}); })
``` ```
</TabItem> </TabItem>
@@ -399,13 +430,13 @@ medusa.admin.batchJobs.confirm(batchJobId)
```jsx ```jsx
fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}/confirm`, { fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}/confirm`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ batch_job }) => { .then(({ batch_job }) => {
console.log(batch_job.status); console.log(batch_job.status)
}); })
``` ```
</TabItem> </TabItem>
@@ -565,7 +565,7 @@ To load resources, such as services, in endpoints, use the `req.scope.resolve` f
For example: For example:
```ts ```ts
const logger = req.scope.resolve('logger'); const logger = req.scope.resolve("logger")
``` ```
Please note that in endpoints some resources, such as repositories, are not available. Please note that in endpoints some resources, such as repositories, are not available.
@@ -577,13 +577,13 @@ In classes such as services, strategies, or subscribers, you can load resources
For example: For example:
```ts ```ts
import { OrderService } from '@medusajs/medusa'; import { OrderService } from "@medusajs/medusa"
class OrderSubscriber { class OrderSubscriber {
protected orderService: OrderService; protected orderService: OrderService
constructor({ orderService }) { constructor({ orderService }) {
this.orderService = orderService; this.orderService = orderService
} }
} }
``` ```
+38 -27
View File
@@ -62,12 +62,13 @@ Next, in the exported function, retrieve the CORS configurations of your server
```ts ```ts
export default (rootDirectory) => { export default (rootDirectory) => {
//... // ...
const { configModule } = getConfigFile<ConfigModule>(rootDirectory, "medusa-config") const { configModule } =
getConfigFile<ConfigModule>(rootDirectory, "medusa-config")
const { projectConfig } = configModule const { projectConfig } = configModule
//.... // ....
} }
``` ```
@@ -94,7 +95,7 @@ Finally, for each route you add, create an `OPTIONS` request and add `cors` 
```ts ```ts
router.options("/admin/hello", cors(corsOptions)) router.options("/admin/hello", cors(corsOptions))
router.get("/admin/hello", cors(corsOptions), (req, res) => { router.get("/admin/hello", cors(corsOptions), (req, res) => {
//... // ...
}) })
``` ```
@@ -202,20 +203,24 @@ Protected routes are routes that should be accessible by logged-in customers or
To make a storefront route protected, first, import the `authenticate-customer` middleware: To make a storefront route protected, first, import the `authenticate-customer` middleware:
```ts ```ts
import authenticate from "@medusajs/medusa/dist/api/middlewares/authenticate-customer" import
authenticate
from "@medusajs/medusa/dist/api/middlewares/authenticate-customer"
``` ```
Then, add the middleware to your route: Then, add the middleware to your route:
```ts ```ts
router.options("/store/hello", cors(corsOptions)) router.options("/store/hello", cors(corsOptions))
router.get("/store/hello", cors(corsOptions), authenticate(), async (req, res) => { router.get("/store/hello", cors(corsOptions), authenticate(),
if (req.user) { async (req, res) => {
//user is logged in if (req.user) {
//to get customer id: req.user.customer_id // user is logged in
// to get customer id: req.user.customer_id
}
// ...
} }
//... )
})
``` ```
Please note that the endpoint is still accessible by all users, however, you’ll be able to access the current logged-in customer if there’s any. Please note that the endpoint is still accessible by all users, however, you’ll be able to access the current logged-in customer if there’s any.
@@ -227,21 +232,25 @@ To disallow guest customers from accessing the endpoint, you can throw an error
To make an admin route protected, first, import the `authenticate` middleware: To make an admin route protected, first, import the `authenticate` middleware:
```ts ```ts
import authenticate from "@medusajs/medusa/dist/api/middlewares/authenticate" import
authenticate
from "@medusajs/medusa/dist/api/middlewares/authenticate"
``` ```
Then, add the middleware to your route: Then, add the middleware to your route:
```ts ```ts
router.options("/admin/products/count", cors(corsOptions)) router.options("/admin/products/count", cors(corsOptions))
router.get("/admin/products/count", cors(corsOptions), authenticate(), async (req, res) => { router.get("/admin/products/count", cors(corsOptions), authenticate(),
//access current user async (req, res) => {
const id = req.user.userId // access current user
const userService = req.scope.resolve("userService") const id = req.user.userId
const userService = req.scope.resolve("userService")
const user = await userService.retrieve(id)
//... const user = await userService.retrieve(id)
}) // ...
}
)
``` ```
Now, only authenticated users can access this endpoint. Now, only authenticated users can access this endpoint.
@@ -257,15 +266,17 @@ You can retrieve any registered service in your endpoint using `req.scope.resol
Here’s an example of an endpoint that retrieves the count of products in your store: Here’s an example of an endpoint that retrieves the count of products in your store:
```ts ```ts
router.get("/admin/products/count", cors(corsOptions), authenticate(), (req, res) => { router.get("/admin/products/count", cors(corsOptions), authenticate(),
const productService = req.scope.resolve("productService") (req, res) => {
const productService = req.scope.resolve("productService")
productService.count().then((count) => { productService.count().then((count) => {
res.json({ res.json({
count, count,
})
}) })
}) }
}) )
``` ```
The `productService` has a `count` method that returns a Promise. This Promise resolves to the count of the products. You return a JSON of the product count. The `productService` has a `count` method that returns a Promise. This Promise resolves to the count of the products. You return a JSON of the product count.
+20 -19
View File
@@ -6,15 +6,15 @@ In this document, you’ll learn how you can create an [Entity](overview.md).
To create an entity, create a TypeScript file in `src/models`. For example, here’s a `Post` entity defined in the file `src/models/post.ts`: To create an entity, create a TypeScript file in `src/models`. For example, here’s a `Post` entity defined in the file `src/models/post.ts`:
```tsx title=src/models/post.ts ```ts title=src/models/post.ts
import { BeforeInsert, Column, Entity, PrimaryColumn } from "typeorm"; import { BeforeInsert, Column, Entity, PrimaryColumn } from "typeorm"
import { BaseEntity} from "@medusajs/medusa"; import { BaseEntity } from "@medusajs/medusa"
import { generateEntityId } from "@medusajs/medusa/dist/utils" import { generateEntityId } from "@medusajs/medusa/dist/utils"
@Entity() @Entity()
export class Post extends BaseEntity { export class Post extends BaseEntity {
@Column({type: 'varchar'}) @Column({ type: "varchar" })
title: string | null; title: string | null
@BeforeInsert() @BeforeInsert()
private beforeInsert(): void { private beforeInsert(): void {
@@ -31,12 +31,12 @@ To generate an ID for your entity that matches the IDs generated for Medusa’s
If you want the entity to also be soft deletable then it should extend `SoftDeletableEntity` instead: If you want the entity to also be soft deletable then it should extend `SoftDeletableEntity` instead:
```tsx ```ts
import { SoftDeletableEntity } from "@medusajs/medusa"; import { SoftDeletableEntity } from "@medusajs/medusa"
@Entity() @Entity()
export class Post extends SoftDeletableEntity { export class Post extends SoftDeletableEntity {
//... // ...
} }
``` ```
@@ -52,7 +52,7 @@ You can learn more about Migrations, how to create them, and how to run them in
Entities data can be easily accessed and modified using Typeorm [Repositories](https://typeorm.io/working-with-repository). To create a repository, create a file in `src/repositories`. For example, here’s a repository `PostRepository` created in `src/repositories/post.ts`: Entities data can be easily accessed and modified using Typeorm [Repositories](https://typeorm.io/working-with-repository). To create a repository, create a file in `src/repositories`. For example, here’s a repository `PostRepository` created in `src/repositories/post.ts`:
```tsx title=src/repositories/post.ts ```ts title=src/repositories/post.ts
import { EntityRepository, Repository } from "typeorm" import { EntityRepository, Repository } from "typeorm"
import { Post } from "../models/post" import { Post } from "../models/post"
@@ -85,24 +85,25 @@ npm run build
You can access your custom entity data in the database in services or subscribers using the repository. For example, here’s a service that lists all posts: You can access your custom entity data in the database in services or subscribers using the repository. For example, here’s a service that lists all posts:
```tsx ```ts
import { TransactionBaseService } from '@medusajs/medusa'; import { TransactionBaseService } from "@medusajs/medusa"
class PostService extends TransactionBaseService { class PostService extends TransactionBaseService {
constructor({ postRepository, manager }) { constructor({ postRepository, manager }) {
super({ postRepository, manager }); super({ postRepository, manager })
this.postRepository = postRepository; this.postRepository = postRepository
this.manager_ = manager; this.manager_ = manager
} }
async list() { async list() {
const postRepository = this.manager_.getCustomRepository(this.postRepository); const postRepository = this.manager_
return await postRepository.find(); .getCustomRepository(this.postRepository)
return await postRepository.find()
} }
} }
export default PostService; export default PostService
``` ```
In the constructor, you can use dependency injection to get access to instances of services and repositories. Here, you initialize class fields `postRepository` and `manager`. The `manager` is a [Typeorm Entity Manager](https://typeorm.io/working-with-entity-manager). In the constructor, you can use dependency injection to get access to instances of services and repositories. Here, you initialize class fields `postRepository` and `manager`. The `manager` is a [Typeorm Entity Manager](https://typeorm.io/working-with-entity-manager).
@@ -123,8 +124,8 @@ This same usage of repositories can be done in subscribers as well.
To delete soft-deletable entities that extend the `SoftDeletableEntity` class, you can use the repository method `softDelete` method: To delete soft-deletable entities that extend the `SoftDeletableEntity` class, you can use the repository method `softDelete` method:
```tsx ```ts
await postRepository.softDelete(post.id); await postRepository.softDelete(post.id)
``` ```
--- ---
@@ -51,9 +51,9 @@ For example, to enable the Tax-Inclusive Pricing beta feature, add the following
```jsx title=medusa-config.js ```jsx title=medusa-config.js
module.exports = { module.exports = {
featureFlags: { featureFlags: {
tax_inclusive_pricing: true tax_inclusive_pricing: true,
}, },
//... // ...
} }
``` ```
@@ -23,23 +23,39 @@ Creating a Notification Provider is as simple as creating a TypeScript or JavaS
For example, create the file `src/services/email-sender.ts` with the following content: For example, create the file `src/services/email-sender.ts` with the following content:
```ts title=src/services/email-sender.ts ```ts title=src/services/email-sender.ts
import { AbstractNotificationService } from "@medusajs/medusa"; import { AbstractNotificationService } from "@medusajs/medusa"
import { EntityManager } from "typeorm"; import { EntityManager } from "typeorm"
class EmailSenderService extends AbstractNotificationService { class EmailSenderService extends AbstractNotificationService {
protected manager_: EntityManager; protected manager_: EntityManager
protected transactionManager_: EntityManager; protected transactionManager_: EntityManager
sendNotification(event: string, data: unknown, attachmentGenerator: unknown): Promise<{ to: string; status: string; data: Record<string, unknown>; }> { sendNotification(
throw new Error("Method not implemented."); 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>; }> { resendNotification(
throw new Error("Method not implemented."); notification: unknown,
config: unknown,
attachmentGenerator: unknown
): Promise<{
to: string;
status: string;
data: Record<string, unknown>;
}> {
throw new Error("Method not implemented.")
} }
} }
export default EmailSenderService; export default EmailSenderService
``` ```
Where `EmailSenderService` is the name of your Notification Provider Service. Where `EmailSenderService` is the name of your Notification Provider Service.
@@ -63,7 +79,10 @@ The value of this property is also used later when you want to subscribe the Not
For example, in the class you created in the previous code snippet you can add the following property: For example, in the class you created in the previous code snippet you can add the following property:
```ts ```ts
static identifier = "email-sender"; class EmailSenderService extends AbstractNotificationService {
static identifier = "email-sender"
// ...
}
``` ```
### constructor ### constructor
@@ -83,23 +102,26 @@ You can learn more about plugins and how to create them in the [Plugins](../plug
Continuing on with the previous example, if you want to use the [`OrderService`](../../../references/services/classes/OrderService.md) later when sending notifications, you can inject it into the constructor: Continuing on with the previous example, if you want to use the [`OrderService`](../../../references/services/classes/OrderService.md) later when sending notifications, you can inject it into the constructor:
```ts ```ts
import { AbstractNotificationService, OrderService } from "@medusajs/medusa"; import {
AbstractNotificationService,
OrderService,
} from "@medusajs/medusa"
class EmailSenderService extends AbstractNotificationService { class EmailSenderService extends AbstractNotificationService {
protected manager_: EntityManager; protected manager_: EntityManager
protected transactionManager_: EntityManager; protected transactionManager_: EntityManager
static identifier = "email-sender"; static identifier = "email-sender"
protected orderService: OrderService; protected orderService: OrderService
constructor(container, options) { constructor(container, options) {
super(container); super(container)
//you can access options here in case you're // you can access options here in case you're
//using a plugin // using a plugin
this.orderService = container.orderService; this.orderService = container.orderService
} }
//... // ...
} }
``` ```
@@ -129,23 +151,34 @@ This method must return an object containing two properties:
Continuing with the previous example you can have the following implementation of the `sendNotification` method: Continuing with the previous example you can have the following implementation of the `sendNotification` method:
```ts ```ts
async sendNotification(event: string, data: any, attachmentGenerator: unknown): Promise<{ to: string; status: string; data: Record<string, unknown>; }> { class EmailSenderService extends AbstractNotificationService {
if (event === 'order.placed') { // ...
//retrieve order async sendNotification(
const order = await this.orderService.retrieve(data.id); event: string,
//TODO send email 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'); console.log("Notification sent")
return { return {
to: order.email, to: order.email,
status: 'done', status: "done",
data: { data: {
//any data necessary to send the email // any data necessary to send the email
//for example: // for example:
subject: 'You placed a new order!', subject: "You placed a new order!",
items: order.items items: order.items,
},
} }
}; }
} }
} }
``` ```
@@ -180,17 +213,29 @@ Similarly to the `sendNotification` method, this method must return an object co
Continuing with the previous example you can have the following implementation of the `resendNotification` method: Continuing with the previous example you can have the following implementation of the `resendNotification` method:
```ts ```ts
async resendNotification(notification: any, config: any, attachmentGenerator: unknown): Promise<{ to: string; status: string; data: Record<string, unknown>; }> { class EmailSenderService extends AbstractNotificationService {
//check if the receiver of the notification should be changed // ...
const to: string = config.to ? config.to : notification.to; async resendNotification(
notification: any,
config: any,
attachmentGenerator: unknown
): Promise<{
to: string;
status: string;
data: Record<string, unknown>;
}> {
// check if the receiver of the notification 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 // TODO resend the notification using the same data
// that is saved under notification.data
console.log('Notification resent'); console.log("Notification resent")
return { return {
to, to,
status: 'done', status: "done",
data: notification.data //you can also make changes to the data data: notification.data, // you can also make changes to the data
}
} }
} }
``` ```
@@ -224,11 +269,12 @@ Following the previous example, to make sure the `email-sender` Notification Pro
```ts title=src/subscribers/notification.js ```ts title=src/subscribers/notification.js
class NotificationSubscriber { class NotificationSubscriber {
constructor({ notificationService }) { constructor({ notificationService }) {
notificationService.subscribe('order.placed', 'email-sender'); notificationService.subscribe("order.placed", "email-sender")
} }
// ...
} }
export default NotificationSubscriber; export default NotificationSubscriber
``` ```
This subscriber accesses the `notificationService` using dependency injection. The `notificationService` contains 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 is subscribing to that event. This subscriber accesses the `notificationService` using dependency injection. The `notificationService` contains 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 is subscribing to that event.
@@ -288,5 +334,5 @@ This request returns the same notification object as the List Notifications endp
- [Events reference](../subscribers/events-list.md) - [Events reference](../subscribers/events-list.md)
- [SendGrid Plugin](../../../add-plugins/sendgrid.mdx) - [SendGrid Plugin](../../../add-plugins/sendgrid.mdx)
- [Create a Subscriber](../subscribers/create-subscriber.md) - [Create a Subscriber](../subscribers/create-subscriber.md)
- [Create a Service](../services/create-service.md). - [Create a Service](../services/create-service.md)
- [Create a Plugin](../plugins/create.md). - [Create a Plugin](../plugins/create.md)
@@ -50,51 +50,60 @@ The first step to create a payment provider is to create a JavaScript or TypeScr
For example, create the file `src/services/my-payment.ts` with the following content: For example, create the file `src/services/my-payment.ts` with the following content:
<!-- eslint-disable max-len -->
```ts title=src/services/my-payment.ts ```ts title=src/services/my-payment.ts
import { AbstractPaymentService, Cart, Data, Payment, PaymentSession, PaymentSessionStatus, TransactionBaseService } from "@medusajs/medusa" import {
import { EntityManager } from "typeorm"; AbstractPaymentService,
Cart, Data, Payment, PaymentSession,
PaymentSessionStatus, TransactionBaseService,
} from "@medusajs/medusa"
import { EntityManager } from "typeorm"
class MyPaymentService extends AbstractPaymentService<TransactionBaseService> { class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
protected manager_: EntityManager; protected manager_: EntityManager
protected transactionManager_: EntityManager; protected transactionManager_: EntityManager
getPaymentData(paymentSession: PaymentSession): Promise<Data> { getPaymentData(paymentSession: PaymentSession): Promise<Data> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.")
} }
updatePaymentData(paymentSessionData: Data, data: Data): Promise<Data> { updatePaymentData(paymentSessionData: Data, data: Data): Promise<Data> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.")
} }
createPayment(cart: Cart): Promise<Data> { createPayment(cart: Cart): Promise<Data> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.")
} }
retrievePayment(paymentData: Data): Promise<Data> { retrievePayment(paymentData: Data): Promise<Data> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.")
} }
updatePayment(paymentSessionData: Data, cart: Cart): Promise<Data> { updatePayment(paymentSessionData: Data, cart: Cart): Promise<Data> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.")
} }
authorizePayment(paymentSession: PaymentSession, context: Data): Promise<{ data: Data; status: PaymentSessionStatus; }> { authorizePayment(
throw new Error("Method not implemented."); paymentSession: PaymentSession,
context: Data
): Promise<{ data: Data; status: PaymentSessionStatus; }> {
throw new Error("Method not implemented.")
} }
capturePayment(payment: Payment): Promise<Data> { capturePayment(payment: Payment): Promise<Data> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.")
} }
refundPayment(payment: Payment, refundAmount: number): Promise<Data> { refundPayment(payment: Payment, refundAmount: number): Promise<Data> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.")
} }
cancelPayment(payment: Payment): Promise<Data> { cancelPayment(payment: Payment): Promise<Data> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.")
} }
deletePayment(paymentSession: PaymentSession): Promise<void> { deletePayment(paymentSession: PaymentSession): Promise<void> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.")
} }
getStatus(data: Data): Promise<PaymentSessionStatus> { getStatus(data: Data): Promise<PaymentSessionStatus> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.")
} }
} }
export default MyPaymentService; export default MyPaymentService
``` ```
Where `MyPaymentService` is the name of your Payment Provider service. For example, Stripe’s Payment Provider Service is called `StripeProviderService`. Where `MyPaymentService` is the name of your Payment Provider service. For example, Stripe’s Payment Provider Service is called `StripeProviderService`.
@@ -123,10 +132,16 @@ You can also use the constructor to initialize your integration with the third-p
Additionally, if you’re creating your Payment Provider as an external plugin to be installed on any Medusa server 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: Additionally, if you’re creating your Payment Provider as an external plugin to be installed on any Medusa server 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:
<!-- eslint-disable max-len -->
```ts ```ts
constructor({ productService }, options) { class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
super(); // ...
//you can access options here constructor({ productService }, options) {
super()
// you can access options here
}
// ...
} }
``` ```
@@ -140,15 +155,20 @@ This method must return an object that is going to be stored in the `data` field
An example of a minimal implementation of `createPayment` that does not interact with any third-party providers: An example of a minimal implementation of `createPayment` that does not interact with any third-party providers:
<!-- eslint-disable max-len -->
```ts ```ts
import { Cart, Data } from "@medusajs/medusa" import { Cart, Data } from "@medusajs/medusa"
//... // ...
async createPayment(cart: Cart): Promise<Data> { class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
return { // ...
id: 'test-payment', async createPayment(cart: Cart): Promise<Data> {
status: 'pending' return {
}; id: "test-payment",
status: "pending",
}
}
} }
``` ```
@@ -162,12 +182,17 @@ This method must return an object containing the data from the third-party provi
An example of a minimal implementation of `retrievePayment` where you don’t need to interact with the third-party provider: An example of a minimal implementation of `retrievePayment` where you don’t need to interact with the third-party provider:
<!-- eslint-disable max-len -->
```ts ```ts
import { Data } from "@medusajs/medusa" import { Data } from "@medusajs/medusa"
//... // ...
async retrievePayment(paymentData: Data): Promise<Data> { class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
return {}; // ...
async retrievePayment(paymentData: Data): Promise<Data> {
return {}
}
} }
``` ```
@@ -189,12 +214,17 @@ This method returns a string that represents the status. The status must be one
An example of a minimal implementation of `getStatus` where you don’t need to interact with the third-party provider: An example of a minimal implementation of `getStatus` where you don’t need to interact with the third-party provider:
<!-- eslint-disable max-len -->
```ts ```ts
import { Data, PaymentSessionStatus } from "@medusajs/medusa" import { Data, PaymentSessionStatus } from "@medusajs/medusa"
//... // ...
async getStatus(data: Data): Promise<PaymentSessionStatus> { class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
return PaymentSessionStatus.AUTHORIZED; // ...
async getStatus(data: Data): Promise<PaymentSessionStatus> {
return PaymentSessionStatus.AUTHORIZED
}
} }
``` ```
@@ -222,12 +252,17 @@ This method must return an object that will be stored in the `data` field of the
An example of a minimal implementation of `updatePayment` that does not need to make any updates on the third-party provider or the `data` field of the Payment Session: An example of a minimal implementation of `updatePayment` that does not need to make any updates on the third-party provider or the `data` field of the Payment Session:
```ts <!-- eslint-disable max-len -->
import { Cart, Data } from "@medusajs/medusa";
//...
async updatePayment(paymentSessionData: Data, cart: Cart): Promise<Data> { ```ts
return paymentSessionData; import { Cart, Data } from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
// ...
async updatePayment(paymentSessionData: Data, cart: Cart): Promise<Data> {
return paymentSessionData
}
} }
``` ```
@@ -243,12 +278,20 @@ This method must return an object that will be stored in the `data` field of the
An example of a minimal implementation of `updatePaymentData` that returns the `updatedData` passed in the body of the request as-is to update the `data` field of the Payment Session. An example of a minimal implementation of `updatePaymentData` that returns the `updatedData` passed in the body of the request as-is to update the `data` field of the Payment Session.
```ts <!-- eslint-disable max-len -->
import { Data } from "@medusajs/medusa";
//...
async updatePaymentData(paymentSessionData: Data, updatedData: Data): Promise<Data> { ```ts
return updatedData; import { Data } from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
// ...
async updatePaymentData(
paymentSessionData: Data,
updatedData: Data
): Promise<Data> {
return updatedData
}
} }
``` ```
@@ -267,12 +310,17 @@ You can use this method to interact with the third-party provider to delete data
An example of a minimal implementation of `deletePayment` where no interaction with a third-party provider is required: An example of a minimal implementation of `deletePayment` where no interaction with a third-party provider is required:
```ts <!-- eslint-disable max-len -->
import { PaymentSession } from "@medusajs/medusa";
//...
async deletePayment(paymentSession: PaymentSession): Promise<void> { ```ts
return; import { PaymentSession } from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
// ...
async deletePayment(paymentSession: PaymentSession): Promise<void> {
return
}
} }
``` ```
@@ -305,17 +353,29 @@ You can utilize this method to interact with the third-party provider and perfor
An example of a minimal implementation of `authorizePayment` that doesn’t need to interact with any third-party provider: An example of a minimal implementation of `authorizePayment` that doesn’t need to interact with any third-party provider:
```ts <!-- eslint-disable max-len -->
import { Data, PaymentSession, PaymentSessionStatus } from "@medusajs/medusa";
//...
async authorizePayment(paymentSession: PaymentSession, context: Data): Promise<{ data: Data; status: PaymentSessionStatus; }> { ```ts
return { import {
status: PaymentSessionStatus.AUTHORIZED, Data,
data: { PaymentSession,
id: 'test' PaymentSessionStatus,
} from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
// ...
async authorizePayment(
paymentSession: PaymentSession,
context: Data
): Promise<{ data: Data; status: PaymentSessionStatus; }> {
return {
status: PaymentSessionStatus.AUTHORIZED,
data: {
id: "test",
},
} }
}; }
} }
``` ```
@@ -329,12 +389,17 @@ This method must return an object to be stored in the `data` field of the Paymen
An example of a minimal implementation of `getPaymentData`: An example of a minimal implementation of `getPaymentData`:
```ts <!-- eslint-disable max-len -->
import { Data, PaymentSession } from "@medusajs/medusa";
//...
async getPaymentData(paymentSession: PaymentSession): Promise<Data> { ```ts
return paymentSession.data; import { Data, PaymentSession } from "@medusajs/medusa"
// ...
class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
// ...
async getPaymentData(paymentSession: PaymentSession): Promise<Data> {
return paymentSession.data
}
} }
``` ```
@@ -352,14 +417,19 @@ This method must return an object that will be stored in the `data` field of the
An example of a minimal implementation of `capturePayment` that doesn’t need to interact with a third-party provider: An example of a minimal implementation of `capturePayment` that doesn’t need to interact with a third-party provider:
```ts <!-- eslint-disable max-len -->
import { Data, Payment } from "@medusajs/medusa";
//...
async capturePayment(payment: Payment): Promise<Data> { ```ts
return { import { Data, Payment } from "@medusajs/medusa"
status: 'captured' // ...
};
class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
// ...
async capturePayment(payment: Payment): Promise<Data> {
return {
status: "captured",
}
}
} }
``` ```
@@ -377,13 +447,21 @@ This method must return an object that is stored in the `data` field of the Paym
An example of a minimal implementation of `refundPayment` that doesn’t need to interact with a third-party provider: An example of a minimal implementation of `refundPayment` that doesn’t need to interact with a third-party provider:
```ts <!-- eslint-disable max-len -->
import { Data, Payment } from "@medusajs/medusa";
//...
async refundPayment(payment: Payment, refundAmount: number): Promise<Data> { ```ts
return { import { Data, Payment } from "@medusajs/medusa"
id: 'test' // ...
class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
// ...
async refundPayment(
payment: Payment,
refundAmount: number
): Promise<Data> {
return {
id: "test",
}
} }
} }
``` ```
@@ -405,13 +483,18 @@ This method must return an object that is stored in the `data` field of the Paym
An example of a minimal implementation of `cancelPayment` that doesn’t need to interact with a third-party provider: An example of a minimal implementation of `cancelPayment` that doesn’t need to interact with a third-party provider:
```ts <!-- eslint-disable max-len -->
import { Data, Payment } from "@medusajs/medusa";
//...
async cancelPayment(payment: Payment): Promise<Data> { ```ts
return { import { Data, Payment } from "@medusajs/medusa"
id: 'test' // ...
class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
// ...
async cancelPayment(payment: Payment): Promise<Data> {
return {
id: "test",
}
} }
} }
``` ```
@@ -438,26 +521,31 @@ If you’re using Medusa’s [Next.js](../../../starters/nextjs-medusa-starter.m
An example of the implementation of `retrieveSavedMethods` taken from Stripe’s Payment Provider: An example of the implementation of `retrieveSavedMethods` taken from Stripe’s Payment Provider:
<!-- eslint-disable max-len -->
```ts ```ts
import { Customer, Data } from "@medusajs/medusa" import { Customer, Data } from "@medusajs/medusa"
//... // ...
/** class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
* Fetches a customers saved payment methods if registered in Stripe. // ...
* @param {object} customer - customer to fetch saved cards for /**
* @returns {Promise<Array<object>>} saved payments methods * Fetches a customers saved payment methods if registered in Stripe.
*/ * @param {object} customer - customer to fetch saved cards for
async retrieveSavedMethods(customer: Customer): Promise<Data[]> { * @return {Promise<Array<object>>} saved payments methods
if (customer.metadata && customer.metadata.stripe_id) { */
const methods = await this.stripe_.paymentMethods.list({ async retrieveSavedMethods(customer: Customer): Promise<Data[]> {
customer: customer.metadata.stripe_id, if (customer.metadata && customer.metadata.stripe_id) {
type: "card", const methods = await this.stripe_.paymentMethods.list({
}) customer: customer.metadata.stripe_id,
type: "card",
})
return methods.data return methods.data
}
return Promise.resolve([])
} }
return Promise.resolve([])
} }
``` ```
@@ -212,7 +212,7 @@ To pass a plugin its configurations on a Medusa server, you have to add it to th
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
//... // ...
{ {
resolve: `medusa-plugin-custom`, resolve: `medusa-plugin-custom`,
options: { options: {
@@ -225,19 +225,23 @@ const plugins = [
Then, you can have access to your plugin configuration in the constructor of services in your plugin: Then, you can have access to your plugin configuration in the constructor of services in your plugin:
```jsx title=src/service/test.ts ```jsx title=src/service/test.ts
//In a service in your plugin // In a service in your plugin
constructor({}, options) { class MyService extends TransactionBaseService {
//options contains plugin configurations constructor(container, options) {
this.name = options.name super(container)
// options contains plugin configurations
this.name = options.name
}
// ...
} }
``` ```
You can also have access to the configurations in endpoints in your plugin: You can also have access to the configurations in endpoints in your plugin:
```jsx title=src/api/index.ts ```jsx title=src/api/index.ts
//in an endpoint in your plugin // in an endpoint in your plugin
export default (rootDirectory, options) => { export default (rootDirectory, options) => {
//options contain the plugin configurations // options contain the plugin configurations
const router = Router() const router = Router()
router.get("/hello-world", (req, res) => { router.get("/hello-world", (req, res) => {
@@ -293,10 +297,10 @@ Then, add your plugin into the array of plugins in `medusa-config.js`:
```jsx title=medusa-config.js ```jsx title=medusa-config.js
const plugins = [ const plugins = [
//... // ...
{ {
resolve: `medusa-plugin-custom`, resolve: `medusa-plugin-custom`,
//if your plugin has configurations // if your plugin has configurations
options: { options: {
name: "My Store", name: "My Store",
}, },
@@ -37,6 +37,8 @@ When you create a price list, you can specify different conditions to control wh
In the body of your request, aside from the required fields, you can send the following fields to apply different conditions: In the body of your request, aside from the required fields, you can send the following fields to apply different conditions:
<!-- eslint-skip -->
```js noReport ```js noReport
{ {
prices: [ prices: [
@@ -70,28 +72,28 @@ For example, sending the following request creates a price list with two prices:
import { PriceListType } from "@medusajs/medusa" import { PriceListType } from "@medusajs/medusa"
medusa.admin.priceLists.create({ medusa.admin.priceLists.create({
name: 'New Price List', name: "New Price List",
description: 'A new price list', description: "A new price list",
type: PriceListType.SALE, type: PriceListType.SALE,
status: 'active', status: "active",
prices: [ prices: [
{ {
amount: 1000, amount: 1000,
variant_id, variant_id,
currency_code: 'eur', currency_code: "eur",
max_quantity: 3 max_quantity: 3,
}, },
{ {
amount: 1500, amount: 1500,
variant_id, variant_id,
currency_code: 'eur', currency_code: "eur",
min_quantity: 4 min_quantity: 4,
} },
] ],
}) })
.then(({ price_list }) => { .then(({ price_list }) => {
console.log(price_list.id); console.log(price_list.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -99,31 +101,31 @@ medusa.admin.priceLists.create({
```jsx ```jsx
fetch(`<SERVER_URL>/admin/price-lists`, { fetch(`<SERVER_URL>/admin/price-lists`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
name: 'New Price List', name: "New Price List",
description: 'A new price list', description: "A new price list",
type: 'sale', type: "sale",
status: 'active', status: "active",
prices: [ prices: [
{ {
amount: 1000, amount: 1000,
variant_id, variant_id,
currency_code: 'eur', currency_code: "eur",
max_quantity: 3 max_quantity: 3,
}, },
{ {
amount: 1500, amount: 1500,
variant_id, variant_id,
currency_code: 'eur', currency_code: "eur",
min_quantity: 4 min_quantity: 4,
} },
] ],
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ price_list }) => { .then(({ price_list }) => {
@@ -179,8 +181,8 @@ You can retrieve all of a price list’s details using the Get a Price List endp
```jsx ```jsx
medusa.admin.priceLists.retrieve(priceListId) medusa.admin.priceLists.retrieve(priceListId)
.then(({ price_list }) => { .then(({ price_list }) => {
console.log(price_list.id); console.log(price_list.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -188,7 +190,7 @@ medusa.admin.priceLists.retrieve(priceListId)
```jsx ```jsx
fetch(`<SERVER_URL>/admin/price-lists/${priceListId}`, { fetch(`<SERVER_URL>/admin/price-lists/${priceListId}`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ price_list }) => { .then(({ price_list }) => {
@@ -199,7 +201,7 @@ fetch(`<SERVER_URL>/admin/price-lists/${priceListId}`, {
</TabItem> </TabItem>
<TabItem value="curl" label="cURL"> <TabItem value="curl" label="cURL">
```jsx ```bash
curl -L -X GET '<SERVER_URL>/admin/price-lists/{id}' \ curl -L -X GET '<SERVER_URL>/admin/price-lists/{id}' \
-H 'Authorization: Bearer <API_TOKEN>' -H 'Authorization: Bearer <API_TOKEN>'
``` ```
@@ -220,11 +222,11 @@ For example, by sending the following request the end date of the price list wil
```jsx ```jsx
medusa.admin.priceLists.update(priceListId, { medusa.admin.priceLists.update(priceListId, {
ends_at: '2022-10-11' ends_at: "2022-10-11",
}) })
.then(({ price_list }) => { .then(({ price_list }) => {
console.log(price_list.id); console.log(price_list.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -232,14 +234,14 @@ medusa.admin.priceLists.update(priceListId, {
```jsx ```jsx
fetch(`<SERVER_URL>/admin/price-lists/${priceListId}`, { fetch(`<SERVER_URL>/admin/price-lists/${priceListId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
ends_at: '2022-10-11' ends_at: "2022-10-11",
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ price_list }) => { .then(({ price_list }) => {
@@ -287,13 +289,13 @@ medusa.admin.priceLists.addPrices(priceListId, {
{ {
amount: 1200, amount: 1200,
variant_id, variant_id,
currency_code: 'eur' currency_code: "eur",
} },
] ],
}) })
.then(({ price_list }) => { .then(({ price_list }) => {
console.log(price_list.id); console.log(price_list.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -301,20 +303,20 @@ medusa.admin.priceLists.addPrices(priceListId, {
```jsx ```jsx
fetch(`<SERVER_URL>/admin/price-lists/${priceListId}/prices/batch`, { fetch(`<SERVER_URL>/admin/price-lists/${priceListId}/prices/batch`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
prices: [ prices: [
{ {
amount: 1200, amount: 1200,
variant_id, variant_id,
currency_code: 'eur' currency_code: "eur",
} },
] ],
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ price_list }) => { .then(({ price_list }) => {
@@ -357,18 +359,23 @@ You can delete all the prices of a product’s variants using the [Delete Produc
```jsx ```jsx
medusa.admin.priceLists.deleteProductPrices(priceListId, productId) medusa.admin.priceLists.deleteProductPrices(priceListId, productId)
.then(({ ids, object, deleted }) => { .then(({ ids, object, deleted }) => {
console.log(ids.length); console.log(ids.length)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
<!-- eslint-disable max-len -->
```jsx ```jsx
fetch(`<SERVER_URL>/admin/price-lists/${priceListId}/products/${productId}/prices`, { fetch(
method: 'DELETE', `<SERVER_URL>/admin/price-lists/${priceListId}/products/${productId}/prices`,
credentials: 'include', {
}) method: "DELETE",
credentials: "include",
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ ids, object, deleted }) => { .then(({ ids, object, deleted }) => {
console.log(ids.length) console.log(ids.length)
@@ -398,18 +405,23 @@ You can delete all the prices of a variant using the [Delete Variant Prices](htt
```jsx ```jsx
medusa.admin.priceLists.deleteVariantPrices(priceListId, variantId) medusa.admin.priceLists.deleteVariantPrices(priceListId, variantId)
.then(({ ids, object, deleted }) => { .then(({ ids, object, deleted }) => {
console.log(ids); console.log(ids)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
<!-- eslint-disable max-len -->
```jsx ```jsx
fetch(`<SERVER_URL>/admin/price-lists/${priceListId}/variants/${variantId}/prices`, { fetch(
method: 'DELETE', `<SERVER_URL>/admin/price-lists/${priceListId}/variants/${variantId}/prices`,
credentials: 'include', {
}) method: "DELETE",
credentials: "include",
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ ids, object, deleted }) => { .then(({ ids, object, deleted }) => {
console.log(ids.length) console.log(ids.length)
@@ -419,7 +431,7 @@ fetch(`<SERVER_URL>/admin/price-lists/${priceListId}/variants/${variantId}/price
</TabItem> </TabItem>
<TabItem value="curl" label="cURL"> <TabItem value="curl" label="cURL">
```jsx ```bash
curl -L -X DELETE '<SERVER_URL>/admin/price-lists/<PRICE_LIST_ID>/variants/<VARIANT_ID>/prices' \ curl -L -X DELETE '<SERVER_URL>/admin/price-lists/<PRICE_LIST_ID>/variants/<VARIANT_ID>/prices' \
-H 'Authorization: Bearer <API_TOKEN>' -H 'Authorization: Bearer <API_TOKEN>'
``` ```
@@ -441,8 +453,8 @@ You can delete a price list, and subsequently all prices defined in it, using th
```jsx ```jsx
medusa.admin.priceLists.delete(priceListId) medusa.admin.priceLists.delete(priceListId)
.then(({ id, object, deleted }) => { .then(({ id, object, deleted }) => {
console.log(id); console.log(id)
}); })
``` ```
</TabItem> </TabItem>
@@ -450,8 +462,8 @@ medusa.admin.priceLists.delete(priceListId)
```jsx ```jsx
fetch(`<SERVER_URL>/admin/price-lists/${priceListId}`, { fetch(`<SERVER_URL>/admin/price-lists/${priceListId}`, {
method: 'DELETE', method: "DELETE",
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ id, object, deleted }) => { .then(({ id, object, deleted }) => {
@@ -462,7 +474,7 @@ fetch(`<SERVER_URL>/admin/price-lists/${priceListId}`, {
</TabItem> </TabItem>
<TabItem value="curl" label="cURL"> <TabItem value="curl" label="cURL">
```jsx ```bash
curl -L -X DELETE '<SERVER_URL>/admin/price-lists/<PRICE_LIST_ID>' \ curl -L -X DELETE '<SERVER_URL>/admin/price-lists/<PRICE_LIST_ID>' \
-H 'Authorization: Bearer <API_TOKEN>' -H 'Authorization: Bearer <API_TOKEN>'
``` ```
@@ -57,12 +57,12 @@ You can create a sales channel by sending a request to the Create a Sales Channe
```jsx ```jsx
medusa.admin.salesChannels.create({ medusa.admin.salesChannels.create({
name: 'App', name: "App",
description: 'Mobile app' description: "Mobile app",
}) })
.then(({ sales_channel }) => { .then(({ sales_channel }) => {
console.log(sales_channel.id); console.log(sales_channel.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -70,20 +70,20 @@ medusa.admin.salesChannels.create({
```jsx ```jsx
fetch(`<SERVER_URL>/admin/sales-channels`, { fetch(`<SERVER_URL>/admin/sales-channels`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
name: 'App', name: "App",
description: 'Mobile app' description: "Mobile app",
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ sales_channel }) => { .then(({ sales_channel }) => {
console.log(sales_channel.id) console.log(sales_channel.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -118,8 +118,8 @@ You can list all sales channels by sending a request to the List Sales Channels
```jsx ```jsx
medusa.admin.salesChannels.list() medusa.admin.salesChannels.list()
.then(({ sales_channels, limit, offset, count }) => { .then(({ sales_channels, limit, offset, count }) => {
console.log(sales_channels.length); console.log(sales_channels.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -127,12 +127,12 @@ medusa.admin.salesChannels.list()
```jsx ```jsx
fetch(`<SERVER_URL>/admin/sales-channels`, { fetch(`<SERVER_URL>/admin/sales-channels`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ sales_channels, limit, offset, count }) => { .then(({ sales_channels, limit, offset, count }) => {
console.log(sales_channels.length) console.log(sales_channels.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -160,8 +160,8 @@ You can retrieve a sales channel’s details by its ID using the Get Sales Chann
```jsx ```jsx
medusa.admin.salesChannels.retrieve(salesChannelId) medusa.admin.salesChannels.retrieve(salesChannelId)
.then(({ sales_channel }) => { .then(({ sales_channel }) => {
console.log(sales_channel.id); console.log(sales_channel.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -169,12 +169,12 @@ medusa.admin.salesChannels.retrieve(salesChannelId)
```jsx ```jsx
fetch(`<SERVER_URL>/admin/sales-channels/${salesChannelId}`, { fetch(`<SERVER_URL>/admin/sales-channels/${salesChannelId}`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ sales_channels, limit, offset, count }) => { .then(({ sales_channels, limit, offset, count }) => {
console.log(sales_channels.length) console.log(sales_channels.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -201,11 +201,11 @@ You can update a Sales Channel’s details and attributes by sending a request t
```jsx ```jsx
medusa.admin.salesChannels.update(salesChannelId, { medusa.admin.salesChannels.update(salesChannelId, {
is_disabled: false is_disabled: false,
}) })
.then(({ sales_channel }) => { .then(({ sales_channel }) => {
console.log(sales_channel.id); console.log(sales_channel.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -213,19 +213,19 @@ medusa.admin.salesChannels.update(salesChannelId, {
```jsx ```jsx
fetch(`<SERVER_URL>/admin/sales-channels/${salesChannelId}`, { fetch(`<SERVER_URL>/admin/sales-channels/${salesChannelId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
is_disabled: false is_disabled: false,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ sales_channel }) => { .then(({ sales_channel }) => {
console.log(sales_channel.id) console.log(sales_channel.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -261,8 +261,8 @@ You can delete a sales channel by sending a request to the Delete Sales Channel
```jsx ```jsx
medusa.admin.salesChannels.delete(salesChannelId) medusa.admin.salesChannels.delete(salesChannelId)
.then(({ id, object, deleted }) => { .then(({ id, object, deleted }) => {
console.log(id); console.log(id)
}); })
``` ```
</TabItem> </TabItem>
@@ -270,13 +270,13 @@ medusa.admin.salesChannels.delete(salesChannelId)
```jsx ```jsx
fetch(`<SERVER_URL>/admin/sales-channels/${salesChannelId}`, { fetch(`<SERVER_URL>/admin/sales-channels/${salesChannelId}`, {
method: 'DELETE', method: "DELETE",
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ id, object, deleted }) => { .then(({ id, object, deleted }) => {
console.log(id) console.log(id)
}); })
``` ```
</TabItem> </TabItem>
@@ -307,37 +307,40 @@ To add a product to a sales channel, send a request to the Sales Channel’s Add
medusa.admin.salesChannels.addProducts(salesChannelId, { medusa.admin.salesChannels.addProducts(salesChannelId, {
product_ids: [ product_ids: [
{ {
id: productId id: productId,
} },
] ],
}) })
.then(({ sales_channel }) => { .then(({ sales_channel }) => {
console.log(sales_channel.id); console.log(sales_channel.id)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```jsx ```jsx
fetch(`<SERVER_URL>/admin/sales-channels/${salesChannelId}/products/batch`, { fetch(
method: 'POST', `<SERVER_URL>/admin/sales-channels/${salesChannelId}/products/batch`,
credentials: 'include', {
headers: { method: "POST",
'Content-Type': 'application/json' credentials: "include",
}, headers: {
body: JSON.stringify({ "Content-Type": "application/json",
product_ids: [ },
{ body: JSON.stringify({
id: productId product_ids: [
} {
] id: productId,
}) },
}) ],
}),
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ sales_channel }) => { .then(({ sales_channel }) => {
console.log(sales_channel.id) console.log(sales_channel.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -373,25 +376,28 @@ You can list the products available in a sales channel by sending a request to t
```jsx ```jsx
medusa.admin.products.list({ medusa.admin.products.list({
sales_channel_id: [ sales_channel_id: [
salesChannelId salesChannelId,
] ],
}) })
.then(({ products, limit, offset, count }) => { .then(({ products, limit, offset, count }) => {
console.log(products.length); console.log(products.length)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```jsx ```jsx
fetch(`<SERVER_URL>/admin/products?sales_channel_id[0]=${salesChannelId}`, { fetch(
credentials: 'include', `<SERVER_URL>/admin/products?sales_channel_id[0]=${salesChannelId}`,
}) {
credentials: "include",
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ products, limit, offset, count }) => { .then(({ products, limit, offset, count }) => {
console.log(products.length) console.log(products.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -424,43 +430,46 @@ You can delete a product from a sales channel by sending a request to the Sales
medusa.admin.salesChannels.removeProducts(salesChannelId, { medusa.admin.salesChannels.removeProducts(salesChannelId, {
product_ids: [ product_ids: [
{ {
id: productId id: productId,
} },
] ],
}) })
.then(({ sales_channel }) => { .then(({ sales_channel }) => {
console.log(sales_channel.id); console.log(sales_channel.id)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```jsx ```jsx
fetch(`<SERVER_URL>/admin/sales-channels/${salesChannelId}/products/batch`, { fetch(
method: 'DELETE', `<SERVER_URL>/admin/sales-channels/${salesChannelId}/products/batch`,
credentials: 'include', {
headers: { method: "DELETE",
'Content-Type': 'application/json' credentials: "include",
}, headers: {
body: JSON.stringify({ "Content-Type": "application/json",
product_ids: [ },
{ body: JSON.stringify({
id: productId product_ids: [
} {
] id: productId,
}) },
}) ],
}),
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ sales_channel }) => { .then(({ sales_channel }) => {
console.log(sales_channel.id) console.log(sales_channel.id)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="curl" label="cURL"> <TabItem value="curl" label="cURL">
```jsx ```bash
curl -L -X DELETE '<SERVER_URL>/admin/sales-channels/<SALES_CHANNEL_ID>/products/batch' \ curl -L -X DELETE '<SERVER_URL>/admin/sales-channels/<SALES_CHANNEL_ID>/products/batch' \
-H 'Authorization: Bearer <API_TOKEN>' \ -H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \ -H 'Content-Type: application/json' \
@@ -492,14 +501,14 @@ You can filter orders by a specific sales channel by sending a request to the Li
```jsx ```jsx
medusa.admin.orders.list({ medusa.admin.orders.list({
sales_channel_id: [ sales_channel_id: [
salesChannelId salesChannelId,
], ],
limit: 50, limit: 50,
offset: 0 offset: 0,
}) })
.then(({ orders, limit, offset, count }) => { .then(({ orders, limit, offset, count }) => {
console.log(orders.length); console.log(orders.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -507,18 +516,18 @@ medusa.admin.orders.list({
```jsx ```jsx
fetch(`<SERVER_URL>/admin/orders?sales_channel_id[0]=${salesChannelId}`, { fetch(`<SERVER_URL>/admin/orders?sales_channel_id[0]=${salesChannelId}`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ orders, limit, offset, count }) => { .then(({ orders, limit, offset, count }) => {
console.log(orders.length) console.log(orders.length)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="curl" label="cURL"> <TabItem value="curl" label="cURL">
```jsx ```bash
curl -L -X GET '<SERVER_URL>/admin/orders?sales_channel_id[0]=<SALES_CHANNEL_ID>' \ curl -L -X GET '<SERVER_URL>/admin/orders?sales_channel_id[0]=<SALES_CHANNEL_ID>' \
-H 'Authorization: Bearer <API_TOKEN>' -H 'Authorization: Bearer <API_TOKEN>'
``` ```
@@ -38,23 +38,25 @@ To create a scheduled job, add the following code in the file you created, which
```ts title=src/loaders/publish.ts ```ts title=src/loaders/publish.ts
const publishJob = async (container, options) => { const publishJob = async (container, options) => {
const jobSchedulerService = container.resolve("jobSchedulerService"); const jobSchedulerService = container.resolve("jobSchedulerService")
jobSchedulerService.create("publish-products", {}, "0 0 * * *", async () => { jobSchedulerService.create("publish-products", {}, "0 0 * * *",
//job to execute async () => {
const productService = container.resolve("productService"); // job to execute
const draftProducts = await productService.list({ const productService = container.resolve("productService")
status: 'draft' const draftProducts = await productService.list({
}); status: "draft",
})
for (const product of draftProducts) { for (const product of draftProducts) {
await productService.update(product.id, { await productService.update(product.id, {
status: 'published' status: "published",
}); })
}
} }
}) )
} }
export default publishJob; export default publishJob
``` ```
:::info :::info
@@ -87,12 +89,12 @@ For example:
```ts ```ts
jobSchedulerService.create("publish-products", { jobSchedulerService.create("publish-products", {
data: { data: {
productId productId,
} },
}, "0 0 * * *", async (job) => { }, "0 0 * * *", async (job) => {
console.log(job.data); // {productId: 'prod_124...'} console.log(job.data) // {productId: 'prod_124...'}
//... // ...
}); })
``` ```
--- ---
@@ -9,12 +9,12 @@ To create a service, create a TypeScript or JavaScript file in `src/services` to
For example, if you want to create a service `helloService`, create the file `hello.ts` in `src/services` with the following content: For example, if you want to create a service `helloService`, create the file `hello.ts` in `src/services` with the following content:
```ts title=/src/services/hello.ts ```ts title=/src/services/hello.ts
import { TransactionBaseService } from '@medusajs/medusa'; import { TransactionBaseService } from "@medusajs/medusa"
import { EntityManager } from 'typeorm'; import { EntityManager } from "typeorm"
class HelloService extends TransactionBaseService { class HelloService extends TransactionBaseService {
protected manager_: EntityManager; protected manager_: EntityManager
protected transactionManager_: EntityManager; protected transactionManager_: EntityManager
getMessage() { getMessage() {
return `Welcome to My Store!` return `Welcome to My Store!`
} }
@@ -32,19 +32,25 @@ As the service extends the `TransactionBaseService` class, all services in Medus
So, if you want your service to use another service, simply add it as part of your constructor’s dependencies and set it to a field inside your service’s class: So, if you want your service to use another service, simply add it as part of your constructor’s dependencies and set it to a field inside your service’s class:
```ts ```ts
private productService: ProductService; class HelloService extends TransactionBaseService {
private productService: ProductService
constructor(container) { constructor(container) {
super(container); super(container)
this.productService = container.productService; this.productService = container.productService
}
// ...
} }
``` ```
Then, you can use that service anywhere in your custom service: Then, you can use that service anywhere in your custom service:
```ts ```ts
async getProductCount() { class HelloService extends TransactionBaseService {
return await this.productService.count(); // ...
async getProductCount() {
return await this.productService.count()
}
} }
``` ```
@@ -69,9 +75,12 @@ npm run build
To use your custom service in another custom service, you can have easy access to it in the dependencies injected to the constructor of your service: To use your custom service in another custom service, you can have easy access to it in the dependencies injected to the constructor of your service:
```ts ```ts
constructor(container) { class MyService extends TransactionBaseService {
super(container); constructor(container) {
this.helloService = container.helloService; super(container)
this.helloService = container.helloService
}
// ...
} }
``` ```
@@ -92,8 +101,11 @@ res.json({
To use your custom service in a subscriber, you can have easy access to it in the subscriber’s dependencies injected to the constructor of your subscriber: To use your custom service in a subscriber, you can have easy access to it in the subscriber’s dependencies injected to the constructor of your subscriber:
```ts ```ts
constructor({ helloService, eventBusService }) { class MySubscriber {
this.helloService = helloService; constructor({ helloService, eventBusService }) {
this.helloService = helloService
}
// ...
} }
``` ```
@@ -32,7 +32,7 @@ class MyFulfillmentService extends FulfillmentService {
} }
export default MyFulfillmentService; export default MyFulfillmentService
``` ```
Fulfillment provider services must extend the `FulfillmentService` class imported from `medusa-interfaces`. Fulfillment provider services must extend the `FulfillmentService` class imported from `medusa-interfaces`.
@@ -55,10 +55,10 @@ The value of this property will also be used to reference the fulfillment provid
import { FulfillmentService } from "medusa-interfaces" import { FulfillmentService } from "medusa-interfaces"
class MyFulfillmentService extends FulfillmentService { class MyFulfillmentService extends FulfillmentService {
static identifier = 'my-fulfillment'; static identifier = "my-fulfillment"
} }
export default MyFulfillmentService; export default MyFulfillmentService
``` ```
### constructor ### constructor
@@ -72,9 +72,12 @@ Additionally, if you’re creating your fulfillment provider as an external plug
For example: For example:
```ts ```ts
constructor({ productService }, options) { class MyFulfillmentService extends FulfillmentService {
super(); // ...
//you can access options here constructor(container, options) {
super(container)
// you can access options here
}
} }
``` ```
@@ -89,15 +92,18 @@ These fulfillment options are defined in the `getFulfillmentOptions` method. Thi
For example: For example:
```ts ```ts
async getFulfillmentOptions () { class MyFulfillmentService extends FulfillmentService {
return [ // ...
{ async getFulfillmentOptions() {
id: 'my-fulfillment' return [
}, {
{ id: "my-fulfillment",
id: 'my-fulfillment-dynamic' },
} {
]; id: "my-fulfillment-dynamic",
},
]
}
} }
``` ```
@@ -116,8 +122,11 @@ This method returns a boolean. If the result is false, an error is thrown and th
For example, you can use this method to ensure that the `id` in the `data` object is correct: For example, you can use this method to ensure that the `id` in the `data` object is correct:
```ts ```ts
async validateOption (data) { class MyFulfillmentService extends FulfillmentService {
return data.id == 'my-fulfillment'; // ...
async validateOption(data) {
return data.id == "my-fulfillment"
}
} }
``` ```
@@ -144,13 +153,16 @@ If everything is valid, this method must return a value that will be stored in t
For example: For example:
```ts ```ts
async validateFulfillmentData(optionData, data, cart) { class MyFulfillmentService extends FulfillmentService {
if (data.id !== "my-fulfillment") { // ...
throw new Error("invalid data"); async validateFulfillmentData(optionData, data, cart) {
} if (data.id !== "my-fulfillment") {
throw new Error("invalid data")
}
return { return {
...data ...data,
}
} }
} }
``` ```
@@ -173,14 +185,17 @@ You can use the `data` property in the shipping method (first parameter) to acce
Here is a basic implementation of `createFulfillment` for a fulfillment provider that does not interact with any third-party provider to create the fulfillment: Here is a basic implementation of `createFulfillment` for a fulfillment provider that does not interact with any third-party provider to create the fulfillment:
```ts ```ts
createFulfillment( class MyFulfillmentService extends FulfillmentService {
methodData, // ...
fulfillmentItems, createFulfillment(
fromOrder, methodData,
fulfillment fulfillmentItems,
) { fromOrder,
// No data is being sent anywhere fulfillment
return Promise.resolve({}) ) {
// No data is being sent anywhere
return Promise.resolve({})
}
} }
``` ```
@@ -206,8 +221,11 @@ This method receives as a parameter the `data` object sent with the request that
For example: For example:
```ts ```ts
canCalculate(data) { class MyFulfillmentService extends FulfillmentService {
return data.id === 'my-fulfillment-dynamic'; // ...
canCalculate(data) {
return data.id === "my-fulfillment-dynamic"
}
} }
``` ```
@@ -224,16 +242,22 @@ This method receives three parameters:
If your fulfillment provider does not provide any dynamically calculated rates you can keep the function empty: If your fulfillment provider does not provide any dynamically calculated rates you can keep the function empty:
```ts ```ts
calculatePrice() { class MyFulfillmentService extends FulfillmentService {
// ...
calculatePrice() {
// leave empty
}
} }
``` ```
Otherwise, you can use it to calculate the price with a custom logic. For example: Otherwise, you can use it to calculate the price with a custom logic. For example:
```ts ```ts
calculatePrice (optionData, data, cart) { class MyFulfillmentService extends FulfillmentService {
return cart.items.length * 1000; // ...
calculatePrice(optionData, data, cart) {
return cart.items.length * 1000
}
} }
``` ```
@@ -250,8 +274,11 @@ It receives the return created as a parameter. The value it returns is set to th
This is the basic implementation of the method for a fulfillment provider that does not contact with a third-party provider to fulfill the return: This is the basic implementation of the method for a fulfillment provider that does not contact with a third-party provider to fulfill the return:
```ts ```ts
createReturn(returnOrder) { class MyFulfillmentService extends FulfillmentService {
return Promise.resolve({}) // ...
createReturn(returnOrder) {
return Promise.resolve({})
}
} }
``` ```
@@ -266,8 +293,11 @@ This method receives the fulfillment being cancelled as a parameter.
This is the basic implementation of the method for a fulfillment provider that does not interact with a third-party provider to cancel the fulfillment: This is the basic implementation of the method for a fulfillment provider that does not interact with a third-party provider to cancel the fulfillment:
```ts ```ts
cancelFulfillment(fulfillment) { class MyFulfillmentService extends FulfillmentService {
return Promise.resolve({}) // ...
cancelFulfillment(fulfillment) {
return Promise.resolve({})
}
} }
``` ```
@@ -18,20 +18,20 @@ After creating the file under `src/subscribers`, in the constructor of your subs
The `eventBusService.subscribe` method receives the name of the event as a first parameter and as a second parameter a method in your subscriber that will handle this event. The `eventBusService.subscribe` method receives the name of the event as a first parameter and as a second parameter a method in your subscriber that will handle this event.
For example, here is the `OrderNotifierSubscriber` class created in `src/subscribers/orderNotifier.js`: For example, here is the `OrderNotifierSubscriber` class created in `src/subscribers/orderNotifier.ts`:
```ts title=src/subscribers/orderNotifier.js ```ts title=src/subscribers/orderNotifier.ts
class OrderNotifierSubscriber { class OrderNotifierSubscriber {
constructor({ eventBusService }) { constructor({ eventBusService }) {
eventBusService.subscribe("order.placed", this.handleOrder); eventBusService.subscribe("order.placed", this.handleOrder)
} }
handleOrder = async (data) => { handleOrder = async (data) => {
console.log("New Order: " + data.id) console.log("New Order: " + data.id)
}; }
} }
export default OrderNotifierSubscriber; export default OrderNotifierSubscriber
``` ```
This subscriber registers the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed. It receives the order ID in the `data` parameter. You can then use the order’s details to perform any kind of task you need. This subscriber registers the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed. It receives the order ID in the `data` parameter. You can then use the order’s details to perform any kind of task you need.
@@ -51,19 +51,25 @@ You can access any service through the dependencies injected to your subscriber
For example: For example:
```ts ```ts
constructor({ productService, eventBusService }) { class OrderNotifierSubscriber {
this.productService = productService; constructor({ productService, eventBusService }) {
this.productService = productService
eventBusService.subscribe("order.placed", this.handleOrder); eventBusService.subscribe("order.placed", this.handleOrder)
}
// ...
} }
``` ```
You can then use `this.productService` anywhere in your subscriber’s methods. For example: You can then use `this.productService` anywhere in your subscriber’s methods. For example:
```ts ```ts
handleOrder = async (data) => { class OrderNotifierSubscriber {
//... // ...
const product = this.productService.list() handleOrder = async (data) => {
// ...
const product = this.productService.list()
}
} }
``` ```
@@ -51,7 +51,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of batch job id // string ID of batch job
} }
``` ```
@@ -72,7 +72,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of batch job id // string ID of batch job
} }
``` ```
@@ -93,7 +93,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of batch job id // string ID of batch job
} }
``` ```
@@ -116,7 +116,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of batch job id // string ID of batch job
} }
``` ```
@@ -139,7 +139,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of batch job id // string ID of batch job
} }
``` ```
@@ -160,7 +160,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of batch job id // string ID of batch job
} }
``` ```
@@ -181,7 +181,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of batch job id // string ID of batch job
} }
``` ```
@@ -202,7 +202,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of batch job id // string ID of batch job
} }
``` ```
@@ -262,7 +262,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of cart id // string ID of cart
} }
``` ```
@@ -329,8 +329,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of claim id, // string ID of claim
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -354,8 +354,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of claim id, // string ID of claim
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -379,8 +379,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of claim id, // string ID of claim
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -404,9 +404,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of claim id, // string ID of claim
fulfillment_id, //string ID of the fulfillment created fulfillment_id, // string ID of the fulfillment created
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -430,9 +430,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of claim id, // string ID of claim
fulfillment_id, //string ID of the fulfillment created fulfillment_id, // string ID of the fulfillment created
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -456,8 +456,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of claim id, // string ID of claim
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -505,7 +505,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of claim item id // string ID of claim item
} }
``` ```
@@ -529,7 +529,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of claim item id // string ID of claim item
} }
``` ```
@@ -553,7 +553,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of claim item id // string ID of claim item
} }
``` ```
@@ -601,7 +601,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
code //string 3 character ISO code of the updated currency. code // string 3 character ISO code of the updated currency.
} }
``` ```
@@ -685,11 +685,11 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of customer id, // string ID of customer
email, //string email of the customer email, // string email of the customer
first_name, //string first name of the customer first_name, // string first name of the customer
last_name, //string last name of the customer last_name, // string last name of the customer
token //string reset password token token // string reset password token
} }
``` ```
@@ -737,7 +737,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of draft order id // string ID of draft order
} }
``` ```
@@ -761,7 +761,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of draft order id // string ID of draft order
} }
``` ```
@@ -858,9 +858,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of invite id // string ID of invite
token, //string token generated to validate the invited user token, // string token generated to validate the invited user
user_email //string email of invited user user_email // string email of invited user
} }
``` ```
@@ -907,7 +907,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of note id // string ID of note
} }
``` ```
@@ -931,7 +931,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of note id // string ID of note
} }
``` ```
@@ -955,7 +955,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of note id // string ID of note
} }
``` ```
@@ -1070,8 +1070,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -1095,8 +1095,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
no_notification //(optional) boolean indicating whether a notification should be sent or not no_notification // (optional) boolean indicating whether a notification should be sent
} }
``` ```
@@ -1120,8 +1120,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -1145,8 +1145,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -1194,7 +1194,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of order id // string ID of order
} }
``` ```
@@ -1218,8 +1218,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -1243,10 +1243,10 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
payment_id, //string ID of Payment payment_id, // string ID of Payment
error, //string error message error, // string error message
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -1270,9 +1270,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
fulfillment_id, //string ID of fulfillment fulfillment_id, // string ID of fulfillment
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -1296,9 +1296,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
fulfillment_id, //string ID of fulfillment fulfillment_id, // string ID of fulfillment
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -1322,9 +1322,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
fulfillment_id, //string ID of fulfillment fulfillment_id, // string ID of fulfillment
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -1348,9 +1348,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
return_id, //string ID of return return_id, // string ID of return
no_notification //(optional) boolean indicating whether a notification should be sent or not no_notification // (optional) boolean indicating whether a notification should be sent
} }
``` ```
@@ -1374,9 +1374,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
return_id, //string ID of return return_id, // string ID of return
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -1400,9 +1400,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
return_id, //string ID of return return_id, // string ID of return
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -1426,9 +1426,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of order id, // string ID of order
refund_id, //string ID of refund refund_id, // string ID of refund
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -1631,7 +1631,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of order edit id // string ID of order edit
} }
``` ```
@@ -1712,7 +1712,7 @@ Triggered when an order edit item change is created.
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of item change id // string ID of item change
} }
``` ```
@@ -1734,7 +1734,7 @@ Triggered when an order edit item change is deleted.
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of item change id // string ID of item change
} }
``` ```
@@ -2027,7 +2027,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of product id // string ID of product
} }
``` ```
@@ -2053,8 +2053,8 @@ In one case, when the `/admin/products/{id}` endpoint is used to update the prod
```js noReport noCopy ```js noReport noCopy
{ {
id, //id of product id, // id of product
fields //an array of field names that were updated fields // an array of field names that were updated
} }
``` ```
@@ -2078,7 +2078,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of product id // string ID of product
} }
``` ```
@@ -2126,8 +2126,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of variant id, // string ID of variant
product_id //string ID of product product_id // string ID of product
} }
``` ```
@@ -2151,9 +2151,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of variant id, // string ID of variant
product_id, //string ID of product product_id, // string ID of product
fields //array of names of updated fields fields // array of names of updated fields
} }
``` ```
@@ -2177,9 +2177,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of variant id, // string ID of variant
product_id, //string ID of product product_id, // string ID of product
metadata //object of the `metadata` field of the variant metadata // object of the `metadata` field of the variant
} }
``` ```
@@ -2239,7 +2239,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of publishable API key id // string ID of publishable API key
} }
``` ```
@@ -2263,7 +2263,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of publishable API key id // string ID of publishable API key
} }
``` ```
@@ -2311,7 +2311,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of region id // string ID of region
} }
``` ```
@@ -2335,8 +2335,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of region id, // string ID of region
fields //array of names of updated fields fields // array of names of updated fields
} }
``` ```
@@ -2360,7 +2360,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of region id // string ID of region
} }
``` ```
@@ -2418,7 +2418,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of sales channel id // string ID of sales channel
} }
``` ```
@@ -2466,7 +2466,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of sales channel id // string ID of sales channel
} }
``` ```
@@ -2513,8 +2513,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of swap id, // string ID of swap
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -2538,9 +2538,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of swap id, // string ID of swap
order_id, //string ID of order order_id, // string ID of order
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -2564,9 +2564,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of swap id, // string ID of swap
fulfillment_id, //string ID of fulfillment fulfillment_id, // string ID of fulfillment
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -2590,9 +2590,9 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of swap id, // string ID of swap
fulfillment_id, //string ID of fulfillment fulfillment_id, // string ID of fulfillment
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -2616,8 +2616,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of swap id, // string ID of swap
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -2641,8 +2641,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of swap id, // string ID of swap
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -2666,8 +2666,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of swap id, // string ID of swap
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -2691,8 +2691,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of swap id, // string ID of swap
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -2716,8 +2716,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id, //string ID of swap id, // string ID of swap
no_notification //boolean indicating whether a notification should be sent or not no_notification // boolean indicating whether a notification should be sent
} }
``` ```
@@ -2816,7 +2816,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of user id // string ID of user
} }
``` ```
@@ -2840,7 +2840,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of user id // string ID of user
} }
``` ```
@@ -2864,8 +2864,8 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
email, //string email of user requesting to reset their password email, // string email of user requesting to reset their password
token //token create to reset the password token // token create to reset the password
} }
``` ```
@@ -2889,7 +2889,7 @@ Object of the following format:
```js noReport noCopy ```js noReport noCopy
{ {
id //string ID of user id // string ID of user
} }
``` ```
@@ -126,7 +126,9 @@ Where `amount` is the amount of the variant’s price in the price list, `taxRat
Here is an example of these fields when tax inclusivity is enabled for both the currency and the price list: Here is an example of these fields when tax inclusivity is enabled for both the currency and the price list:
```jsx noReport <!-- eslint-skip -->
```js noReport
{ {
original_price: 110, original_price: 110,
calculated_price: 100, calculated_price: 100,
@@ -29,7 +29,7 @@ You can, however, force calculating the taxes of the cart by passing in the thir
For example: For example:
```jsx ```jsx
cartService.retrieve('cart_01G8Z...', { }, { force_taxes: true }); cartService.retrieve("cart_01G8Z...", { }, { force_taxes: true })
``` ```
:::tip :::tip
@@ -45,20 +45,20 @@ Another way you can use the `CartService` to calculate taxes is using the method
```jsx ```jsx
export default () => { export default () => {
//... // ...
router.get("/store/line-taxes", async (req, res) => { router.get("/store/line-taxes", async (req, res) => {
//example of retrieving cart // example of retrieving cart
const cartService = req.scope.resolve("cartService"); const cartService = req.scope.resolve("cartService")
const cart = await cartService.retrieve(cart_id) const cart = await cartService.retrieve(cart_id)
//... // ...
//retrieve taxes of line items // retrieve taxes of line items
const data = await decorateTotals(cart, { const data = await decorateTotals(cart, {
force_taxes: true force_taxes: true,
}) })
return res.status(200).json({ cart: data }); return res.status(200).json({ cart: data })
}) })
} }
``` ```
@@ -72,7 +72,7 @@ You can calculate and retrieve taxes of line items using the `getLineItemTotals`
```jsx ```jsx
const itemTotals = await totalsService const itemTotals = await totalsService
.getLineItemTotals(item, cart, { .getLineItemTotals(item, cart, {
include_tax: true include_tax: true,
}) })
``` ```
@@ -35,28 +35,29 @@ If you use a `.env` file to load environment variables on your server, you need
You can add the following code snippet at the top of the file which uses the [dotenv](https://www.npmjs.com/package/dotenv) package to load the environment variables based on the current Node environment: You can add the following code snippet at the top of the file which uses the [dotenv](https://www.npmjs.com/package/dotenv) package to load the environment variables based on the current Node environment:
```jsx ```jsx
const dotenv = require('dotenv') const dotenv = require("dotenv")
let ENV_FILE_NAME = ''; let ENV_FILE_NAME = ""
switch (process.env.NODE_ENV) { switch (process.env.NODE_ENV) {
case 'production': case "production":
ENV_FILE_NAME = '.env.production'; ENV_FILE_NAME = ".env.production"
break; break
case 'staging': case "staging":
ENV_FILE_NAME = '.env.staging'; ENV_FILE_NAME = ".env.staging"
break; break
case 'test': case "test":
ENV_FILE_NAME = '.env.test'; ENV_FILE_NAME = ".env.test"
break; break
case 'development': case "development":
default: default:
ENV_FILE_NAME = '.env'; ENV_FILE_NAME = ".env"
break; break
} }
try { try {
dotenv.config({ path: process.cwd() + '/' + ENV_FILE_NAME }); dotenv.config({ path: process.cwd() + "/" + ENV_FILE_NAME })
} catch (e) { } catch (e) {
// handle error
} }
``` ```
@@ -31,19 +31,19 @@ Instead of using `customerService.retrieveByEmail`, you should now use the metho
The `customerService.retrieveRegisteredByEmail` method allows you to retrieve a registered customer by email: The `customerService.retrieveRegisteredByEmail` method allows you to retrieve a registered customer by email:
```ts ```ts
customerService.retrieveRegisteredByEmail("example@gmail.com"); customerService.retrieveRegisteredByEmail("example@gmail.com")
``` ```
On the other hand, the `retrieveUnregisteredByEmail` method allows to retrieve guest customers by email: On the other hand, the `retrieveUnregisteredByEmail` method allows to retrieve guest customers by email:
```jsx ```jsx
customerService.retrieveUnregisteredByEmail("example@gmail.com"); customerService.retrieveUnregisteredByEmail("example@gmail.com")
``` ```
To retrieve a customer by email regardless of whether they are registered or not, you can use the `customerService.list` method instead: To retrieve a customer by email regardless of whether they are registered or not, you can use the `customerService.list` method instead:
```ts ```ts
customerService.list({ customerService.list({
email: "example@gmail.com" email: "example@gmail.com",
}) })
``` ```
@@ -56,13 +56,13 @@ In your loader file that creates a cron job, replace the use of `eventBus` to `j
```ts ```ts
const myJob = async (container, options) => { const myJob = async (container, options) => {
const jobSchedulerService = container.resolve("jobSchedulerService"); const jobSchedulerService = container.resolve("jobSchedulerService")
jobSchedulerService.create("my-job", {}, "0 0 * * *", async () => { jobSchedulerService.create("my-job", {}, "0 0 * * *", async () => {
//... // ...
}); })
} }
export default myJob; export default myJob
``` ```
You can learn more in the [How to Create a Scheduled Job](../scheduled-jobs/create.md) documentation. You can learn more in the [How to Create a Scheduled Job](../scheduled-jobs/create.md) documentation.
@@ -73,15 +73,26 @@ This version of Medusa introduces a change in how payment providers are implemen
Although this change is currently backwards compatible, it is recommended to change the signature of these methods to the following: Although this change is currently backwards compatible, it is recommended to change the signature of these methods to the following:
<!-- eslint-disable max-len -->
```ts ```ts
import { PaymentContext, PaymentSessionResponse } from '@medusajs/medusa' import { PaymentContext, PaymentSessionResponse } from "@medusajs/medusa"
// ...
async createPayment(context: PaymentContext): Promise<PaymentSessionResponse> { class MyPaymentService extends AbstractPaymentService<TransactionBaseService> {
//... // ...
} async createPayment(
context: PaymentContext
): Promise<PaymentSessionResponse> {
// ...
}
async updatePayment(paymentSessionData: PaymentSessionData, context: PaymentContext): Promise<PaymentSessionResponse> { async updatePayment(
//... paymentSessionData: PaymentSessionData,
context: PaymentContext
): Promise<PaymentSessionResponse> {
// ...
}
} }
``` ```
@@ -47,7 +47,7 @@ You can learn more about subscribers in the [Subscribers](../backend/subscribers
Create the file `src/subscribers/claim-order.ts` with the following content: Create the file `src/subscribers/claim-order.ts` with the following content:
```ts title=src/subscribers/claim-order.ts ```ts title=src/subscribers/claim-order.ts
import { EventBusService } from "@medusajs/medusa"; import { EventBusService } from "@medusajs/medusa"
type InjectedDependencies = { type InjectedDependencies = {
eventBusService: EventBusService, eventBusService: EventBusService,
@@ -59,7 +59,7 @@ class ClaimOrderSubscriber {
} }
} }
export default ClaimOrderSubscriber; export default ClaimOrderSubscriber
``` ```
If you want to add any other dependencies, you can add them to the `InjectedDependencies` type. If you want to add any other dependencies, you can add them to the `InjectedDependencies` type.
@@ -77,8 +77,14 @@ You can learn more about dependency injection in [this documentation](../backend
In the subscriber you created, add the following in the `constructor`: In the subscriber you created, add the following in the `constructor`:
```ts title=src/subscribers/claim-order.ts ```ts title=src/subscribers/claim-order.ts
constructor({ eventBusService }: InjectedDependencies) { class ClaimOrderSubscriber {
eventBusService.subscribe("order-update-token.created", this.handleRequestClaimOrder); constructor({ eventBusService }: InjectedDependencies) {
eventBusService.subscribe(
"order-update-token.created",
this.handleRequestClaimOrder
)
}
// ...
} }
``` ```
@@ -90,14 +96,14 @@ In the subscriber, add a new method `handleRequestClaimOrder`:
```ts title=src/subscribers/claim-order.ts ```ts title=src/subscribers/claim-order.ts
class ClaimOrderSubscriber { class ClaimOrderSubscriber {
//... // ...
handleRequestClaimOrder = async (data) { handleRequestClaimOrder = async (data) => {
//TODO: handle event // TODO: handle event
} }
} }
export default ClaimOrderSubscriber; export default ClaimOrderSubscriber
``` ```
The `handleRequestClaimOrder` event receives a `data` object as a parameter. This object holds the following properties: The `handleRequestClaimOrder` event receives a `data` object as a parameter. This object holds the following properties:
@@ -118,7 +124,7 @@ The page would then send a request to the server to verify that the `token` is v
For example, you can implement this subscriber to send emails using SendGrid: For example, you can implement this subscriber to send emails using SendGrid:
```ts title=src/subscribers/claim-order.ts ```ts title=src/subscribers/claim-order.ts
import { EventBusService } from "@medusajs/medusa"; import { EventBusService } from "@medusajs/medusa"
type InjectedDependencies = { type InjectedDependencies = {
eventBusService: EventBusService, eventBusService: EventBusService,
@@ -126,28 +132,31 @@ type InjectedDependencies = {
} }
class ClaimOrderSubscriber { class ClaimOrderSubscriber {
protected sendGridService: any; protected sendGridService: any
constructor({ eventBusService, sendgridService }: InjectedDependencies) { constructor({ eventBusService, sendgridService }: InjectedDependencies) {
this.sendGridService = sendgridService; this.sendGridService = sendgridService
eventBusService.subscribe("order-update-token.created", this.handleRequestClaimOrder); eventBusService.subscribe(
"order-update-token.created",
this.handleRequestClaimOrder
)
} }
handleRequestClaimOrder = async (data) => { handleRequestClaimOrder = async (data) => {
this.sendGridService.sendEmail({ this.sendGridService.sendEmail({
templateId: 'order-claim-confirmation', templateId: "order-claim-confirmation",
from: 'hello@medusajs.com', from: "hello@medusajs.com",
to: data.old_email, to: data.old_email,
data: { data: {
link: `http://example-storefront.com/confirm-order-claim/${data.token}`, link: `http://example.com/confirm-order-claim/${data.token}`,
//other data... // other data...
} },
}) })
} }
} }
export default ClaimOrderSubscriber; export default ClaimOrderSubscriber
``` ```
Notice how the `token` is passed to the storefront link as a parameter. Notice how the `token` is passed to the storefront link as a parameter.
@@ -60,8 +60,8 @@ medusa.customers.create({
last_name, last_name,
}) })
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -69,19 +69,19 @@ medusa.customers.create({
```ts ```ts
fetch(`<SERVER_URL>/store/customers`, { fetch(`<SERVER_URL>/store/customers`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
email, email,
password, password,
first_name, first_name,
last_name, last_name,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -112,11 +112,11 @@ You can log in a customer into your store by sending a request to the [Customer
```ts ```ts
medusa.auth.authenticate({ medusa.auth.authenticate({
email, email,
password password,
}) })
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -124,17 +124,17 @@ medusa.auth.authenticate({
```ts ```ts
fetch(`<SERVER_URL>/store/auth`, { fetch(`<SERVER_URL>/store/auth`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
email, email,
password password,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -158,8 +158,8 @@ You can log out a customer by sending a request to the [Customer Logout](/api/st
```ts ```ts
medusa.auth.deleteSession() medusa.auth.deleteSession()
.then(() => { .then(() => {
//success // success
}); })
``` ```
</TabItem> </TabItem>
@@ -167,12 +167,12 @@ medusa.auth.deleteSession()
```ts ```ts
fetch(`<SERVER_URL>/store/auth`, { fetch(`<SERVER_URL>/store/auth`, {
method: 'DELETE', method: "DELETE",
credentials: 'include' credentials: "include",
}) })
.then(() => { .then(() => {
//success // success
}); })
``` ```
</TabItem> </TabItem>
@@ -197,7 +197,7 @@ You can request to reset a customer’s password by sending a request to the [Re
```ts ```ts
medusa.customers.generatePasswordToken({ medusa.customers.generatePasswordToken({
email email,
}) })
.then(() => { .then(() => {
// successful // successful
@@ -212,11 +212,11 @@ medusa.customers.generatePasswordToken({
```ts ```ts
fetch(`<SERVER_URL>/store/customers/password-token`, { fetch(`<SERVER_URL>/store/customers/password-token`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
email email,
}) }),
}) })
.then(() => { .then(() => {
// successful // successful
@@ -252,11 +252,11 @@ You can then reset the customer’s password to the new password they enter by s
medusa.customers.resetPassword({ medusa.customers.resetPassword({
email, email,
password, password,
token token,
}) })
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -264,18 +264,18 @@ medusa.customers.resetPassword({
```ts ```ts
fetch(`<SERVER_URL>/store/customers/password-reset`, { fetch(`<SERVER_URL>/store/customers/password-reset`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
email, email,
password, password,
token token,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -302,11 +302,11 @@ You can edit a customer’s info using the [Update Customer](/api/store/#tag/Cus
```ts ```ts
medusa.customers.update({ medusa.customers.update({
first_name first_name,
}) })
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -314,16 +314,16 @@ medusa.customers.update({
```ts ```ts
fetch(`<SERVER_URL>/store/customers/me`, { fetch(`<SERVER_URL>/store/customers/me`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
first_name first_name,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -365,12 +365,12 @@ medusa.customers.addresses.addAddress({
company, company,
address_2, address_2,
province, province,
metadata metadata,
} },
}) })
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -378,8 +378,8 @@ medusa.customers.addresses.addAddress({
```ts ```ts
fetch(`<SERVER_URL>/store/customers/me/addresses`, { fetch(`<SERVER_URL>/store/customers/me/addresses`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
address: { address: {
first_name, first_name,
@@ -392,14 +392,14 @@ fetch(`<SERVER_URL>/store/customers/me/addresses`, {
company, company,
address_2, address_2,
province, province,
metadata metadata,
} },
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -427,11 +427,11 @@ You can edit a customer’s shipping address using the [Update a Shipping Addres
```ts ```ts
medusa.customers.addresses.updateAddress(addressId, { medusa.customers.addresses.updateAddress(addressId, {
first_name first_name,
}) })
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -439,16 +439,16 @@ medusa.customers.addresses.updateAddress(addressId, {
```ts ```ts
fetch(`<SERVER_URL>/store/customers/me/addresses/${addressId}`, { fetch(`<SERVER_URL>/store/customers/me/addresses/${addressId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
first_name first_name,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -468,8 +468,8 @@ You can delete a shipping address by sending a request to the [Delete an Address
```ts ```ts
medusa.customers.addresses.deleteAddress(addressId) medusa.customers.addresses.deleteAddress(addressId)
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -477,13 +477,13 @@ medusa.customers.addresses.deleteAddress(addressId)
```ts ```ts
fetch(`<SERVER_URL>/store/customers/me/addresses/${addressId}`, { fetch(`<SERVER_URL>/store/customers/me/addresses/${addressId}`, {
method: 'DELETE', method: "DELETE",
credentials: 'include' credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ customer }) => { .then(({ customer }) => {
console.log(customer.id); console.log(customer.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -505,8 +505,8 @@ You can retrieve a customer’s orders by sending a request to the [List Orders]
```ts ```ts
medusa.customers.listOrders() medusa.customers.listOrders()
.then(({ orders, limit, offset, count }) => { .then(({ orders, limit, offset, count }) => {
console.log(orders); console.log(orders)
}); })
``` ```
</TabItem> </TabItem>
@@ -514,12 +514,12 @@ medusa.customers.listOrders()
```ts ```ts
fetch(`<SERVER_URL>/store/customers/me/orders`, { fetch(`<SERVER_URL>/store/customers/me/orders`, {
credentials: 'include' credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ orders, limit, offset, count }) => { .then(({ orders, limit, offset, count }) => {
console.log(orders); console.log(orders)
}); })
``` ```
</TabItem> </TabItem>
@@ -78,9 +78,9 @@ You can retrieve a single order edit by its ID by sending a request to the [Get
```ts ```ts
medusa.orderEdits.retrieve(orderEditId) medusa.orderEdits.retrieve(orderEditId)
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.changes); console.log(order_edit.changes)
//show changed items to the customer // show changed items to the customer
}); })
``` ```
</TabItem> </TabItem>
@@ -90,9 +90,9 @@ medusa.orderEdits.retrieve(orderEditId)
fetch(`<SERVER_URL>/store/order-edits/${orderEditId}`) fetch(`<SERVER_URL>/store/order-edits/${orderEditId}`)
.then((response) => response.json()) .then((response) => response.json())
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.changes); console.log(order_edit.changes)
//show changed items to the customer // show changed items to the customer
}); })
``` ```
</TabItem> </TabItem>
@@ -116,6 +116,8 @@ You can learn more about what fields to expect in the [API reference](/api/store
All data about changes to the original order’s items can be found in `order_edit.changes`. `changes` is an array of item changes. Each item change includes the following fields: All data about changes to the original order’s items can be found in `order_edit.changes`. `changes` is an array of item changes. Each item change includes the following fields:
<!-- eslint-skip -->
```ts ```ts
{ {
type: string, type: string,
@@ -134,14 +136,22 @@ Here’s an example of how you can use this data to show the customer the reques
```jsx ```jsx
<ul> <ul>
{orderEdit.changes.map(itemChange => ( {orderEdit.changes.map((itemChange) => (
<li key={itemChange.id}> <li key={itemChange.id}>
<strong>{itemChange.line_item ? itemChange.line_item.title : itemChange.original_line_item.title}</strong> <strong>
{itemChange.type === 'added' && <span>New Item</span>} {
{itemChange.type === 'removed' && <span>Removed Item</span>} itemChange.line_item ?
{itemChange.type === 'edited' && itemChange.line_item.title :
itemChange.original_line_item.title
}
</strong>
{itemChange.type === "added" && <span>New Item</span>}
{itemChange.type === "removed" && <span>Removed Item</span>}
{itemChange.type === "edited" &&
<span> <span>
Edited Item. Old Quantity: {itemChange.original_line_item.quantity}. New Quantity: {itemChange.line_item.quantity} Edited Item
Old Quantity: {itemChange.original_line_item.quantity}
New Quantity: {itemChange.line_item.quantity}
</span>} </span>}
</li> </li>
))} ))}
@@ -178,31 +188,34 @@ If `difference_due` is greater than 0, then additional payment from the customer
```ts ```ts
medusa.paymentCollections.managePaymentSession(paymentCollectionId, { medusa.paymentCollections.managePaymentSession(paymentCollectionId, {
provider_id provider_id,
}) })
.then(({ payment_collection }) => { .then(({ payment_collection }) => {
console.log(payment_collection.payment_sessions); console.log(payment_collection.payment_sessions)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```ts ```ts
fetch(`<SERVER_URL>/store/payment-collections/${paymentCollectionId}/sessions`, { fetch(
method: 'POST', `<SERVER_URL>/store/payment-collections/${paymentCollectionId}/sessions`,
credentials: 'include', {
headers: { method: "POST",
'Content-Type': 'application/json' credentials: "include",
}, headers: {
body: JSON.stringify({ "Content-Type": "application/json",
provider_id },
}) body: JSON.stringify({
}) provider_id,
}),
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ payment_collection }) => { .then(({ payment_collection }) => {
console.log(payment_collection.payment_sessions); console.log(payment_collection.payment_sessions)
}); })
``` ```
</TabItem> </TabItem>
@@ -215,24 +228,29 @@ fetch(`<SERVER_URL>/store/payment-collections/${paymentCollectionId}/sessions`,
<TabItem value="client" label="Medusa JS Client" default> <TabItem value="client" label="Medusa JS Client" default>
```ts ```ts
medusa.paymentCollection.authorizePaymentSession(paymentCollectionId, paymentSessionId) medusa.paymentCollection
.authorizePaymentSession(paymentCollectionId, paymentSessionId)
.then(({ payment_session }) => { .then(({ payment_session }) => {
console.log(payment_session.id); console.log(payment_session.id)
}); })
``` ```
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```ts ```ts
fetch(`<SERVER_URL>/store/payment-collection/${paymentCollectionId}/sessions/${paymentSessionId}/authorize`, { fetch(
method: 'POST', `<SERVER_URL>/store/payment-collection/${paymentCollectionId}` +
credentials: 'include', `/sessions/${paymentSessionId}/authorize`,
}) {
method: "POST",
credentials: "include",
}
)
.then((response) => response.json()) .then((response) => response.json())
.then(({ payment_session }) => { .then(({ payment_session }) => {
console.log(payment_session.id); console.log(payment_session.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -252,8 +270,8 @@ To confirm and complete the order edit, send a request to the [Complete Order Ed
```ts ```ts
medusa.orderEdits.complete(orderEditId) medusa.orderEdits.complete(orderEditId)
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.confirmed_at); console.log(order_edit.confirmed_at)
}); })
``` ```
</TabItem> </TabItem>
@@ -261,13 +279,13 @@ medusa.orderEdits.complete(orderEditId)
```ts ```ts
fetch(`<SERVER_URL>/store/order-edits/${orderEditId}/complete`, { fetch(`<SERVER_URL>/store/order-edits/${orderEditId}/complete`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.confirmed_at); console.log(order_edit.confirmed_at)
}); })
``` ```
</TabItem> </TabItem>
@@ -296,11 +314,11 @@ If the customer wants to decline the Order Edit, you can do that by sending a re
```ts ```ts
medusa.orderEdits.decline(orderEditId, { medusa.orderEdits.decline(orderEditId, {
decline_reason: 'I am not satisfied' decline_reason: "I am not satisfied",
}) })
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.declined_at); console.log(order_edit.declined_at)
}); })
``` ```
</TabItem> </TabItem>
@@ -308,19 +326,19 @@ medusa.orderEdits.decline(orderEditId, {
```ts ```ts
fetch(`<SERVER_URL>/store/order-edits/${orderEditId}/decline`, { fetch(`<SERVER_URL>/store/order-edits/${orderEditId}/decline`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
decline_reason: 'I am not satisfied' decline_reason: "I am not satisfied",
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ order_edit }) => { .then(({ order_edit }) => {
console.log(order_edit.declined_at); console.log(order_edit.declined_at)
}); })
``` ```
</TabItem> </TabItem>
@@ -64,11 +64,11 @@ medusa.carts.update(cartId, {
country_code, country_code,
province, province,
postal_code, postal_code,
phone phone,
}, },
}) })
.then(({ cart }) => { .then(({ cart }) => {
console.log(cart.shipping_address); console.log(cart.shipping_address)
}) })
``` ```
@@ -77,8 +77,8 @@ medusa.carts.update(cartId, {
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}`, { fetch(`<SERVER_URL>/store/carts/${cartId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
shipping_address: { shipping_address: {
company, company,
@@ -90,17 +90,17 @@ fetch(`<SERVER_URL>/store/carts/${cartId}`, {
country_code, country_code,
province, province,
postal_code, postal_code,
phone phone,
}, },
}), }),
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => { .then(({ cart }) => {
console.log(cart.shipping_address); console.log(cart.shipping_address)
}); })
``` ```
</TabItem> </TabItem>
@@ -124,7 +124,7 @@ You can retrieve the list of shipping options by sending a `GET` request to the
```jsx ```jsx
medusa.shippingOptions.listCartOptions(cartId) medusa.shippingOptions.listCartOptions(cartId)
.then(({ shipping_options }) => { .then(({ shipping_options }) => {
console.log(shipping_options.length); console.log(shipping_options.length)
}) })
``` ```
@@ -133,11 +133,11 @@ medusa.shippingOptions.listCartOptions(cartId)
```jsx ```jsx
fetch(`<SERVER_URL>/store/shipping-options/${cartId}`, { fetch(`<SERVER_URL>/store/shipping-options/${cartId}`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ shipping_options }) => { .then(({ shipping_options }) => {
console.log(shipping_options.length); console.log(shipping_options.length)
}) })
``` ```
@@ -155,7 +155,7 @@ Once the customer chooses one of the available shipping options, send a `POST` r
```jsx ```jsx
medusa.carts.addShippingMethod(cartId, { medusa.carts.addShippingMethod(cartId, {
option_id: shippingOptionId //the ID of the selected option option_id: shippingOptionId, // the ID of the selected option
}) })
.then(({ cart }) => { .then(({ cart }) => {
console.log(cart.shipping_methods) console.log(cart.shipping_methods)
@@ -167,14 +167,14 @@ medusa.carts.addShippingMethod(cartId, {
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}/shipping-methods`, { fetch(`<SERVER_URL>/store/carts/${cartId}/shipping-methods`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
option_id: shippingOptionId //the ID of the selected option option_id: shippingOptionId, // the ID of the selected option
}), }),
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => { .then(({ cart }) => {
@@ -216,8 +216,8 @@ medusa.carts.createPaymentSessions(cartId)
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}/payment-sessions`, { fetch(`<SERVER_URL>/store/carts/${cartId}/payment-sessions`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => { .then(({ cart }) => {
@@ -239,7 +239,8 @@ When the customer chooses the payment provider they want to complete purchase wi
```jsx ```jsx
medusa.carts.setPaymentSession(cartId, { medusa.carts.setPaymentSession(cartId, {
provider_id: paymentProviderId // retrieved from the payment session selected by the customer // retrieved from the payment session selected by the customer
provider_id: paymentProviderId,
}) })
.then(({ cart }) => { .then(({ cart }) => {
console.log(cart.payment_session) console.log(cart.payment_session)
@@ -251,14 +252,15 @@ medusa.carts.setPaymentSession(cartId, {
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}/payment-session`, { fetch(`<SERVER_URL>/store/carts/${cartId}/payment-session`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
provider_id: paymentProviderId // retrieved from the payment session selected by the customer // retrieved from the payment session selected by the customer
provider_id: paymentProviderId,
}), }),
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => { .then(({ cart }) => {
@@ -291,10 +293,10 @@ If you need to update that data at any point before the purchase is made, send a
```jsx ```jsx
medusa.carts.updatePaymentSession(cartId, paymentProviderId, { medusa.carts.updatePaymentSession(cartId, paymentProviderId, {
data: { data: {
//pass any data you want to add in the `data` attribute // pass any data you want to add in the `data` attribute
//for example: // for example:
"test": true "test": true,
} },
}) })
.then(({ cart }) => { .then(({ cart }) => {
console.log(cart.payment_session.data) console.log(cart.payment_session.data)
@@ -304,21 +306,26 @@ medusa.carts.updatePaymentSession(cartId, paymentProviderId, {
</TabItem> </TabItem>
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
<!-- eslint-disable max-len -->
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}/payment-sessions/${paymentProviderId}`, { fetch(
method: 'POST', `<SERVER_URL>/store/carts/${cartId}/payment-sessions/${paymentProviderId}`,
credentials: 'include', {
body: JSON.stringify({ method: "POST",
data: { credentials: "include",
//pass any data you want to add in the `data` attribute body: JSON.stringify({
//for example: data: {
"test": true // pass any data you want to add in the `data` attribute
} // for example:
}), "test": true,
headers: { },
'Content-Type': 'application/json' }),
headers: {
"Content-Type": "application/json",
},
} }
}) )
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => { .then(({ cart }) => {
console.log(cart.payment_session.data) console.log(cart.payment_session.data)
@@ -344,7 +351,7 @@ To complete a cart, send a `POST` request to the [Complete a Cart](/api/store/#t
```jsx ```jsx
medusa.carts.complete(cartId) medusa.carts.complete(cartId)
.then(({ type, data }) => { .then(({ type, data }) => {
console.log(type, data); console.log(type, data)
}) })
``` ```
@@ -353,15 +360,15 @@ medusa.carts.complete(cartId)
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}/complete`, { fetch(`<SERVER_URL>/store/carts/${cartId}/complete`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ type, data }) => { .then(({ type, data }) => {
console.log(type, data); console.log(type, data)
}) })
``` ```
@@ -82,7 +82,7 @@ medusa.orders.claimOrders({
}) })
.catch(() => { .catch(() => {
// an error occurred // an error occurred
}); })
``` ```
</TabItem> </TabItem>
@@ -90,23 +90,23 @@ medusa.orders.claimOrders({
```tsx ```tsx
fetch(`<SERVER_URL>/store/orders/batch/customer/token`, { fetch(`<SERVER_URL>/store/orders/batch/customer/token`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
order_ids: [ order_ids: [
order_id, order_id,
], ],
}), }),
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
}) })
.then(() => { .then(() => {
//successful // successful
}) })
.catch(() => { .catch(() => {
//display an error to the customer // display an error to the customer
}); })
``` ```
</TabItem> </TabItem>
@@ -131,14 +131,14 @@ Then, you send a request to the Verify Claim Order endpoint:
```tsx ```tsx
medusa.orders.confirmRequest({ medusa.orders.confirmRequest({
token token,
}) })
.then(() => { .then(() => {
// successful // successful
}) })
.catch(() => { .catch(() => {
// an error occurred // an error occurred
}); })
``` ```
</TabItem> </TabItem>
@@ -146,21 +146,21 @@ medusa.orders.confirmRequest({
```tsx ```tsx
fetch(`<SERVER_URL>/store/orders/customer/confirm`, { fetch(`<SERVER_URL>/store/orders/customer/confirm`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
token token,
}), }),
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
}) })
.then(() => { .then(() => {
//successful // successful
}) })
.catch(() => { .catch(() => {
//display an error to the customer // display an error to the customer
}); })
``` ```
</TabItem> </TabItem>
@@ -58,17 +58,17 @@ You can add a discount to a customer’s cart by sending the [Update Cart reques
medusa.carts.update(cartId, { medusa.carts.update(cartId, {
discounts: [ discounts: [
{ {
code code,
} },
] ],
}) })
.then(({ cart }) => { .then(({ cart }) => {
console.log(cart.discounts) console.log(cart.discounts)
}) })
.catch((e) => { .catch((e) => {
//display an error to the customer // display an error to the customer
alert('Discount is invalid') alert("Discount is invalid")
}); })
``` ```
</TabItem> </TabItem>
@@ -76,27 +76,27 @@ medusa.carts.update(cartId, {
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}`, { fetch(`<SERVER_URL>/store/carts/${cartId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
discounts: [ discounts: [
{ {
code code,
} },
], ],
}), }),
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => { .then(({ cart }) => {
console.log(cart.discounts); console.log(cart.discounts)
}) })
.catch((e) => { .catch((e) => {
//display an error to the customer // display an error to the customer
alert('Discount is invalid') alert("Discount is invalid")
}); })
``` ```
</TabItem> </TabItem>
@@ -222,7 +222,7 @@ You can remove a discount from a customer’s cart using the [Remove Discount re
medusa.carts.deleteDiscount(cartId, code) medusa.carts.deleteDiscount(cartId, code)
.then(({ cart }) => { .then(({ cart }) => {
console.log(cart.discounts) console.log(cart.discounts)
}); })
``` ```
</TabItem> </TabItem>
@@ -230,13 +230,13 @@ medusa.carts.deleteDiscount(cartId, code)
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}/discounts/${code}`, { fetch(`<SERVER_URL>/store/carts/${cartId}/discounts/${code}`, {
method: 'DELETE', method: "DELETE",
credentials: 'include' credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => { .then(({ cart }) => {
console.log(cart.discounts); console.log(cart.discounts)
}); })
``` ```
</TabItem> </TabItem>
@@ -47,9 +47,9 @@ You can retrieve available regions by sending a request to the [List Regions](/a
```tsx ```tsx
medusa.regions.list() medusa.regions.list()
.then(({ regions }) => { .then(({ regions }) => {
console.log(regions.length); console.log(regions.length)
//show customers available regions // show customers available regions
}); })
``` ```
</TabItem> </TabItem>
@@ -57,13 +57,13 @@ medusa.regions.list()
```tsx ```tsx
fetch(`<SERVER_URL>/store/regions`, { fetch(`<SERVER_URL>/store/regions`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ regions }) => { .then(({ regions }) => {
console.log(regions.length); console.log(regions.length)
//show customers available regions // show customers available regions
}); })
``` ```
</TabItem> </TabItem>
@@ -86,12 +86,12 @@ For example:
```tsx ```tsx
medusa.products.list({ medusa.products.list({
region_id: regionId region_id: regionId,
}) })
.then(({ products, limit, offset, count }) => { .then(({ products, limit, offset, count }) => {
console.log(products.length); console.log(products.length)
//show customer the products // show customer the products
}); })
``` ```
</TabItem> </TabItem>
@@ -99,13 +99,13 @@ medusa.products.list({
```tsx ```tsx
fetch(`<SERVER_URL>/store/products?region_id=${regionId}`, { fetch(`<SERVER_URL>/store/products?region_id=${regionId}`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ products, limit, offset, count }) => { .then(({ products, limit, offset, count }) => {
console.log(products.length); console.log(products.length)
//show customer the products // show customer the products
}); })
``` ```
</TabItem> </TabItem>
@@ -136,11 +136,11 @@ For example:
```tsx ```tsx
medusa.carts.update(cartId, { medusa.carts.update(cartId, {
region_id: regionId region_id: regionId,
}) })
.then(({ cart }) => { .then(({ cart }) => {
console.log(cart.id); console.log(cart.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -148,16 +148,16 @@ medusa.carts.update(cartId, {
```tsx ```tsx
fetch(`<SERVER_URL>/store/carts/${cartId}`, { fetch(`<SERVER_URL>/store/carts/${cartId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
region_id: regionId region_id: regionId,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => { .then(({ cart }) => {
console.log(cart.id); console.log(cart.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -39,12 +39,12 @@ To filter products by a specific sales channel, pass the `sales_channel_id` quer
```jsx ```jsx
medusa.products.list({ medusa.products.list({
sales_channel_id: [ sales_channel_id: [
salesChannelId salesChannelId,
] ],
}) })
.then(({ products, limit, offset, count }) => { .then(({ products, limit, offset, count }) => {
console.log(products.length); console.log(products.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -55,7 +55,7 @@ fetch(`<SERVER_URL>/store/products?sales_channel_id[0]=${salesChannelId}`)
.then((response) => response.json()) .then((response) => response.json())
.then(({ products, limit, offset, count }) => { .then(({ products, limit, offset, count }) => {
console.log(products.length) console.log(products.length)
}); })
``` ```
</TabItem> </TabItem>
@@ -79,11 +79,11 @@ To associate a sales channel with a cart while creating it, you can pass the `sa
```jsx ```jsx
medusa.carts.create({ medusa.carts.create({
sales_channel_id: salesChannelId sales_channel_id: salesChannelId,
})
.then(({ cart }) => {
console.log(cart.id)
}) })
.then(({cart}) => {
console.log(cart.id);
});
``` ```
</TabItem> </TabItem>
@@ -91,18 +91,18 @@ medusa.carts.create({
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts`, { fetch(`<SERVER_URL>/store/carts`, {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
sales_channel_id: salesChannelId sales_channel_id: salesChannelId,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({cart}) => { .then(({ cart }) => {
console.log(cart.id); console.log(cart.id)
}); })
``` ```
</TabItem> </TabItem>
@@ -119,11 +119,11 @@ You can update the sales channel of an existing cart by passing the `sales_chann
```jsx ```jsx
medusa.carts.update(cartId, { medusa.carts.update(cartId, {
sales_channel_id: salesChannelId sales_channel_id: salesChannelId,
})
.then(({ cart }) => {
console.log(cart.id)
}) })
.then(({cart}) => {
console.log(cart.id);
});
``` ```
</TabItem> </TabItem>
@@ -131,16 +131,16 @@ medusa.carts.update(cartId, {
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}`, { fetch(`<SERVER_URL>/store/carts/${cartId}`, {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
sales_channel_id: salesChannelId sales_channel_id: salesChannelId,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({cart}) => console.log(cart.id)); .then(({ cart }) => console.log(cart.id))
``` ```
</TabItem> </TabItem>
+66 -5
View File
@@ -130,10 +130,10 @@ import TabItem from '@theme/TabItem';
<TabItem value="client" label="Medusa JS Client" default> <TabItem value="client" label="Medusa JS Client" default>
```jsx ```jsx
medusa.admin.uploads.create(file) //file is an instance of File medusa.admin.uploads.create(file) // file is an instance of File
.then(({ uploads }) => { .then(({ uploads }) => {
const key = uploads[0].key; const key = uploads[0].key
}); })
``` ```
</TabItem> </TabItem>
@@ -157,7 +157,10 @@ If you want to add a title to a code block with tabs, add the `codeTitle` prop t
For example: For example:
```md ```md
<Tabs groupId="request-type" wrapperClassName="code-tabs" codeTitle="/src/services/hello.ts"> <Tabs
groupId="request-type"
wrapperClassName="code-tabs"
codeTitle="/src/services/hello.ts">
``` ```
### Add Title to Code Block without Tabs ### Add Title to Code Block without Tabs
@@ -242,7 +245,7 @@ npm install @medusajs/medusa-cli -g
Medusa uses Vale to lint documentation pages and perform checks on incoming PRs into the repository. Medusa uses Vale to lint documentation pages and perform checks on incoming PRs into the repository.
### Result of PR Checks ### Result of Vale PR Checks
You can check the result of running the "lint" action on your PR by clicking the Details link next to it. You can find there all errors that you need to fix. You can check the result of running the "lint" action on your PR by clicking the Details link next to it. You can find there all errors that you need to fix.
@@ -293,6 +296,64 @@ If you use this in your PR, you must justify its usage.
--- ---
## Linting with ESLint
Medusa uses Eslint to lint code blocks in the documentation and perform checks on incoming PRs into the repository.
### Result of ESLint PR Checks
You can check the result of running the "eslint" action on your PR by clicking the Details link next to it. You can find there all errors that you need to fix.
### Running ESLint locally
If you want to check your work locally, you can do that by:
1. Installing the dependencies in the root directory:
```bash
yarn install
```
2\. Run the lint command:
```bash
yarn lint:docs
```
You can also pass the `--fix` option to automatically fix errors.
### ESLint Exceptions
If some code blocks have errors that can't or shouldn't be fixed, you can add the following command before the code block:
~~~md
<!-- eslint-skip -->
```js
console.log("This block isn't linted")
```
```js
console.log("This block is linted")
```
~~~
You can also disable specific rules. For example:
~~~md
<!-- eslint-disable semi -->
```js
console.log("This block can use semicolons");
```
```js
console.log("This block can't use semi colons")
```
~~~
---
## Need Additional Help ## Need Additional Help
If you need any additional help while contributing, you can join Medusa's [Discord server](https://discord.gg/medusajs) and ask Medusa’s core team as well as the community any questions. If you need any additional help while contributing, you can join Medusa's [Discord server](https://discord.gg/medusajs) and ask Medusa’s core team as well as the community any questions.
@@ -52,13 +52,15 @@ In `medusa-config.js`, the `DATABASE_URL` variable is set to the environment var
Replace the previous declaration of `DATABASE_URL` in `medusa-config.js` with the following: Replace the previous declaration of `DATABASE_URL` in `medusa-config.js` with the following:
```js ```js
const DB_USERNAME = process.env.DB_USERNAME; const DB_USERNAME = process.env.DB_USERNAME
const DB_PASSWORD = process.env.DB_PASSWORD; const DB_PASSWORD = process.env.DB_PASSWORD
const DB_HOST = process.env.DB_HOST; const DB_HOST = process.env.DB_HOST
const DB_PORT = process.env.DB_PORT; const DB_PORT = process.env.DB_PORT
const DB_DATABASE = process.env.DB_DATABASE; const DB_DATABASE = process.env.DB_DATABASE
const DATABASE_URL = `postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_DATABASE}`; const DATABASE_URL =
`postgres://${DB_USERNAME}:${DB_PASSWORD}` +
`@${DB_HOST}:${DB_PORT}/${DB_DATABASE}`
``` ```
In addition, you must add to `projectConfig` in the exported object a new property `database_extra`: In addition, you must add to `projectConfig` in the exported object a new property `database_extra`:
@@ -66,10 +68,10 @@ In addition, you must add to `projectConfig` in the exported object a new proper
```js ```js
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//... // ...
database_extra: { ssl: { rejectUnauthorized: false } } database_extra: { ssl: { rejectUnauthorized: false } },
}, },
}; }
``` ```
--- ---
@@ -187,7 +187,7 @@ module.exports = {
: {}, : {},
}, },
plugins, plugins,
}; }
``` ```
#### package.json #### package.json
+69 -69
View File
@@ -49,10 +49,10 @@ You can create a cart with the following code snippet:
```jsx ```jsx
medusa.carts.create() medusa.carts.create()
.then(({ cart }) => { .then(({ cart }) => {
localStorage.setItem('cart_id', cart.id); localStorage.setItem("cart_id", cart.id)
//assuming you have a state variable to store the cart // assuming you have a state variable to store the cart
setCart(cart); setCart(cart)
}); })
``` ```
</TabItem> </TabItem>
@@ -60,15 +60,15 @@ medusa.carts.create()
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts`, { fetch(`<SERVER_URL>/store/carts`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => { .then(({ cart }) => {
localStorage.setItem('cart_id', cart.id); localStorage.setItem("cart_id", cart.id)
//assuming you have a state variable to store the cart // assuming you have a state variable to store the cart
setCart(cart) setCart(cart)
}); })
``` ```
</TabItem> </TabItem>
@@ -85,13 +85,13 @@ Otherwise, you can assign it a specific region during creation:
```jsx ```jsx
medusa.carts.create({ medusa.carts.create({
region_id region_id,
}) })
.then(({ cart }) => { .then(({ cart }) => {
localStorage.setItem('cart_id', cart.id); localStorage.setItem("cart_id", cart.id)
//assuming you have a state variable to store the cart // assuming you have a state variable to store the cart
setCart(cart); setCart(cart)
}); })
``` ```
</TabItem> </TabItem>
@@ -99,21 +99,21 @@ medusa.carts.create({
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts`, { fetch(`<SERVER_URL>/store/carts`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
region_id region_id,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => { .then(({ cart }) => {
localStorage.setItem('cart_id', cart.id); localStorage.setItem("cart_id", cart.id)
//assuming you have a state variable to store the cart // assuming you have a state variable to store the cart
setCart(cart) setCart(cart)
}); })
``` ```
</TabItem> </TabItem>
@@ -139,11 +139,11 @@ You can retrieve the cart at any given point using its ID with the following cod
<TabItem value="client" label="Medusa JS Client" default> <TabItem value="client" label="Medusa JS Client" default>
```jsx ```jsx
const id = localStorage.getItem('cart_id'); const id = localStorage.getItem("cart_id")
if (id) { if (id) {
medusa.carts.retrieve(id) medusa.carts.retrieve(id)
.then(({ cart }) => setCart(cart)); .then(({ cart }) => setCart(cart))
} }
``` ```
@@ -151,14 +151,14 @@ if (id) {
<TabItem value="fetch" label="Fetch API"> <TabItem value="fetch" label="Fetch API">
```jsx ```jsx
const id = localStorage.getItem('cart_id'); const id = localStorage.getItem("cart_id")
if (id) { if (id) {
fetch(`<SERVER_URL>/store/carts/${id}`, { fetch(`<SERVER_URL>/store/carts/${id}`, {
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => setCart(cart)); .then(({ cart }) => setCart(cart))
} }
``` ```
@@ -188,9 +188,9 @@ You can use the following snippet to update any of the cart’s data:
```jsx ```jsx
medusa.carts.update(cartId, { medusa.carts.update(cartId, {
region_id region_id,
}) })
.then(({ cart }) => setCart(cart)); .then(({ cart }) => setCart(cart))
``` ```
</TabItem> </TabItem>
@@ -198,17 +198,17 @@ medusa.carts.update(cartId, {
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}`, { fetch(`<SERVER_URL>/store/carts/${cartId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
region_id region_id,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => setCart(cart)); .then(({ cart }) => setCart(cart))
``` ```
</TabItem> </TabItem>
@@ -231,9 +231,9 @@ You can do that using the same update operation:
```jsx ```jsx
medusa.carts.update(cartId, { medusa.carts.update(cartId, {
customer_id customer_id,
}) })
.then(({ cart }) => setCart(cart)); .then(({ cart }) => setCart(cart))
``` ```
</TabItem> </TabItem>
@@ -241,17 +241,17 @@ medusa.carts.update(cartId, {
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}`, { fetch(`<SERVER_URL>/store/carts/${cartId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
customer_id customer_id,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => setCart(cart)); .then(({ cart }) => setCart(cart))
``` ```
</TabItem> </TabItem>
@@ -270,9 +270,9 @@ You can do that using the same update operation:
```jsx ```jsx
medusa.carts.update(cartId, { medusa.carts.update(cartId, {
email: 'user@example.com' email: "user@example.com",
}) })
.then(({ cart }) => setCart(cart)); .then(({ cart }) => setCart(cart))
``` ```
</TabItem> </TabItem>
@@ -280,17 +280,17 @@ medusa.carts.update(cartId, {
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}`, { fetch(`<SERVER_URL>/store/carts/${cartId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
email: 'user@example.com' email: "user@example.com",
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => setCart(cart)); .then(({ cart }) => setCart(cart))
``` ```
</TabItem> </TabItem>
@@ -308,9 +308,9 @@ To create a line item of a product and add it to a cart, you can use the followi
```jsx ```jsx
medusa.carts.lineItems.create(cartId, { medusa.carts.lineItems.create(cartId, {
variant_id, variant_id,
quantity: 1 quantity: 1,
}) })
.then(({ cart }) => setCart(cart)); .then(({ cart }) => setCart(cart))
``` ```
</TabItem> </TabItem>
@@ -318,18 +318,18 @@ medusa.carts.lineItems.create(cartId, {
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}/line-items`, { fetch(`<SERVER_URL>/store/carts/${cartId}/line-items`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
variant_id, variant_id,
quantity: 1 quantity: 1,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => setCart(cart)); .then(({ cart }) => setCart(cart))
``` ```
</TabItem> </TabItem>
@@ -358,7 +358,7 @@ To update a line item's quantity in the cart, you can use the following code sni
```jsx ```jsx
medusa.carts.lineItems.update(cartId, lineItemId, { medusa.carts.lineItems.update(cartId, lineItemId, {
quantity: 3 quantity: 3,
}) })
.then(({ cart }) => setCart(cart)) .then(({ cart }) => setCart(cart))
``` ```
@@ -368,17 +368,17 @@ medusa.carts.lineItems.update(cartId, lineItemId, {
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}/line-items/${lineItemId}`, { fetch(`<SERVER_URL>/store/carts/${cartId}/line-items/${lineItemId}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
quantity: 3 quantity: 3,
}) }),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => setCart(cart)); .then(({ cart }) => setCart(cart))
``` ```
</TabItem> </TabItem>
@@ -407,11 +407,11 @@ medusa.carts.lineItems.delete(cartId, lineItemId)
```jsx ```jsx
fetch(`<SERVER_URL>/store/carts/${cartId}/line-items/${lineItemId}`, { fetch(`<SERVER_URL>/store/carts/${cartId}/line-items/${lineItemId}`, {
method: 'DELETE', method: "DELETE",
credentials: 'include', credentials: "include",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then(({ cart }) => setCart(cart)); .then(({ cart }) => setCart(cart))
``` ```
</TabItem> </TabItem>
+3 -3
View File
@@ -37,9 +37,9 @@ import Medusa from "@medusajs/medusa-js"
const medusa = new Medusa() const medusa = new Medusa()
//use method // use method
medusa.customers.create({ medusa.customers.create({
//data // data
}) })
``` ```
@@ -47,7 +47,7 @@ The `customers` resource also has another resource `addresses` nested inside it
```js ```js
medusa.customers.addresses.addAddress({ medusa.customers.addresses.addAddress({
//data // data
}) })
``` ```
+3 -2
View File
@@ -10,9 +10,10 @@ In your `medusa-config.js` , you should ensure that you've configured your CORS
The default configuration uses the following CORS settings: The default configuration uses the following CORS settings:
```jsx title=medusa-config.js ```js title=medusa-config.js
// CORS when consuming Medusa from admin // CORS when consuming Medusa from admin
const ADMIN_CORS = process.env.ADMIN_CORS || "http://localhost:7000,http://localhost:7001" const ADMIN_CORS = process.env.ADMIN_CORS ||
"http://localhost:7000,http://localhost:7001"
// CORS to avoid issues when consuming Medusa from a client // CORS to avoid issues when consuming Medusa from a client
const STORE_CORS = process.env.STORE_CORS || "http://localhost:8000" const STORE_CORS = process.env.STORE_CORS || "http://localhost:8000"
@@ -2,9 +2,9 @@
You add payment providers to your Medusa instance by adding them as plugins in `medusa-config.js`: You add payment providers to your Medusa instance by adding them as plugins in `medusa-config.js`:
```jsx title=medusa-config.js ```js title=medusa-config.js
const plugins = [ const plugins = [
... // ...
// You can create a Stripe account via: https://stripe.com // You can create a Stripe account via: https://stripe.com
{ {
resolve: `medusa-payment-stripe`, resolve: `medusa-payment-stripe`,
@@ -13,8 +13,7 @@ const plugins = [
webhook_secret: STRIPE_WEBHOOK_SECRET, webhook_secret: STRIPE_WEBHOOK_SECRET,
}, },
}, },
... ]
];
``` ```
And installing them with your favourite package manager: And installing them with your favourite package manager:
+1 -1
View File
@@ -11,7 +11,7 @@ After installing it, make sure to configure your Medusa server to use Redis:
```jsx title=medusa-config.js ```jsx title=medusa-config.js
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//... // ...
redis_url: REDIS_URL, redis_url: REDIS_URL,
}, },
} }
@@ -19,7 +19,7 @@ Then in your `medusa-config.js`, you should change the project configuration to
```jsx title=medusa-config.js ```jsx title=medusa-config.js
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//... // ...
database_url: DATABASE_URL, database_url: DATABASE_URL,
database_type: "postgres", database_type: "postgres",
}, },
@@ -31,6 +31,8 @@ Where `DATABASE_URL` is the connection string to your PostgreSQL database. You c
Make sure to also remove the following lines that are used to configure an SQLite database: Make sure to also remove the following lines that are used to configure an SQLite database:
<!-- eslint-skip -->
```jsx title=medusa-config.js ```jsx title=medusa-config.js
database_type: "sqlite", database_type: "sqlite",
database_database: "./medusa-db.sql", database_database: "./medusa-db.sql",
+28 -28
View File
@@ -47,11 +47,11 @@ For SQLite you mainly need two configurations:
```jsx ```jsx
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//...other configurations // ...other configurations
database_type: "sqlite", database_type: "sqlite",
database_database: "./medusa-db.sql", database_database: "./medusa-db.sql",
}, },
}; }
``` ```
Where `database_type` is `sqlite` and `database_database` is the location you want the SQLite database to be created in. Where `database_type` is `sqlite` and `database_database` is the location you want the SQLite database to be created in.
@@ -69,12 +69,12 @@ For PostgreSQL you mainly need two configurations:
```jsx ```jsx
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//...other configurations // ...other configurations
database_type: "postgres", database_type: "postgres",
database_url: DATABASE_URL, database_url: DATABASE_URL,
database_schema: process.env.DATABASE_SCHEMA //optional database_schema: process.env.DATABASE_SCHEMA, // optional
}, },
}; }
``` ```
Where `database_type` is `postgres` and `DATABASE_URL` is the URL connection string to your PostgreSQL database. You can check out how to format it in [PostgreSQL’s documentation](https://www.postgresql.org/docs/current/libpq-connect.html). Where `database_type` is `postgres` and `DATABASE_URL` is the URL connection string to your PostgreSQL database. You can check out how to format it in [PostgreSQL’s documentation](https://www.postgresql.org/docs/current/libpq-connect.html).
@@ -109,11 +109,11 @@ These configurations are not required and can be omitted.
```jsx ```jsx
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//...other configurations // ...other configurations
database_logging: true, database_logging: true,
database_extra: {} database_extra: {},
}, },
}; }
``` ```
--- ---
@@ -125,10 +125,10 @@ Medusa uses Redis to handle the event queue, among other usages. You need to set
```jsx ```jsx
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//...other configurations // ...other configurations
redis_url: REDIS_URL redis_url: REDIS_URL,
}, },
}; }
``` ```
Where `REDIS_URL` is the URL used to connect to Redis. The format of the connection string is `redis[s]://[[username][:password]@][host][:port][/db-number]`. Where `REDIS_URL` is the URL used to connect to Redis. The format of the connection string is `redis[s]://[[username][:password]@][host][:port][/db-number]`.
@@ -164,10 +164,10 @@ Medusa uses JSON Web Token (JWT) to handle user authentication. To set the JWT s
```jsx ```jsx
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//...other configurations // ...other configurations
jwt_secret: "very secure string", jwt_secret: "very secure string",
}, },
}; }
``` ```
Where `jwt_secret` is the secret used to create the tokens. The more secure it is the better. Where `jwt_secret` is the secret used to create the tokens. The more secure it is the better.
@@ -195,10 +195,10 @@ This configuration is used to sign the session ID cookie. To set the cookie secr
```jsx ```jsx
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//...other configurations // ...other configurations
cookie_secret: "very secure string", cookie_secret: "very secure string",
}, },
}; }
``` ```
Where `cookie_secret` is the secret used to create the tokens. The more secure it is the better. Where `cookie_secret` is the secret used to create the tokens. The more secure it is the better.
@@ -239,10 +239,10 @@ To make sure your Admin dashboard can access the Medusa server’s admin endpoin
```jsx ```jsx
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//...other configurations // ...other configurations
admin_cors: ADMIN_CORS, admin_cors: ADMIN_CORS,
}, },
}; }
``` ```
Where `ADMIN_CORS` is the URL of your admin dashboard. By default, it’s `http://localhost:7000,http://localhost:7001`. Where `ADMIN_CORS` is the URL of your admin dashboard. By default, it’s `http://localhost:7000,http://localhost:7001`.
@@ -268,10 +268,10 @@ To make sure your Storefront dashboard can access the Medusa server, set this co
```jsx ```jsx
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//...other configurations // ...other configurations
store_cors: STORE_CORS, store_cors: STORE_CORS,
}, },
}; }
``` ```
Where `STORE_CORS` is the URL of your storefront. By default, it’s `http://localhost:8000`. Where `STORE_CORS` is the URL of your storefront. By default, it’s `http://localhost:8000`.
@@ -307,10 +307,10 @@ Aside from installing the plugin with NPM, you need to pass the plugin you insta
```jsx ```jsx
module.exports = { module.exports = {
projectConfig: { projectConfig: {
//previous configurations mentioned... // previous configurations mentioned...
}, },
plugins, plugins,
}; }
``` ```
### Add a Plugin Without Configuration ### Add a Plugin Without Configuration
@@ -319,9 +319,9 @@ To add a plugin that doesn’t need any configurations, you can simply add its n
```jsx ```jsx
const plugins = [ const plugins = [
//other plugins... // other plugins...
`medusa-my-plugin`, `medusa-my-plugin`,
]; ]
``` ```
### Add a Plugin With Configuration ### Add a Plugin With Configuration
@@ -330,14 +330,14 @@ To add a plugin with configurations, you need to add an object to the `plugins`
```jsx ```jsx
const plugins = [ const plugins = [
//other plugins... // other plugins...
{ {
resolve: `medusa-my-plugin`, resolve: `medusa-my-plugin`,
options: { options: {
apiKey: `test` apiKey: `test`,
} },
} },
]; ]
``` ```
:::tip :::tip
+4
View File
@@ -16,6 +16,7 @@
"@babel/plugin-transform-instanceof": "^7.10.4", "@babel/plugin-transform-instanceof": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.11.5", "@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.11.5", "@babel/preset-env": "^7.11.5",
"@babel/preset-react": "^7.18.6",
"@babel/register": "^7.11.5", "@babel/register": "^7.11.5",
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@redocly/cli": "latest", "@redocly/cli": "latest",
@@ -29,7 +30,9 @@
"eslint": "^8.23.0", "eslint": "^8.23.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-markdown": "^3.0.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.11",
"express": "^4.17.1", "express": "^4.17.1",
"get-port": "^5.1.1", "get-port": "^5.1.1",
"husky": "^7.0.2", "husky": "^7.0.2",
@@ -60,6 +63,7 @@
"hooks:uninstall": "husky uninstall", "hooks:uninstall": "husky uninstall",
"build": "turbo run build --no-daemon", "build": "turbo run build --no-daemon",
"lint": "eslint --ignore-path .eslintignore --ext .js,.ts,.tsx .", "lint": "eslint --ignore-path .eslintignore --ext .js,.ts,.tsx .",
"lint:docs": "eslint -c docs/.eslintrc.js --ignore-path docs/.eslintignore docs/content",
"prettier": "prettier", "prettier": "prettier",
"jest": "jest", "jest": "jest",
"test": "turbo run test --no-daemon --filter=!integration-tests-api --filter=!integration-tests-plugins", "test": "turbo run test --no-daemon --filter=!integration-tests-api --filter=!integration-tests-plugins",
+274 -3
View File
@@ -1826,7 +1826,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/preset-react@npm:^7.12.10, @babel/preset-react@npm:^7.14.0": "@babel/preset-react@npm:^7.12.10, @babel/preset-react@npm:^7.14.0, @babel/preset-react@npm:^7.18.6":
version: 7.18.6 version: 7.18.6
resolution: "@babel/preset-react@npm:7.18.6" resolution: "@babel/preset-react@npm:7.18.6"
dependencies: dependencies:
@@ -9888,6 +9888,19 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"array-includes@npm:^3.1.6":
version: 3.1.6
resolution: "array-includes@npm:3.1.6"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
es-abstract: ^1.20.4
get-intrinsic: ^1.1.3
is-string: ^1.0.7
checksum: d0caeaa57bea7d14b8480daee30cf8611899321006b15a6cd872b831bd7aaed7649f8764e060d01c5d33b8d9e998e5de5c87f4901874e1c1f467f429b7db2929
languageName: node
linkType: hard
"array-union@npm:^1.0.2": "array-union@npm:^1.0.2":
version: 1.0.2 version: 1.0.2
resolution: "array-union@npm:1.0.2" resolution: "array-union@npm:1.0.2"
@@ -9942,6 +9955,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"array.prototype.flatmap@npm:^1.3.1":
version: 1.3.1
resolution: "array.prototype.flatmap@npm:1.3.1"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
es-abstract: ^1.20.4
es-shim-unscopables: ^1.0.0
checksum: 2bd58a0e79d5d90cb4f5ef0e287edf8b28e87c65428f54025ac6b7b4c204224b92811c266f296c53a2dbc93872117c0fcea2e51d3c9e8cecfd5024d4a4a57db4
languageName: node
linkType: hard
"array.prototype.map@npm:^1.0.4": "array.prototype.map@npm:^1.0.4":
version: 1.0.4 version: 1.0.4
resolution: "array.prototype.map@npm:1.0.4" resolution: "array.prototype.map@npm:1.0.4"
@@ -9968,6 +9993,19 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"array.prototype.tosorted@npm:^1.1.1":
version: 1.1.1
resolution: "array.prototype.tosorted@npm:1.1.1"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
es-abstract: ^1.20.4
es-shim-unscopables: ^1.0.0
get-intrinsic: ^1.1.3
checksum: fd5f57aca3c7ddcd1bb83965457b625f3a67d8f334f5cbdb8ac8ef33d5b0d38281524114db2936f8c08048115d5158af216c94e6ae1eb966241b9b6f4ab8a7e8
languageName: node
linkType: hard
"arrify@npm:^1.0.1": "arrify@npm:^1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "arrify@npm:1.0.1" resolution: "arrify@npm:1.0.1"
@@ -13756,7 +13794,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.1": "debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.1":
version: 4.3.4 version: 4.3.4
resolution: "debug@npm:4.3.4" resolution: "debug@npm:4.3.4"
dependencies: dependencies:
@@ -14894,6 +14932,39 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"es-abstract@npm:^1.20.4":
version: 1.20.5
resolution: "es-abstract@npm:1.20.5"
dependencies:
call-bind: ^1.0.2
es-to-primitive: ^1.2.1
function-bind: ^1.1.1
function.prototype.name: ^1.1.5
get-intrinsic: ^1.1.3
get-symbol-description: ^1.0.0
gopd: ^1.0.1
has: ^1.0.3
has-property-descriptors: ^1.0.0
has-symbols: ^1.0.3
internal-slot: ^1.0.3
is-callable: ^1.2.7
is-negative-zero: ^2.0.2
is-regex: ^1.1.4
is-shared-array-buffer: ^1.0.2
is-string: ^1.0.7
is-weakref: ^1.0.2
object-inspect: ^1.12.2
object-keys: ^1.1.1
object.assign: ^4.1.4
regexp.prototype.flags: ^1.4.3
safe-regex-test: ^1.0.0
string.prototype.trimend: ^1.0.6
string.prototype.trimstart: ^1.0.6
unbox-primitive: ^1.0.2
checksum: c99f8a3e9ac15890cde5bf78d1910d72758d3e79c710bd1ad2ab6b595d39ad07209626befaa7c18591bd935a02f6a3bdba3a7e9c8dd6a01146e655df09dc4353
languageName: node
linkType: hard
"es-array-method-boxes-properly@npm:^1.0.0": "es-array-method-boxes-properly@npm:^1.0.0":
version: 1.0.0 version: 1.0.0
resolution: "es-array-method-boxes-properly@npm:1.0.0" resolution: "es-array-method-boxes-properly@npm:1.0.0"
@@ -15265,6 +15336,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"eslint-plugin-markdown@npm:^3.0.0":
version: 3.0.0
resolution: "eslint-plugin-markdown@npm:3.0.0"
dependencies:
mdast-util-from-markdown: ^0.8.5
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
checksum: 3424fd97b6990cbed7be3764ae8bec7eae9bd1cc03cd76d8d1cf38e088a9777ed4491921e7e1a8d3a146361bcd935e5ed1b2bfd4fd623bcdafdb824bccfdd2b2
languageName: node
linkType: hard
"eslint-plugin-prettier@npm:^4.2.1": "eslint-plugin-prettier@npm:^4.2.1":
version: 4.2.1 version: 4.2.1
resolution: "eslint-plugin-prettier@npm:4.2.1" resolution: "eslint-plugin-prettier@npm:4.2.1"
@@ -15313,6 +15395,31 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"eslint-plugin-react@npm:^7.31.11":
version: 7.31.11
resolution: "eslint-plugin-react@npm:7.31.11"
dependencies:
array-includes: ^3.1.6
array.prototype.flatmap: ^1.3.1
array.prototype.tosorted: ^1.1.1
doctrine: ^2.1.0
estraverse: ^5.3.0
jsx-ast-utils: ^2.4.1 || ^3.0.0
minimatch: ^3.1.2
object.entries: ^1.1.6
object.fromentries: ^2.0.6
object.hasown: ^1.1.2
object.values: ^1.1.6
prop-types: ^15.8.1
resolve: ^2.0.0-next.3
semver: ^6.3.0
string.prototype.matchall: ^4.0.8
peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
checksum: 2f8d27adbfe1b551a170cc1340dd8bbecf6c9fdbfcde04dd1097bbf198e99f0c981af84bc3c69f851af5736cc4d6520687a03dfb3271717079f88983d79cb9cd
languageName: node
linkType: hard
"eslint-plugin-testing-library@npm:^5.5.1": "eslint-plugin-testing-library@npm:^5.5.1":
version: 5.5.1 version: 5.5.1
resolution: "eslint-plugin-testing-library@npm:5.5.1" resolution: "eslint-plugin-testing-library@npm:5.5.1"
@@ -17753,6 +17860,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"get-intrinsic@npm:^1.1.3":
version: 1.1.3
resolution: "get-intrinsic@npm:1.1.3"
dependencies:
function-bind: ^1.1.1
has: ^1.0.3
has-symbols: ^1.0.3
checksum: 6f201d5f95ea0dd6c8d0dc2c265603aff0b9e15614cb70f8f4674bb3d2b2369d521efaa84d0b70451d2c00762ebd28402758bf46279c6f2a00d242ebac0d8442
languageName: node
linkType: hard
"get-own-enumerable-property-symbols@npm:^3.0.0": "get-own-enumerable-property-symbols@npm:^3.0.0":
version: 3.0.2 version: 3.0.2
resolution: "get-own-enumerable-property-symbols@npm:3.0.2" resolution: "get-own-enumerable-property-symbols@npm:3.0.2"
@@ -18152,6 +18270,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"gopd@npm:^1.0.1":
version: 1.0.1
resolution: "gopd@npm:1.0.1"
dependencies:
get-intrinsic: ^1.1.3
checksum: 505c05487f7944c552cee72087bf1567debb470d4355b1335f2c262d218ebbff805cd3715448fe29b4b380bae6912561d0467233e4165830efd28da241418c63
languageName: node
linkType: hard
"got@npm:^11.8.2, got@npm:^11.8.5": "got@npm:^11.8.2, got@npm:^11.8.5":
version: 11.8.5 version: 11.8.5
resolution: "got@npm:11.8.5" resolution: "got@npm:11.8.5"
@@ -19613,6 +19740,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"is-callable@npm:^1.2.7":
version: 1.2.7
resolution: "is-callable@npm:1.2.7"
checksum: ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f
languageName: node
linkType: hard
"is-ci@npm:^2.0.0": "is-ci@npm:^2.0.0":
version: 2.0.0 version: 2.0.0
resolution: "is-ci@npm:2.0.0" resolution: "is-ci@npm:2.0.0"
@@ -23624,6 +23758,19 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mdast-util-from-markdown@npm:^0.8.5":
version: 0.8.5
resolution: "mdast-util-from-markdown@npm:0.8.5"
dependencies:
"@types/mdast": ^3.0.0
mdast-util-to-string: ^2.0.0
micromark: ~2.11.0
parse-entities: ^2.0.0
unist-util-stringify-position: ^2.0.0
checksum: 86e7589e574378817c180f10ab602db844b6b71b7b1769314947a02ef42ac5c1435f5163d02a975ae8cdab8b6e6176acbd9188da1848ddd5f0d5e09d0291c870
languageName: node
linkType: hard
"mdast-util-to-hast@npm:10.0.1": "mdast-util-to-hast@npm:10.0.1":
version: 10.0.1 version: 10.0.1
resolution: "mdast-util-to-hast@npm:10.0.1" resolution: "mdast-util-to-hast@npm:10.0.1"
@@ -23647,6 +23794,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mdast-util-to-string@npm:^2.0.0":
version: 2.0.0
resolution: "mdast-util-to-string@npm:2.0.0"
checksum: a4231085133cdfec24644b694c13661e5a01d26716be0105b6792889faa04b8030e4abbf72d4be3363098b2b38b2b98f1f1f1f0858eb6580dc04e2aca1436a37
languageName: node
linkType: hard
"mdn-data@npm:2.0.14": "mdn-data@npm:2.0.14":
version: 2.0.14 version: 2.0.14
resolution: "mdn-data@npm:2.0.14" resolution: "mdn-data@npm:2.0.14"
@@ -24723,6 +24877,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"micromark@npm:~2.11.0":
version: 2.11.4
resolution: "micromark@npm:2.11.4"
dependencies:
debug: ^4.0.0
parse-entities: ^2.0.0
checksum: 67307cbacae621ab1eb23e333a5addc7600cf97d3b40cad22fc1c2d03d734d6d9cbc3f5a7e5d655a8c0862a949abe590ab7cfa96be366bfe09e239a94e6eea55
languageName: node
linkType: hard
"micromatch@npm:4.x, micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": "micromatch@npm:4.x, micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5":
version: 4.0.5 version: 4.0.5
resolution: "micromatch@npm:4.0.5" resolution: "micromatch@npm:4.0.5"
@@ -26102,7 +26266,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"object-inspect@npm:^1.12.0, object-inspect@npm:^1.9.0": "object-inspect@npm:^1.12.0, object-inspect@npm:^1.12.2, object-inspect@npm:^1.9.0":
version: 1.12.2 version: 1.12.2
resolution: "object-inspect@npm:1.12.2" resolution: "object-inspect@npm:1.12.2"
checksum: e1bd625f4c44a2f733bd69cfccce6469f71333fb09c6de151f4f346c16d658ef7555727b12652c108e20c2afb908ae7cd165f52ca53745a1d6cbf228cdb46ebe checksum: e1bd625f4c44a2f733bd69cfccce6469f71333fb09c6de151f4f346c16d658ef7555727b12652c108e20c2afb908ae7cd165f52ca53745a1d6cbf228cdb46ebe
@@ -26154,6 +26318,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"object.assign@npm:^4.1.4":
version: 4.1.4
resolution: "object.assign@npm:4.1.4"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
has-symbols: ^1.0.3
object-keys: ^1.1.1
checksum: 2f286118c023e557757620e647b02e7c88d3d417e0c568fca0820de8ec9cca68928304854d5b03e99763eddad6e78a6716e2930f7e6372e4b9b843f3fd3056f3
languageName: node
linkType: hard
"object.entries@npm:^1.1.0, object.entries@npm:^1.1.5": "object.entries@npm:^1.1.0, object.entries@npm:^1.1.5":
version: 1.1.5 version: 1.1.5
resolution: "object.entries@npm:1.1.5" resolution: "object.entries@npm:1.1.5"
@@ -26165,6 +26341,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"object.entries@npm:^1.1.6":
version: 1.1.6
resolution: "object.entries@npm:1.1.6"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
es-abstract: ^1.20.4
checksum: 8782c71db3a068ccbae9e0541e6b4ac2c25dc67c63f97b7e6ad3c88271d7820197e7398e37747f96542ed47c27f0b81148cdf14c42df15dc22f64818ae7bb5bf
languageName: node
linkType: hard
"object.fromentries@npm:^2.0.0 || ^1.0.0, object.fromentries@npm:^2.0.5": "object.fromentries@npm:^2.0.0 || ^1.0.0, object.fromentries@npm:^2.0.5":
version: 2.0.5 version: 2.0.5
resolution: "object.fromentries@npm:2.0.5" resolution: "object.fromentries@npm:2.0.5"
@@ -26176,6 +26363,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"object.fromentries@npm:^2.0.6":
version: 2.0.6
resolution: "object.fromentries@npm:2.0.6"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
es-abstract: ^1.20.4
checksum: db6759ea68131cbdb70b1152f9984b49db03e81de4f6de079b39929bebd8b45501e5333ca2351991e07ee56f4651606c023396644e8f25c0806fa39a26c4c6e6
languageName: node
linkType: hard
"object.getownpropertydescriptors@npm:^2.0.3, object.getownpropertydescriptors@npm:^2.1.1, object.getownpropertydescriptors@npm:^2.1.2": "object.getownpropertydescriptors@npm:^2.0.3, object.getownpropertydescriptors@npm:^2.1.1, object.getownpropertydescriptors@npm:^2.1.2":
version: 2.1.4 version: 2.1.4
resolution: "object.getownpropertydescriptors@npm:2.1.4" resolution: "object.getownpropertydescriptors@npm:2.1.4"
@@ -26198,6 +26396,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"object.hasown@npm:^1.1.2":
version: 1.1.2
resolution: "object.hasown@npm:1.1.2"
dependencies:
define-properties: ^1.1.4
es-abstract: ^1.20.4
checksum: 419fc1c74a2aea7ebb4d49b79d5b1599a010b26c18eae35bd061ccdd013ccb749c499d8dd6ee21a91e6d7264ccc592573d0f13562970f76e25fc844d8c1b02ce
languageName: node
linkType: hard
"object.pick@npm:^1.3.0": "object.pick@npm:^1.3.0":
version: 1.3.0 version: 1.3.0
resolution: "object.pick@npm:1.3.0" resolution: "object.pick@npm:1.3.0"
@@ -26218,6 +26426,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"object.values@npm:^1.1.6":
version: 1.1.6
resolution: "object.values@npm:1.1.6"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
es-abstract: ^1.20.4
checksum: 3381204390f10c9f653a4875a50d221c67b5c16cb80a6ac06c706fc82a7cad8400857d4c7a0731193b0abb56b84fe803eabcf7addcf32de76397bbf207e68c66
languageName: node
linkType: hard
"objectFitPolyfill@npm:^2.3.5": "objectFitPolyfill@npm:^2.3.5":
version: 2.3.5 version: 2.3.5
resolution: "objectFitPolyfill@npm:2.3.5" resolution: "objectFitPolyfill@npm:2.3.5"
@@ -30150,6 +30369,7 @@ __metadata:
"@babel/plugin-transform-instanceof": ^7.10.4 "@babel/plugin-transform-instanceof": ^7.10.4
"@babel/plugin-transform-runtime": ^7.11.5 "@babel/plugin-transform-runtime": ^7.11.5
"@babel/preset-env": ^7.11.5 "@babel/preset-env": ^7.11.5
"@babel/preset-react": ^7.18.6
"@babel/register": ^7.11.5 "@babel/register": ^7.11.5
"@babel/runtime": ^7.11.2 "@babel/runtime": ^7.11.2
"@changesets/changelog-github": ^0.4.5 "@changesets/changelog-github": ^0.4.5
@@ -30167,7 +30387,9 @@ __metadata:
eslint: ^8.23.0 eslint: ^8.23.0
eslint-config-google: ^0.14.0 eslint-config-google: ^0.14.0
eslint-config-prettier: ^8.5.0 eslint-config-prettier: ^8.5.0
eslint-plugin-markdown: ^3.0.0
eslint-plugin-prettier: ^4.2.1 eslint-plugin-prettier: ^4.2.1
eslint-plugin-react: ^7.31.11
express: ^4.17.1 express: ^4.17.1
get-port: ^5.1.1 get-port: ^5.1.1
global: ^4.4.0 global: ^4.4.0
@@ -30295,6 +30517,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"safe-regex-test@npm:^1.0.0":
version: 1.0.0
resolution: "safe-regex-test@npm:1.0.0"
dependencies:
call-bind: ^1.0.2
get-intrinsic: ^1.1.3
is-regex: ^1.1.4
checksum: 14a81a7e683f97b2d6e9c8be61fddcf8ed7a02f4e64a825515f96bb1738eb007145359313741d2704d28b55b703a0f6300c749dde7c1dbc13952a2b85048ede2
languageName: node
linkType: hard
"safe-regex@npm:^1.1.0": "safe-regex@npm:^1.1.0":
version: 1.1.0 version: 1.1.0
resolution: "safe-regex@npm:1.1.0" resolution: "safe-regex@npm:1.1.0"
@@ -31814,6 +32047,22 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"string.prototype.matchall@npm:^4.0.8":
version: 4.0.8
resolution: "string.prototype.matchall@npm:4.0.8"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
es-abstract: ^1.20.4
get-intrinsic: ^1.1.3
has-symbols: ^1.0.3
internal-slot: ^1.0.3
regexp.prototype.flags: ^1.4.3
side-channel: ^1.0.4
checksum: 644523d05c1ee93bab7474e999a5734ee5f6ad2d7ad24ed6ea8706c270dc92b352bde0f2a5420bfbeed54e28cb6a770c3800e1988a5267a70fd5e677c7750abc
languageName: node
linkType: hard
"string.prototype.padend@npm:^3.0.0": "string.prototype.padend@npm:^3.0.0":
version: 3.1.3 version: 3.1.3
resolution: "string.prototype.padend@npm:3.1.3" resolution: "string.prototype.padend@npm:3.1.3"
@@ -31847,6 +32096,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"string.prototype.trimend@npm:^1.0.6":
version: 1.0.6
resolution: "string.prototype.trimend@npm:1.0.6"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
es-abstract: ^1.20.4
checksum: 51b663e3195a74b58620a250b3fc4efb58951000f6e7d572a9f671c038f2f37f24a2b8c6994500a882aeab2f1c383fac1e8c023c01eb0c8b4e52d2f13b6c4513
languageName: node
linkType: hard
"string.prototype.trimstart@npm:^1.0.5": "string.prototype.trimstart@npm:^1.0.5":
version: 1.0.5 version: 1.0.5
resolution: "string.prototype.trimstart@npm:1.0.5" resolution: "string.prototype.trimstart@npm:1.0.5"
@@ -31858,6 +32118,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"string.prototype.trimstart@npm:^1.0.6":
version: 1.0.6
resolution: "string.prototype.trimstart@npm:1.0.6"
dependencies:
call-bind: ^1.0.2
define-properties: ^1.1.4
es-abstract: ^1.20.4
checksum: 13b9970d4e234002dfc8069c655c1fe19e83e10ced208b54858c41bb0f7544e581ac0ce746e92b279563664ad63910039f7253f36942113fec413b2b4e7c1fcd
languageName: node
linkType: hard
"string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1": "string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1":
version: 1.3.0 version: 1.3.0
resolution: "string_decoder@npm:1.3.0" resolution: "string_decoder@npm:1.3.0"