# PayCow Compact Safe Integration Prompt

**For small coding models (5B-8B). Last reviewed: 2026-09-23.**

You are integrating PayCow into an existing backend. Keep the change small. Inspect the repository before editing. Follow the existing framework, database, validation, error handling, and test conventions. Do not rewrite unrelated code.

## Stop and ask if these four answers are missing

1. Backend framework and version
2. Database and ORM
3. Payment use case: order, balance top-up, or subscription
4. Networks: `TRC20`, `ERC20`, or both

Do not write integration code until these answers and the relevant repository files are available.

## Fixed PayCow contract

- Base URL: `https://paycow.net`
- Create payment: `POST /api/payment/create`
- Protected status fallback: `GET /api/payment/status/:sessionId`
- Hosted checkout URL comes from the create-payment response as `paymentUrl`
- Supported asset: USDT
- Supported network values: `TRC20` and `ERC20`

Create-payment headers:

```http
Content-Type: application/json
x-api-key: <PAYCOW_API_KEY>
x-api-secret: <PAYCOW_API_SECRET>
Idempotency-Key: <UNIQUE_LOCAL_ORDER_ID>
```

Minimum request body:

```json
{
  "userId": "local-order-or-user-id",
  "amount": 29.99,
  "network": "TRC20"
}
```

Use the returned `paymentUrl` for redirect. Store the returned `sessionId`, `paymentReference`, requested amount, payable amount, network, local record ID, and current status.

## Security rules that cannot be changed

1. Keep `PAYCOW_API_KEY`, `PAYCOW_API_SECRET`, and `PAYCOW_WEBHOOK_SECRET` in backend environment variables.
2. Never send these secrets to browser code, logs, URLs, or source control.
3. Calculate the amount on the backend from trusted product or plan data. Never trust a browser-submitted price.
4. Send a stable unique `Idempotency-Key` for each local order or top-up.
5. Use HTTPS only.
6. Do not invent PayCow endpoints, fields, statuses, or event types.
7. Do not fulfill from a frontend redirect or browser polling result.
8. Do not fulfill from `payment.detected`, `payment.confirming`, or `webhook.test`.
9. Fulfill only when the signed event type is `payment.paid` and `data.status` is `PAID`.
10. Make fulfillment atomic and idempotent in the database.

## Required implementation order

### 1. Database

Add or reuse a payment record linked to the local order, user, or subscription. Add a webhook event ledger with a unique index on PayCow `event.id`.

The payment update and business action must happen in one database transaction. A duplicate event must return success without repeating the action.

### 2. Backend PayCow service

Implement only:

- `createPayment({ localId, userId, amount, network })`
- `getPaymentStatus(sessionId)` for backend recovery

Validate network against `TRC20` and `ERC20`. Validate amount as a positive server-derived value. Set request timeouts. Handle non-2xx responses without exposing secrets.

### 3. Checkout route

The local checkout route must:

1. Authenticate or validate the local request.
2. Load the real amount from the database.
3. Create or reuse the local pending payment.
4. Call PayCow with the local record ID as `userId` and idempotency key.
5. Save PayCow identifiers and return only the hosted `paymentUrl` needed by the frontend.

### 4. Webhook receiver

Create a public HTTPS POST route such as `/webhooks/paycow`. Register this route before the framework JSON parser so the exact raw request bytes remain available.

Read these headers case-insensitively:

```http
PayCow-Event-Id
PayCow-Timestamp
PayCow-Signature
```

Verify all of the following before parsing JSON:

- Timestamp is an integer within 300 seconds of server time.
- Signature format is exactly `v1=` plus 64 hexadecimal characters.
- Expected digest is `HMAC-SHA256(PAYCOW_WEBHOOK_SECRET, timestamp + "." + rawBody)`.
- Expected and received digest bytes are compared with a timing-safe function.

If verification fails, return `401`. If JSON is invalid or the body event ID differs from `PayCow-Event-Id`, return `400`.

After verification:

1. Start a database transaction.
2. Insert `event.id` into the unique webhook ledger.
3. If it already exists, commit nothing else and return `200`.
4. If type is `payment.paid` and status is `PAID`, lock the matching local payment.
5. If it is not already fulfilled, mark it paid and perform the order, balance, or subscription action once.
6. Commit and return `200`.

Do not perform slow external work before responding. Queue email or other secondary work after durable acceptance.

### 5. Dashboard setup

After deploying the receiver:

1. Open PayCow Merchant Dashboard.
2. Go to Developer -> Webhooks.
3. Save the public HTTPS receiver URL.
4. Copy the one-time `whsec_...` value into `PAYCOW_WEBHOOK_SECRET`.
5. Send a test webhook.
6. Confirm HTTP 2xx in Webhook Delivery History.

If the secret is lost, rotate it and update the backend immediately. The old secret stops working.

## Event types

- `payment.detected`: informational
- `payment.confirming`: informational
- `payment.paid`: fulfill once
- `payment.expired`: mark expired if still pending
- `payment.rejected`: mark rejected if appropriate
- `webhook.test`: acknowledge only

PayCow delivery is at least once. The same `event.id` can be retried. Use the event ledger, not memory, for deduplication.

## Required tests

Write focused tests for:

1. Valid signature accepted
2. Invalid signature rejected
3. Stale timestamp rejected
4. Parsed or changed body fails signature verification
5. `webhook.test` causes no fulfillment
6. First `payment.paid` fulfills once
7. Replayed `payment.paid` does not fulfill again
8. Concurrent duplicate events still produce one fulfillment
9. Browser-supplied amount cannot override the database amount
10. API and webhook secrets never appear in responses or logs

## Final response format

Report only:

- Files changed
- Database migration or index added
- Environment variables required
- Dashboard steps still required
- Tests run and their result
- Any blocker that prevented raw-body verification or atomic idempotency

Do not claim the integration is production-ready if raw-body verification, a unique event index, or atomic fulfillment is missing.
