Crystallize logo

Qliro

Qliro is a Swedish payment provider. Its embedded checkout, Qliro Checkout (also known as Qliro One), puts pay-later options (invoice and part payment), card payments, and other Nordic payment methods in a single iframe. Follow the steps in this guide to set up Qliro payments and connect them to your frontend.

Getting Qliro Credentials

To get started, contact Qliro to open a merchant account. You will get access to a test environment first. Once your account is set up, you will need:

  • an API key: this key identifies your store. It is sent as MerchantApiKey in the body of each order creation request.
  • an API secret: by nature, it should be kept secret server-side. It is used to sign every request your service layer sends to Qliro.
  • a base URL: https://pago.qit.nu for the test environment and https://payments.qit.nu for production.

Qliro does not use bearer tokens. Every request carries an Authorization: Qliro <token> header. The token is the Base64-encoded SHA-256 hash of the JSON payload followed by your API secret (see Qliro's authorization guide).

Qliro has its own flow and concepts, which are described in Qliro's developer documentation.

Next.js Furnitut Accelerator - Example

The Furnitut Next.js accelerator includes a full Qliro integration, including a confirmation webhook. It reads the credentials from the QLIRO_BASE_URL, QLIRO_API_KEY, and QLIRO_API_SECRET environment variables.

Before the payment step, the checkout form sets the customer on the cart and places it with the Shop API. Placing the cart makes it immutable. The same step also creates the customer in Crystallize.

On the checkout page, we render the Qliro component, which does 2 things:

  • creates a Qliro order (server-to-server) via the service layer. This is Qliro's equivalent of a payment intent.
  • renders the checkout HTML snippet (OrderHtmlSnippet) returned by Qliro. The snippet contains <script> tags, which the browser does not run when they are set through innerHTML, so the component re-creates them.

To create the Qliro order, the service layer:

  • fetches the cart's order intent from the Shop API
  • maps the cart items to Qliro OrderItems (SKU, name, quantity, and prices with and without VAT)
  • prefills the customer's email and addresses, so the customer does not have to type them again
  • uses the first 25 characters of the cart ID as MerchantReference (Qliro's maximum length) and stores the full cart ID in MerchantProvidedMetadata
  • points MerchantConfirmationUrl to the /order/cart/${cartId} page and the push URLs to the webhook endpoint
  • sends the order to Qliro with a small signed client, then fetches it back (GetOrder) to get the HTML snippet

In this accelerator, the cart lives in Crystallize, and we push the order to Crystallize only when the payment is successful.

When the customer completes the purchase in the Qliro iframe, Qliro redirects them to the /order/cart/${cartId} page. This page waits for the cart to be saved as an order in Crystallize.

At the same time, Qliro calls the webhook endpoint with a checkout status notification. Qliro does not sign these notifications, so the endpoint does not trust the payload. It fetches the order again from Qliro with a signed request.

When the order's CustomerCheckoutStatus is Completed, the endpoint:

  • reads the cart ID from the order metadata
  • creates the order in Crystallize with a Custom payment (PaymentMethod: Qliro)
  • fulfills the cart with the new order ID, allowing the waiting page to update to the order confirmation

Other statuses (InProcess, OnHold, Refused) are ignored. An OnHold order gets a new notification when it moves to Completed or Refused.

Going Further with Qliro

The accelerator covers the checkout. Qliro Checkout offers a lot more that you can connect to Crystallize:

  • Order management: after the purchase, Qliro's Admin API lets you mark items as shipped (which captures the payment), cancel orders, handle returns and refunds, and update items. You can drive these calls from Crystallize fulfilment pipelines: for example, a webhook on the "Shipped" stage calls Qliro, and Qliro reports the result to your MerchantOrderManagementStatusPushUrl. To do this, save the Qliro OrderId on the Crystallize order, for example as a property of the Custom payment. See Qliro order management.
  • Order validation: set MerchantOrderValidationUrl so that Qliro asks your service layer to confirm stock and prices just before the purchase is completed.
  • Shipping: Qliro Checkout can show shipping choices, from a static AvailableShippingMethods list, from a dynamic MerchantOrderAvailableShippingMethodsUrl, or through shipping integrations such as Ingrid and Unifaun.
  • Thank-you page: once the order is completed, GetOrder returns a new HTML snippet with Qliro's thank-you page, which you can render on your confirmation page. See rendering the thank-you page.
  • Customer and B2B options: lock prefilled fields (LockCustomerEmail, LockCustomerAddress, and others), accept only companies with EnforcedJuridicalType, require BankID verification in Sweden with RequireIdentityVerification, or set a MinimumCustomerAge.
  • Look and feel: match your brand with PrimaryColor, CallToActionColor, BackgroundColor, CornerRadius, and ButtonCornerRadius.
  • Payment link: instead of embedding the iframe, redirect the customer to the PaymentLink returned when the order is created.
  • Frontend listeners: Qliro's Frontend API lets your page react to what happens in the iframe, such as a new address or payment method, and keep your UI in sync.
  • Markets: the accelerator sends a fixed country, currency, and language (NO, NOK, en-us). In production, derive them from the cart's market and locale.

Things to Keep in Mind

  • Push notifications are not authenticated. Always fetch the order again with GetOrder before acting on a notification, as the accelerator does. You can also add a short-lived token to your push URLs.
  • Qliro may send the same notification more than once, and retries failed notifications for up to 3 days. Make your webhook idempotent: discard duplicates using OrderId, status, and Timestamp, or check that the cart has not already become an order.
  • Use the items returned by GetOrder as the source of truth for what the customer actually paid for.
  • Qliro's push URLs should use HTTPS and must be reachable from the internet. While developing locally, use a tunnel such as ngrok.