diff --git a/.env b/.env index c255c381..10e495fb 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -MS_INIT_CONFIGS='[{"microservice":"*","type":"db","params":{"host":"db","port":5432,"username":"postgres","password":"example"}},{"microservice":"authentication","type":"config","params":{"jwtOptions":{"secretKey":"DemoSecretKey"}}},{"microservice":"authorization","type":"config","params":{}},{"microservice":"content","type":"config","params":{}},{"microservice":"gateway","type":"config","params":{"corsOptions":{},"webhookUrl":"/webhook/"}},{"microservice":"users","type":"config","params":{"removedAccountRestoreTime":0}},{"microservice":"notification","type":"config","params":{"defaultEmailFrom":"change@me.com","transportOptions":{"host":"smtp.ethereal.email","port":587,"secure":false,"auth":{"user":"generated ethereal user","pass":"generated ethereal password"}}}},{"microservice":"files","type":"config","params":{"imageProcessingConfig":{"thumbnails":[{"name":"thumbnail","options":{"width":75}},{"name":"small","options":{"width":150}},{"name":"medium","options":{"width":300}},{"name":"large","options":{"width":600}},{"name":"extra-large","options":{"width":1200}}],"outputOptions":{"jpeg":{"quality":80,"mozjpeg":true},"png":{"quality":80},"webp":{"quality":80}},"isWebp":true}}},{"microservice":"payment-stripe","type":"config","params":{"paymentMethods":["bancontact","card"],"apiKey":"your test key from stripe or other service","config":{"apiVersion":"2022-11-15"},"payoutCoeff":0.3,"webhookKeys":{"connect":"your test webhook key from stripe or other service for connect account"},"fees":{"stablePaymentUnit":30,"stableDisputeFeeUnit":1500,"paymentPercent":2.9,"instantPayoutPercent":1},"duplicatedCardsUsage":"reject"}}]' +MS_INIT_CONFIGS='[{"microservice":"*","type":"db","params":{"host":"db","port":5432,"username":"postgres","password":"example"}},{"microservice":"authentication","type":"config","params":{"jwtOptions":{"secretKey":"DemoSecretKey"}}},{"microservice":"authorization","type":"config","params":{}},{"microservice":"content","type":"config","params":{}},{"microservice":"gateway","type":"config","params":{"corsOptions":{},"webhookUrl":"/webhook/"}},{"microservice":"users","type":"config","params":{"removedAccountRestoreTime":0}},{"microservice":"notification","type":"config","params":{"defaultEmailFrom":"change@me.com","transportOptions":{"host":"smtp.ethereal.email","port":587,"secure":false,"auth":{"user":"generated ethereal user","pass":"generated ethereal password"}}}},{"microservice":"files","type":"config","params":{"imageProcessingConfig":{"thumbnails":[{"name":"thumbnail","options":{"width":75}},{"name":"small","options":{"width":150}},{"name":"medium","options":{"width":300}},{"name":"large","options":{"width":600}},{"name":"extra-large","options":{"width":1200}}],"outputOptions":{"jpeg":{"quality":80,"mozjpeg":true},"png":{"quality":80},"webp":{"quality":80}},"isWebp":true}}},{"microservice":"payment-stripe","type":"config","params":{"paymentMethods":["bancontact","card"],"apiKey":"your test key from stripe or other service","config":{"apiVersion":"2025-06-30.basil"},"payoutCoeff":0.3,"webhookKeys":{"connect":"your test webhook key from stripe or other service for connect account"},"fees":{"stablePaymentUnit":30,"stableDisputeFeeUnit":1500,"paymentPercent":2.9,"instantPayoutPercent":1},"duplicatedCardsUsage":"reject"}}]' MS_INIT_MIDDLEWARES='[{"target":"gateway","targetMethod":"*","sender":"authentication","senderMethod":"token.identify","type":"request","order":10,"description":"Validate JWT token and add payload with token info.","params":{"type":"request","isRequired":true,"maxValueSize":100,"exclude":["users.user.sign-in","users.identity-provider.sign-in","authentication.cookies.remove","authentication.token.renew"],"strategy":"transform","convertResult":{"payload.authentication.tokenId":"$middleware.tokenId","payload.authentication.userId":"$middleware.userId","payload.authentication.isAuth":"$middleware.isAuth","payload.authentication.provider":"$middleware.provider"}}},{"target":"gateway","targetMethod":"*","sender":"authorization","senderMethod":"endpoint.enforce","type":"request","order":20,"description":"Whether the user is allowed to call the method. Filter method input params.","params":{"type":"request","isRequired":true,"isCleanResult":true,"strategy":"transform","reqParams":{"isInternal":true},"convertParams":{"userId":"$task.params.payload.authentication.userId","method":"$task.method","filterInput":"$task.params"},"convertResult":{".":"$middleware.filteredInput","payload":"$task.params.payload","payload.authorization.isAllow":"$middleware.isAllow","payload.authorization.roles":"$middleware.roles","payload.authorization.filter":"$middleware.filters"}}},{"target":"gateway","targetMethod":"*","sender":"authorization","senderMethod":"endpoint.filter","type":"response","order":20,"description":"Filter microservice response fields.","params":{"type":"response","isRequired":true,"isCleanResult":true,"strategy":"transform","reqParams":{"isInternal":true},"convertParams":{"type":"out","userId":"$task.params.payload.authentication.userId","method":"$task.method","filterInput":"$result"},"convertResult":{".":"$middleware.filtered"}}},{"target":"users","targetMethod":"identity-provider.sign-in","sender":"authentication","senderMethod":"token.create","type":"response","order":10,"description":"Create JWT auth tokens and attach to response after successful sign in.","params":{"type":"response","isRequired":true,"strategy":"transform","extraRequests":[{"key":"rolesResp","method":"authorization.user-role.view","params":{"userId":"<%= result.user.id %>"}}],"convertParams":{"type":"jwt","userId":"$result.user.id","params":"$task.params.payload.headers.user-info","jwtPayload.roles":"$rolesResp.roles","returnType":"<%= _.get(task, \"params.payload.headers.user-info.authType\", \"cookies\") %>"},"convertResult":{"payload.cookies":"$middleware.payload.cookies","tokens.access":"$middleware.access","tokens.refresh":"$middleware.refresh"}}},{"target":"users","targetMethod":"user.sign-in","sender":"authentication","senderMethod":"token.create","type":"response","order":10,"description":"Create JWT auth tokens and attach to response after successful sign in.","params":{"type":"response","isRequired":true,"strategy":"transform","extraRequests":[{"key":"rolesResp","method":"authorization.user-role.view","params":{"userId":"<%= result.user.id %>"}}],"convertParams":{"type":"jwt","userId":"$result.user.id","params":"$task.params.payload.headers.user-info","jwtPayload.roles":"$rolesResp.roles","returnType":"<%= _.get(task, \"params.payload.headers.user-info.authType\", \"cookies\") %>"},"convertResult":{"payload.cookies":"$middleware.payload.cookies","tokens.access":"$middleware.access","tokens.refresh":"$middleware.refresh"}}},{"target":"users","targetMethod":"user.sign-up","sender":"authentication","senderMethod":"token.create","type":"response","order":10,"description":"Create JWT auth tokens and attach to response after successful sign up.","params":{"type":"response","isRequired":true,"strategy":"transform","extraRequests":[{"key":"rolesResp","method":"authorization.user-role.view","params":{"userId":"<%= result.user.id %>"}}],"convertParams":{"type":"jwt","userId":"$result.user.id","params":"$task.params.payload.headers.user-info","jwtPayload.roles":"$rolesResp.roles","returnType":"<%= _.get(task, \"params.payload.headers.user-info.authType\", \"cookies\") %>"},"convertResult":{"payload.cookies":"$middleware.payload.cookies","tokens.access":"$middleware.access","tokens.refresh":"$middleware.refresh"}}},{"target":"users","targetMethod":"user.sign-out","sender":"authentication","senderMethod":"token.remove","type":"response","order":10,"description":"Remove JWT auth token after successful sign out.","params":{"type":"response","isRequired":true,"strategy":"transform","extraRequests":[{"key":"rolesResp","method":"authentication.cookies.remove","condition":"<%= _.get(task, \"params.payload.headers.authType\", \"cookies\") === \"cookies\" %>"}],"convertParams":{"query.where.id":"$task.params.payload.authentication.tokenId"},"convertResult":{"payload.cookies":"$rolesResp.payload.cookies"}}},{"target":"users","targetMethod":"user.remove","sender":"authentication","senderMethod":"token.remove","type":"response","order":10,"description":"Remove JWT auth tokens after successful sign out.","params":{"type":"response","isRequired":true,"strategy":"transform","extraRequests":[{"key":"rolesResp","method":"authentication.cookies.remove","condition":"<%= _.get(task, \"params.payload.headers.authType\", \"cookies\") === \"cookies\" %>"}],"convertParams":{"query.where.userId":"$task.params.payload.authentication.userId"},"convertResult":{"payload.cookies":"$rolesResp.payload.cookies"}}}]' MS_INIT_TASKS='[{"rule":"0 1 * * *","method":"users.confirm-code.remove","description":"Cleanup old confirmation codes","payload":{"params":{"query":{"where":{"expirationAt":{"<":"<%= Math.floor(Date.now() / 1000) %>"}}},"payload":{"authorization":{"filter":{"methodOptions":{"isAllowMultiple":true}}}}},"allowErrorCodes":[-33485],"responseTemplate":"<%= `deleted: ${deleted.length}` %>"}},{"rule":"0 1 * * *","method":"authentication.token.remove","description":"Cleanup old auth tokens","payload":{"params":{"query":{"where":{"expirationAt":{"<":"<%= Math.floor(Date.now() / 1000) %>"}}},"payload":{"authorization":{"filter":{"methodOptions":{"isAllowMultiple":true}}}}},"allowErrorCodes":[-33485],"responseTemplate":"<%= `deleted: ${deleted.length}` %>"}},{"rule":"* * * * *","method":"notification.job.task.process","description":"Process tasks","payload":{"responseTemplate":"<%= `Process tasks counts: total is ${total}, completed is ${completed} and failed is ${failed}` %>"}}]' MS_IMPORT_PERMISSION=2 diff --git a/configs/config.local.json b/configs/config.local.json index a3054f58..24ea59ca 100644 --- a/configs/config.local.json +++ b/configs/config.local.json @@ -119,7 +119,7 @@ "paymentMethods": ["bancontact", "card"], "apiKey": "your test key from stripe or other service", "config": { - "apiVersion": "2022-11-15" + "apiVersion": "2025-06-30.basil" }, "payoutCoeff": 0.3, "webhookKeys": { diff --git a/microservices/authorization/migrations/permissions/list/models/payment-stripe.json b/microservices/authorization/migrations/permissions/list/models/payment-stripe.json index 0e9e0736..a380fa2e 100644 --- a/microservices/authorization/migrations/permissions/list/models/payment-stripe.json +++ b/microservices/authorization/migrations/permissions/list/models/payment-stripe.json @@ -63,7 +63,6 @@ "admin": "allow" } }, - "product": "payment-stripe.Product", "metadata": { "in": { "admin": "allow" @@ -71,7 +70,16 @@ "out": { "admin": "allow" } - } + }, + "customUnitAmount": { + "in": { + "admin": "allow" + }, + "out": { + "admin": "allow" + } + }, + "product": "payment-stripe.Product" }, "createdAt": "2023-05-26T13:01:39.186Z" }, @@ -314,15 +322,7 @@ } }, "customer": "payment-stripe.Customer", - "product": "payment-stripe.Product", - "customAmount": { - "in": { - "admin": "allow" - }, - "out": { - "user": "allow" - } - } + "product": "payment-stripe.Product" }, "createdAt": "2023-05-26T13:01:39.186Z" }, @@ -1126,14 +1126,6 @@ "out": { "admin": "allow" } - }, - "customAmount": { - "in": { - "admin": "allow" - }, - "out": { - "admin": "allow" - } } }, "createdAt": "2023-05-26T13:01:39.186Z" @@ -1845,6 +1837,14 @@ "out": { "user": "allow" } + }, + "customUnitAmount": { + "in": { + "admin": "allow" + }, + "out": { + "admin": "allow" + } } }, "createdAt": "2023-05-26T13:01:39.186Z" diff --git a/microservices/payment-stripe/__tests__/services/payment-gateway/stripe.ts b/microservices/payment-stripe/__tests__/services/payment-gateway/stripe.ts index 65de46ba..1dd8fc4b 100644 --- a/microservices/payment-stripe/__tests__/services/payment-gateway/stripe.ts +++ b/microservices/payment-stripe/__tests__/services/payment-gateway/stripe.ts @@ -26,11 +26,11 @@ interface IStripeMockParams { describe('services/payment-gateway/stripe', () => { const sandbox = sinon.createSandbox(); const config: StripeTypes.StripeConfig = { - apiVersion: '2022-11-15', + apiVersion: '2025-06-30.basil', }; const remoteConfigMock = { config: { - apiVersion: '2022-11-15', + apiVersion: '2025-06-30.basil', }, paymentMethods: ['bancontact', 'card'], apiKey: 'fake-api-key', diff --git a/microservices/payment-stripe/migrations/1753736526000-add-custom-unit-amount-to-price.ts b/microservices/payment-stripe/migrations/1753736526000-add-custom-unit-amount-to-price.ts new file mode 100644 index 00000000..5246d432 --- /dev/null +++ b/microservices/payment-stripe/migrations/1753736526000-add-custom-unit-amount-to-price.ts @@ -0,0 +1,35 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export default class AddCustomUnitAmountToPrice1753736526000 implements MigrationInterface { + name = 'AddCustomUnitAmountToPrice1753736526000'; + + public async up(queryRunner: QueryRunner): Promise { + // Add customUnitAmount column to price table + await queryRunner.query(` + ALTER TABLE "price" + ADD COLUMN "customUnitAmount" jsonb DEFAULT NULL + `); + + // Remove customAmount column from transaction table + await queryRunner.query(` + ALTER TABLE "transaction" + DROP + COLUMN IF EXISTS "customAmount" + `); + } + + public async down(queryRunner: QueryRunner): Promise { + // Remove customUnitAmount column from price table + await queryRunner.query(` + ALTER TABLE "price" + DROP + COLUMN "customUnitAmount" + `); + + // Add back customAmount column to transaction table + await queryRunner.query(` + ALTER TABLE "transaction" + ADD COLUMN "customAmount" integer DEFAULT NULL + `); + } +} diff --git a/microservices/payment-stripe/package-lock.json b/microservices/payment-stripe/package-lock.json index c840e598..9d260830 100644 --- a/microservices/payment-stripe/package-lock.json +++ b/microservices/payment-stripe/package-lock.json @@ -15,7 +15,7 @@ "class-validator": "^0.14.0", "class-validator-jsonschema": "^5.0.0", "lodash": "^4.17.21", - "stripe": "^11.16.0", + "stripe": "^18.3.0", "typeorm": "0.2.41", "uuidv4": "^6.2.13" }, @@ -3040,15 +3040,23 @@ } }, "node_modules/stripe": { - "version": "11.16.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-11.16.0.tgz", - "integrity": "sha512-Ej1ZtbrMw+Z7L8Kxj80sDNF0E/lPypY2nwymU/wGcOjn7CppTa2HXxPTsQRQ8U0MSVRcwRWyGMrizQWuaUqVGw==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-18.3.0.tgz", + "integrity": "sha512-FkxrTUUcWB4CVN2yzgsfF/YHD6WgYHduaa7VmokCy5TLCgl5UNJkwortxcedrxSavQ8Qfa4Ir4JxcbIYiBsyLg==", + "license": "MIT", "dependencies": { - "@types/node": ">=8.1.0", "qs": "^6.11.0" }, "engines": { "node": ">=12.*" + }, + "peerDependencies": { + "@types/node": ">=12.x.x" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/supports-color": { @@ -5803,11 +5811,10 @@ } }, "stripe": { - "version": "11.16.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-11.16.0.tgz", - "integrity": "sha512-Ej1ZtbrMw+Z7L8Kxj80sDNF0E/lPypY2nwymU/wGcOjn7CppTa2HXxPTsQRQ8U0MSVRcwRWyGMrizQWuaUqVGw==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-18.3.0.tgz", + "integrity": "sha512-FkxrTUUcWB4CVN2yzgsfF/YHD6WgYHduaa7VmokCy5TLCgl5UNJkwortxcedrxSavQ8Qfa4Ir4JxcbIYiBsyLg==", "requires": { - "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, diff --git a/microservices/payment-stripe/package.json b/microservices/payment-stripe/package.json index 76750a8d..70c066de 100644 --- a/microservices/payment-stripe/package.json +++ b/microservices/payment-stripe/package.json @@ -44,7 +44,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "class-validator-jsonschema": "^5.0.0", - "stripe": "^11.16.0", + "stripe": "^18.3.0", "typeorm": "0.2.41", "uuidv4": "^6.2.13", "lodash": "^4.17.21" diff --git a/microservices/payment-stripe/src/constants/index.ts b/microservices/payment-stripe/src/constants/index.ts index d14835d2..b7eb773c 100644 --- a/microservices/payment-stripe/src/constants/index.ts +++ b/microservices/payment-stripe/src/constants/index.ts @@ -8,7 +8,7 @@ const msNameDefault = 'payment-stripe'; const constants = { ...GetConstants({ msNameDefault, version, isBuild, packageName: name, withDb: true }), MS_API_KEY: process.env.MS_API_KEY ?? '', - MS_CONFIG: JSON.parse(process.env.MS_CONFIG ?? '{"apiVersion": "2022-11-15"}'), + MS_CONFIG: JSON.parse(process.env.MS_CONFIG ?? '{"apiVersion": "2025-06-30.basil"}'), MS_PAYMENT_METHODS: JSON.parse(process.env.MS_PAYMENT_METHODS ?? '["bancontact", "card"]'), MS_WEBHOOK_KEYS: JSON.parse(process.env.MS_WEBHOOK_KEYS ?? '{}'), MS_PAYOUT_COEFF: Number(process.env.MS_PAYOUT_COEFF) ?? 0.3, diff --git a/microservices/payment-stripe/src/entities/price.ts b/microservices/payment-stripe/src/entities/price.ts index abdeb199..85cc6c42 100644 --- a/microservices/payment-stripe/src/entities/price.ts +++ b/microservices/payment-stripe/src/entities/price.ts @@ -47,6 +47,16 @@ class Price { @Column({ type: 'jsonb', default: null }) metadata: Record | null; + @JSONSchema({ + description: 'Custom unit amount configuration for Pay What You Want pricing', + }) + @Column({ type: 'jsonb', default: null }) + customUnitAmount: { + preset?: number; + minimum?: number; + maximum?: number; + } | null; + @IsTypeormDate() @CreateDateColumn() createdAt: Date; diff --git a/microservices/payment-stripe/src/entities/transaction.ts b/microservices/payment-stripe/src/entities/transaction.ts index 19d044cc..84a99b73 100644 --- a/microservices/payment-stripe/src/entities/transaction.ts +++ b/microservices/payment-stripe/src/entities/transaction.ts @@ -215,15 +215,6 @@ class Transaction { @IsNumber() amount: number; - @JSONSchema({ - description: 'Custom amount paid by user for PWYW transactions (in cents)', - }) - @Column({ type: 'int', default: null }) - @IsUndefinable() - @IsNullable() - @IsNumber() - customAmount: number | null; - @JSONSchema({ description: `Sales tax or other, that should be paid to the government by tax collector. Tax included in the payment intent amount and storing as collected fees amount.`, diff --git a/microservices/payment-stripe/src/methods/stripe/create-checkout.ts b/microservices/payment-stripe/src/methods/stripe/create-checkout.ts index a4dce7f7..3e7e0064 100644 --- a/microservices/payment-stripe/src/methods/stripe/create-checkout.ts +++ b/microservices/payment-stripe/src/methods/stripe/create-checkout.ts @@ -1,5 +1,5 @@ import { Endpoint, IsNullable, IsUndefinable } from '@lomray/microservice-helpers'; -import { IsBoolean, IsNumber, IsString, Length, Min } from 'class-validator'; +import { IsBoolean, IsString, Length } from 'class-validator'; import Stripe from '@services/payment-gateway/stripe'; class CreateCheckoutInput { @@ -19,11 +19,6 @@ class CreateCheckoutInput { @IsBoolean() @IsUndefinable() isAllowPromoCode?: boolean; - - @IsNumber() - @Min(0) - @IsUndefinable() - customAmount?: number; } class CreateCheckoutOutput { @@ -41,7 +36,7 @@ const createCheckout = Endpoint.custom( output: CreateCheckoutOutput, description: 'Setup intent and return client secret key', }), - async ({ priceId, successUrl, cancelUrl, userId, isAllowPromoCode, customAmount }) => { + async ({ priceId, successUrl, cancelUrl, userId, isAllowPromoCode }) => { const service = await Stripe.init(); return { @@ -51,7 +46,6 @@ const createCheckout = Endpoint.custom( successUrl, cancelUrl, isAllowPromoCode, - customAmount, }), }; }, diff --git a/microservices/payment-stripe/src/services/payment-gateway/abstract.ts b/microservices/payment-stripe/src/services/payment-gateway/abstract.ts index ca1090af..5b444631 100644 --- a/microservices/payment-stripe/src/services/payment-gateway/abstract.ts +++ b/microservices/payment-stripe/src/services/payment-gateway/abstract.ts @@ -40,8 +40,14 @@ export interface IPriceParams { productId: string; userId: string; currency: string; - unitAmount: number; + unitAmount?: number; // Optional when customUnitAmount is provided metadata?: Record; + customUnitAmount?: { + enabled: boolean; + preset?: number; + minimum?: number; + maximum?: number; + }; } export interface ITransactionParams { @@ -57,7 +63,6 @@ export interface ITransactionParams { tax?: number; fee?: number; params?: ITransactionEntityParams; - customAmount?: number; } export interface IProductParams { @@ -245,7 +250,7 @@ abstract class Abstract { * Create new price */ public async createPrice(params: IPriceParams, priceId: string = uuid()): Promise { - const { productId, currency, unitAmount, userId, metadata } = params; + const { productId, currency, unitAmount = 0, userId, metadata, customUnitAmount } = params; const price = this.priceRepository.create({ priceId, @@ -254,6 +259,7 @@ abstract class Abstract { currency, unitAmount, ...(metadata ? { metadata } : {}), + ...(customUnitAmount ? { customUnitAmount } : {}), }); await this.priceRepository.save(price); diff --git a/microservices/payment-stripe/src/services/payment-gateway/stripe.ts b/microservices/payment-stripe/src/services/payment-gateway/stripe.ts index 78a89062..065cf20f 100644 --- a/microservices/payment-stripe/src/services/payment-gateway/stripe.ts +++ b/microservices/payment-stripe/src/services/payment-gateway/stripe.ts @@ -99,7 +99,6 @@ interface ICheckoutParams { successUrl: string; cancelUrl: string; isAllowPromoCode?: boolean; - customAmount?: number; } interface ICheckoutEvent { @@ -128,9 +127,7 @@ interface ICheckoutCart { clientSecret: string | null; } -type TCardData = - | StripeSdk.PaymentMethodCreateParams.Card1 - | StripeSdk.PaymentMethodCreateParams.Card2; +type TCardData = StripeSdk.PaymentMethodCreateParams.Card; interface IStripeCouponParams extends ICouponParams { currency?: TCurrency; @@ -373,15 +370,18 @@ class Stripe extends Abstract { * Create new price */ public async createPrice(params: IPriceParams): Promise { - const { currency, unitAmount, productId, userId, metadata } = params; + const { currency, unitAmount, productId, userId, metadata, customUnitAmount } = params; + /* eslint-disable camelcase */ const { id }: StripeSdk.Price = await this.sdk.prices.create({ currency, product: productId, - // eslint-disable-next-line camelcase - unit_amount: unitAmount, ...(metadata ? { metadata } : {}), + ...(customUnitAmount + ? { custom_unit_amount: customUnitAmount } + : { unit_amount: unitAmount }), }); + /* eslint-enable camelcase */ return super.createPrice( { @@ -390,35 +390,17 @@ class Stripe extends Abstract { currency, unitAmount, metadata, + customUnitAmount, }, id, ); } - /** - * Validate PWYW custom amount against minimum price - */ - private validatePWYWAmount(customAmount: number, price: Price): void { - if (price.metadata?.isPWYW === 'true') { - const minimumPrice = price.metadata?.minimumPrice ? Number(price.metadata.minimumPrice) : 0; - - if (customAmount < minimumPrice) { - Log.error( - `Custom amount ${customAmount} is below minimum price ${minimumPrice} for PWYW show`, - ); - throw new BaseException({ - status: 400, - message: `Amount must be at least $${(minimumPrice / 100).toFixed(2)}`, - }); - } - } - } - /** * Create checkout session and return url to redirect user for payment */ public async createCheckout(params: ICheckoutParams): Promise { - const { priceId, userId, successUrl, cancelUrl, isAllowPromoCode, customAmount } = params; + const { priceId, userId, successUrl, cancelUrl, isAllowPromoCode } = params; const { customerId } = await super.getCustomer(userId); const price = await this.priceRepository.findOne({ priceId }, { relations: ['product'] }); @@ -429,41 +411,20 @@ class Stripe extends Abstract { return null; } - // Validate PWYW minimum price if custom amount is provided - if (customAmount) { - this.validatePWYWAmount(customAmount, price); - } - /* eslint-disable camelcase */ const sessionParams: StripeSdk.Checkout.SessionCreateParams = { mode: 'payment', customer: customerId, success_url: successUrl, cancel_url: cancelUrl, - allow_promotion_codes: isAllowPromoCode && !customAmount, // No promo codes for PWYW - }; - - if (customAmount) { - // PWYW mode - create custom line item with user-defined amount - sessionParams.line_items = [ - { - price_data: { - currency: price.currency, - product: price.productId, - unit_amount: customAmount, - }, - quantity: 1, - }, - ]; - } else { - // Fixed price mode - sessionParams.line_items = [ + allow_promotion_codes: isAllowPromoCode, + line_items: [ { price: priceId, quantity: 1, }, - ]; - } + ], + }; const { id, url } = await this.sdk.checkout.sessions.create(sessionParams); /* eslint-enable camelcase */ @@ -471,12 +432,11 @@ class Stripe extends Abstract { await this.createTransaction( { type: TransactionType.CREDIT, - amount: customAmount ? customAmount : price.unitAmount, + amount: price.unitAmount, userId, productId: price.productId, entityId: price.product.entityId, status: TransactionStatus.INITIAL, - customAmount: customAmount ? customAmount : undefined, // Store custom amount }, id, ); @@ -1789,38 +1749,38 @@ class Stripe extends Abstract { * Account events */ case 'account.updated': { - const handlers = { + const handlers: Record Promise> = { connect: () => webhookHandlers.account.handleAccountUpdated(event), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } case 'account.external_account.created': { - const handlers = { + const handlers: Record Promise> = { connect: () => webhookHandlers.externalAccount.handleExternalAccountCreated(event), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } case 'account.external_account.updated': { - const handlers = { + const handlers: Record Promise> = { connect: () => webhookHandlers.externalAccount.handleExternalAccountUpdated(event), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } case 'account.external_account.deleted': { - const handlers = { + const handlers: Record Promise> = { connect: () => webhookHandlers.externalAccount.handleExternalAccountDeleted(event), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } @@ -1828,30 +1788,30 @@ class Stripe extends Abstract { * Payment method events */ case 'setup_intent.succeeded': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.setupIntent.handleSetupIntentSucceed(event, this.sdk), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } case 'payment_method.updated': case 'payment_method.automatically_updated': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.paymentMethod.handlePaymentMethodUpdated(event), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } case 'payment_method.detached': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.paymentMethod.handlePaymentMethodDetached(event), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } @@ -1859,11 +1819,11 @@ class Stripe extends Abstract { * Transfer events */ case 'transfer.reversed': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.transfer.transferReversed(event), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } @@ -1873,21 +1833,21 @@ class Stripe extends Abstract { case 'payment_intent.processing': case 'payment_intent.succeeded': case 'payment_intent.canceled': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.paymentIntent.handlePaymentIntent(event, this.sdk), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } case 'payment_intent.payment_failed': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.paymentIntent.handlePaymentIntentPaymentFailed(event, this.sdk), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } @@ -1895,21 +1855,21 @@ class Stripe extends Abstract { * Application fee events */ case 'application_fee.refund.updated': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.applicationFee.handleApplicationFeeRefundUpdated(event, this.sdk), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } case 'application_fee.refunded': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.applicationFee.handleApplicationFeeRefunded(event), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } @@ -1917,11 +1877,11 @@ class Stripe extends Abstract { * Refund events */ case 'charge.refund.updated': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.charge.handleRefundUpdated(event, this.manager, this.sdk), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } @@ -1929,31 +1889,31 @@ class Stripe extends Abstract { * Charge events */ case 'charge.refunded': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.charge.handleChargeRefunded(event, this.manager, this.sdk), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } case 'charge.dispute.created': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.charge.handleChargeDisputeCreated(event, this.manager), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } case 'charge.dispute.updated': case 'charge.dispute.closed': case 'charge.dispute.funds_reinstated': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.charge.handleChargeDisputeUpdated(event, this.manager), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } @@ -1961,11 +1921,11 @@ class Stripe extends Abstract { * Customer events */ case 'customer.updated': { - const handlers = { + const handlers: Record Promise> = { account: () => webhookHandlers.customer.handleCustomerUpdated(event, this.manager), }; - await handlers?.[webhookType](); + await handlers[webhookType]?.(); break; } @@ -1978,12 +1938,12 @@ class Stripe extends Abstract { case 'payout.canceled': case 'payout.reconciliation_completed': case 'payout.paid': { - const handlers = { + const handlers: Record Promise> = { // Handle payout of connected account connect: () => webhookHandlers.payout.handlePayoutOccur(event), }; - await handlers?.[webhookType]?.(); + await handlers[webhookType]?.(); break; } }