Payment Ops MCP Server
by srik4442
README.md
<div align="center">

[](https://github.com/srik4442/Payment-Ops-mcp)
<br/>






</div>
---
> An MCP server that lets Claude handle payment support โ look up customers, review charges, and issue refunds โ by orchestrating between **Stripe** and an **internal orders database**, with human approval required before any money moves.
---
## ๐ก Why This Exists
Stripe ships an official MCP server โ but it only knows Stripe. Real companies wrap the payment provider with internal data and business rules. This server is that orchestration layer:
- ๐ Joins Stripe payment data with internal order records via `stripe_customer_id`
- ๐
Enforces refund policy (no refunds older than 90 days)
- ๐ Requires human confirmation before moving money
- ๐ Writes a full audit log of every refund to the orders DB
That's the layer every company has to build itself. The provider can't.
---
## ๐๏ธ Architecture

---
## ๐ ๏ธ Tools
| Tool | Type | What it does |
|---|---|---|
| `lookup_customer` | ๐ Read | Merges internal DB record + live Stripe status by email |
| `list_payments` | ๐ Read | Lists recent Stripe PaymentIntents for a customer |
| `get_order_history` | ๐ Read | Returns full order history from internal DB |
| `issue_refund` | โ๏ธ **Write** | Two-step: preview โ confirm โ refund Stripe + log DB |
| `create_payment_link` | โ๏ธ Write | Generates a Stripe test payment link |
| `revenue_summary` | ๐ Read | Aggregates paid orders over a date range |
| `flag_for_review` | โ๏ธ Write | Flags a charge in the audit log for manual review |
Plus 2 **resources** (`customer://email`, `payment://charge_id`) and 2 **prompts** (`daily_revenue_report`, `find_refund_candidates`).
---
## ๐ Safety Design
Three layers โ never trust the model alone for money-moving actions:
1. ๐ **Human-in-the-loop gate** โ `issue_refund` requires `confirmed=True`, which the model only sets after explicitly asking the user
2. ๐
**Server-side business rule** โ refunds older than 90 days are rejected regardless of what the model requests
3. ๐๏ธ **Least-privilege Stripe key** โ restricted key scoped to Charges + Customers + Refunds only, limits blast radius
---
## ๐๏ธ Database Schema
```
customers
id (uuid, pk)
email (unique)
name
stripe_customer_id โ join key to Stripe
orders
id (uuid, pk)
customer_id (fk)
stripe_charge_id โ Stripe PaymentIntent ID (pi_xxx)
amount_cents
currency
status (paid / refunded / partially_refunded)
created_at
refund_log โ audit trail
id (uuid, pk)
order_id (fk)
stripe_refund_id
amount_cents
reason
refunded_by ("ai-assistant")
created_at
```
---
## โ๏ธ Setup
### 1. Clone and install
```bash
git clone https://github.com/srik4442/Payment-Ops-mcp.git
cd Payment-Ops-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
### 2. Get a Stripe test key
1. Create a free account at [stripe.com](https://stripe.com) (test mode, no card needed)
2. Go to **Developers โ API keys โ Create restricted key**
3. Grant **Write** access to: Charges and Refunds, Customers, Payment Intents, Payment Links, Products
4. Copy the `rk_test_...` key
### 3. Configure environment
```bash
cp .env.example .env
# Edit .env and add your key:
# STRIPE_API_KEY=rk_test_your_key_here
# DATABASE_URL=sqlite:///./payments.db
```
### 4. Seed test data
```bash
python scripts/seed.py
```
Creates 5 test customers in both your local DB and Stripe, each with 2โ3 test charges.
### 5. Connect Claude Desktop
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"payment-ops": {
"command": "/absolute/path/to/.venv/bin/python",
"args": ["/absolute/path/to/src/mcp_server.py"],
"env": {
"STRIPE_API_KEY": "rk_test_your_key_here",
"DATABASE_URL": "sqlite:////absolute/path/to/payments.db"
}
}
}
}
```
Restart Claude Desktop โ you should see `payment-ops` connected.
### 6. Run the demo
Type into Claude Desktop:
> *"A customer, sarah@example.com, emailed about a double charge. Look into it and refund the most recent payment."*
---
## ๐งช Tests
```bash
python -m pytest tests/ --cov=src -v
```
24 tests ยท 99% coverage on business logic ยท Stripe client fully mocked (no real API calls in tests)
```
tests/test_payment_service.py::TestLookupCustomer::test_lookup_customer_merges_db_and_stripe PASSED
tests/test_payment_service.py::TestIssueRefund::test_issue_refund_preview_when_not_confirmed PASSED
tests/test_payment_service.py::TestIssueRefund::test_issue_refund_writes_refund_log_when_confirmed PASSED
tests/test_payment_service.py::TestIssueRefund::test_issue_refund_rejected_for_charge_older_than_90_days PASSED
...
```
---
## ๐ค Tech Decisions
**Why MCP instead of a backend script?**
A script automates one fixed workflow. MCP exposes capabilities to any AI client so a human can drive novel, multi-step support tasks in natural language โ without pre-coding every path.
**Why SQLite?**
Zero-config for a portfolio project. The ORM (SQLAlchemy) makes swapping to Postgres a one-line config change.
**Why test mode only?**
Test mode uses the identical Stripe API and code path as live โ the only difference is the key prefix. Switching to production is one environment variable.
**Why a restricted Stripe key?**
Least-privilege: the key can only touch Charges, Customers, and Refunds. Even if the key were compromised, the blast radius is contained.
**Why store `pi_xxx` IDs (PaymentIntent) instead of `ch_xxx` (Charge)?**
Modern Stripe workflows are built around PaymentIntents. Refunds via `payment_intent` work reliably across all payment methods.
---
## ๐ฌ Demo
> **Prompt typed into Claude Desktop:**
> *"A customer, sarah@example.com, emailed about a double charge. Look into it and refund the most recent payment."*
```
STEP 1 Claude calls lookup_customer("sarah@example.com")
โ queries Orders DB โ finds Sarah + Stripe customer ID
STEP 2 Claude calls list_payments(customer="cus_xxx")
โ queries Stripe (test mode) โ returns recent charges
STEP 3 Claude PAUSES:
"I found a duplicate $49.99 charge. Refund the most recent one? Please confirm."
โ human-in-the-loop gate before any money moves
STEP 4 You reply "yes"
โ Claude calls issue_refund(confirmed=True)
โ refund issued in Stripe
โ order status updated + audit row written to DB
STEP 5 Claude confirms:
"Refunded $49.99 to Sarah (re_3To18wCLuf9LMqJ11xb4XutS).
Logged in orders DB as duplicate-charge reversal."
```

---
## ๐ Resume Bullet
**AI Payment Operations Assistant (MCP Server)** ยท Python, MCP SDK, Stripe API, SQLAlchemy, pytest
- Built a Model Context Protocol server that lets AI assistants perform payment support operations by orchestrating between the Stripe API and an internal order database, exposing 7 tools, resources, and prompt templates
- Implemented human-in-the-loop confirmation and server-side business rules to gate money-moving actions, with a full refund audit log written to the database for every transaction
- Applied production payment patterns โ least-privilege restricted API keys, idempotency, and prompt-injection mitigation โ and validated orchestration logic with a mocked-Stripe pytest suite at 99% coverage
<div align="center">

</div>
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues