Imported from payroc/skills (
plugins/payroc/transaction/skills/integrate-payroc-cloud/SKILL.md). Install upstream withnpx skills add payroc/skills --skill integrate-payroc-cloud. Copyright stays with the author.
Integrate Payroc Cloud
Scope: card-present payments via Payroc Cloud — the API bit of the semi-integrated product.
Submitting, polling, and retrieving the cloud instructions that drive a physical payment device:
sales, pre-auths, refunds (referenced + unreferenced), reversals, signature capture, and closed-loop
reads, plus read-only device lookup. Physical device configuration/pairing is out of scope — this
skill picks up once you have a device's serialNumber.
Version check (run this first)
Before announcing anything or starting the flow, confirm this skill is current:
- Read this skill's version from the
metadata.versionfield in the frontmatter above. - Fetch the published copy and read its
metadata.version:https://raw.githubusercontent.com/payroc/skills/main/plugins/payroc/transaction/skills/integrate-payroc-cloud/SKILL.md - Compare the two as semantic versions:
-
This version >= published → continue silently, no message. (A developer running an unreleased newer version is expected and fine.)
-
This version < published → tell the developer:
⚠️ A newer version of this skill (v<published>) has been published — you're running v<current>. Upgrading is recommended for the best results.
Then ask whether they'd like to continue with the current version or stop and upgrade first, and honour their answer.
-
Couldn't fetch (offline, network error, 404) → note briefly that the version couldn't be verified and continue.
-
How Payroc Cloud works
In a semi-integrated model the POS never talks to the payment device directly. The POS sends an instruction to the Payroc gateway over HTTPS; the gateway relays it to the device; the device runs the card interaction (tap/insert/PIN/sign); the result flows back through the gateway, which the POS retrieves.
POS / POS app --HTTPS--> Payroc gateway --> payment device (Payroc App)
^ | |
+-------- poll status <----+<--- result ------------+
Because everything routes through the gateway, your integration is pure REST — there is no device SDK to embed and no card data on your servers. This skill covers that REST surface.
What's in scope vs out:
- In scope (pure API): build & submit instructions (payment / refund / signature); poll them;
retrieve the resulting payment/refund/signature; cancel an in-progress instruction; read closed-loop
payloads; referenced/unreferenced refunds and reversals;
GET /devicesto find aserialNumber. - Out of scope (hardware): configuring or pairing the physical device (Android / Ingenico / ID TECH setup), the tap/insert/swipe/PIN/on-glass-signing interaction, binding a real serial number to a terminal. If the developer needs these, say so plainly — they're device setup, not API work.
This skill treats the device + serialNumber as a given input. If the developer doesn't have one,
GET /devices finds one (read-only, no hardware), and the Payroc Cloud Simulator
(cloud.uat.payroc.com) issues a mock serial number per browser tab for testing without hardware.
References — read these, don't emit from memory
This skill emits from the local references/ files below — not from live lookups and not from memory.
There are two complementary kinds of source, each owning a different kind of question; using the
wrong one is a known failure mode:
references/api-schema.md— what crosses the wire. Field names, types, required flags, enum values, request/response shapes, the endpoint inventory, and the error envelope. Whenever you need what a field is called or what values it accepts, read it. Emit every enum and field name from here, not from memory — a plausible-sounding value (e.g. a status of"approved", anentryMethodof"chip") that isn't in the enum is the most common way this goes wrong.references/narrative-run-a-sale.mdandreferences/narrative-extend.md— how to sequence. Which call comes first, how the poll-then-follow-link flow composes, how the follow-on flows (signature, referenced/unreferenced refund, reverse) build on the core pattern.references/error-response-format.md— error envelope (RFC 7807) + Payroc errors[] + canonical error type catalog. The envelope shape and the canonical errortypecatalog for the server-side API calls;api-schema.mdcarries the per-status table.
Source URLs and last-synced dates are in references/_sources.md.
Honest limitation. These references were curated from Payroc's published docs without a Cloud-enabled account or hardware to validate against. The contract (paths, fields, enums, the instruction lifecycle) comes straight from the docs and is reliable; the error response shape for Cloud is assumed to match the cross-skill standard and is not confirmed for Cloud. Flag this if a developer is building hard error-handling logic.
Core principles
- Inspect before asking. Read the developer's codebase first — language, HTTP client, how config and secrets are handled, any existing Payroc or POS integration — and frame questions in their terms instead of asking what you can infer.
- Read-then-emit, per value. Before emitting any enum or field name (
currency,entryMethod, instructionstatus,mitAgreement,tip.type, refundresponseCode,typeon a payment search, …), read it fromreferences/api-schema.md. The skill's inline examples exist so you recognise the shape — they are not a substitute for reading the reference as you build each body. - One pattern, learned once. Sales, refunds, and signatures are all the same async instruction: submit (202) → poll → follow the link → retrieve. Internalise it once and the rest is variation.
- The instruction id is not the resource id. Polling returns a
paymentInstructionId; the completed instruction'slink.hrefpoints at a different id — thepaymentId. Follow the link; never synthesise the resource URL from the instruction id. - 202 ≠ approved. A
202means the gateway accepted the instruction and is relaying it to the device. The sale's actual outcome (bank approval/decline) lives on the retrieved payment, not on the instruction. - Never hardcode credentials. The API key and any terminal/serial values come from environment variables or a secrets manager — never source code.
Quick reference
Base URL (test): https://api.uat.payroc.com/v1 Identity (test): https://identity.uat.payroc.com/authorize
Base URL (prod): https://api.payroc.com/v1 Identity (prod): https://identity.payroc.com/authorize
# Instruction submit (runs on the device) — Idempotency-Key required
POST /devices/{serialNumber}/payment-instructions -> 202
POST /devices/{serialNumber}/refund-instructions -> 202 (unreferenced refund)
POST /devices/{serialNumber}/signature-instructions -> 202
# Poll an instruction (gateway long-holds ~60s) / cancel it
GET /payment-instructions/{id} -> 200
DELETE /payment-instructions/{id} -> 204 (only while inProgress, else 409)
# ...same GET/DELETE shape for refund-instructions and signature-instructions
# Retrieve the resulting resource (follow link.href from the completed instruction)
GET /payments/{paymentId} GET /refunds/{refundId} GET /signatures/{signatureId}
GET /closed-loop-reads/{closedLoopReadId}
# Referenced refund & reversal (standard payments API, NOT device instructions) — Idempotency-Key required
POST /payments/{paymentId}/refunds -> 201
POST /payments/{paymentId}/reverse -> 200
GET /payments GET /payments/{paymentId} # find / retrieve a payment
GET /devices # find a serialNumber
Headers: Authorization: Bearer <token> Content-Type: application/json Idempotency-Key: <uuid-v4>
Step 1 — Get a bearer token
Read
references/identity-call.mdbefore emitting any auth code. Do not guess the endpoint URL, header name, or response shape — use only what the reference documents.
Tokens expire in ~1 hour (expires_in: 3600). Exchange the API key before each session, refreshing
proactively before expiry.
# Test / sandbox (production: identity.payroc.com)
curl -X POST https://identity.uat.payroc.com/authorize \
-H "x-api-key: $PAYROC_API_KEY"
Response carries access_token, expires_in, token_type: "Bearer". Send
Authorization: Bearer <access_token> on every subsequent request. The Payroc SDKs handle token
exchange automatically — see https://docs.payroc.com/api/payroc-sd-ks-beta.
Step 2 — The instruction pattern (submit → poll → follow link → retrieve)
Read references/narrative-run-a-sale.md. This is the heart of Payroc Cloud; every operation uses it.
- Submit the instruction to the device with a fresh
Idempotency-Key. You get202 Acceptedwith{ status: "inProgress", <x>InstructionId, link }. Persist the instruction id. - Poll
GET /<x>-instructions/{id}. The gateway holds the response for up to ~60 seconds waiting for the status to change — so don't write a tight loop or your own sleep. Issue the GET, wait for it to return, and only re-issue if it's stillinProgress. The statuses areinProgress,completed,failure,canceled— read them fromreferences/api-schema.md, not memory. - On
completed, the response'slink.hrefpoints at the real resource. Follow it:GET /payments/{paymentId}(or/refunds/{id},/signatures/{id}). Don't build this URL from the instruction id — they are different ids. The retrieved payment carries the bank outcome intransactionResult— branch ontransactionResult.statusreading the full approval set fromreferences/api-schema.md(e.g.ready= authorized + queued for capture is an approval), not on a single remembered value likecomplete. - On
failure, readerrorMessage. Oncanceled, the instruction was cancelled first.
Cancelling. DELETE /<x>-instructions/{id} returns 204 — but only while the instruction is
inProgress. Once it has completed/failed/cancelled, the DELETE returns 409 Conflict. If unsure of
the current state, GET the instruction and read status before cancelling.
Checkpoint
Submit returns 202 with an instruction id and a self link; polling eventually yields completed
(or failure with an errorMessage); following the completed link retrieves the resource.
Step 3 — Build a sale instruction
Read references/api-schema.md (the paymentInstructionRequest section) as you build the body.
Required: processingTerminalId and order (orderId, amount, currency).
POST /v1/devices/{serialNumber}/payment-instructions
{
"processingTerminalId": "1234001",
"order": {
"orderId": "OrderRef6543",
"amount": 4999,
"currency": "USD",
"breakdown": {
"subtotal": 4500,
"tip": { "type": "fixedAmount", "mode": "prompted", "amount": 499 },
"taxes": [ { "rate": 0.08, "name": "Sales Tax" } ]
}
},
"customizationOptions": { "entryMethod": "deviceRead" },
"autoCapture": true
}
Key choices (read the enum/field details from the reference):
amountis integer cents in the order'scurrency(ISO 4217).4999= $49.99. Never send a decimal.- Sale vs pre-auth vs settle:
autoCapture: true(default) = sale;autoCapture: false= pre-authorization (capture later);processAsSale: truesettles immediately and ignoresautoCapture. Pick deliberately. entryMethoddefaults todeviceRead; the other values are in the reference — don't invent one.- Tokenize on the sale with
credentialOnFile.tokenize: true(andmitAgreementfor merchant-initiated follow-ons).
Step 4 — Extend (signatures, refunds, reversals)
Read references/narrative-extend.md for sequencing, references/api-schema.md for shapes.
- Capture a signature — standalone, not tied to a payment. Submit
{ "processingTerminalId": "…" }to/signature-instructions, poll, then follow thesignaturelink toGET /signatures/{id}and decode the Base64signature(itscontentType, e.g.image/png). - Unreferenced refund — a device instruction:
POST /devices/{serialNumber}/refund-instructions. Noteorder.descriptionis required on a refund order. Poll → follow link →GET /refunds/{id}. - Referenced refund — not a device instruction; it uses the standard payments API and needs the
original
paymentId. Get it viaGET /payments/{paymentId}or searchGET /payments(last4,cardholderName,orderId,dateFrom/dateTo, …), thenPOST /payments/{paymentId}/refundswith{ amount, currency }. - Reverse a payment — also the payments API, for a payment still in an open batch:
POST /payments/{paymentId}/reverse(omitamountfor full, supply it for partial). No funds move. - Reverse vs refund: open batch, funds not yet taken → reverse; already settled → refund. Beware: a referenced refund against an open-batch payment auto-reverses it instead of creating a refund — so be deliberate about which you intend.
- Closed-loop reads — if you've issued a closed-loop instruction (e.g. a MiFare card read),
retrieve the payload with
GET /closed-loop-reads/{id}. Itsdataobject is unstructured (shape varies by card); don't assume a fixed schema. Seereferences/api-schema.md.
Idempotency
Every instruction POST (and every refund/reverse POST) takes an Idempotency-Key header — a fresh
UUID v4 per new submission. To retry a submission that may not have landed (network blip, timeout),
resend the byte-for-byte identical body with the same key: the gateway returns the original
result instead of submitting a second instruction to the device. Changing the body while reusing the
key is a 409. Polls (GET) and cancels (DELETE) don't take an idempotency key.
Handle errors
Errors use the RFC 7807 problem-details envelope (type, title, status, detail, instance) extended with a Payroc errors[] array. See references/error-response-format.md for the envelope shape and the canonical error type catalog; read errors[].parameter to map each failure to your request body. See references/api-schema.md for the per-status table.
Cloud error shape is not yet verified. We applied the standard Payroc envelope (confirmed on boarding endpoints) on the assumption Cloud is consistent, but could not confirm against a live Cloud account. Read
errorMessageon afailureinstruction anderrors[]on a 4xx, but don't hard-code assumptions about Cloud-specific error text.
| Status | Scenario | Action |
|---|---|---|
| 400 | Validation error on submit | Map each errors[].parameter to the field; fix and resubmit (reuse the same idempotency key for an identical retry). |
| 401 | Token expired/invalid | Re-authenticate for a fresh bearer token. |
| 403 | Insufficient permissions | Check the API key's scope. |
| 404 | Instruction or resource not found | Verify the id — and check you didn't confuse the instruction id with the resource id. |
| 409 | Conflict — cancelling an instruction no longer inProgress, or reusing an idempotency key with a changed body |
GET the instruction's current status first; don't blindly retry. |
| 500 | Server error | Retry with backoff; surface errors if present. |
Plus the instruction-level outcomes: poll status: "failure" → read errorMessage; status: "canceled" → cancelled before completing.
Common pitfalls
- Synthesising the resource URL from the instruction id. The completed instruction gives you a
link.hrefto a different id. Follow the link; don't build/payments/{paymentInstructionId}. - Treating
202(or200on a poll) as success.202= accepted for relay; a poll can return200withstatus: "failure". The sale outcome is on the retrieved payment. - Tight polling loops / manual sleeps. The gateway already long-holds the poll ~60s. Just await the
GET and re-issue only if still
inProgress. - Cancelling too late.
DELETEonly works whileinProgress; otherwise it's a409. Checkstatusfirst. - Decimal amounts.
amountis integer cents, always. - Inventing enum values.
entryMethod, instructionstatus,currency,mitAgreement,tip.type, refundresponseCode, payment-searchstatus/typeare all enums — read them fromreferences/api-schema.md. - Forgetting
order.descriptionon a refund instruction — it's required there (unlike on a payment). - Refund vs reverse on an open batch. A referenced refund on an open-batch payment auto-reverses
it. Use
POST /payments/{paymentId}/reversewhen you mean to cancel an unsettled payment. - Reusing or omitting the idempotency key. Fresh UUID per new submit; same key only to retry an identical body; none on GET/DELETE.
- Trying to "set up" the device. Pairing/configuration is hardware, out of scope. The skill starts
from a known
serialNumber(find one withGET /devices).
Full field reference
Read references/api-schema.md for: the endpoint inventory; the instruction object and its status
enum; paymentInstructionRequest / refundInstructionRequest / signatureInstructionRequest
schemas (with order, breakdown, credentialOnFile, customizationOptions); signature and
closed-loop retrieval; the referenced-refund and reverse endpoints; GET /payments and GET /devices
search; and the error envelope.