Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
3b4eff9
Initial work on monetization
Villagers654 May 23, 2026
1a25a49
Merge branch 'develop' into monetization
Villagers654 May 23, 2026
ccca3b5
Clean up donation modal
Villagers654 May 23, 2026
5f5e805
Move project finance management into the project's settings
Villagers654 May 23, 2026
36ea303
nice skeleton for finance manager
Villagers654 May 23, 2026
b95af47
better uh... hitboxes
Villagers654 May 23, 2026
8823a03
allow status modal to fully take over the screen
Villagers654 May 23, 2026
da80998
Properly integrate stripe
Villagers654 May 23, 2026
e5ffbc2
Split finance controllers and services
Villagers654 May 23, 2026
3134052
Merge branch 'develop' into monetization
Villagers654 May 23, 2026
0d530ff
Merge branch 'develop' into monetization
Villagers654 May 23, 2026
90dfe42
Merge develop into monetization
Villagers654 Jun 11, 2026
a9e4e79
Merge develop into monetization
Villagers654 Jun 21, 2026
17b223a
Merge branch 'develop' into monetization
Villagers654 Jun 21, 2026
d1eb664
Merge branch 'main' into monetization
Villagers654 Sep 3, 2026
ba21860
Merge branch 'develop' into monetization
Villagers654 Sep 3, 2026
ef02ba5
Align finance access with admin permissions
Villagers654 Sep 3, 2026
e502a4e
Merge branch 'develop' into monetization
Villagers654 Sep 3, 2026
7333bcd
Merge develop into monetization
Villagers654 Sep 3, 2026
70bcaf6
Merge develop into monetization
Villagers654 Sep 4, 2026
5d7b033
Merge develop into monetization
Villagers654 Sep 8, 2026
cdfd601
Merge develop into monetization
Villagers654 Sep 22, 2026
4f1777b
Merge develop into monetization
Villagers654 Sep 22, 2026
6e8f430
Merge launcher path resolution fix into monetization
Villagers654 Sep 22, 2026
6a552ca
Merge latest develop changes into monetization
Villagers654 Sep 22, 2026
7cf4d78
Merge launcher validation fix into monetization
Villagers654 Sep 22, 2026
1e84e38
Merge latest validated develop changes into monetization
Villagers654 Sep 22, 2026
f7b69fa
Merge wardrobe test synchronization into monetization
Villagers654 Sep 22, 2026
dd47d60
Merge frontend caching and download fixes into monetization
Villagers654 Sep 22, 2026
64c3123
Protect creator balances and build verified payment settlement
Villagers654 Sep 30, 2026
fdef559
Keep push tests running when pull requests have conflicts
Villagers654 Sep 30, 2026
ca2ee33
Add recurring support, protected withdrawals and refund accounting
Villagers654 Sep 30, 2026
f0d001a
Bring monetization up to date with develop
Villagers654 Sep 30, 2026
c68902a
Preserve transfer authorization and pace transaction retries
Villagers654 Sep 30, 2026
29482fe
Expose the finance preview route and verify deployment
Villagers654 Sep 30, 2026
a7ac1f9
Restore protected hydration in the finance preview
Villagers654 Sep 30, 2026
d4bf205
Keep financial reports aligned with wallet balances
Villagers654 Sep 30, 2026
a5ea743
Polish support frequency and populated earnings states
Villagers654 Sep 30, 2026
175daf9
Make optional creator support compact and clear
Villagers654 Sep 30, 2026
d106df9
Preserve owner IDs and project launcher activity
Villagers654 Sep 30, 2026
18ac507
Stage ad revenue evidence without funding creator wallets
Villagers654 Sep 30, 2026
79a67d2
Add provisional ad review and payout readiness states
Villagers654 Sep 30, 2026
7523e8e
Align download controller tests with owner ID tracking
Villagers654 Sep 30, 2026
6b27a3e
Verify support terms and exercise payment pipelines
Villagers654 Sep 30, 2026
093f4c8
Reconcile uncertain payouts against verified transfers
Villagers654 Sep 30, 2026
ef3cc9c
Add audited transfer review controls
Villagers654 Sep 30, 2026
66dace4
Resolve disputes with audited financial evidence
Villagers654 Sep 30, 2026
606349d
Add explicit dispute review and evidence refresh
Villagers654 Sep 30, 2026
4141b3a
Preserve newer recurring support cancellation state
Villagers654 Sep 30, 2026
50af62e
Add payment setup checks and isolated Stripe sandbox tests
Villagers654 Sep 30, 2026
4e693b2
Record verified provider costs without allocating creator debt
Villagers654 Sep 30, 2026
339c00a
Verify initial recurring payments without clock simulation
Villagers654 Sep 30, 2026
558cc14
Stop sandbox checks when a provider assertion fails
Villagers654 Sep 30, 2026
9916d6e
Preserve support errors after session token refresh
Villagers654 Sep 30, 2026
c6d0e71
Recover billing popups and concurrent creator payouts
Villagers654 Oct 1, 2026
3d4054f
Keep recipient review consistent with concurrent confirmations
Villagers654 Oct 1, 2026
b9c2df1
Merge develop into monetization
Villagers654 Oct 2, 2026
7189241
Merge develop cache validation fixes into monetization
Villagers654 Oct 2, 2026
a4750db
Merge develop API diagnostics into monetization
Villagers654 Oct 2, 2026
837fdfa
Merge branch 'develop' into monetization
Villagers654 Oct 2, 2026
1d15824
Merge branch 'develop' into monetization
Villagers654 Oct 2, 2026
ac1678d
Merge branch 'develop' into monetization
Villagers654 Oct 2, 2026
83d0c36
Merge branch 'develop' into monetization
Villagers654 Oct 2, 2026
a1b112c
Merge branch 'develop' into monetization
Villagers654 Oct 2, 2026
39a419d
Merge tested secret bundle lifecycle safeguards
Villagers654 Oct 2, 2026
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
31 changes: 23 additions & 8 deletions .github/scripts/should-run-tests-workflow.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ event_name="${GITHUB_EVENT_NAME:-}"
repo="${GITHUB_REPOSITORY:-}"
repo_owner="${GITHUB_REPOSITORY_OWNER:-${repo%%/*}}"
ref_name="${GITHUB_REF_NAME:-}"
head_sha="${GITHUB_SHA:-}"

should_run=true
reason="This workflow run owns the work."
Expand All @@ -13,27 +14,41 @@ gh_available() {
command -v gh >/dev/null 2>&1 && [[ -n "${GH_TOKEN:-}" ]]
}

open_pr_count_for_branch() {
gh api --method GET "repos/$repo/pulls" \
open_pr_can_own_tests() {
local numbers number state mergeable pr_head
numbers="$(gh api --method GET "repos/$repo/pulls" \
-f state=open \
-f head="$repo_owner:$ref_name" \
--jq 'length'
--jq '.[].number')" || return 1
while IFS= read -r number; do
[[ -z "$number" ]] && continue
[[ "$number" =~ ^[0-9]+$ ]] || return 1
state="$(gh api "repos/$repo/pulls/$number" --jq '[.mergeable, .head.sha] | @tsv')" || return 1
IFS=$'\t' read -r mergeable pr_head <<< "$state"
if [[ "$mergeable" == "true" && "$pr_head" == "$head_sha" ]]; then
echo true
return 0
fi
done <<< "$numbers"
echo false
}

# PR runs always own their tests. A queued push may itself skip because a PR
# exists, so its presence cannot prove that the commit has test coverage.
# Conflicted PRs do not trigger pull_request workflows. Unknown mergeability
# or a different head must therefore retain the push run's test coverage.
if [[ "$event_name" == "push" ]]; then
if gh_available && [[ -n "$repo" && -n "$repo_owner" && -n "$ref_name" ]]; then
if open_pr_count="$(open_pr_count_for_branch)"; then
if [[ "$open_pr_count" =~ ^[0-9]+$ && "$open_pr_count" -gt 0 ]]; then
if gh_available && [[ -n "$repo" && -n "$repo_owner" && -n "$ref_name" && "$head_sha" =~ ^[[:xdigit:]]{40}$ ]]; then
if pr_can_own_tests="$(open_pr_can_own_tests)"; then
if [[ "$pr_can_own_tests" == "true" ]]; then
should_run=false
reason="Skipping push workflow because this branch has an open PR; the pull_request run owns this commit."
reason="Skipping push workflow because an open, mergeable PR has this exact head; the pull_request run owns this commit."
fi
else
echo "::warning::Could not check for open pull requests; running tests to avoid missing coverage."
fi
else
echo "::warning::GitHub CLI or token unavailable; running tests to avoid missing coverage."
echo "::warning::GitHub CLI, token, or commit context unavailable; running tests to avoid missing coverage."
fi
fi

Expand Down
41 changes: 37 additions & 4 deletions .github/tests/workflow-scripts.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function run(script, overrides = {}) {
GITHUB_REPOSITORY: 'Modtale/modtale',
GITHUB_REPOSITORY_OWNER: 'Modtale',
GITHUB_REF_NAME: 'audit',
GITHUB_SHA: 'a'.repeat(40),
GH_TOKEN: 'test-token',
...overrides,
},
Expand All @@ -42,14 +43,46 @@ test('PR creation and synchronization always retain their own test coverage', ()
}
});

test('push skips only when the GitHub API confirms an open PR', () => {
function mockPullRequestApi() {
const bin = path.join(directory, 'bin');
fs.mkdirSync(bin);
const gh = path.join(bin, 'gh');
fs.writeFileSync(gh, '#!/bin/sh\nprintf "1\\n"\n', { mode: 0o755 });
const env = { GITHUB_EVENT_NAME: 'push', PATH: `${bin}${path.delimiter}${process.env.PATH}` };
fs.writeFileSync(gh, `#!/bin/sh
case "$*" in
*"/pulls/"*)
[ "\${MOCK_PR_ERROR:-}" = "yes" ] && exit 1
printf '%s\\t%s\\n' "\${MOCK_MERGEABLE:-true}" "\${MOCK_HEAD:-$GITHUB_SHA}"
;;
*) printf '%s\\n' "\${MOCK_PR_NUMBERS-25}" ;;
esac
`, { mode: 0o755 });
return { GITHUB_EVENT_NAME: 'push', PATH: `${bin}${path.delimiter}${process.env.PATH}` };
}

test('push skips only when an open PR is positively mergeable at the same head', () => {
const env = mockPullRequestApi();
assert.match(run('should-run-tests-workflow.sh', env), /^should_run=false$/m);
fs.writeFileSync(gh, '#!/bin/sh\nexit 1\n', { mode: 0o755 });
});

test('conflicted, unknown, stale, and absent PRs keep push tests enabled', () => {
const env = mockPullRequestApi();
for (const scenario of [
{ MOCK_MERGEABLE: 'false' },
{ MOCK_MERGEABLE: 'null' },
{ MOCK_MERGEABLE: 'unexpected' },
{ MOCK_HEAD: 'b'.repeat(40) },
{ MOCK_PR_NUMBERS: '' },
{ MOCK_PR_NUMBERS: 'not-a-number' },
{ MOCK_PR_ERROR: 'yes' },
{ GITHUB_SHA: '' },
]) {
assert.match(run('should-run-tests-workflow.sh', { ...env, ...scenario }), /^should_run=true$/m);
}
});

test('PR-list API failure keeps push tests enabled', () => {
const env = mockPullRequestApi();
fs.writeFileSync(path.join(directory, 'bin', 'gh'), '#!/bin/sh\nexit 1\n', { mode: 0o755 });
assert.match(run('should-run-tests-workflow.sh', env), /^should_run=true$/m);
});

Expand Down
27 changes: 17 additions & 10 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Modtale CI/CD
name: Modtale CI/CD

on:
push:
Expand Down Expand Up @@ -105,7 +105,7 @@ jobs:
fi
echo "GIT_BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
echo "BRANCH_SLUG=$BRANCH_SLUG" >> $GITHUB_ENV

if [ "$BRANCH_NAME" = "main" ]; then
echo "ENV_TYPE=prod" >> $GITHUB_ENV
echo "BUILD_SERVICE_ACCOUNT=${{ vars.GCP_BUILD_SERVICE_ACCOUNT }}" >> $GITHUB_ENV
Expand All @@ -130,7 +130,7 @@ jobs:
echo "WARDEN_ENABLED=true" >> $GITHUB_ENV
echo "OAUTH_ENABLED=true" >> $GITHUB_ENV
echo "WARDEN_SECRET_NAME=WARDEN_API_KEY" >> $GITHUB_ENV

elif [ "$BRANCH_NAME" = "develop" ]; then
echo "ENV_TYPE=dev" >> $GITHUB_ENV
echo "BUILD_SERVICE_ACCOUNT=${{ vars.GCP_BUILD_SERVICE_ACCOUNT }}" >> $GITHUB_ENV
Expand All @@ -155,7 +155,7 @@ jobs:
echo "WARDEN_ENABLED=true" >> $GITHUB_ENV
echo "OAUTH_ENABLED=true" >> $GITHUB_ENV
echo "WARDEN_SECRET_NAME=WARDEN_API_KEY" >> $GITHUB_ENV

else
SLUG="$BRANCH_SLUG"
BRANCH_PREVIEW_SOURCE_R2_BUCKET_NAME="${{ vars.GCP_BRANCH_PREVIEW_SOURCE_R2_BUCKET_NAME }}"
Expand Down Expand Up @@ -511,7 +511,7 @@ jobs:
else
echo "Reusing the existing backend image for a configuration-only rollout."
fi

ARGS=(
"--image" "gcr.io/$PROJECT_ID/modtale-backend:${{ env.TAG }}"
"--region" "$REGION"
Expand Down Expand Up @@ -766,7 +766,8 @@ jobs:
: "${RUNTIME_SERVICE_ACCOUNT:?Set the GitHub environment runtime service account variable for this environment.}"

bash ../.github/scripts/build-container.sh frontend "$TAG" \
--build-arg "PUBLIC_API_URL=$API_URL" --build-arg "SSR_API_URL=$SSR_API_URL"
--build-arg "PUBLIC_API_URL=$API_URL" --build-arg "SSR_API_URL=$SSR_API_URL" \
--build-arg "PUBLIC_FINANCE_DEMO=${{ env.ENV_TYPE == 'preview' && 'true' || 'false' }}"

FRONTEND_ARGS=(
"--image" "gcr.io/$PROJECT_ID/modtale-frontend:$TAG"
Expand Down Expand Up @@ -838,6 +839,12 @@ jobs:
echo "FINAL_FRONTEND_URL=${{ env.FRONTEND_DOMAIN }}" >> $GITHUB_ENV
fi

- name: Verify synthetic finance preview route
if: env.ENV_TYPE == 'preview' && (steps.filter.outputs.frontend == 'true' || env.FRONTEND_EXISTS == 'false')
run: |
curl --fail --silent --show-error --retry 4 --retry-delay 3 "$FINAL_FRONTEND_URL/finance-preview" -o /tmp/finance-preview.html
grep -Fq '<title>Finance demo' /tmp/finance-preview.html

- name: Update Backend CORS and Self-Awareness
id: update_backend_self_awareness
# Only preview URLs are unknown during the initial backend deploy. Production
Expand All @@ -857,7 +864,7 @@ jobs:
else
PUBLIC_BACKEND_URL=$B_URL
fi

if [ "$MODTALE_SECRET_BUNDLES_ENABLED" = "true" ]; then
python3 .github/scripts/secret_bundle_ci.py deploy -- \
--update-env-vars "FRONTEND_URL=$FINAL_FRONTEND_URL,BACKEND_URL=$PUBLIC_BACKEND_URL" >/dev/null
Expand Down Expand Up @@ -953,21 +960,21 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Component | Status | Target Service | Service URL |" >> $GITHUB_STEP_SUMMARY
echo "|---|---|---|---|" >> $GITHUB_STEP_SUMMARY

if [ -n "$PUBLIC_API_URL" ]; then
echo "| **Backend API** | $BACKEND_STATUS | \`${{ env.BACKEND_SERVICE }}\` | [API Endpoint]($PUBLIC_API_URL) |" >> $GITHUB_STEP_SUMMARY
elif [ -n "$B_URL" ]; then
echo "| **Backend API** | $BACKEND_STATUS | \`${{ env.BACKEND_SERVICE }}\` | [API Endpoint]($B_URL/api/v1) |" >> $GITHUB_STEP_SUMMARY
else
echo "| **Backend API** | $BACKEND_STATUS | \`${{ env.BACKEND_SERVICE }}\` | *N/A* |" >> $GITHUB_STEP_SUMMARY
fi

if [ -n "$F_URL" ]; then
echo "| **Frontend App** | $FRONTEND_STATUS | \`${{ env.FRONTEND_SERVICE }}\` | [App URL]($F_URL) |" >> $GITHUB_STEP_SUMMARY
else
echo "| **Frontend App** | $FRONTEND_STATUS | \`${{ env.FRONTEND_SERVICE }}\` | *N/A* |" >> $GITHUB_STEP_SUMMARY
fi

echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "*View deployment details in [Google Cloud Console](https://console.cloud.google.com/run?project=${{ env.PROJECT_ID }}).* " >> $GITHUB_STEP_SUMMARY
Expand Down
114 changes: 114 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ jobs:
with:
node-version: 22.12.0

- name: Test sandbox runner safety guards
run: python3 -m unittest discover -s backend/scripts/tests -v

- name: Test workflow and fixture scripts
run: node --test .github/tests/*.test.mjs mock-db/tests/*.test.mjs

Expand Down Expand Up @@ -146,6 +149,117 @@ jobs:
- name: Run backend tests
run: ./gradlew test

finance-frontend-integration:
name: Finance Frontend Session and Browser Tests
needs: detect-changes
if: needs.detect-changes.outputs.backend == 'true' || needs.detect-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22.12.0
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Install isolated Chromium test browser
working-directory: frontend
env:
PLAYWRIGHT_BROWSERS_PATH: "0"
run: npx playwright install --with-deps chromium
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 26
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3
- name: Exercise frontend cookies and finance routes on loopback
working-directory: backend
run: ./gradlew financeFrontendIntegrationTest
- name: Upload session integration reports
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: finance-frontend-session-reports
path: |
backend/build/reports/tests/financeFrontendIntegrationTest
backend/build/test-results/financeFrontendIntegrationTest
backend/build/test-results/finance-frontend-ui.xml
backend/build/test-results/finance-browser-ui.xml
backend/build/finance-browser-artifacts
if-no-files-found: error

finance-transactions:
name: Finance Transaction Tests
needs: detect-changes
if: needs.detect-changes.outputs.backend == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
env:
FINANCE_TEST_MONGO_URI: mongodb://127.0.0.1:27018/?replicaSet=finance-test
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 26
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3
- name: Start isolated MongoDB replica set
run: |
docker run -d --name finance-mongo -p 127.0.0.1:27018:27018 mongo:8.0.32 --replSet finance-test --port 27018 --bind_ip_all
for i in {1..60}; do
if docker exec finance-mongo mongosh --port 27018 --quiet --eval 'db.adminCommand({ping: 1}).ok' >/dev/null 2>&1; then break; fi
sleep 1
done
docker exec finance-mongo mongosh --port 27018 --quiet --eval 'rs.initiate({_id: "finance-test", members: [{_id: 0, host: "127.0.0.1:27018"}]})'
for i in {1..60}; do
if docker exec finance-mongo mongosh --port 27018 --quiet --eval 'if (!db.hello().isWritablePrimary) quit(1)' >/dev/null 2>&1; then exit 0; fi
sleep 1
done
exit 1
- name: Verify ledger and withdrawal concurrency
working-directory: backend
run: |
./gradlew test --tests 'net.modtale.service.finance.*'
python3 - <<'PYTHON'
import pathlib, xml.etree.ElementTree as ET
reports = sorted(pathlib.Path('build/test-results/test').glob('TEST-net.modtale.service.finance.*.xml'))
assert reports, 'Finance test reports are missing'
total = 0
for report in reports:
suite = ET.parse(report).getroot()
counts = {key: int(suite.get(key, 0)) for key in ('tests', 'failures', 'errors', 'skipped')}
print(suite.get('name'), counts)
assert counts['tests'] > 0 and not any(counts[key] for key in ('failures', 'errors', 'skipped')), 'Finance CI must execute every test without skips'
total += counts['tests']
print(f'Finance replica validation: {total} tests executed, zero failures/errors/skips')
PYTHON
- name: Upload finance test reports
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: finance-test-reports
path: |
backend/build/reports/tests/test
backend/build/test-results/test
if-no-files-found: error
- name: Stop disposable database
if: always()
run: docker rm -f finance-mongo || true

launcher:
name: Launcher Tests
needs: detect-changes
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ mock-db/node_modules/

# Local campaign briefs, drafts, and review exports
/media/2026-09-update/

# Python test runner bytecode
__pycache__/
*.pyc
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ The Spring Boot backend relies on environment variables. You can set these in yo
| `R2_ENDPOINT` | Storage Endpoint URL | `https://<accountid>.r2.cloudflarestorage.com` |
| `R2_PUBLIC_DOMAIN` | Optional public storage URL | `https://cdn.example.test` |
| `WARDEN_ENABLED` | **Must be false locally** | `false` |
| `STRIPE_SECRET_KEY` | Stripe server-side API key (`sk_test_...`) | `sk_test_...` |
| `STRIPE_PUBLISHABLE_KEY` | Stripe client key (`pk_test_...`) | `pk_test_...` |
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret (`whsec_...`) | `whsec_...` |
| `STRIPE_MOCK_ENABLED` | Optional Stripe mock mode for local testing | `false` |
| `PRE_AUTH_SECRET` | Shared random MFA pre-auth signing secret; required for consistent token validation across multiple instances | Set through your deployment secret manager |
| `STATUS_DISCORD_WEBHOOK_URL` | Optional Discord webhook for the continually updated status mirror | `https://discord.com/api/webhooks/...` |
| `STATUS_CHECKER_ENABLED` | Opt into the legacy embedded backend checker | `false` |
Expand Down Expand Up @@ -150,6 +154,28 @@ To test social logins, provide each provider's client ID and secret (for example
client ID by default and requires `HYTALE_CLIENT_SECRET`; its production callback
is `https://api.modtale.net/login/oauth2/code/hytale`.

### Stripe Integration Setup (Step-by-Step)

1. Create a Stripe account and switch to **Test mode** in the Stripe dashboard.
2. Create/get API keys:
- `STRIPE_SECRET_KEY` from **Developers -> API keys** (`sk_test_...`)
- `STRIPE_PUBLISHABLE_KEY` from the same screen (`pk_test_...`)
3. Configure backend env vars before starting Spring Boot:
- `STRIPE_SECRET_KEY=...`
- `STRIPE_PUBLISHABLE_KEY=...`
- `STRIPE_MOCK_ENABLED=false`
4. Start backend (`./gradlew bootRun`) and frontend (`npm run dev`).
5. In Stripe dashboard, ensure your account can use Checkout + Connect in test mode.
6. Ensure `STRIPE_MOCK_ENABLED=false` in backend configuration for real integration testing.
7. Connect a creator Stripe account from the in-app Finance Manager (`Connect / Continue Stripe`) and then click `Refresh Stripe Status`.
8. Test one-time donations:
- Open a project with donations enabled.
- Start donation checkout and complete payment with Stripe test cards.
- Return to project page; donation is only counted after Stripe confirms paid/completed.
9. Optional local-only testing without real Stripe API:
- Set `STRIPE_MOCK_ENABLED=true` in backend configuration.
- Mock mode now simulates checkout links/status only and does **not** auto-mark donations as paid.

### 3. Run the Backend

Open a terminal in the `backend/` directory and use the Gradle wrapper.
Expand Down
Loading
Loading