Integration path
Integration Guide: Paygreen Meal Vouchers
This guide explains how to accept meal vouchers (Conecs titres-restaurant, Swile, Restoflash) on an order created through the API, using the restaurant's Paygreen module: exactly what POST /orders expects in the payment metadata field, how the customer pays on Paygreen's hosted page, and how the order is confirmed afterwards.
How Paygreen works at Innovorder
- Paygreen is configured per restaurant in the Innovorder back office (Paygreen Shop ID and secret key). There is nothing to configure through the API.
- The payment happens on Paygreen's hosted payment page: the API creates a Paygreen Payment Order and returns its URL, and you send the customer there. The voucher network (Conecs, Swile, Restoflash) and any complementary card payment are chosen on that page.
- The order is confirmed asynchronously: Paygreen notifies Innovorder by webhook once the payment is captured, and the order then becomes a paid order. There is no confirm endpoint to call on your side.
- The Paygreen environment is set per Innovorder platform, not per restaurant: the production platform only talks to Paygreen production. Sandbox Paygreen credentials cannot be used on a production restaurant (the back office refuses them when saved). To test against your Paygreen sandbox, ask support@innovorder.fr for a test brand on the Innovorder preproduction platform, which is wired to Paygreen's sandbox.
1. Find the Paygreen Payment Method
Call List Payment Methods (GET /payment_methods) for the restaurant and channel of the order, and pick the entry whose code is Paygreen. Its paymentMethodId is the value to send in payments[]. The payment method must be enabled for the consumption mode of the order, otherwise POST /orders answers payment_method_not_activated (403).
isEligibleLuncheon flag. You do not send it: amount is the total the customer pays through Paygreen, vouchers and complementary card included.2. Create the Order - the Metadata Contract
Create the order with POST /orders (see Create Orders for the full request body). The payment entry carries a metadata field, which must be a JSON-encoded string with the following keys:
| Key | Required | Description |
|---|---|---|
| paygreenPaymentType | Yes | Voucher family: TRD (meal vouchers, Conecs), RESTOFLASH or LUNCHR (Swile). Any other value is rejected with paygreen_payment_metadata_invalid. |
| firstName, lastName, email | Guest orders | Identity of the payer, forwarded to Paygreen. All three are required when the order is not placed by an authenticated Innovorder customer; they are ignored otherwise. |
POST/ordersCreate an order paid with meal vouchers through Paygreen
Standard order creation with a Paygreen payment entry. The metadata field carries the voucher family and, for a guest order, the payer identity, as a JSON-encoded string. The response is a 200 with code required_action: the order exists but is not paid yet. We recommend sending an idempotency-key header to protect against double submissions.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| idempotency-key | string (header) | No | Recommended. Unique key protecting against double order submission. |
Request Body
{
"restaurantId": 2285,
"channelId": 2,
"consumptionMode": "MODE_TAKE_AWAY",
"menuId": 24194,
"cart": [
{
"productId": 1670891,
"quantity": 1,
"steps": []
}
],
"expectedAt": "2026-09-15T12:30:00.000Z",
"payments": [
{
"paymentMethodId": 169093,
"amount": 3150,
"quantity": 1,
"metadata": "{\"paygreenPaymentType\":\"TRD\",\"firstName\":\"Alice\",\"lastName\":\"Martin\",\"email\":\"alice.martin@example.com\"}"
}
]
}Request Body Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| restaurantId | integer | 2285 | Identifier of the restaurant. |
| channelId | integer | 2 | Identifier of the associated channel. |
| consumptionMode | string | "MODE_TAKE_AWAY" | The consumption mode value. |
| menuId | integer | 24194 | Identifier of the menu. |
| cart | array | […] | List of cart entries. |
| cart[] | object | {…} | Object containing cart fields. |
| cart[].productId | integer | 1670891 | Identifier of the product. |
| cart[].quantity | integer | 1 | The quantity value. |
| cart[].steps | array | [] | List of steps entries. |
| expectedAt | string | "2026-09-15T12:30:00.000Z" | Date or timestamp for expected. |
| payments | array | […] | List of payments entries. |
| payments[] | object | {…} | Object containing payments fields. |
| payments[].paymentMethodId | integer | 169093 | Identifier of the associated payment method. |
| payments[].amount | integer | 3150 | The amount value. |
| payments[].quantity | integer | 1 | The quantity value. |
| payments[].metadata | string | "{\"paygreenPaymentType\":\"TRD\",\"firstName\":\"Alice\",\"lastName\":\"Mart… | Additional metadata supplied with the response. |
Response
{
"status": 200,
"code": "required_action",
"message": "Additional action required.",
"data": {
"orderId": 1927055,
"requiredAction": {
"type": "paygreenPaymentToProceed",
"transactionId": "po_3f9c2a1b7d8e4c5f9a1b2c3d4e5f6a7b",
"paymentUrl": "https://payment.paygreen.fr/po_3f9c2a1b7d8e4c5f9a1b2c3d4e5f6a7b",
"result": {
"status": "payment_order.pending",
"threeDSecureStatus": null,
"paymentErrorStatus": null
}
}
}
}Response Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "required_action" | Machine-readable application code for the result. |
| message | string | "Additional action required." | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.orderId | integer | 1927055 | Identifier of the order. |
| data.requiredAction | object | {…} | Object containing required action fields. |
| data.requiredAction.type | string | "paygreenPaymentToProceed" | The type value. |
| data.requiredAction.transactionId | string | "po_3f9c2a1b7d8e4c5f9a1b2c3d4e5f6a7b" | Identifier of the transaction. |
| data.requiredAction.paymentUrl | string | "https://payment.paygreen.fr/po_3f9c2a1b7d8e4c5f9a1b2c3d4e5f6a7b" | The payment url value. |
| data.requiredAction.result | object | {…} | Object containing result fields. |
| data.requiredAction.result.status | string | "payment_order.pending" | HTTP status code returned by the API. |
| data.requiredAction.result.threeDSecureStatus | null | null | The three dsecure status value. |
| data.requiredAction.result.paymentErrorStatus | null | null | The payment error status value. |
paymentMethodId is Innovorder's internal id of the "Paygreen" payment method configured for the restaurant (step 1). requiredAction.transactionId is the Paygreen Payment Order id (po_…): store it on your side, it is your reconciliation key with the Paygreen back office and it is not returned in the order's payments[] afterwards.metadata is a JSON string, not a JSON object: "metadata": "{\"paygreenPaymentType\":\"TRD\"}". An object is rejected with invalid_parameters (400).3. Send the Customer to the Payment Page
Open requiredAction.paymentUrl for the customer, either as a full-page redirect or inside an iframe. Always use the URL returned by the API, never build it yourself. The customer selects the voucher network, pays the eligible part with vouchers and any remainder by card, all on the Paygreen page.
The return and cancel URLs are fixed by Innovorder: after paying or cancelling, Paygreen sends the customer back to an Innovorder-hosted page whose only job is to post a window.postMessage to its parent window. It is therefore designed for the iframe integration:
window.addEventListener('message', (event) => {
if (event.data?.id === 'paygreen-insite') {
// Customer came back from the payment page.
// event.data.message = { pid: 'po_...', result: 'payment_order.successed' | ..., orderId: '1927055' }
closePaymentIframe();
startPollingOrder(event.data.message.orderId); // step 4 - the order status is the only proof of payment
} else if (event.data?.id === 'close-modal') {
// Customer cancelled on the payment page: the order will end refused.
closePaymentIframe();
}
});With a full-page redirect the customer lands on that (blank) Innovorder page: prefer the iframe, or keep your own "waiting for payment" screen open in a first tab and poll the order from there (step 4). In both cases, treat the message only as a signal to start polling, never as the payment confirmation.
4. Wait for the Confirmation
Paygreen notifies Innovorder by webhook when the Payment Order reaches its final state. Until then the order is awaiting action and unpaid, and it is not yet part of the order read endpoints: GET /v1/orders/{orderId} answers omnichannel_order_not_found (400) in the meantime.
- Poll Get Order Details (
GET /v1/orders/{orderId}) every few seconds with theorderIdof step 2, until it returns the order withstatusPAID. Ticket, kitchen and customer notifications are triggered at that very moment. - If Paygreen reports the payment as refused, expired or cancelled, the order is set to refused and never appears in the order endpoints. Create a new order for a retry; do not reuse the
orderId. - Give up after the lifetime of the Paygreen payment page (a few minutes): a customer who never came back leaves an expired Payment Order and a refused order behind, nothing to clean up on your side.
Error Reference
| Code | HTTP | Meaning |
|---|---|---|
| paygreen_payment_metadata_invalid | 400 | The payment metadata is missing, is not valid JSON, or its paygreenPaymentType is not TRD, RESTOFLASH or LUNCHR. |
| invalid_parameters | 400 | Schema violation - e.g. metadata sent as an object instead of a string. |
| paygreen_not_activated | 403 | The Paygreen module is not configured or not active on the restaurant. |
| payment_method_not_activated | 403 | The paymentMethodId is not enabled for the consumption mode of the order. |
| paygreen_payment_type_not_supported | 403 | The requested voucher family is disabled on the restaurant's Paygreen module. |
| paygreen_payment_refused | 422 | Paygreen refused to create the Payment Order (shop not activated, invalid sub-shop…). The order is not created. |
| required_action | 200 | Not an error - the order is created and waits for the customer to pay on paymentUrl (step 3). |
| omnichannel_order_not_found | 400 | On GET /v1/orders/{orderId}: the order is not paid yet (keep polling) or ended refused. |