Skip to content

Clean up Stripe DB and webhooks on uninstall#2367

Open
lkostrowski wants to merge 2 commits into
mainfrom
lkostrowski/stripe-app-deleted-webhook
Open

Clean up Stripe DB and webhooks on uninstall#2367
lkostrowski wants to merge 2 commits into
mainfrom
lkostrowski/stripe-app-deleted-webhook

Conversation

@lkostrowski

Copy link
Copy Markdown
Member
  1. Reuse existing abstraction to clean up APL
  2. Extend data access layer (repositories) to be able to prune data
  3. Prune data on uninstall
  4. Clean up webhooks

Copilot AI review requested due to automatic review settings May 26, 2026 07:38
@lkostrowski lkostrowski requested a review from a team as a code owner May 26, 2026 07:38
@lkostrowski lkostrowski requested a review from stmpn May 26, 2026 07:38
@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
saleor-app-avatax Ready Ready Preview, Comment Jun 11, 2026 11:05am
saleor-app-cms Ready Ready Preview, Comment Jun 11, 2026 11:05am
saleor-app-klaviyo Ready Ready Preview, Comment Jun 11, 2026 11:05am
saleor-app-payment-np-atobarai Ready Ready Preview, Comment Jun 11, 2026 11:05am
saleor-app-payment-stripe Ready Ready Preview, Comment Jun 11, 2026 11:05am
saleor-app-products-feed Ready Ready Preview, Comment Jun 11, 2026 11:05am
saleor-app-search Ready Ready Preview, Comment Jun 11, 2026 11:05am
saleor-app-segment Ready Ready Preview, Comment Jun 11, 2026 11:05am
saleor-app-smtp Ready Ready Preview, Comment Jun 11, 2026 11:05am

Request Review

@changeset-bot

changeset-bot Bot commented May 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 14ff8ef

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov

codecov Bot commented May 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.43988% with 169 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.07%. Comparing base (03081f4) to head (14ff8ef).
⚠️ Report is 17 commits behind head on main.

Files with missing lines Patch % Lines
.../repositories/dynamodb/dynamodb-app-config-repo.ts 2.80% 104 Missing ⚠️
...ies/dynamodb/dynamodb-transaction-recorder-repo.ts 6.97% 40 Missing ⚠️
...app/api/webhooks/app-deleted/webhook-definition.ts 64.70% 12 Missing ⚠️
...s/stripe/src/app/api/webhooks/app-deleted/route.ts 0.00% 7 Missing and 1 partial ⚠️
...rc/modules/app-uninstall/wipe-app-data-use-case.ts 97.77% 3 Missing ⚠️
...sitories/dynamodb/recorded-transaction-db-model.ts 50.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2367      +/-   ##
==========================================
+ Coverage   38.01%   38.07%   +0.06%     
==========================================
  Files        1048     1051       +3     
  Lines       67139    67479     +340     
  Branches     3580     3600      +20     
==========================================
+ Hits        25521    25694     +173     
- Misses      41228    41395     +167     
  Partials      390      390              
Flag Coverage Δ
avatax 57.57% <ø> (ø)
cms 20.35% <ø> (ø)
domain 100.00% <ø> (ø)
dynamo-config-repository 79.29% <ø> (ø)
errors 92.00% <ø> (ø)
logger 28.81% <ø> (ø)
np-atobarai 72.66% <ø> (ø)
products-feed 6.01% <ø> (ø)
search 32.31% <ø> (ø)
segment 33.65% <ø> (ø)
shared 56.07% <ø> (ø)
smtp 36.32% <ø> (ø)
stripe 69.96% <50.43%> (-0.94%) ⬇️
webhook-utils 21.35% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an APP_DELETED webhook to the Stripe app to perform best-effort uninstall cleanup: removing Stripe-side webhooks and pruning app-owned DynamoDB data (configs, channel mappings, recorded transactions) in addition to APL pruning.

Changes:

  • Introduce WipeAppDataUseCase and wire it into a new APP_DELETED webhook route, and register it in the app manifest.
  • Extend the DynamoDB repositories to support “remove all for app” operations for configs/mappings/transactions.
  • Add unit + integration tests covering uninstall cleanup and new repository pruning methods.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
apps/stripe/src/modules/transactions-recording/repositories/transaction-recorder-repo.ts Extends repo contract with removeAllForApp and a new error type.
apps/stripe/src/modules/transactions-recording/repositories/dynamodb/recorded-transaction-db-model.ts Adds an SK prefix helper to query all transaction items in a partition.
apps/stripe/src/modules/transactions-recording/repositories/dynamodb/dynamodb-transaction-recorder-repo.ts Implements removeAllForApp via query + delete.
apps/stripe/src/modules/app-uninstall/wipe-app-data-use-case.ts New use case orchestrating Stripe webhook deletion + DynamoDB data pruning on uninstall.
apps/stripe/src/modules/app-uninstall/wipe-app-data-use-case.test.ts Unit tests for uninstall cleanup behavior and error tolerance.
apps/stripe/src/modules/app-config/repositories/dynamodb/dynamodb-app-config-repo.ts Adds getAllConfigs, removeAllConfigs, removeAllChannelMappings.
apps/stripe/src/modules/app-config/repositories/app-config-repo.ts Extends AppConfigRepo interface with bulk read/delete operations.
apps/stripe/src/app/api/webhooks/app-deleted/webhook-definition.ts New APP_DELETED webhook definition wiring uninstall cleanup + APL handling.
apps/stripe/src/app/api/webhooks/app-deleted/route.ts New App Router route for APP_DELETED webhook.
apps/stripe/src/app/api/manifest/route.ts Registers APP_DELETED webhook in the manifest.
apps/stripe/src/app/api/manifest/route.test.ts Updates manifest test expectations to include APP_DELETED webhook.
apps/stripe/src/tests/mocks/mocked-transaction-recorder.ts Updates mock repo with removeAllForApp.
apps/stripe/src/tests/mocks/app-config-repo.ts Updates mock AppConfigRepo with new bulk methods.
apps/stripe/src/tests/integration/transaction-recorder/remove-all-transactions.integration.test.ts Integration coverage for transaction repo bulk deletion.
apps/stripe/src/tests/integration/config-repo/remove-all-app-data.integration.test.ts Integration coverage for config repo bulk read/delete operations.
.changeset/stripe-app-deleted-handler.md Changeset describing the new uninstall handler behavior.

Comment on lines +192 to +211
const query = this.entity.table
.build(QueryCommand)
.entities(this.entity)
.query({
range: {
beginsWith: DynamoDbRecordedTransaction.accessPattern.getSKforAllItems(),
},
partition: DynamoDbRecordedTransaction.accessPattern.getPK(accessPattern),
})
.options({ maxPages: Infinity });

try {
const result = await query.send();
const items = result.Items ?? [];

await Promise.all(
items.map((item) =>
this.entity.build(DeleteItemCommand).key({ PK: item.PK, SK: item.SK }).send(),
),
);
Comment on lines +427 to +446
const query = this.stripeConfigEntity.table
.build(QueryCommand)
.entities(this.stripeConfigEntity)
.query({
range: {
beginsWith: DynamoDbStripeConfig.accessPattern.getSKforAllItems(),
},
partition: DynamoDbStripeConfig.accessPattern.getPK(access),
})
.options({ maxPages: Infinity });

try {
const result = await query.send();
const items = result.Items ?? [];

await Promise.all(
items.map((item) =>
this.stripeConfigEntity.build(DeleteItemCommand).key({ PK: item.PK, SK: item.SK }).send(),
),
);
Comment on lines +468 to +490
const query = this.channelConfigMappingEntity.table
.build(QueryCommand)
.entities(this.channelConfigMappingEntity)
.query({
range: {
beginsWith: DynamoDbChannelConfigMapping.accessPattern.getSKforAllChannels(),
},
partition: DynamoDbChannelConfigMapping.accessPattern.getPK(access),
})
.options({ maxPages: Infinity });

try {
const result = await query.send();
const items = result.Items ?? [];

await Promise.all(
items.map((item) =>
this.channelConfigMappingEntity
.build(DeleteItemCommand)
.key({ PK: item.PK, SK: item.SK })
.send(),
),
);
Comment on lines +36 to +66
async execute({ saleorApiUrl, appId }: ExecuteArgs): Promise<void> {
const access = { saleorApiUrl, appId };

this.logger.info("Wiping app data for uninstall", { saleorApiUrl, appId });

const configsResult = await this.appConfigRepo.getAllConfigs(access);

const configs = configsResult.isOk() ? configsResult.value : [];

if (configsResult.isErr()) {
this.logger.error("Failed to fetch configs; Stripe webhooks will not be removed", {
error: configsResult.error,
saleorApiUrl,
appId,
});
}

this.logger.info("Fetched configs for uninstall cleanup", {
saleorApiUrl,
appId,
configCount: configs.length,
});

const [stripeWebhookOutcomes, removeConfigsResult, removeMappingsResult, removeTxResult] =
await Promise.all([
this.removeStripeWebhooks(configs),
this.appConfigRepo.removeAllConfigs(access),
this.appConfigRepo.removeAllChannelMappings(access),
this.transactionRecorderRepo.removeAllForApp(access),
]);

Comment on lines +135 to +168
const results = await Promise.allSettled(
configs.map(async (config) => {
if (!config.webhookId) {
this.logger.warn("StripeConfig missing webhookId; skipping Stripe webhook removal", {
configId: config.id,
});
skipped += 1;

return;
}

const result = await this.stripeWebhookManager.removeWebhook({
webhookId: config.webhookId,
restrictedKey: config.restrictedKey,
});

if (result.isErr()) {
this.logger.error("Failed to remove Stripe webhook", {
configId: config.id,
stripeWhId: config.webhookId,
error: result.error,
});
failed += 1;

return;
}

this.logger.info("Removed Stripe webhook", {
configId: config.id,
stripeWhId: config.webhookId,
});
deleted += 1;
}),
);
Comment on lines +2 to +5
"saleor-app-stripe": minor
---

Implemented APP_DELETED handler. On Saleor 3.23+, when the app is uninstalled, it now removes its DynamoDB data (Stripe configurations, channel-to-config mappings, recorded transactions) and best-effort deletes the webhooks it had registered on the Stripe side, in addition to pruning APL data.
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