Added prettier and prettied the code.

Created and added the razorpay payment module
This commit is contained in:
2025-02-16 04:37:30 +00:00
parent 513664dcbe
commit 6e3216a0ff
20 changed files with 767 additions and 415 deletions

6
.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"semi": true,
"singleQuote": true,
"jsxSingleQuote": true,
"printWidth": 120
}

View File

@ -5,18 +5,18 @@ The `medusa-test-utils` package provides utility functions to create integration
For example:
```ts
import { medusaIntegrationTestRunner } from "medusa-test-utils";
import { medusaIntegrationTestRunner } from 'medusa-test-utils';
medusaIntegrationTestRunner({
testSuite: ({ api, getContainer }) => {
describe("Custom endpoints", () => {
describe("GET /store/custom", () => {
it("returns correct message", async () => {
describe('Custom endpoints', () => {
describe('GET /store/custom', () => {
it('returns correct message', async () => {
const response = await api.get(`/store/custom`);
expect(response.status).toEqual(200);
expect(response.data).toHaveProperty("message");
expect(response.data.message).toEqual("Hello, World!");
expect(response.data).toHaveProperty('message');
expect(response.data.message).toEqual('Hello, World!');
});
});
});

View File

@ -1,13 +1,13 @@
import { medusaIntegrationTestRunner } from "medusa-test-utils";
import { medusaIntegrationTestRunner } from 'medusa-test-utils';
jest.setTimeout(60 * 1000);
medusaIntegrationTestRunner({
inApp: true,
env: {},
testSuite: ({ api }) => {
describe("Ping", () => {
it("ping the server health endpoint", async () => {
const response = await api.get("/health");
describe('Ping', () => {
it('ping the server health endpoint', async () => {
const response = await api.get('/health');
expect(response.status).toEqual(200);
});
});

View File

@ -1,26 +1,26 @@
const { loadEnv } = require("@medusajs/utils");
loadEnv("test", process.cwd());
const { loadEnv } = require('@medusajs/utils');
loadEnv('test', process.cwd());
module.exports = {
transform: {
"^.+\\.[jt]s$": [
"@swc/jest",
'^.+\\.[jt]s$': [
'@swc/jest',
{
jsc: {
parser: { syntax: "typescript", decorators: true },
parser: { syntax: 'typescript', decorators: true },
},
},
],
},
testEnvironment: "node",
moduleFileExtensions: ["js", "ts", "json"],
modulePathIgnorePatterns: ["dist/"],
testEnvironment: 'node',
moduleFileExtensions: ['js', 'ts', 'json'],
modulePathIgnorePatterns: ['dist/'],
};
if (process.env.TEST_TYPE === "integration:http") {
module.exports.testMatch = ["**/integration-tests/http/*.spec.[jt]s"];
} else if (process.env.TEST_TYPE === "integration:modules") {
module.exports.testMatch = ["**/src/modules/*/__tests__/**/*.[jt]s"];
} else if (process.env.TEST_TYPE === "unit") {
module.exports.testMatch = ["**/src/**/__tests__/**/*.unit.spec.[jt]s"];
if (process.env.TEST_TYPE === 'integration:http') {
module.exports.testMatch = ['**/integration-tests/http/*.spec.[jt]s'];
} else if (process.env.TEST_TYPE === 'integration:modules') {
module.exports.testMatch = ['**/src/modules/*/__tests__/**/*.[jt]s'];
} else if (process.env.TEST_TYPE === 'unit') {
module.exports.testMatch = ['**/src/**/__tests__/**/*.unit.spec.[jt]s'];
}

View File

@ -1,53 +1,52 @@
import { loadEnv, defineConfig } from "@medusajs/framework/utils";
import { Modules } from "@medusajs/framework/utils";
import { loadEnv, defineConfig } from '@medusajs/framework/utils';
import { Modules } from '@medusajs/framework/utils';
loadEnv(process.env.NODE_ENV || "development", process.cwd());
loadEnv(process.env.NODE_ENV || 'development', process.cwd());
module.exports = defineConfig({
admin: {
disable: process.env.DISABLE_MEDUSA_ADMIN === "true",
disable: process.env.DISABLE_MEDUSA_ADMIN === 'true',
},
projectConfig: {
databaseUrl: process.env.DATABASE_URL,
database_ssl: true, // or remove if not needed
workerMode: process.env.MEDUSA_WORKER_MODE,
database_ssl: true, // or remove if not needed
workerMode: process.env.MEDUSA_WORKER_MODE || 'server',
http: {
storeCors: process.env.STORE_CORS,
adminCors: process.env.ADMIN_CORS,
authCors: process.env.AUTH_CORS,
jwtSecret: process.env.JWT_SECRET || "supersecret",
cookieSecret: process.env.COOKIE_SECRET || "supersecret",
storeCors: process.env.STORE_CORS!,
adminCors: process.env.ADMIN_CORS!,
authCors: process.env.AUTH_CORS!,
jwtSecret: process.env.JWT_SECRET || 'supersecret',
cookieSecret: process.env.COOKIE_SECRET || 'supersecret',
},
},
modules: {
[Modules.CACHE]: {
resolve: "@medusajs/medusa/cache-redis",
modules: [
{
resolve: '@medusajs/medusa/cache-redis',
options: {
redisUrl: process.env.CACHE_REDIS_URL,
},
},
[Modules.EVENT_BUS]: {
resolve: "@medusajs/medusa/event-bus-redis",
{
resolve: '@medusajs/medusa/event-bus-redis',
options: {
redisUrl: process.env.CACHE_REDIS_URL,
},
},
[Modules.WORKFLOW_ENGINE]: {
resolve: "@medusajs/workflow-engine-redis",
{
resolve: '@medusajs/workflow-engine-redis',
options: {
redis: {
url: process.env.CACHE_REDIS_URL,
},
redisurl: process.env.CACHE_REDIS_URL,
},
},
[Modules.FILE]: {
resolve: "@medusajs/file",
{
resolve: '@medusajs/file',
options: {
providers: [
{
resolve: "@medusajs/medusa/file-s3",
id: "s3",
resolve: '@medusajs/medusa/file-s3',
id: 's3',
options: {
file_url: process.env.S3_FILE_URL,
access_key_id: process.env.S3_ACCESS_KEY_ID,
@ -66,5 +65,20 @@ module.exports = defineConfig({
],
},
},
},
{
resolve: '@medusajs/medusa/payment',
options: {
providers: [
{
resolve: './src/modules/razorpay',
id: 'razorpay',
options: {
key_id: process.env.RAZORPAY_KEY_ID || '',
key_secret: process.env.RAZORPAY_KEY_SECRET || '',
},
},
],
},
},
],
});

View File

@ -35,7 +35,8 @@
"@mikro-orm/migrations": "^6.4.3",
"@mikro-orm/postgresql": "^6.4.3",
"awilix": "^8.0.1",
"pg": "^8.13.0"
"pg": "^8.13.0",
"razorpay": "^2.9.5"
},
"devDependencies": {
"@medusajs/test-utils": "^2.4.0",
@ -47,6 +48,7 @@
"@types/react": "^18.3.2",
"@types/react-dom": "^18.2.25",
"jest": "^29.7.0",
"prettier": "^3.5.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@ -9,7 +9,7 @@ A widget is a React component that can be injected into an existing page in the
For example, create the file `src/admin/widgets/product-widget.tsx` with the following content:
```tsx title="src/admin/widgets/product-widget.tsx"
import { defineWidgetConfig } from "@medusajs/admin-sdk";
import { defineWidgetConfig } from '@medusajs/admin-sdk';
// The widget
const ProductWidget = () => {
@ -22,7 +22,7 @@ const ProductWidget = () => {
// The widget's configurations
export const config = defineWidgetConfig({
zone: "product.details.after",
zone: 'product.details.after',
});
export default ProductWidget;

View File

@ -7,11 +7,11 @@ An API Route is created in a TypeScript or JavaScript file under the `/src/api`
For example, to create a `GET` API Route at `/store/hello-world`, create the file `src/api/store/hello-world/route.ts` with the following content:
```ts
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework";
import type { MedusaRequest, MedusaResponse } from '@medusajs/framework';
export async function GET(req: MedusaRequest, res: MedusaResponse) {
res.json({
message: "Hello world!",
message: 'Hello world!',
});
}
```
@ -33,7 +33,7 @@ You can define a handler for each of these methods by exporting a function with
For example:
```ts
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework";
import type { MedusaRequest, MedusaResponse } from '@medusajs/framework';
export async function GET(req: MedusaRequest, res: MedusaResponse) {
// Handle GET requests
@ -55,7 +55,7 @@ To create an API route that accepts a path parameter, create a directory within
For example, if you want to define a route that takes a `productId` parameter, you can do so by creating a file called `/api/products/[productId]/route.ts`:
```ts
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework";
import type { MedusaRequest, MedusaResponse } from '@medusajs/framework';
export async function GET(req: MedusaRequest, res: MedusaResponse) {
const { productId } = req.params;
@ -75,14 +75,12 @@ For example, if you want to define a route that takes both a `productId` and a `
The Medusa container is available on `req.scope`. Use it to access modules' main services and other registered resources:
```ts
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework";
import { IProductModuleService } from "@medusajs/framework/types";
import { Modules } from "@medusajs/framework/utils";
import type { MedusaRequest, MedusaResponse } from '@medusajs/framework';
import { IProductModuleService } from '@medusajs/framework/types';
import { Modules } from '@medusajs/framework/utils';
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const productModuleService: IProductModuleService = req.scope.resolve(
Modules.PRODUCT,
);
const productModuleService: IProductModuleService = req.scope.resolve(Modules.PRODUCT);
const [, count] = await productModuleService.listAndCount();
@ -99,26 +97,18 @@ You can apply middleware to your routes by creating a file called `/api/middlewa
For example, if you want to apply a custom middleware function to the `/store/custom` route, you can do so by adding the following to your `/api/middlewares.ts` file:
```ts
import { defineMiddlewares } from "@medusajs/medusa";
import type {
MedusaRequest,
MedusaResponse,
MedusaNextFunction,
} from "@medusajs/framework";
import { defineMiddlewares } from '@medusajs/medusa';
import type { MedusaRequest, MedusaResponse, MedusaNextFunction } from '@medusajs/framework';
async function logger(
req: MedusaRequest,
res: MedusaResponse,
next: MedusaNextFunction,
) {
console.log("Request received");
async function logger(req: MedusaRequest, res: MedusaResponse, next: MedusaNextFunction) {
console.log('Request received');
next();
}
export default defineMiddlewares({
routes: [
{
matcher: "/store/custom",
matcher: '/store/custom',
middlewares: [logger],
},
],

View File

@ -1,8 +1,5 @@
import { MedusaRequest, MedusaResponse } from "@medusajs/framework";
import { MedusaRequest, MedusaResponse } from '@medusajs/framework';
export async function GET(
req: MedusaRequest,
res: MedusaResponse,
): Promise<void> {
export async function GET(req: MedusaRequest, res: MedusaResponse): Promise<void> {
res.sendStatus(200);
}

View File

@ -1,8 +1,5 @@
import { MedusaRequest, MedusaResponse } from "@medusajs/framework";
import { MedusaRequest, MedusaResponse } from '@medusajs/framework';
export async function GET(
req: MedusaRequest,
res: MedusaResponse,
): Promise<void> {
export async function GET(req: MedusaRequest, res: MedusaResponse): Promise<void> {
res.sendStatus(200);
}

View File

@ -7,16 +7,11 @@ A scheduled job is created in a TypeScript or JavaScript file under the `src/job
For example, create the file `src/jobs/hello-world.ts` with the following content:
```ts
import {
IProductModuleService,
MedusaContainer,
} from "@medusajs/framework/types";
import { Modules } from "@medusajs/framework/utils";
import { IProductModuleService, MedusaContainer } from '@medusajs/framework/types';
import { Modules } from '@medusajs/framework/utils';
export default async function myCustomJob(container: MedusaContainer) {
const productService: IProductModuleService = container.resolve(
Modules.PRODUCT,
);
const productService: IProductModuleService = container.resolve(Modules.PRODUCT);
const products = await productService.listAndCountProducts();
@ -24,8 +19,8 @@ export default async function myCustomJob(container: MedusaContainer) {
}
export const config = {
name: "daily-product-report",
schedule: "0 0 * * *", // Every day at midnight
name: 'daily-product-report',
schedule: '0 0 * * *', // Every day at midnight
};
```

View File

@ -5,14 +5,11 @@ A module link forms an association between two data models of different modules,
For example:
```ts
import HelloModule from "../modules/hello";
import ProductModule from "@medusajs/medusa/product";
import { defineLink } from "@medusajs/framework/utils";
import HelloModule from '../modules/hello';
import ProductModule from '@medusajs/medusa/product';
import { defineLink } from '@medusajs/framework/utils';
export default defineLink(
ProductModule.linkable.product,
HelloModule.linkable.myCustom,
);
export default defineLink(ProductModule.linkable.product, HelloModule.linkable.myCustom);
```
This defines a link between the Product Module's `product` data model and the Hello Module (custom module)'s `myCustom` data model.

View File

@ -13,7 +13,7 @@ For example, create the file `src/modules/hello/service.ts` with the following c
```ts title="src/modules/hello/service.ts"
export default class HelloModuleService {
getMessage() {
return "Hello, world!";
return 'Hello, world!';
}
}
```
@ -25,10 +25,10 @@ A module must have an `index.ts` file in its root directory that exports its def
For example, create the file `src/modules/hello.index.ts` with the following content:
```ts title="src/modules/hello.index.ts" highlights={[["4", "", "The main service of the module."]]}
import HelloModuleService from "./service";
import { Module } from "@medusajs/framework/utils";
import HelloModuleService from './service';
import { Module } from '@medusajs/framework/utils';
export const HELLO_MODULE = "helloModuleService";
export const HELLO_MODULE = 'helloModuleService';
export default Module(HELLO_MODULE, {
service: HelloModuleService,
@ -42,13 +42,13 @@ The last step is to add the module in Medusas configurations.
In `medusa-config.js`, add the module to the `modules` object:
```js title="medusa-config.js"
import { HELLO_MODULE } from "./src/modules/hello";
import { HELLO_MODULE } from './src/modules/hello';
module.exports = defineConfig({
// ...
modules: {
[HELLO_MODULE]: {
resolve: "./modules/hello",
resolve: './modules/hello',
},
},
});
@ -61,16 +61,12 @@ Its key (`helloModuleService` or `HELLO_MODULE`) is the name of the modules m
You can resolve the main service of the module in other resources, such as an API route:
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework";
import HelloModuleService from "../../../modules/hello/service";
import { HELLO_MODULE } from "../../../modules/hello";
import { MedusaRequest, MedusaResponse } from '@medusajs/framework';
import HelloModuleService from '../../../modules/hello/service';
import { HELLO_MODULE } from '../../../modules/hello';
export async function GET(
req: MedusaRequest,
res: MedusaResponse,
): Promise<void> {
const helloModuleService: HelloModuleService =
req.scope.resolve(HELLO_MODULE);
export async function GET(req: MedusaRequest, res: MedusaResponse): Promise<void> {
const helloModuleService: HelloModuleService = req.scope.resolve(HELLO_MODULE);
res.json({
message: helloModuleService.getMessage(),

View File

@ -0,0 +1,6 @@
import RazorpayPaymentProvider from './service';
import { ModuleProvider, Modules } from '@medusajs/framework/utils';
export default ModuleProvider(Modules.PAYMENT, {
services: [RazorpayPaymentProvider],
});

View File

@ -0,0 +1,371 @@
import Razorpay from 'razorpay';
// Adjust these imports to match your actual Medusa versions/paths
import { AbstractPaymentProvider, BigNumber, MedusaError, PaymentSessionStatus } from '@medusajs/framework/utils';
import {
CreatePaymentProviderSession,
Logger,
ProviderWebhookPayload,
WebhookActionResult,
} from '@medusajs/framework/types';
import {
UpdatePaymentProviderSession,
PaymentProviderError,
PaymentProviderSessionResponse,
} from '@medusajs/framework/types';
type RazorpayProviderOptions = {
key_id: string;
key_secret: string;
// Add other config options if needed
};
type InjectedDependencies = {
logger: Logger;
};
export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayProviderOptions> {
protected logger_: Logger;
protected options_: RazorpayProviderOptions;
// The unique identifier used by Medusa
static identifier = 'razorpay';
protected razorpay: Razorpay;
constructor(container: InjectedDependencies, options: RazorpayProviderOptions) {
super(container, options);
this.logger_ = container.logger;
this.options_ = options;
this.razorpay = new Razorpay({
key_id: this.options_.key_id,
key_secret: this.options_.key_secret,
});
}
static validateOptions(options: Record<any, any>) {
if (!options.key_id) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, "Key ID is required in the provider's options.");
}
if (!options.key_secret) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, "Key Secret is required in the provider's options.");
}
}
/**
* Initiates a payment session (e.g. create an order in Razorpay).
* Return a PaymentProviderSessionResponse:
* { session_data: Record<string, unknown> }
*/
async initiatePayment(
context: CreatePaymentProviderSession,
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
// Example: create an order in Razorpay
const { currency_code, context: customerDetails } = context;
try {
const amount = (context.amount as number) * 100;
// const currency = (data.currency as string) || "INR"
const order = await this.razorpay.orders.create({
amount,
currency: currency_code.toUpperCase(),
receipt: `receipt_${Date.now()}`,
payment_capture: true, // auto-capture
});
// Return a PaymentProviderSessionResponse
return {
...order,
data: {
id: order.id,
},
};
} catch (e) {
console.log(e);
return {
error: e,
code: 'unknown',
detail: e,
};
}
}
/**
* Retrieves the payment session data from Razorpay or from your local records.
* Must return either session_data or throw a PaymentProviderError.
*/
async retrievePayment(
paymentSessionData: Record<string, unknown>,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
const externalId = paymentSessionData.id;
try {
const razorpayOrderId = externalId as string;
if (!razorpayOrderId) {
throw new Error('No Razorpay order ID found.');
}
// Example: fetch the order from Razorpay
const order = await this.razorpay.orders.fetch(razorpayOrderId);
return {
...order,
status: order.status || 'created',
id: externalId,
};
} catch (e) {
return {
error: e,
code: 'unknown',
detail: e,
};
}
}
/**
* Updates the payment session. The signature typically expects:
* (context: UpdatePaymentProviderSession) => Promise<PaymentProviderError | PaymentProviderSessionResponse>
*/
async updatePayment(
context: UpdatePaymentProviderSession,
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
// Deconstruct the data from context
const { amount, currency_code, context: customerDetails, data } = context;
const externalId = data.id;
try {
const response = await this.razorpay.payments.edit(externalId as string, {
notes: {
amount: amount as number,
currency: currency_code,
},
});
return {
...response,
data: {
id: response.id,
},
};
} catch (e) {
return {
error: e,
code: 'unknown',
detail: e,
};
}
}
/**
* Authorize the payment (e.g., verify webhook signature, check that payment is successful).
* Return updated session data or PaymentProviderError.
*/
async authorizePayment(
paymentSessionData: Record<string, unknown>,
context: Record<string, unknown>,
): Promise<
| PaymentProviderError
| {
status: PaymentSessionStatus;
data: PaymentProviderSessionResponse['data'];
}
> {
const externalId = paymentSessionData.id;
try {
console.log('authorizePayment externalId', externalId);
// Here you could verify a signature or confirm the payment status in Razorpay
const paymentData = await this.razorpay.orders.fetchPayments(externalId as string);
// if the externalId is paymentId, then use this.razorpay.payments.fetch(externalId as string)
// const paymentData = await this.razorpay.payments.fetch(externalId as string)
return {
data: {
...paymentSessionData,
id: externalId,
},
status: PaymentSessionStatus.AUTHORIZED,
};
} catch (error) {
return {
error: error,
code: 'unknown',
detail: error,
};
}
}
/**
* Capture the payment if not auto-captured.
*/
async capturePayment(
paymentData: Record<string, unknown>,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
try {
const externalId = paymentData.id;
const { amount } = paymentData;
// e.g. this.razorpay.payments.capture(paymentId, amount) if not auto-captured
const newData = await this.razorpay.payments.capture(externalId as string, amount as number, 'INR');
return {
...newData,
// status: "captured",
id: externalId,
};
} catch (e) {
return {
error: e,
code: 'unknown',
detail: e,
};
}
}
/**
* Refund the payment in Razorpay.
*/
async refundPayment(
paymentData: Record<string, unknown>,
refundAmount: number,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
const externalId = paymentData.id;
try {
const newData = await this.razorpay.payments.refund(externalId as string, { amount: refundAmount });
return {
...newData,
id: externalId,
// refunded_amount: refundAmount,
// status: "refunded",
};
} catch (e) {
return {
error: e,
code: 'unknown',
detail: e,
};
}
}
/**
* Cancel a payment if it hasn't been paid. For Razorpay, you may just mark the order as canceled in your system.
*/
async cancelPayment(
paymentData: Record<string, unknown>,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
try {
const externalId = paymentData.id;
return {
id: externalId,
status: 'canceled',
};
} catch (error) {
return {
error: error,
code: 'unknown',
detail: error,
};
}
}
/**
* Delete a payment session. Must return either updated session or PaymentProviderError.
* The base signature often is:
* (paymentSessionData: Record<string, unknown>) => Promise<Record<string, unknown> | PaymentProviderError>
* or might require returning PaymentProviderSessionResponse. Adjust if needed.
*/
async deletePayment(
paymentSessionData: Record<string, unknown>,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
const externalId = paymentSessionData.id;
try {
// If Razorpay does not allow deleting an order, just mark locally as deleted
// Return the final state or an empty object
return {
id: externalId,
deleted: true,
};
} catch (error) {
return {
error: error,
code: 'unknown',
detail: error,
};
}
}
/**
* Get the status (PaymentSessionStatus) for the payment.
* Must return a PaymentSessionStatus enum value (e.g., "AUTHORIZED", "PENDING", "REQUIRES_MORE", "ERROR", etc.)
*/
async getPaymentStatus(paymentSessionData: Record<string, unknown>): Promise<PaymentSessionStatus> {
// For a minimal example:
const payment = await this.razorpay.payments.fetch(paymentSessionData.id as string);
const status = payment.status;
switch (status?.toLowerCase()) {
case 'created':
return PaymentSessionStatus.PENDING;
case 'authorized':
return PaymentSessionStatus.AUTHORIZED;
case 'captured':
return PaymentSessionStatus.CAPTURED;
case 'refunded':
return PaymentSessionStatus.CANCELED; // Payment was authorized before refund
case 'failed':
return PaymentSessionStatus.ERROR;
default:
return PaymentSessionStatus.ERROR;
}
}
/**
* Handle webhook events. The base signature often is:
* getWebhookActionAndData(data: {
* data: Record<string, unknown>;
* rawData: string | Buffer<ArrayBufferLike>;
* headers: Record<string, unknown>;
* }): Promise<WebhookActionResult>
*
* Must return an object with:
* { action: PaymentActions; data: Record<string, unknown> }
*/
async getWebhookActionAndData(
// data: {
// data: Record<string, unknown>
// rawData: string | Buffer<ArrayBufferLike>
// headers: Record<string, unknown>
// }
payload: ProviderWebhookPayload['payload'],
): Promise<WebhookActionResult> {
const { data, rawData, headers } = payload;
try {
switch (data.event_type) {
case 'authorized_amount':
return {
action: 'authorized',
data: {
session_id: (data.metadata as Record<string, any>).session_id,
amount: new BigNumber(data.amount as number),
},
};
case 'success':
return {
action: 'captured',
data: {
session_id: (data.metadata as Record<string, any>).session_id,
amount: new BigNumber(data.amount as number),
},
};
default:
return {
action: 'not_supported',
};
}
} catch (e) {
return {
action: 'failed',
data: {
session_id: (data.metadata as Record<string, any>).session_id,
amount: new BigNumber(data.amount as number),
},
};
}
}
}
export default RazorpayPaymentProvider;

View File

@ -9,13 +9,11 @@ To create a custom CLI script, create a TypeScript or JavaScript file under the
For example, create the file `src/scripts/my-script.ts` with the following content:
```ts title="src/scripts/my-script.ts"
import { ExecArgs, IProductModuleService } from "@medusajs/framework/types";
import { Modules } from "@medusajs/framework/utils";
import { ExecArgs, IProductModuleService } from '@medusajs/framework/types';
import { Modules } from '@medusajs/framework/utils';
export default async function myScript({ container }: ExecArgs) {
const productModuleService: IProductModuleService = container.resolve(
Modules.PRODUCT,
);
const productModuleService: IProductModuleService = container.resolve(Modules.PRODUCT);
const [, count] = await productModuleService.listAndCountProducts();
@ -44,7 +42,7 @@ Your script can accept arguments from the command line. Arguments are passed to
For example:
```ts
import { ExecArgs } from "@medusajs/framework/types";
import { ExecArgs } from '@medusajs/framework/types';
export default async function myScript({ args }: ExecArgs) {
console.log(`The arguments you passed: ${args}`);

View File

@ -12,13 +12,9 @@ import {
linkSalesChannelsToApiKeyWorkflow,
linkSalesChannelsToStockLocationWorkflow,
updateStoresWorkflow,
} from "@medusajs/medusa/core-flows";
import { ExecArgs } from "@medusajs/framework/types";
import {
ContainerRegistrationKeys,
Modules,
ProductStatus,
} from "@medusajs/framework/utils";
} from '@medusajs/medusa/core-flows';
import { ExecArgs } from '@medusajs/framework/types';
import { ContainerRegistrationKeys, Modules, ProductStatus } from '@medusajs/framework/utils';
export default async function seedDemoData({ container }: ExecArgs) {
const logger = container.resolve(ContainerRegistrationKeys.LOGGER);
@ -28,23 +24,21 @@ export default async function seedDemoData({ container }: ExecArgs) {
const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL);
const storeModuleService = container.resolve(Modules.STORE);
const countries = ["gb", "de", "dk", "se", "fr", "es", "it"];
const countries = ['gb', 'de', 'dk', 'se', 'fr', 'es', 'it'];
logger.info("Seeding store data...");
logger.info('Seeding store data...');
const [store] = await storeModuleService.listStores();
let defaultSalesChannel = await salesChannelModuleService.listSalesChannels({
name: "Default Sales Channel",
name: 'Default Sales Channel',
});
if (!defaultSalesChannel.length) {
// create the default sales channel
const { result: salesChannelResult } = await createSalesChannelsWorkflow(
container,
).run({
const { result: salesChannelResult } = await createSalesChannelsWorkflow(container).run({
input: {
salesChannelsData: [
{
name: "Default Sales Channel",
name: 'Default Sales Channel',
},
],
},
@ -58,53 +52,51 @@ export default async function seedDemoData({ container }: ExecArgs) {
update: {
supported_currencies: [
{
currency_code: "eur",
currency_code: 'eur',
is_default: true,
},
{
currency_code: "usd",
currency_code: 'usd',
},
],
default_sales_channel_id: defaultSalesChannel[0].id,
},
},
});
logger.info("Seeding region data...");
logger.info('Seeding region data...');
const { result: regionResult } = await createRegionsWorkflow(container).run({
input: {
regions: [
{
name: "Europe",
currency_code: "eur",
name: 'Europe',
currency_code: 'eur',
countries,
payment_providers: ["pp_system_default"],
payment_providers: ['pp_system_default'],
},
],
},
});
const region = regionResult[0];
logger.info("Finished seeding regions.");
logger.info('Finished seeding regions.');
logger.info("Seeding tax regions...");
logger.info('Seeding tax regions...');
await createTaxRegionsWorkflow(container).run({
input: countries.map((country_code) => ({
country_code,
})),
});
logger.info("Finished seeding tax regions.");
logger.info('Finished seeding tax regions.');
logger.info("Seeding stock location data...");
const { result: stockLocationResult } = await createStockLocationsWorkflow(
container,
).run({
logger.info('Seeding stock location data...');
const { result: stockLocationResult } = await createStockLocationsWorkflow(container).run({
input: {
locations: [
{
name: "European Warehouse",
name: 'European Warehouse',
address: {
city: "Copenhagen",
country_code: "DK",
address_1: "",
city: 'Copenhagen',
country_code: 'DK',
address_1: '',
},
},
],
@ -117,58 +109,57 @@ export default async function seedDemoData({ container }: ExecArgs) {
stock_location_id: stockLocation.id,
},
[Modules.FULFILLMENT]: {
fulfillment_provider_id: "manual_manual",
fulfillment_provider_id: 'manual_manual',
},
});
logger.info("Seeding fulfillment data...");
const { result: shippingProfileResult } =
await createShippingProfilesWorkflow(container).run({
input: {
data: [
{
name: "Default",
type: "default",
},
],
},
});
logger.info('Seeding fulfillment data...');
const { result: shippingProfileResult } = await createShippingProfilesWorkflow(container).run({
input: {
data: [
{
name: 'Default',
type: 'default',
},
],
},
});
const shippingProfile = shippingProfileResult[0];
const fulfillmentSet = await fulfillmentModuleService.createFulfillmentSets({
name: "European Warehouse delivery",
type: "shipping",
name: 'European Warehouse delivery',
type: 'shipping',
service_zones: [
{
name: "Europe",
name: 'Europe',
geo_zones: [
{
country_code: "gb",
type: "country",
country_code: 'gb',
type: 'country',
},
{
country_code: "de",
type: "country",
country_code: 'de',
type: 'country',
},
{
country_code: "dk",
type: "country",
country_code: 'dk',
type: 'country',
},
{
country_code: "se",
type: "country",
country_code: 'se',
type: 'country',
},
{
country_code: "fr",
type: "country",
country_code: 'fr',
type: 'country',
},
{
country_code: "es",
type: "country",
country_code: 'es',
type: 'country',
},
{
country_code: "it",
type: "country",
country_code: 'it',
type: 'country',
},
],
},
@ -187,23 +178,23 @@ export default async function seedDemoData({ container }: ExecArgs) {
await createShippingOptionsWorkflow(container).run({
input: [
{
name: "Standard Shipping",
price_type: "flat",
provider_id: "manual_manual",
name: 'Standard Shipping',
price_type: 'flat',
provider_id: 'manual_manual',
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
type: {
label: "Standard",
description: "Ship in 2-3 days.",
code: "standard",
label: 'Standard',
description: 'Ship in 2-3 days.',
code: 'standard',
},
prices: [
{
currency_code: "usd",
currency_code: 'usd',
amount: 10,
},
{
currency_code: "eur",
currency_code: 'eur',
amount: 10,
},
{
@ -213,35 +204,35 @@ export default async function seedDemoData({ container }: ExecArgs) {
],
rules: [
{
attribute: "enabled_in_store",
attribute: 'enabled_in_store',
value: '"true"',
operator: "eq",
operator: 'eq',
},
{
attribute: "is_return",
value: "false",
operator: "eq",
attribute: 'is_return',
value: 'false',
operator: 'eq',
},
],
},
{
name: "Express Shipping",
price_type: "flat",
provider_id: "manual_manual",
name: 'Express Shipping',
price_type: 'flat',
provider_id: 'manual_manual',
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
type: {
label: "Express",
description: "Ship in 24 hours.",
code: "express",
label: 'Express',
description: 'Ship in 24 hours.',
code: 'express',
},
prices: [
{
currency_code: "usd",
currency_code: 'usd',
amount: 10,
},
{
currency_code: "eur",
currency_code: 'eur',
amount: 10,
},
{
@ -251,20 +242,20 @@ export default async function seedDemoData({ container }: ExecArgs) {
],
rules: [
{
attribute: "enabled_in_store",
attribute: 'enabled_in_store',
value: '"true"',
operator: "eq",
operator: 'eq',
},
{
attribute: "is_return",
value: "false",
operator: "eq",
attribute: 'is_return',
value: 'false',
operator: 'eq',
},
],
},
],
});
logger.info("Finished seeding fulfillment data.");
logger.info('Finished seeding fulfillment data.');
await linkSalesChannelsToStockLocationWorkflow(container).run({
input: {
@ -272,18 +263,16 @@ export default async function seedDemoData({ container }: ExecArgs) {
add: [defaultSalesChannel[0].id],
},
});
logger.info("Finished seeding stock location data.");
logger.info('Finished seeding stock location data.');
logger.info("Seeding publishable API key data...");
const { result: publishableApiKeyResult } = await createApiKeysWorkflow(
container,
).run({
logger.info('Seeding publishable API key data...');
const { result: publishableApiKeyResult } = await createApiKeysWorkflow(container).run({
input: {
api_keys: [
{
title: "Webshop",
type: "publishable",
created_by: "",
title: 'Webshop',
type: 'publishable',
created_by: '',
},
],
},
@ -296,29 +285,27 @@ export default async function seedDemoData({ container }: ExecArgs) {
add: [defaultSalesChannel[0].id],
},
});
logger.info("Finished seeding publishable API key data.");
logger.info('Finished seeding publishable API key data.');
logger.info("Seeding product data...");
logger.info('Seeding product data...');
const { result: categoryResult } = await createProductCategoriesWorkflow(
container,
).run({
const { result: categoryResult } = await createProductCategoriesWorkflow(container).run({
input: {
product_categories: [
{
name: "Shirts",
name: 'Shirts',
is_active: true,
},
{
name: "Sweatshirts",
name: 'Sweatshirts',
is_active: true,
},
{
name: "Pants",
name: 'Pants',
is_active: true,
},
{
name: "Merch",
name: 'Merch',
is_active: true,
},
],
@ -329,181 +316,179 @@ export default async function seedDemoData({ container }: ExecArgs) {
input: {
products: [
{
title: "Medusa T-Shirt",
category_ids: [
categoryResult.find((cat) => cat.name === "Shirts").id,
],
title: 'Medusa T-Shirt',
category_ids: [categoryResult.find((cat) => cat.name === 'Shirts').id],
description:
"Reimagine the feeling of a classic T-shirt. With our cotton T-shirts, everyday essentials no longer have to be ordinary.",
handle: "t-shirt",
'Reimagine the feeling of a classic T-shirt. With our cotton T-shirts, everyday essentials no longer have to be ordinary.',
handle: 't-shirt',
weight: 400,
status: ProductStatus.PUBLISHED,
images: [
{
url: "https://medusa-public-images.s3.eu-west-1.amazonaws.com/tee-black-front.png",
url: 'https://medusa-public-images.s3.eu-west-1.amazonaws.com/tee-black-front.png',
},
{
url: "https://medusa-public-images.s3.eu-west-1.amazonaws.com/tee-black-back.png",
url: 'https://medusa-public-images.s3.eu-west-1.amazonaws.com/tee-black-back.png',
},
{
url: "https://medusa-public-images.s3.eu-west-1.amazonaws.com/tee-white-front.png",
url: 'https://medusa-public-images.s3.eu-west-1.amazonaws.com/tee-white-front.png',
},
{
url: "https://medusa-public-images.s3.eu-west-1.amazonaws.com/tee-white-back.png",
url: 'https://medusa-public-images.s3.eu-west-1.amazonaws.com/tee-white-back.png',
},
],
options: [
{
title: "Size",
values: ["S", "M", "L", "XL"],
title: 'Size',
values: ['S', 'M', 'L', 'XL'],
},
{
title: "Color",
values: ["Black", "White"],
title: 'Color',
values: ['Black', 'White'],
},
],
variants: [
{
title: "S / Black",
sku: "SHIRT-S-BLACK",
title: 'S / Black',
sku: 'SHIRT-S-BLACK',
options: {
Size: "S",
Color: "Black",
Size: 'S',
Color: 'Black',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "S / White",
sku: "SHIRT-S-WHITE",
title: 'S / White',
sku: 'SHIRT-S-WHITE',
options: {
Size: "S",
Color: "White",
Size: 'S',
Color: 'White',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "M / Black",
sku: "SHIRT-M-BLACK",
title: 'M / Black',
sku: 'SHIRT-M-BLACK',
options: {
Size: "M",
Color: "Black",
Size: 'M',
Color: 'Black',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "M / White",
sku: "SHIRT-M-WHITE",
title: 'M / White',
sku: 'SHIRT-M-WHITE',
options: {
Size: "M",
Color: "White",
Size: 'M',
Color: 'White',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "L / Black",
sku: "SHIRT-L-BLACK",
title: 'L / Black',
sku: 'SHIRT-L-BLACK',
options: {
Size: "L",
Color: "Black",
Size: 'L',
Color: 'Black',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "L / White",
sku: "SHIRT-L-WHITE",
title: 'L / White',
sku: 'SHIRT-L-WHITE',
options: {
Size: "L",
Color: "White",
Size: 'L',
Color: 'White',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "XL / Black",
sku: "SHIRT-XL-BLACK",
title: 'XL / Black',
sku: 'SHIRT-XL-BLACK',
options: {
Size: "XL",
Color: "Black",
Size: 'XL',
Color: 'Black',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "XL / White",
sku: "SHIRT-XL-WHITE",
title: 'XL / White',
sku: 'SHIRT-XL-WHITE',
options: {
Size: "XL",
Color: "White",
Size: 'XL',
Color: 'White',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
@ -515,95 +500,93 @@ export default async function seedDemoData({ container }: ExecArgs) {
],
},
{
title: "Medusa Sweatshirt",
category_ids: [
categoryResult.find((cat) => cat.name === "Sweatshirts").id,
],
title: 'Medusa Sweatshirt',
category_ids: [categoryResult.find((cat) => cat.name === 'Sweatshirts').id],
description:
"Reimagine the feeling of a classic sweatshirt. With our cotton sweatshirt, everyday essentials no longer have to be ordinary.",
handle: "sweatshirt",
'Reimagine the feeling of a classic sweatshirt. With our cotton sweatshirt, everyday essentials no longer have to be ordinary.',
handle: 'sweatshirt',
weight: 400,
status: ProductStatus.PUBLISHED,
images: [
{
url: "https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatshirt-vintage-front.png",
url: 'https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatshirt-vintage-front.png',
},
{
url: "https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatshirt-vintage-back.png",
url: 'https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatshirt-vintage-back.png',
},
],
options: [
{
title: "Size",
values: ["S", "M", "L", "XL"],
title: 'Size',
values: ['S', 'M', 'L', 'XL'],
},
],
variants: [
{
title: "S",
sku: "SWEATSHIRT-S",
title: 'S',
sku: 'SWEATSHIRT-S',
options: {
Size: "S",
Size: 'S',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "M",
sku: "SWEATSHIRT-M",
title: 'M',
sku: 'SWEATSHIRT-M',
options: {
Size: "M",
Size: 'M',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "L",
sku: "SWEATSHIRT-L",
title: 'L',
sku: 'SWEATSHIRT-L',
options: {
Size: "L",
Size: 'L',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "XL",
sku: "SWEATSHIRT-XL",
title: 'XL',
sku: 'SWEATSHIRT-XL',
options: {
Size: "XL",
Size: 'XL',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
@ -615,93 +598,93 @@ export default async function seedDemoData({ container }: ExecArgs) {
],
},
{
title: "Medusa Sweatpants",
category_ids: [categoryResult.find((cat) => cat.name === "Pants").id],
title: 'Medusa Sweatpants',
category_ids: [categoryResult.find((cat) => cat.name === 'Pants').id],
description:
"Reimagine the feeling of classic sweatpants. With our cotton sweatpants, everyday essentials no longer have to be ordinary.",
handle: "sweatpants",
'Reimagine the feeling of classic sweatpants. With our cotton sweatpants, everyday essentials no longer have to be ordinary.',
handle: 'sweatpants',
weight: 400,
status: ProductStatus.PUBLISHED,
images: [
{
url: "https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-front.png",
url: 'https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-front.png',
},
{
url: "https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-back.png",
url: 'https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-back.png',
},
],
options: [
{
title: "Size",
values: ["S", "M", "L", "XL"],
title: 'Size',
values: ['S', 'M', 'L', 'XL'],
},
],
variants: [
{
title: "S",
sku: "SWEATPANTS-S",
title: 'S',
sku: 'SWEATPANTS-S',
options: {
Size: "S",
Size: 'S',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "M",
sku: "SWEATPANTS-M",
title: 'M',
sku: 'SWEATPANTS-M',
options: {
Size: "M",
Size: 'M',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "L",
sku: "SWEATPANTS-L",
title: 'L',
sku: 'SWEATPANTS-L',
options: {
Size: "L",
Size: 'L',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "XL",
sku: "SWEATPANTS-XL",
title: 'XL',
sku: 'SWEATPANTS-XL',
options: {
Size: "XL",
Size: 'XL',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
@ -713,93 +696,93 @@ export default async function seedDemoData({ container }: ExecArgs) {
],
},
{
title: "Medusa Shorts",
category_ids: [categoryResult.find((cat) => cat.name === "Merch").id],
title: 'Medusa Shorts',
category_ids: [categoryResult.find((cat) => cat.name === 'Merch').id],
description:
"Reimagine the feeling of classic shorts. With our cotton shorts, everyday essentials no longer have to be ordinary.",
handle: "shorts",
'Reimagine the feeling of classic shorts. With our cotton shorts, everyday essentials no longer have to be ordinary.',
handle: 'shorts',
weight: 400,
status: ProductStatus.PUBLISHED,
images: [
{
url: "https://medusa-public-images.s3.eu-west-1.amazonaws.com/shorts-vintage-front.png",
url: 'https://medusa-public-images.s3.eu-west-1.amazonaws.com/shorts-vintage-front.png',
},
{
url: "https://medusa-public-images.s3.eu-west-1.amazonaws.com/shorts-vintage-back.png",
url: 'https://medusa-public-images.s3.eu-west-1.amazonaws.com/shorts-vintage-back.png',
},
],
options: [
{
title: "Size",
values: ["S", "M", "L", "XL"],
title: 'Size',
values: ['S', 'M', 'L', 'XL'],
},
],
variants: [
{
title: "S",
sku: "SHORTS-S",
title: 'S',
sku: 'SHORTS-S',
options: {
Size: "S",
Size: 'S',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "M",
sku: "SHORTS-M",
title: 'M',
sku: 'SHORTS-M',
options: {
Size: "M",
Size: 'M',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "L",
sku: "SHORTS-L",
title: 'L',
sku: 'SHORTS-L',
options: {
Size: "L",
Size: 'L',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
{
title: "XL",
sku: "SHORTS-XL",
title: 'XL',
sku: 'SHORTS-XL',
options: {
Size: "XL",
Size: 'XL',
},
prices: [
{
amount: 10,
currency_code: "eur",
currency_code: 'eur',
},
{
amount: 15,
currency_code: "usd",
currency_code: 'usd',
},
],
},
@ -813,13 +796,13 @@ export default async function seedDemoData({ container }: ExecArgs) {
],
},
});
logger.info("Finished seeding product data.");
logger.info('Finished seeding product data.');
logger.info("Seeding inventory levels.");
logger.info('Seeding inventory levels.');
const { data: inventoryItems } = await query.graph({
entity: "inventory_item",
fields: ["id"],
entity: 'inventory_item',
fields: ['id'],
});
const inventoryLevels = [];
@ -838,5 +821,5 @@ export default async function seedDemoData({ container }: ExecArgs) {
},
});
logger.info("Finished seeding inventory levels data.");
logger.info('Finished seeding inventory levels data.');
}

View File

@ -7,16 +7,16 @@ The subscriber is created in a TypeScript or JavaScript file under the `src/subs
For example, create the file `src/subscribers/product-created.ts` with the following content:
```ts
import { type SubscriberConfig } from "@medusajs/framework";
import { type SubscriberConfig } from '@medusajs/framework';
// subscriber function
export default async function productCreateHandler() {
console.log("A product was created");
console.log('A product was created');
}
// subscriber config
export const config: SubscriberConfig = {
event: "product.created",
event: 'product.created',
};
```
@ -33,19 +33,14 @@ A subscriber receives an object having the following properties:
- `container`: The Medusa container. Use it to resolve modules' main services and other registered resources.
```ts
import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework";
import { IProductModuleService } from "@medusajs/framework/types";
import { Modules } from "@medusajs/framework/utils";
import type { SubscriberArgs, SubscriberConfig } from '@medusajs/framework';
import { IProductModuleService } from '@medusajs/framework/types';
import { Modules } from '@medusajs/framework/utils';
export default async function productCreateHandler({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
export default async function productCreateHandler({ event: { data }, container }: SubscriberArgs<{ id: string }>) {
const productId = data.id;
const productModuleService: IProductModuleService = container.resolve(
Modules.PRODUCT,
);
const productModuleService: IProductModuleService = container.resolve(Modules.PRODUCT);
const product = await productModuleService.retrieveProduct(productId);
@ -53,6 +48,6 @@ export default async function productCreateHandler({
}
export const config: SubscriberConfig = {
event: "product.created",
event: 'product.created',
};
```

View File

@ -7,13 +7,9 @@ The workflow is created in a TypeScript or JavaScript file under the `src/workfl
For example:
```ts
import {
createStep,
createWorkflow,
StepResponse,
} from "@medusajs/framework/workflows-sdk";
import { createStep, createWorkflow, StepResponse } from '@medusajs/framework/workflows-sdk';
const step1 = createStep("step-1", async () => {
const step1 = createStep('step-1', async () => {
return new StepResponse(`Hello from step one!`);
});
@ -21,7 +17,7 @@ type WorkflowInput = {
name: string;
};
const step2 = createStep("step-2", async ({ name }: WorkflowInput) => {
const step2 = createStep('step-2', async ({ name }: WorkflowInput) => {
return new StepResponse(`Hello ${name} from step two!`);
});
@ -29,18 +25,15 @@ type WorkflowOutput = {
message: string;
};
const myWorkflow = createWorkflow<WorkflowInput, WorkflowOutput>(
"hello-world",
function (input) {
const str1 = step1();
// to pass input
step2(input);
const myWorkflow = createWorkflow<WorkflowInput, WorkflowOutput>('hello-world', function (input) {
const str1 = step1();
// to pass input
step2(input);
return {
message: str1,
};
},
);
return {
message: str1,
};
});
export default myWorkflow;
```
@ -52,8 +45,8 @@ You can execute the workflow from other resources, such as API routes, scheduled
For example, to execute the workflow in an API route:
```ts
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework";
import myWorkflow from "../../../workflows/hello-world";
import type { MedusaRequest, MedusaResponse } from '@medusajs/framework';
import myWorkflow from '../../../workflows/hello-world';
export async function GET(req: MedusaRequest, res: MedusaResponse) {
const { result } = await myWorkflow(req.scope).run({

View File

@ -5699,7 +5699,7 @@ axios@^0.21.4:
dependencies:
follow-redirects "^1.14.0"
axios@^1.7.4:
axios@^1.6.8, axios@^1.7.4:
version "1.7.9"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a"
integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==
@ -9223,6 +9223,11 @@ postgres-interval@^1.1.0:
dependencies:
xtend "^4.0.0"
prettier@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.0.tgz#50325a28887c6dfdf2ca3f8eaba02b66a8429ca7"
integrity sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==
pretty-format@^29.0.0, pretty-format@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
@ -9350,6 +9355,13 @@ raw-body@2.5.2:
iconv-lite "0.4.24"
unpipe "1.0.0"
razorpay@^2.9.5:
version "2.9.5"
resolved "https://registry.yarnpkg.com/razorpay/-/razorpay-2.9.5.tgz#6f0b1e31e09f796a360503fc4aebd46501a7e85f"
integrity sha512-bmybwyszgfbYWAdO4igyHFk5zFj/D4YuoZAFNbyIYnPwzd+FBY5WvtpfUA9lVBMgnV4NzVEhncxR3It9RI/gCQ==
dependencies:
axios "^1.6.8"
react-aria@^3.33.1:
version "3.36.0"
resolved "https://registry.yarnpkg.com/react-aria/-/react-aria-3.36.0.tgz#95a8e3340ab400bfec4d159e47da8861469e5bcd"