Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add sync customer command and drop subscription customer constraint #9131

Merged
merged 3 commits into from
Dec 19, 2024

Conversation

anamarn
Copy link
Contributor

@anamarn anamarn commented Dec 18, 2024

TLDR:
Solves (https://github.com/twentyhq/private-issues/issues/212)
Add command to sync customer data from stripe to BillingCustomerTable for all active workspaces. Drop foreign key contraint on billingCustomer in BillingSubscription (in order to not break the DB).

In order to test:

  • Billing should be enabled
  • Have some workspaces that are active and whose id's are not mentioned in BillingCustomer (but the customer are present in stripe).

Run the command:
npx nx run twenty-server:command billing:sync-customer-data

Take into consideration
Due that all the previous subscriptions in Stripe have the workspaceId in their metadata, we use that information as source of true for the data sync

Things to do:

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

This PR adds a command to sync Stripe customer data with the BillingCustomer table and modifies database constraints to facilitate this synchronization.

  • Added BillingSyncCustomerDataCommand in /billing/commands/billing-sync-customer-data.command.ts to sync Stripe customer data for active workspaces
  • Removed foreign key constraint between billingSubscription and billingCustomer tables via migration 1734532875877-dropSubscriptionCustomerConstraint.ts
  • Added getCustomerData() in StripeService lacks error handling for empty subscription results and array bounds checking
  • Potential data integrity risk from removing foreign key constraints without compensating application-level checks
  • Command assumes Stripe API availability and lacks comprehensive error recovery strategy

5 file(s) reviewed, 6 comment(s)
Edit PR Review Bot Settings | Greptile


public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."billingSubscription" DROP CONSTRAINT "FK_9120b7586c3471463480b58d20a"`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: dropping this constraint removes referential integrity - ensure application code properly handles subscription cleanup when customers are deleted

Comment on lines 82 to 86
{
nullable: false,
onDelete: 'CASCADE',
createForeignKeyConstraints: false,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: disabling foreign key constraints removes database-level data integrity checks - ensure application code properly validates customer existence before subscription operations

Comment on lines 72 to 79
if (!options.dryRun && !billingCustomer) {
const customerData =
await this.stripeService.getCustomerData(workspaceId);

await this.billingCustomerRepository.upsert(customerData, {
conflictPaths: ['workspaceId'],
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: No validation that customerData exists or is valid before upserting. Could create empty/invalid records if Stripe call fails silently.

Comment on lines +81 to +85
if (options.verbose) {
this.logger.log(
chalk.yellow(`Added ${workspaceId} to billingCustomer table`),
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Log message indicates addition but this runs even for failed operations. Move inside the if block after successful upsert.

Comment on lines +45 to +51
} catch (error) {
this.logger.log(
chalk.red(
`Running command on workspace ${workspaceId} failed with error: ${error}, ${error.stack}`,
),
);
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Error is logged but not tracked/reported. Consider adding error metrics or alerting for failed syncs.

query: `metadata['workspaceId']:'${workspaceId}'`,
limit: 1,
});
const stripeCustomerId = String(subscription.data[0].customer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: direct array access without length check could throw error - validate subscription.data.length > 0 first


public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."billingSubscription" DROP CONSTRAINT "FK_9120b7586c3471463480b58d20a"`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be removed from the original migration, otherwise we will apply it first before removing it and end up in the same issue while applying it

Copy link
Member

@charlesBochet charlesBochet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you, I've left an important comment :)

@charlesBochet charlesBochet merged commit 028e5cd into main Dec 19, 2024
21 of 22 checks passed
@charlesBochet charlesBochet deleted the feat/add-billing-sync-customer-command branch December 19, 2024 10:30
Copy link

Fails
🚫

node failed.

Log

�[31mError: �[39m RequestError [HttpError]: You have exceeded a secondary rate limit. Please wait a few minutes before you try again. If you reach out to GitHub Support for help, please include the request ID 7007:2DC47C:219D9BB:429058A:6763F5E0.
    at /home/runner/work/twenty/twenty/node_modules/�[4m@octokit�[24m/request/dist-node/index.js:86:21
�[90m    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)�[39m {
  status: �[33m403�[39m,
  response: {
    url: �[32m'https://api.github.com/search/issues?q=is%3Apr%20author%3Aanamarn%20is%3Aclosed%20repo%3Atwentyhq%2Ftwenty&per_page=2&page=1'�[39m,
    status: �[33m403�[39m,
    headers: {
      �[32m'access-control-allow-origin'�[39m: �[32m'*'�[39m,
      �[32m'access-control-expose-headers'�[39m: �[32m'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'�[39m,
      connection: �[32m'close'�[39m,
      �[32m'content-encoding'�[39m: �[32m'gzip'�[39m,
      �[32m'content-security-policy'�[39m: �[32m"default-src 'none'"�[39m,
      �[32m'content-type'�[39m: �[32m'application/json; charset=utf-8'�[39m,
      date: �[32m'Thu, 19 Dec 2024 10:30:56 GMT'�[39m,
      �[32m'referrer-policy'�[39m: �[32m'origin-when-cross-origin, strict-origin-when-cross-origin'�[39m,
      server: �[32m'github.com'�[39m,
      �[32m'strict-transport-security'�[39m: �[32m'max-age=31536000; includeSubdomains; preload'�[39m,
      �[32m'transfer-encoding'�[39m: �[32m'chunked'�[39m,
      vary: �[32m'Accept-Encoding, Accept, X-Requested-With'�[39m,
      �[32m'x-content-type-options'�[39m: �[32m'nosniff'�[39m,
      �[32m'x-frame-options'�[39m: �[32m'deny'�[39m,
      �[32m'x-github-api-version-selected'�[39m: �[32m'2022-11-28'�[39m,
      �[32m'x-github-media-type'�[39m: �[32m'github.v3; format=json'�[39m,
      �[32m'x-github-request-id'�[39m: �[32m'7007:2DC47C:219D9BB:429058A:6763F5E0'�[39m,
      �[32m'x-ratelimit-limit'�[39m: �[32m'30'�[39m,
      �[32m'x-ratelimit-remaining'�[39m: �[32m'30'�[39m,
      �[32m'x-ratelimit-reset'�[39m: �[32m'1734604316'�[39m,
      �[32m'x-ratelimit-resource'�[39m: �[32m'search'�[39m,
      �[32m'x-ratelimit-used'�[39m: �[32m'1'�[39m,
      �[32m'x-xss-protection'�[39m: �[32m'0'�[39m
    },
    data: {
      documentation_url: �[32m'https://docs.github.com/free-pro-team@latest/rest/overview/rate-limits-for-the-rest-api#about-secondary-rate-limits'�[39m,
      message: �[32m'You have exceeded a secondary rate limit. Please wait a few minutes before you try again. If you reach out to GitHub Support for help, please include the request ID 7007:2DC47C:219D9BB:429058A:6763F5E0.'�[39m
    }
  },
  request: {
    method: �[32m'GET'�[39m,
    url: �[32m'https://api.github.com/search/issues?q=is%3Apr%20author%3Aanamarn%20is%3Aclosed%20repo%3Atwentyhq%2Ftwenty&per_page=2&page=1'�[39m,
    headers: {
      accept: �[32m'application/vnd.github.v3+json'�[39m,
      �[32m'user-agent'�[39m: �[32m'octokit-rest.js/18.12.0 octokit-core.js/3.6.0 Node.js/18.20.5 (linux; x64)'�[39m,
      authorization: �[32m'token [REDACTED]'�[39m
    },
    request: { hook: �[36m[Function: bound bound register]�[39m }
  }
}
danger-results://tmp/danger-results-0bc4d402.json

Generated by 🚫 dangerJS against 5b1baa6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants