Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion configs/config.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,23 @@
"admin": "allow"
}
},
"product": "payment-stripe.Product",
"metadata": {
"in": {
"admin": "allow"
},
"out": {
"admin": "allow"
}
}
},
"customUnitAmount": {
"in": {
"admin": "allow"
},
"out": {
"admin": "allow"
}
},
"product": "payment-stripe.Product"
},
"createdAt": "2023-05-26T13:01:39.186Z"
},
Expand Down Expand Up @@ -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"
},
Expand Down Expand Up @@ -1126,14 +1126,6 @@
"out": {
"admin": "allow"
}
},
"customAmount": {
"in": {
"admin": "allow"
},
"out": {
"admin": "allow"
}
}
},
"createdAt": "2023-05-26T13:01:39.186Z"
Expand Down Expand Up @@ -1845,6 +1837,14 @@
"out": {
"user": "allow"
}
},
"customUnitAmount": {
"in": {
"admin": "allow"
},
"out": {
"admin": "allow"
}
}
},
"createdAt": "2023-05-26T13:01:39.186Z"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export default class AddCustomUnitAmountToPrice1753736526000 implements MigrationInterface {
name = 'AddCustomUnitAmountToPrice1753736526000';

public async up(queryRunner: QueryRunner): Promise<void> {
// 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<void> {
// 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
`);
}
}
25 changes: 16 additions & 9 deletions microservices/payment-stripe/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion microservices/payment-stripe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion microservices/payment-stripe/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions microservices/payment-stripe/src/entities/price.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ class Price {
@Column({ type: 'jsonb', default: null })
metadata: Record<string, any> | 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;
Expand Down
9 changes: 0 additions & 9 deletions microservices/payment-stripe/src/entities/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.`,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -19,11 +19,6 @@ class CreateCheckoutInput {
@IsBoolean()
@IsUndefinable()
isAllowPromoCode?: boolean;

@IsNumber()
@Min(0)
@IsUndefinable()
customAmount?: number;
}

class CreateCheckoutOutput {
Expand All @@ -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 {
Expand All @@ -51,7 +46,6 @@ const createCheckout = Endpoint.custom(
successUrl,
cancelUrl,
isAllowPromoCode,
customAmount,
}),
};
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,14 @@ export interface IPriceParams {
productId: string;
userId: string;
currency: string;
unitAmount: number;
unitAmount?: number; // Optional when customUnitAmount is provided
metadata?: Record<string, string>;
customUnitAmount?: {
enabled: boolean;
preset?: number;
minimum?: number;
maximum?: number;
};
}

export interface ITransactionParams {
Expand All @@ -57,7 +63,6 @@ export interface ITransactionParams {
tax?: number;
fee?: number;
params?: ITransactionEntityParams;
customAmount?: number;
}

export interface IProductParams {
Expand Down Expand Up @@ -245,7 +250,7 @@ abstract class Abstract {
* Create new price
*/
public async createPrice(params: IPriceParams, priceId: string = uuid()): Promise<Price> {
const { productId, currency, unitAmount, userId, metadata } = params;
const { productId, currency, unitAmount = 0, userId, metadata, customUnitAmount } = params;

const price = this.priceRepository.create({
priceId,
Expand All @@ -254,6 +259,7 @@ abstract class Abstract {
currency,
unitAmount,
...(metadata ? { metadata } : {}),
...(customUnitAmount ? { customUnitAmount } : {}),
});

await this.priceRepository.save(price);
Expand Down
Loading
Loading