Inspiration

I am a solo builder and a student club officer at National Yang Ming Chiao Tung University’s Entrepreneurship & Innovation Club (創創社) in Taiwan. For every workshop, officers still do the same work by hand. We rewrite one event across Instagram, Stories, LINE, email, and school forms. Then we collect registration fees, invoice sponsors, accept donations, pay speakers, track dues, issue refunds, and assemble a reimbursement report. The money trail usually lives in spreadsheets, group chats, and whoever happens to be holding the club PayPal login.

ClubFlow started as a way to keep a single event brief and generate the channel copy from it. That version, created on 7 November 2026, was submitted to Next Byte Hacks V5. It did not move money.

This PayPal AI Hackathon pass is the missing half. If the copy and the cash belong to the same event, they should live on the same desk, run through PayPal’s sandbox, and be operable by an officer agent that can propose actions — and must wait for a human Confirm before anything irreversible moves.

The in-app demo club and roster (Jonah, Mia, Priya, Sofia, Ken Lin, Lane Lab, and the example.com addresses) are fictional, so judges can click through without emailing real people. The problem the product is built for is not.

What it does

ClubFlow is an officer copilot for a student club.

One event brief still becomes ready-to-copy Instagram, Story, LINE, and email previews. Nothing is posted to those channels. The new Money desk then runs PayPal sandbox money for that same event, in USD, on one screen:

  • Left. A seeded workshop (title, time, room, fees) and the generated channel copy.
  • Middle. The attendee roster with payment status, plus the eight PayPal flows: registration checkout, sponsor invoices, donations, speaker payouts, monthly dues, one-click refunds, a capture webhook, and the reimbursement report.
  • Right. An officer agent and an on-screen outbox.

What each flow does:

  1. Registration. Orders v2 create and capture, with PayPal JavaScript SDK buttons when a sandbox Client ID is present. After capture, a receipt is written. A webhook can mark the roster paid if the capture arrives later.
  2. Sponsor invoice. Invoicing v2 create, then send. Sending waits for Confirm.
  3. Donation. Another Orders v2 capture. On success, ClubFlow writes a thank-you email and a PDF certificate.
  4. Speaker fee. Payouts API. Confirm is required. This is a custom agent tool, because the official toolkit does not cover payouts.
  5. Monthly dues. Catalog products, billing plans, and Subscriptions. The sandbox returns an approve link; mock mode activates the member immediately so the roster change is still visible.
  6. Refund. Payments v2 capture refund. The roster status becomes refunded, including when a refund webhook arrives later. Confirm is required.
  7. Reimbursement report. PayPal Transaction Search plus a local ledger of captures and webhooks, downloadable as CSV. If Transaction Search is empty or lagging, the report still includes the local ledger and says so.
  8. Officer agent. With an OPENAI_API_KEY, the agent uses an OpenAI-compatible model and @paypal/agent-toolkit/openai. Without a key, a built-in intent parser still runs the scripted officer phrases. Refunds, payouts, and sending an invoice always wait for Confirm. Webhook status is also a custom tool.

Email goes out through Resend or SMTP when configured. Otherwise the message is stored in the on-screen outbox. A failed email provider does not roll back a captured payment.

The app talks to the sandbox only. It refuses PAYPAL_ENV=live.

How we built it

The original ClubFlow is a Vite + TypeScript browser app that turns one brief into channel previews with a local template generator. This update adds an Express API beside it (server/), a JSON ledger (data/store.json, gitignored), and the Money desk UI (src/desk.ts).

npm run dev starts the API on port 8787 and the Vite app on port 43123, with /api proxied. npm run build && npm start serves the UI and API from one process, which is what the included Render config runs. Vercel config is also included; because serverless instances do not share a disk, Render is the recommended host when the roster and webhooks must stay together.

PayPal calls are made through a sandbox REST client (server/paypal.ts). The mapping is:

Desk action PayPal surface
Registration fee Orders v2 + PayPal JavaScript SDK buttons
Mark paid / refunded later Webhooks (PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.REFUNDED, CHECKOUT.ORDER.APPROVED, BILLING.SUBSCRIPTION.ACTIVATED), with signature verification when PAYPAL_WEBHOOK_ID is set
Sponsor invoice Invoicing v2 create and send
Donation Orders v2, then thank-you email + PDF certificate
Speaker fee Payouts API (POST /v1/payments/payouts)
Monthly dues Catalog products, Billing plans, Subscriptions
Refund Payments v2 capture refund
Reimbursement report Transaction Search (/v1/reporting/transactions) + local ledger

The officer agent (server/agent.ts) uses @paypal/agent-toolkit/openai for orders, invoices, products, plans, subscriptions, refunds, and transaction list. That package does not cover payouts or webhooks, so ClubFlow adds propose_payout and club_webhook_status. Toolkit calls that would send an invoice, refund a capture, or capture an order are held until the officer confirms. The same tool list is what the PayPal MCP server exposes; this app drives it through the toolkit’s OpenAI adapter rather than a second MCP process.

The model is any OpenAI-compatible chat API (default gpt-4o-mini). The key is optional. With no key, the intent parser handles the demo phrases and still opens the confirm card before money moves.

If PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET are missing, or if MOCK_PAYPAL=1, the desk simulates every PayPal call and the banner says so. npm run check:mock exercises the API flows without a PayPal network. The JavaScript SDK loads only when a Client ID is present.

Challenges we ran into

The official Agent Toolkit is a strong fit for orders, invoices, subscriptions, refunds, and listing transactions, but a club money desk also needs speaker payouts and webhooks. Those had to be first-class ClubFlow tools, with the same Confirm rule as the toolkit’s money-moving calls. Getting that confirm step in front of irreversible actions — including when the model wants to send an invoice or refund a capture — was more important than letting the agent finish the job in one shot.

PayPal Transaction Search often needs to be enabled on the app and can lag for hours in the sandbox. A reimbursement report that only trusted Search would look empty during a demo. The report therefore always includes the local capture and webhook ledger, and it states when Search returned nothing.

This work was implemented first without sandbox credentials in the build environment, so the real sandbox client and a documented mock path were built side by side. The flows were later tested against the real PayPal sandbox. These passed: Orders create (returns an approve URL), Invoicing create and send, Payouts (item SUCCESS), Subscriptions plan publish and subscription (approval_pending with an approve link), and the Transaction Search report. Buyer-approval steps — registration capture, donation capture, and refund — were verified in mock mode only; they need a sandbox buyer login. Mock mode is not a hidden fallback: the banner names it, npm run check:mock covers it, and judges can run the desk without creating a PayPal account.

Sandbox apps need the Invoicing, Transaction Search, Payouts, and Subscriptions features enabled; newly enabled scopes may require a new app. PayPal blocks a Taiwan merchant from paying a Taiwan payer, so a US sandbox buyer and recipient should be used.

A few other constraints shaped the design. Seeded roster rows named Mia, Priya, and Sofia are history for the mock confirm flow; they are not sandbox captures, so in sandbox mode those refunds should follow a new sandbox payment. Email failure must not undo a completed capture, which is why the outbox keeps the copy and any provider error. Sandbox support for TWD is limited, so the desk is USD. And a Vercel serverless instance cannot reliably share the ledger with a webhook on another instance, which is why Render is recommended for a hosted demo.

Accomplishments that we're proud of

  • Putting eight PayPal surfaces on one officer desk for a single event, instead of a checkout button with a separate back office.
  • An officer agent on the official PayPal Agent Toolkit, plus the two club tools the toolkit does not ship, with Confirm before a refund, payout, or invoice send.
  • A demo that still works if the judge has no OpenAI key and no PayPal account: the rules parser, the mock PayPal client, and a banner that says what is being simulated.
  • A hard sandbox-only guard. The app refuses to run against live PayPal.
  • A reimbursement report that remains useful when Transaction Search is empty, by pairing it with a local ledger and a CSV export.
  • A significant update to an existing MIT-licensed project, rather than a throwaway demo: the Next Byte channel-copy generator is still there, and the money desk is built on top of the same event.

What we learned

Agentic commerce is not “let the model call PayPal.” For a club officer, the useful pattern is: the agent can inspect the roster, propose a refund or a payout, and then wait. The confirm card is part of the product, not a disclaimer.

We also learned that official tooling and club operations do not overlap completely. Payouts and webhooks had to be written as custom tools. Transaction Search is the right reporting API, but in the sandbox it cannot be the only source of truth for a same-day demo. And communications without money is only half of the officer’s job — the original ClubFlow made that obvious the moment we tried to tell this story to PayPal judges.

Finally, a judge-ready sandbox project needs a documented mock path. Building the real client and the simulation side by side, then saying so in the banner and the README, was the only honest way to ship when credentials were not in the build environment.

What's next

  • Host a long-running Render demo so judges can open the desk without running it locally, with sandbox keys and a webhook URL on that same process.
  • Give the ledger a durable store so roster state, receipts, and webhooks survive multiple instances.
  • Enable Transaction Search on the sandbox app and keep the local ledger as the same-day fallback.
  • Optionally generate channel copy with a model. Today that generator is still a local template; the model is only used by the officer agent.
  • Let officers approve and then actually send the drafts (email, LINE, and so on) when they choose to, instead of copy-paste only.
  • Support more than one event and more than one club on the same desk.
  • Offer the club’s local currency when PayPal sandbox support for it is reliable enough for a demo.

Significant update since October 1, 2026

ClubFlow is an existing project, meaningfully updated during this hackathon.

The repository was created on 7 November 2026 as a channel-copy generator for student club officers and was submitted to Next Byte Hacks V5. That version produced Instagram, Story, LINE, Google Form, Gmail, Linktree, and school-document previews from one event brief. It did not call PayPal, did not keep a roster, and did not include an officer agent. The copy generator, the older gallery, and the MIT license remain.

This PayPal AI Hackathon pass adds the Money desk on top of that generator (see PR #1, squash-merged into main as 97e8e2a7):

  • Express API beside the Vite app, with one npm install && npm run dev entry
  • Seeded USD workshop roster (registration 12, speaker fee 150, dues 8/month, sponsor invoice 500, suggested donation 25)
  • PayPal Checkout (Orders v2) and JavaScript SDK buttons, plus a mock pay button
  • Webhook handler that marks the roster paid or refunded and writes a receipt
  • Invoicing v2 create and send, with confirm before send
  • Donation capture, thank-you email, and PDF certificate
  • Payouts API for the speaker fee, exposed as a custom agent tool
  • Subscriptions API (product, plan, and subscription approve link)
  • One-click refund that updates the roster
  • Transaction Search with a local ledger fallback and a CSV reimbursement report
  • Officer agent on the PayPal Agent Toolkit, with a no-key intent parser and Confirm before refund, payout, or invoice send
  • In-app outbox when no email provider is configured
  • Render and Vercel deploy files
  • .env.example only; no secrets in git

AI tools used

In the product. The officer agent is the AI surface. When OPENAI_API_KEY is set, ClubFlow calls an OpenAI-compatible chat API (default model gpt-4o-mini, default base URL https://api.openai.com/v1) through @paypal/agent-toolkit/openai, plus two custom tools for payouts and webhook status. When no key is set, a deterministic intent parser handles the scripted officer phrases so the demo still runs. In both cases, refunds, payouts, and sending an invoice wait for an officer to press Confirm. Channel copy is still produced by a local template generator, not by a model.

In development. The Money desk and PayPal integration were implemented with Cursor, including a cloud agent that opened PR #1. No other AI sponsor tools (AG Grid, APIMatic, Bryntum, Channel3, etc.) are required to run the project.

Judge testing instructions

Repository: https://github.com/cmy-hulala/ClubFlow
License: MIT (license file in the repo)
Video: https://youtu.be/tnZq0ojGeeY

Do not put secrets in the repository. The app refuses PAYPAL_ENV=live. Currency in the desk is USD. People and example.com addresses in the seeded roster are fictional; please do not email them.

A. Mock mode (about 5 minutes, no PayPal account required)

git clone https://github.com/cmy-hulala/ClubFlow.git
cd ClubFlow
npm install
npm run dev

Optional API check, no PayPal network:

npm run check:mock
  1. Open http://127.0.0.1:43123/#/desk. Read the banner. It should say credentials are missing and the desk is simulating PayPal.
  2. Left. Press Generate channel copy. Copy an Instagram caption. Open Full output gallery if you want the older modules, then return to Money desk.
  3. Middle. On Jonah Wu, press Pay 12.00 USD. His status becomes paid. Open Outbox on the right and read the receipt.
  4. Press Refund on Mia Chen, then Confirm. The roster shows refunded.
  5. Sponsor invoice. Create draft, then Send invoice, then Confirm. Lane Lab’s copy appears in the outbox.
  6. Sponsor donation. Pay 25.00 USD (the form is filled). Open the certificate link and the thank-you in the outbox.
  7. Pay speaker fee on Ken Lin, then Confirm.
  8. Publish plan to PayPal, then Start subscription. In mock mode the member becomes active immediately.
  9. Build report, then Download CSV. The notice explains the local ledger fallback.
  10. Right. Ask Who has not paid? Confirm a refund the agent proposes (Refund Priya Shah) and watch the roster change. Ask Build the reimbursement report.
  11. Press Simulate capture webhook to mark the next unpaid attendee paid through POST /api/paypal/webhook.

Press Reset money demo to restore the seeded roster.

B. Sandbox mode (optional, uses a PayPal Developer sandbox app)

  1. At PayPal Developer Dashboard, switch to Sandbox, open Apps & Credentials, and create or open a REST app. Enable the Invoicing, Transaction Search, Payouts, and Subscriptions features. Newly enabled scopes may require a new app.
  2. Copy .env.example to .env. Set PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET. Leave MOCK_PAYPAL unset. Leave PAYPAL_ENV=sandbox.
  3. Under Sandbox → Accounts, put the business account email in PAYPAL_MERCHANT_EMAIL (required for invoicing; it must belong to the same sandbox business that owns the REST app). Put a personal sandbox account email in PAYPAL_SPEAKER_EMAIL if you will run the speaker payout. Payouts only succeed to a sandbox account email. PayPal blocks a Taiwan merchant from paying a Taiwan payer, so use a US sandbox buyer and a US sandbox recipient.
  4. Restart npm run dev. The banner should say the sandbox is connected. The JavaScript SDK buttons load because a Client ID is present.
  5. Repeat mock-mode steps 3 and 6 with the SDK buttons. In the PayPal window, log in as the sandbox personal buyer. A live PayPal login will not pay a sandbox order.
  6. Step 7 calls Payouts. Step 5 calls Invoicing. Step 8 returns a subscription approve link instead of activating immediately — open it as the sandbox buyer. Step 9 calls Transaction Search and still shows the local ledger if Search is empty or delayed.
  7. Optional webhook: register https://<your-host>/api/paypal/webhook for PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.REFUNDED, CHECKOUT.ORDER.APPROVED, and BILLING.SUBSCRIPTION.ACTIVATED, then set PAYPAL_WEBHOOK_ID. Without that id, events are applied and marked unverified so a local demo can still update the roster. Do that only on a sandbox host.

Seeded Mia, Priya, and Sofia captures are not real PayPal captures. In sandbox mode, refund those rows only after a new sandbox payment. An optional OPENAI_API_KEY turns the officer chat from the intent parser into the toolkit-backed model; the Confirm step is the same either way.

Built With

Share this project:

Updates

Submission history