What the agent does
The agent clones the app into its own cloud sandbox and reads how it is put together before adding payments to it. In a Django project like wagtail/bakerydemo, requirements/base.txt asks for Django>=6.0,<6.1 and wagtail>=8.0,<8.1, .python-version says 3.12, and the code is split into apps under bakerydemo/ (base, blog, breads, locations, recipes, search) listed in INSTALLED_APPS in bakerydemo/settings/base.py. bakerydemo/urls.py mounts the Django admin, the Wagtail admin, search/, the API routes and finally wagtail_urls as a catch-all, so any new URL has to be added above that last line or Wagtail's page router will answer it. The repository's own AGENTS.md asks for tightly scoped changes and a pull request that says how to test them.
It adds a small bakerydemo/payments app instead of spreading payment code through the bread pages. views.py has a create_checkout_session view that calls stripe.checkout.Session.create with mode="payment", one line item from a price id in settings, and success_url and cancel_url built with request.build_absolute_uri, then redirects to the session's url. A success view renders a template that reads the session_id query parameter but never treats it as proof of payment. A webhook view, marked @csrf_exempt and accepting only POST, reads the raw request.body and the Stripe-Signature header, calls stripe.Webhook.construct_event with the endpoint secret, answers 400 on a bad signature, and on checkout.session.completed records the session id, amount, currency and customer email in a Payment model, keyed on the session id so a retried event does not create a second row.
Keys stay out of the code. STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET and STRIPE_PRICE_ID are read from the environment in settings/base.py, matching how the file already reads DATABASE_URL and the CSP settings, and .env.example gets the three names with no values. The agent adds stripe to requirements/base.txt with a version range like its neighbours, and makes the migration for Payment, because CI runs ./manage.py makemigrations --check --noinput and fails when one is missing.
Then it tests in two ways. Unit tests in bakerydemo/payments/tests/ post signed and unsigned payloads to the webhook, signing them with a test secret the same way Stripe does, and assert the 400, the recorded Payment and that a duplicate event records nothing new. With a Stripe test-mode key available, the agent runs ./manage.py runserver, starts stripe listen --forward-to localhost:8000/payments/webhook/, uses the signing secret it prints, and runs stripe trigger checkout.session.completed so a real event from Stripe reaches the handler. It runs CI's commands, ./manage.py check, the migration check and DJANGO_SETTINGS_MODULE=bakerydemo.settings.test ./manage.py test, plus make lint-server.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Add Stripe Checkout to this Django app for one fixed-price product. Read the settings, urls.py and AGENTS.md first and follow the project's patterns. Add an endpoint that creates a Checkout Session and redirects to it, a success page, and a webhook endpoint that verifies the Stripe-Signature header with stripe.Webhook.construct_event, returns 400 on a bad signature, and records checkout.session.completed once per session id in a model. Read STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET and STRIPE_PRICE_ID from the environment and add the names to .env.example. Add the stripe package and the migration. Write tests that sign webhook payloads with a test secret, and if a Stripe test-mode key is available, run the app with stripe listen forwarding to the webhook and stripe trigger checkout.session.completed. Run manage.py check, makemigrations --check, the test suite and the linters, and open a pull request with the test output and how to test it.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
- Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
- Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.
What comes back
What comes back is a pull request that adds bakerydemo/payments (models, views, URLs, a template, a migration and tests), one include in bakerydemo/urls.py above the Wagtail catch-all, the app in INSTALLED_APPS, three settings read from the environment, stripe in requirements/base.txt and the three names in .env.example. The description says how to test it: set the three variables, run stripe listen, buy with Stripe's test card, and see the Payment row. It quotes the test output and, when a test-mode key was available, the stripe trigger and stripe listen lines showing the event delivered and answered.
The checks are the repository's ci.yml: lint with ruff, curlylint and djhtml, then manage.py check, the migration check and the test suite. The session's transcript keeps every command from the sandbox and its output.
Before merging, a reviewer checks that the success page grants nothing on its own, since anyone can visit a success URL, and that only the webhook marks an order paid. They confirm the webhook reads the raw body before anything parses it, which signature checking depends on, and that no key appears in the diff.
Run it on every client that needs payments
An agency or a product that adds payments for many clients can send this request once per client repository, with that repository in resources and the client's product and currency in the prompt. Each run happens in its own sandbox, so one client's code and Stripe keys never meet another's.
With a webhook URL on the API key, Rush posts run.completed or run.failed with the execution_id and session_url when each turn ends. Your handler reads the session for the pull request and sends it to the client for review.
The use case this job follows, with its automation and trigger: Run an agent per customer task.
Why run it on Rush?
Payment code is the code you most want proven before it merges, and a webhook can only be proven by receiving a real signed event. In a Rush sandbox the agent runs the Django server, the Stripe CLI and the tests together on its own machine, so stripe trigger sends a real test-mode event to a real handler without anyone opening a tunnel from their laptop.
Every command and its output is kept in the transcript, including the signature failures and the duplicate-event test, and the repository's CI runs on the pull request. Test-mode keys reach the run as Project secrets, so they are never pasted into the prompt or committed.
Frequently asked questions
How does the agent get a Stripe test key?
Save STRIPE_SECRET_KEY as a Project secret with POST /o/{org}/secrets and start the session in that Project with POST /o/{org}/p/{project}/sessions; the agent reads it as an environment variable. Without a key the agent still writes and runs the signed-payload tests, and says the live Stripe CLI check was not run.
Why verify the webhook signature?
Anyone can POST to a public URL. stripe.Webhook.construct_event checks the Stripe-Signature header against your endpoint secret, so only events Stripe signed are recorded, and a bad one gets a 400.
What if Stripe sends the same event twice?
Stripe retries deliveries, so the handler records each Checkout Session once, keyed on its id. A test sends the same signed event twice and asserts a single row.
Does it work with a Django app that is not Wagtail?
Yes. The payment app, the settings and the tests are plain Django. Wagtail only changes where the URL include goes, which in bakerydemo is above the wagtail_urls catch-all.