Skip to content

Commit

Permalink
feat(payment, payment-stripe): Add Stripe module provider (#6311)
Browse files Browse the repository at this point in the history
  • Loading branch information
olivermrbl authored Feb 26, 2024
1 parent ac829fc commit ce39b9b
Show file tree
Hide file tree
Showing 49 changed files with 1,253 additions and 116 deletions.
7 changes: 7 additions & 0 deletions .changeset/swift-frogs-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@medusajs/medusa": patch
"@medusajs/types": patch
"@medusajs/utils": patch
---

feat(payment-stripe): new Stripe payment provider
4 changes: 2 additions & 2 deletions packages/auth/src/providers/google.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils"
import {
AuthenticationInput,
AuthenticationResponse,
ModulesSdkTypes,
} from "@medusajs/types"
import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils"
import { AuthUserService } from "@services"
import jwt, { JwtPayload } from "jsonwebtoken"

import { AuthUserService } from "@services"
import { AuthorizationCode } from "simple-oauth2"
import url from "url"

Expand Down
6 changes: 2 additions & 4 deletions packages/medusa-payment-stripe/src/api/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import {
IdempotencyKeyService,
PostgresError,
} from "@medusajs/medusa"
import { ConfigModule, MedusaContainer } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import { AwilixContainer } from "awilix"
import { EOL } from "os"
import Stripe from "stripe"
import { StripeOptions } from "../../types"

const PAYMENT_PROVIDER_KEY = "pp_stripe"

Expand Down Expand Up @@ -179,7 +177,7 @@ async function capturePaymenCollectiontIfNecessary({
await manager.transaction(async (manager) => {
await paymentCollectionService
.withTransaction(manager)
.capture(payment.id)
.capture(payment.id) // TODO: revisit - this method doesn't exists ATM
})
}
}
Expand Down Expand Up @@ -257,4 +255,4 @@ async function completeCartIfNecessary({
)
}
}
}
}
9 changes: 9 additions & 0 deletions packages/medusa/src/api-v2/hooks/middlewares.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { MiddlewareRoute } from "../../types/middlewares"

export const hooksRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["POST"],
bodyParser: { preserveRawBody: true },
matcher: "/hooks/payment/:provider",
},
]
32 changes: 32 additions & 0 deletions packages/medusa/src/api-v2/hooks/payment/[provider]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { PaymentWebhookEvents } from "@medusajs/utils"
import { PaymentModuleOptions } from "@medusajs/types"

import { MedusaRequest, MedusaResponse } from "../../../../types/routing"

export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
try {
const { provider } = req.params

const options: PaymentModuleOptions =
req.scope.resolve(ModuleRegistrationName.PAYMENT).options || {}

const event = {
provider,
payload: { data: req.body, rawData: req.rawBody, headers: req.headers },
}

const eventBus = req.scope.resolve("eventBusService")

// we delay the processing of the event to avoid a conflict caused by a race condition
await eventBus.emit(PaymentWebhookEvents.WebhookReceived, event, {
delay: options.webhook_delay || 5000,
attempts: options.webhook_retries || 3,
})
} catch (err) {
res.status(400).send(`Webhook Error: ${err.message}`)
return
}

res.sendStatus(200)
}
2 changes: 2 additions & 0 deletions packages/medusa/src/api-v2/middlewares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { authRoutesMiddlewares } from "./auth/middlewares"
import { storeCartRoutesMiddlewares } from "./store/carts/middlewares"
import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares"
import { storeRegionRoutesMiddlewares } from "./store/regions/middlewares"
import { hooksRoutesMiddlewares } from "./hooks/middlewares"

export const config: MiddlewaresConfig = {
routes: [
Expand All @@ -29,5 +30,6 @@ export const config: MiddlewaresConfig = {
...adminUserRoutesMiddlewares,
...adminInviteRoutesMiddlewares,
...adminApiKeyRoutesMiddlewares,
...hooksRoutesMiddlewares,
],
}
3 changes: 1 addition & 2 deletions packages/medusa/src/loaders/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ export default async ({
next()
})

app.use(bodyParser.json())

if (featureFlagRouter?.isFeatureEnabled(FeatureFlagUtils.MedusaV2Flag.key)) {
// TODO: Figure out why this is causing issues with test when placed inside ./api.ts
// Adding this here temporarily
Expand All @@ -52,6 +50,7 @@ export default async ({
throw Error("An error occurred while registering Medusa Core API Routes")
}
} else {
app.use(bodyParser.json())
app.use("/", routes(container, configModule.projectConfig))
}

Expand Down
17 changes: 14 additions & 3 deletions packages/medusa/src/loaders/helpers/routing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import {
RouteConfig,
RouteDescriptor,
RouteVerb,
ParserConfigArgs,
} from "./types"
import { MedusaRequest, MedusaResponse } from "../../../types/routing"

const log = ({
activityId,
Expand Down Expand Up @@ -150,9 +152,18 @@ function findMatch(
* Returns an array of body parser middlewares that are applied on routes
* out-of-the-box.
*/
function getBodyParserMiddleware(sizeLimit?: string | number | undefined) {
function getBodyParserMiddleware(args?: ParserConfigArgs) {
const sizeLimit = args?.sizeLimit
const preserveRawBody = args?.preserveRawBody
return [
json({ limit: sizeLimit }),
json({
limit: sizeLimit,
verify: preserveRawBody
? (req: MedusaRequest, res: MedusaResponse, buf: Buffer) => {
req.rawBody = buf
}
: undefined,
}),
text({ limit: sizeLimit }),
urlencoded({ limit: sizeLimit, extended: true }),
]
Expand Down Expand Up @@ -556,7 +567,7 @@ export class RoutesLoader {

this.router[method.toLowerCase()](
path,
...getBodyParserMiddleware(sizeLimit)
...getBodyParserMiddleware(mostSpecificConfig?.bodyParser)
)

return
Expand Down
11 changes: 6 additions & 5 deletions packages/medusa/src/loaders/helpers/routing/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ export type MedusaErrorHandlerFunction = (
next: MedusaNextFunction
) => Promise<void> | void

type ParserConfig =
| false
| {
sizeLimit?: string | number | undefined
}
export type ParserConfigArgs = {
sizeLimit?: string | number | undefined
preserveRawBody?: boolean
}

type ParserConfig = false | ParserConfigArgs

export type MiddlewareRoute = {
method?: MiddlewareVerb | MiddlewareVerb[]
Expand Down
50 changes: 50 additions & 0 deletions packages/medusa/src/subscribers/payment-webhook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { PaymentWebhookEvents } from "@medusajs/utils"

import {
IEventBusService,
IPaymentModuleService,
ProviderWebhookPayload,
Subscriber,
} from "@medusajs/types"
import { EventBusService } from "../services"

type SerializedBuffer = {
data: ArrayBuffer
type: "Buffer"
}

type InjectedDependencies = {
paymentModuleService: IPaymentModuleService
eventBusService: EventBusService
}

class PaymentWebhookSubscriber {
private readonly eventBusService_: IEventBusService
private readonly paymentModuleService_: IPaymentModuleService

constructor({ eventBusService, paymentModuleService }: InjectedDependencies) {
this.eventBusService_ = eventBusService
this.paymentModuleService_ = paymentModuleService

this.eventBusService_.subscribe(
PaymentWebhookEvents.WebhookReceived,
this.processEvent as Subscriber
)
}

/**
* TODO: consider moving this to a workflow
*/
processEvent = async (data: ProviderWebhookPayload): Promise<void> => {
if (
(data.payload.rawData as unknown as SerializedBuffer).type === "Buffer"
) {
data.payload.rawData = Buffer.from(
(data.payload.rawData as unknown as SerializedBuffer).data
)
}
await this.paymentModuleService_.processEvent(data)
}
}

export default PaymentWebhookSubscriber
1 change: 1 addition & 0 deletions packages/medusa/src/types/routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface MedusaRequest extends Request {
user?: (User | Customer) & { customer_id?: string; userId?: string }
scope: MedusaContainer
session?: any
rawBody?: any
requestId?: string
auth_user?: { id: string; app_metadata: Record<string, any>; scope: string }
}
Expand Down
4 changes: 4 additions & 0 deletions packages/payment-stripe/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist
node_modules
.DS_store
yarn.lock
Empty file.
Empty file.
13 changes: 13 additions & 0 deletions packages/payment-stripe/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
globals: {
"ts-jest": {
tsconfig: "tsconfig.spec.json",
isolatedModules: false,
},
},
transform: {
"^.+\\.[jt]s?$": "ts-jest",
},
testEnvironment: `node`,
moduleFileExtensions: [`js`, `jsx`, `ts`, `tsx`, `json`],
}
48 changes: 48 additions & 0 deletions packages/payment-stripe/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@medusajs/payment-stripe",
"version": "0.0.1",
"description": "Stripe payment provider for Medusa",
"main": "dist/index.js",
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
"directory": "packages/payment-stripe"
},
"files": [
"dist"
],
"engines": {
"node": ">=16"
},
"author": "Medusa",
"license": "MIT",
"scripts": {
"prepublishOnly": "cross-env NODE_ENV=production tsc --build",
"test": "jest --passWithNoTests src",
"build": "rimraf dist && tsc -p ./tsconfig.json",
"watch": "tsc --watch"
},
"devDependencies": {
"@medusajs/medusa": "^1.19.1",
"@types/stripe": "^8.0.417",
"awilix": "^8.0.1",
"cross-env": "^5.2.1",
"jest": "^25.5.4",
"rimraf": "^5.0.1",
"typescript": "^4.9.5"
},
"peerDependencies": {
"@medusajs/medusa": "^1.12.0"
},
"dependencies": {
"@medusajs/utils": "^1.11.3",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"stripe": "latest"
},
"gitHead": "81a7ff73d012fda722f6e9ef0bd9ba0232d37808",
"keywords": [
"medusa-plugin",
"medusa-plugin-payment"
]
}
Loading

0 comments on commit ce39b9b

Please sign in to comment.