Inspiration
A shop that already collected a PayPal payment still has to answer the dispute. The usual agent writes a letter and leaves the merchant in the Resolution Center. The settlement never moves.
Dispute Recover is the seller's recover desk for that queue. The decision is a stamp, and the stamp is a Customer Disputes v1 call.
What it does
Cedar Route in Bend has three open chargebacks. The agent reads the reason, the due date, and the fulfillment pack, then stamps one of three actions:
- FOLD posts
accept-claim. PayPal closes the dispute in the buyer's favour and refunds the buyer from the merchant. - DEFEND posts
provide-evidencefor documents that are actually in the pack. The dispute leavesWAITING_FOR_SELLER_RESPONSE. - OFFER posts
make-offerwith a partial refund. The dispute waits on the buyer. If the buyer accepts, PayPal refunds the offer, not the full amount.
On the no-key desk those three land as: lantern $186.00 folded and refunded (PP-D-4012, RESOLVED / RESOLVED_BUYER_FAVOUR), trail stove $240.00 defended into UNDER_REVIEW with the money unmoved (PP-D-27803), wool bedroll offered at 40% which is $51.20 (PP-D-88411, WAITING_FOR_BUYER_RESPONSE). Check UPS tracking and a delivery receipt on the lantern and file again: the stamp becomes DEFEND and the endpoint becomes provide-evidence. The earlier refund leaves the current ledger. The earlier FOLD stays in the archive.
There is no fourth action called "draft a reply."
How we built it
The desk is Python 3, standard library only. recover/stamp.py is a three-row router: DEFEND to provide_evidence, FOLD to accept_claim, OFFER to make_offer. file_case reads the pack, stamps, validates, and POSTs the HATEOAS link the stamp names. A stamp outside those three, a DEFEND that cites nothing in the pack, or a missing link does not send a request.
The default PAYPAL_MODE=local is an in-memory Disputes v1 ledger with the same status, outcome, fund movements, offers, and links as the sandbox. It makes no network call. PAYPAL_ENV=live, PAYPAL_MODE=live, and any host other than api-m.sandbox.paypal.com are refused before a request is sent.
With no XAI_API_KEY, a local policy stamps from the written rules. Set the key and the same policy goes to grok-4.7. The model's JSON is the stamp that gets filed. If the model errors, that dispute is not filed and the desk does not quietly fall back to the local policy.
Sandbox filing is optional: PAYPAL_MODE=sandbox plus a sandbox REST app. provide-evidence is multipart/related with a JSON part named input.
Challenges we ran into
A dispute action is only valid when that HATEOAS rel is on the dispute. The client follows the link it was given and will not invent a production URL.
accept-claim on a resolved dispute must not refund twice. The buyer's accept-offer must refund the offer amount only. Local replay can reopen a fixture dispute with the same id. A live PayPal dispute cannot be rewound, and sandbox mode refuses replay.
A model that stamps FOLD on a delivered order still posts accept-claim. The pack does not get to pick a different endpoint behind the stamp's back. That binding is the product, and it is covered by tests that never call PayPal.
Accomplishments that we're proud of
A judge can run python3 server.py, open http://127.0.0.1:4183, and click File the open cases with no PayPal key and no model key. Weak evidence folds and leaves a buyer refund. Strong fulfillment defends and leaves WAITING_FOR_SELLER_RESPONSE. Editing the pack changes both the stamp and the endpoint.
33 unit tests, standard library only. They include the production-host refusal and a forged grok-4.7 stamp that is what the ledger executes.
What we learned
A recover agent that cannot post accept-claim, provide-evidence, or make-offer is a writing aid. The PayPal state is the result. Take the client away and there is nothing to demo.
What's next
Run the same stamps against a free PayPal sandbox seller once a sandbox dispute exists. Talk to merchants before calling this a business. The model today is experimental: no interviews and no paying customers. Dispute Recover is not an official PayPal product.
Try it
No hosted URL. From the repo:
python3 server.py
Open http://127.0.0.1:4183 and click File the open cases. See README.md for the replay step and for the buyer accept-offer, which refunds only $51.20. Do not point this at production.
Built With
- css
- grok
- html
- javascript
- paypal
- paypalapi
- python
- xai
Log in or sign up for Devpost to join the conversation.