Skip to content
innovorder
⌘K

Integration path

Integration Guide: Paygreen Meal Vouchers

System map · Paygreen payment
Order
Paygreen
Paid order

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).

Eligible amount: the part of the order payable with meal vouchers is computed server-side from the products' 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:

KeyRequiredDescription
paygreenPaymentTypeYesVoucher family: TRD (meal vouchers, Conecs), RESTOFLASH or LUNCHR (Swile). Any other value is rejected with paygreen_payment_metadata_invalid.
firstName, lastName, emailGuest ordersIdentity 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

NameTypeRequiredDescription
idempotency-keystring (header)NoRecommended. Unique key protecting against double order submission.

Request Body

json
{
  "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.

PropertyTypeExampleDescription
restaurantIdinteger2285Identifier of the restaurant.
channelIdinteger2Identifier of the associated channel.
consumptionModestring"MODE_TAKE_AWAY"The consumption mode value.
menuIdinteger24194Identifier of the menu.
cartarray[…]List of cart entries.
cart[]object{…}Object containing cart fields.
cart[].productIdinteger1670891Identifier of the product.
cart[].quantityinteger1The quantity value.
cart[].stepsarray[]List of steps entries.
expectedAtstring"2026-09-15T12:30:00.000Z"Date or timestamp for expected.
paymentsarray[…]List of payments entries.
payments[]object{…}Object containing payments fields.
payments[].paymentMethodIdinteger169093Identifier of the associated payment method.
payments[].amountinteger3150The amount value.
payments[].quantityinteger1The quantity value.
payments[].metadatastring"{\"paygreenPaymentType\":\"TRD\",\"firstName\":\"Alice\",\"lastName\":\"Mart…Additional metadata supplied with the response.

Response

json
{
  "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.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"required_action"Machine-readable application code for the result.
messagestring"Additional action required."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.orderIdinteger1927055Identifier of the order.
data.requiredActionobject{…}Object containing required action fields.
data.requiredAction.typestring"paygreenPaymentToProceed"The type value.
data.requiredAction.transactionIdstring"po_3f9c2a1b7d8e4c5f9a1b2c3d4e5f6a7b"Identifier of the transaction.
data.requiredAction.paymentUrlstring"https://payment.paygreen.fr/po_3f9c2a1b7d8e4c5f9a1b2c3d4e5f6a7b"The payment url value.
data.requiredAction.resultobject{…}Object containing result fields.
data.requiredAction.result.statusstring"payment_order.pending"HTTP status code returned by the API.
data.requiredAction.result.threeDSecureStatusnullnullThe three dsecure status value.
data.requiredAction.result.paymentErrorStatusnullnullThe payment error status value.
Note: 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.
Classic trap: as for Stripe, 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:

javascript
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.

  1. Poll Get Order Details (GET /v1/orders/{orderId}) every few seconds with the orderId of step 2, until it returns the order with status PAID. Ticket, kitchen and customer notifications are triggered at that very moment.
  2. 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.
  3. 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

CodeHTTPMeaning
paygreen_payment_metadata_invalid400The payment metadata is missing, is not valid JSON, or its paygreenPaymentType is not TRD, RESTOFLASH or LUNCHR.
invalid_parameters400Schema violation - e.g. metadata sent as an object instead of a string.
paygreen_not_activated403The Paygreen module is not configured or not active on the restaurant.
payment_method_not_activated403The paymentMethodId is not enabled for the consumption mode of the order.
paygreen_payment_type_not_supported403The requested voucher family is disabled on the restaurant's Paygreen module.
paygreen_payment_refused422Paygreen refused to create the Payment Order (shop not activated, invalid sub-shop…). The order is not created.
required_action200Not an error - the order is created and waits for the customer to pay on paymentUrl (step 3).
omnichannel_order_not_found400On GET /v1/orders/{orderId}: the order is not paid yet (keep polling) or ended refused.