Skip to content

Commit 9624b62

Browse files
committed
verify data integrity
1 parent 35ba3bc commit 9624b62

2 files changed

Lines changed: 104 additions & 14 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
version: 0.2
2+
3+
env:
4+
variables:
5+
NODE_ENV: test
6+
CI: "true"
7+
STACK_ENABLE_HARDCODED_PASSKEY_CHALLENGE_FOR_TESTING: "yes"
8+
STACK_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/stackframe"
9+
# disable read replica: replication is slow because we're restoring the whole prod db
10+
STACK_DATABASE_REPLICA_CONNECTION_STRING: ""
11+
STACK_DATABASE_REPLICATION_WAIT_STRATEGY: "none"
12+
STACK_EMAIL_BRANCHING_DISABLE_QUEUE_SENDING: "true"
13+
DB_USER: rds_iam_user
14+
DB_PORT: 5432
15+
DB_NAME: stackframe
16+
AWS_REGION: us-east-1
17+
18+
phases:
19+
install:
20+
runtime-versions:
21+
nodejs: 22
22+
commands:
23+
- npm install -g pnpm
24+
- yum -y install jq postgresql15
25+
26+
pre_build:
27+
commands:
28+
- export PGPASSWORD="$(aws rds generate-db-auth-token --region "$AWS_REGION" --hostname "$RDS_HOST" --port "$DB_PORT" --username "$DB_USER")"
29+
- echo "${PGPASSWORD:0:10}***"
30+
- echo "Using RDS host - $RDS_HOST"
31+
- psql "host=$RDS_HOST port=$DB_PORT dbname=$DB_NAME user=$DB_USER sslmode=require" -c "select now();"
32+
33+
- echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
34+
- docker compose -f docker/dependencies/docker.compose.yaml up --pull always -d
35+
- pnpm install --frozen-lockfile
36+
37+
- cp apps/backend/.env.development apps/backend/.env.test.local
38+
- cp apps/dashboard/.env.development apps/dashboard/.env.test.local
39+
- cp apps/e2e/.env.development apps/e2e/.env.test.local
40+
- cp docs/.env.development docs/.env.test.local
41+
- cp examples/cjs-test/.env.development examples/cjs-test/.env.test.local
42+
- cp examples/demo/.env.development examples/demo/.env.test.local
43+
- cp examples/docs-examples/.env.development examples/docs-examples/.env.test.local
44+
- cp examples/e-commerce/.env.development examples/e-commerce/.env.test.local
45+
- cp examples/middleware/.env.development examples/middleware/.env.test.local
46+
- cp examples/supabase/.env.development examples/supabase/.env.test.local
47+
- cp examples/convex/.env.development examples/convex/.env.test.local
48+
49+
build:
50+
commands:
51+
- export DUMP_DIR=/tmp/pg_dump
52+
- mkdir -p "$DUMP_DIR"
53+
- pg_dump --format=custom --no-owner --no-acl --host="$RDS_HOST" --port="$DB_PORT" --username="$DB_USER" --dbname="$DB_NAME" -Fd -j 4 -f "$DUMP_DIR"
54+
- du -sh "$DUMP_DIR"
55+
- export PGPASSWORD="PASSWORD-PLACEHOLDER--uqfEC1hmmv"
56+
- pnpm run wait-until-postgres-is-ready:pg_isready
57+
- pg_restore --no-owner --host=localhost --port=8128 --username="postgres" --dbname="stackframe" -j 4 "$DUMP_DIR"
58+
59+
- pnpm build
60+
- pnpx wait-on tcp:localhost:8129
61+
- pnpx wait-on tcp:localhost:8113
62+
- pnpx wait-on tcp:localhost:8134
63+
- pnpm run db:migrate
64+
- |
65+
psql "host=localhost port=8128 dbname=stackframe user=postgres" -c \
66+
"INSERT INTO \"ApiKeySet\" (\"projectId\", \"id\", \"description\", \"expiresAt\", \"superSecretAdminKey\", \"createdAt\", \"updatedAt\") \
67+
VALUES ('internal', '3142e763-b230-44b5-8636-aa62f7489c26', 'Internal API key set (dev override)', '2099-12-31T23:59:59Z', 'this-super-secret-admin-key-is-for-local-development-only', now(), now()) \
68+
ON CONFLICT (\"projectId\", \"id\") DO UPDATE SET \"superSecretAdminKey\" = EXCLUDED.\"superSecretAdminKey\", \"updatedAt\" = now();"
69+
- pnpm run start:backend --log-order=stream > /dev/null 2>&1 &
70+
- pnpx wait-on http://localhost:8102
71+
- pnpm run start:dashboard --log-order=stream &
72+
- pnpx wait-on http://localhost:8101
73+
- pnpm run start:mock-oauth-server --log-order=stream &
74+
- pnpx wait-on tcp:localhost:8105
75+
- sleep 10
76+
77+
- pnpm run verify-data-integrity --skip-neon

apps/backend/scripts/verify-data-integrity.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ type LedgerTransaction = {
3737
expirationTime: Date,
3838
};
3939

40-
type StripePayoutList = {
40+
type StripeBalanceTransactionList = {
4141
data: Array<{
4242
id: string,
4343
amount: number,
4444
currency: string,
45+
reporting_category?: string | null,
4546
}>,
4647
has_more: boolean,
4748
};
@@ -912,7 +913,7 @@ function sumMoneyTransfersUsdMinorUnits(transactions: Transaction[]): bigint {
912913
return total;
913914
}
914915

915-
async function fetchStripePayoutTotalUsdMinorUnits(options: {
916+
async function fetchStripeBalanceTransactionTotalUsdMinorUnits(options: {
916917
tenancy: Tenancy,
917918
stripeAccountId: string,
918919
}): Promise<bigint> {
@@ -922,18 +923,27 @@ async function fetchStripePayoutTotalUsdMinorUnits(options: {
922923
});
923924

924925
let total = 0n;
926+
const includeCategories = new Set([
927+
"charge",
928+
"refund",
929+
"dispute",
930+
"dispute_reversal",
931+
"partial_capture_reversal",
932+
]);
925933
let startingAfter: string | undefined = undefined;
926934

927935
do {
928-
const payouts: StripePayoutList = await stripe.payouts.list({
936+
const page: StripeBalanceTransactionList = await stripe.balanceTransactions.list({
929937
limit: 100,
930938
...(startingAfter ? { starting_after: startingAfter } : {}),
931939
});
932-
for (const payout of payouts.data) {
933-
if (payout.currency !== "usd") continue;
934-
total += BigInt(payout.amount);
940+
for (const balanceTransaction of page.data) {
941+
if (balanceTransaction.currency !== "usd") continue;
942+
if (!balanceTransaction.reporting_category) continue;
943+
if (!includeCategories.has(balanceTransaction.reporting_category)) continue;
944+
total += BigInt(balanceTransaction.amount);
935945
}
936-
startingAfter = payouts.has_more ? payouts.data.at(-1)?.id : undefined;
946+
startingAfter = page.has_more ? page.data.at(-1)?.id : undefined;
937947
} while (startingAfter);
938948

939949
return total;
@@ -944,21 +954,24 @@ async function verifyStripePayoutIntegrity(options: {
944954
tenancy: Tenancy,
945955
stripeAccountId: string,
946956
}) {
957+
if (options.projectId === '6fbbf22e-f4b2-4c6e-95a1-beab6fa41063') {
958+
// Dummy project doesn't have a real stripe account, so we skip the verification.
959+
return;
960+
}
947961
const transactions = await fetchAllTransactionsForProject(options.projectId);
948962
const moneyTransferTotalUsdMinor = sumMoneyTransfersUsdMinorUnits(transactions);
949-
const stripePayoutTotalUsdMinor = await fetchStripePayoutTotalUsdMinorUnits({
963+
const stripeBalanceTransactionTotalUsdMinor = await fetchStripeBalanceTransactionTotalUsdMinorUnits({
950964
tenancy: options.tenancy,
951965
stripeAccountId: options.stripeAccountId,
952966
});
953-
954-
if (moneyTransferTotalUsdMinor !== stripePayoutTotalUsdMinor) {
967+
if (moneyTransferTotalUsdMinor !== stripeBalanceTransactionTotalUsdMinor) {
955968
throw new StackAssertionError(deindent`
956-
Stripe payout mismatch for project ${options.projectId}.
957-
Money transfers total USD ${formatMinorUnitsToMoneyString(moneyTransferTotalUsdMinor, 2)} vs Stripe payouts USD ${formatMinorUnitsToMoneyString(stripePayoutTotalUsdMinor, 2)}.
969+
Stripe balance transaction mismatch for project ${options.projectId}.
970+
Money transfers total USD ${formatMinorUnitsToMoneyString(moneyTransferTotalUsdMinor, 2)} vs Stripe balance transactions USD ${formatMinorUnitsToMoneyString(stripeBalanceTransactionTotalUsdMinor, 2)}.
958971
`, {
959972
projectId: options.projectId,
960973
moneyTransferTotalUsdMinor: moneyTransferTotalUsdMinor.toString(),
961-
stripePayoutTotalUsdMinor: stripePayoutTotalUsdMinor.toString(),
974+
stripeBalanceTransactionTotalUsdMinor: stripeBalanceTransactionTotalUsdMinor.toString(),
962975
});
963976
}
964977
}
@@ -1008,7 +1021,7 @@ async function createPaymentsVerifier(options: {
10081021
.join("; ");
10091022
console.warn(`Skipping payments verification for project ${options.projectId} due to include-by-default conflicts (${conflictSummary}).`);
10101023
return {
1011-
verifyCustomerPayments: async () => {},
1024+
verifyCustomerPayments: async () => { },
10121025
customCustomerIds: new Set<string>(),
10131026
};
10141027
}

0 commit comments

Comments
 (0)