Use external payment frame
Overview
Learn how to implement a checkout flow using an external payment provider frame. This guide covers creating a checkout session, selecting shipping, displaying the payment frame, and completing the order.
Prerequisites
- Merchant API key
- Existing cart with items
- Payment provider configured with frame/iframe support
Goal
- Create a checkout session and retrieve payment frame HTML
- Display the payment frame in your frontend
- Complete the cart after payment
Architecture at a glance
- Create checkout → Select shipping → Get payment frame → Display frame → Complete cart
Step-by-step
Create checkout and get shipping options
Start by creating a checkout session. This will return available shipping and payment options.
Request example
mutation createOrUpdateCheckout(
$cartId: String!
$checkout: CheckoutInputType
$channelId: String
$languageId: String
$marketId: String
) {
createOrUpdateCheckout(
cartId: $cartId
checkout: $checkout
channelId: $channelId
languageId: $languageId
marketId: $marketId
) {
cart {
id
summary {
total {
regularPriceIncVat
}
}
}
shippingOptions {
id
displayName
feeIncVat
isSelected
}
paymentOptions {
id
displayName
paymentType
isSelected
paymentData
}
}
}
{
"Accept": "application/json",
"X-ApiKey": "{MERCHANT_API_KEY}"
}
{
"cartId": "{CART_ID}",
"channelId": "{CHANNEL_ID}",
"languageId": "{LANGUAGE_ID}",
"marketId": "{MARKET_ID}"
}
curl -X POST https://merchantapi.geins.io/graphql \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-ApiKey: {MERCHANT_API_KEY}" \
-d '{"query":"mutation createOrUpdateCheckout($cartId: String!, $checkout: CheckoutInputType, $channelId: String, $languageId: String, $marketId: String) { createOrUpdateCheckout(cartId: $cartId, checkout: $checkout, channelId: $channelId, languageId: $languageId, marketId: $marketId) { cart { id summary { total { regularPriceIncVat } } } shippingOptions { id name description fee isSelected } paymentOptions { id name paymentType isSelected paymentData } } }","variables":{"cartId":"{CART_ID}","channelId":"{CHANNEL_ID}","languageId":"{LANGUAGE_ID}","marketId":"{MARKET_ID}"}}'
Response example
200 OK{
"data": {
"createOrUpdateCheckout": {
"cart": {
"id": "{CART_ID}",
"summary": {
"total": {
"regularPriceIncVat": 299
}
}
},
"shippingOptions": [
{
"id": 1,
"displayName": "Standard",
"feeIncVat": 59,
"isSelected": true
},
{
"id": 7,
"displayName": "Store pickup",
"feeIncVat": 0,
"isSelected": false
}
],
"paymentOptions": [
{
"id": 23,
"displayName": "Klarna Checkout",
"paymentType": "KLARNA",
"isSelected": false,
"paymentData": null
},
{
"id": 27,
"displayName": "Geins Pay",
"paymentType": "GEINS_PAY",
"isSelected": false,
"paymentData": null
},
]
}
}
}
Select shipping and payment options to get payment frame
Update the checkout with the selected shipping method and payment option to get the payment frame HTML.
Request example
mutation createOrUpdateCheckout(
$cartId: String!
$checkout: CheckoutInputType
$channelId: String
$languageId: String
$marketId: String
) {
createOrUpdateCheckout(
cartId: $cartId
checkout: $checkout
channelId: $channelId
languageId: $languageId
marketId: $marketId
) {
cart {
id
summary {
total {
regularPriceIncVat
}
}
}
paymentOptions {
id
name
displayName
paymentType
isSelected
paymentData
}
}
}
{
"Accept": "application/json",
"X-ApiKey": "{MERCHANT_API_KEY}"
}
{
"cartId": "{CART_ID}",
"checkout": {
"shippingId": {SHIPPING_ID},
"paymentId": {PAYMENT_ID}
},
"channelId": "{CHANNEL_ID}",
"languageId": "{LANGUAGE_ID}",
"marketId": "{MARKET_ID}"
}
curl -X POST https://merchantapi.geins.io/graphql \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-ApiKey: {MERCHANT_API_KEY}" \
-d '{"query":"mutation createOrUpdateCheckout($cartId: String!, $checkout: CheckoutInputType, $channelId: String, $languageId: String, $marketId: String) { createOrUpdateCheckout(cartId: $cartId, checkout: $checkout, channelId: $channelId, languageId: $languageId, marketId: $marketId) { cart { id summary { total { regularPriceIncVat } } } paymentOptions { id name paymentType isSelected frame } } }","variables":{"cartId":"{CART_ID}","checkout":{"shippingId":{SHIPPING_ID},"paymentId":{PAYMENT_ID},"channelId":"{CHANNEL_ID}","languageId":"{LANGUAGE_ID}","marketId":"{MARKET_ID}"}}'
Response example
200 OK{
"data": {
"createOrUpdateCheckout": {
"cart": {
"id": "{CART_ID}",
"summary": {
"total": {
"regularPriceIncVat": 348
}
}
},
"paymentOptions": [
{
"id": {PAYMENT_ID},
"displayName": "Klarna Checkout",
"paymentType": "KLARNA",
"isSelected": true,
"paymentData": "<div id=\"klarna-checkout-container\"><!-- Klarna iframe HTML --></div><script>/* Klarna script */</script>"
}
]
}
}
}
Display the payment frame
Inject the frame HTML into your checkout page. The payment provider's frame will handle customer payment processing. As long as you haven't completed the purchase in the frame, you can still update the checkout by calling createOrUpdateCheckout again.
createOrUpdateCheckout again to refresh the payment frame with the updated total.Get and display confirmation frame
To get the confirmation frame (if your payment provider supports it), you will need your external order ID. Most likely you will have set up your callback URL to include the external order ID and other valuable information as query parameters. For example:
https://yourshop.com/checkout/confirmation?externalOrderId={EXTERNAL_ORDER_ID}&cartId={CART_ID}&paymentType={PAYMENT_TYPE}
Get the confirmation frame by calling the checkout query with the external order ID and payment type. Then, display the confirmation frame HTML on your confirmation page.
Request example
query checkout(
$id: String!
$cartId: String
$paymentType: PaymentType!
$channelId: String
$languageId: String
$marketId: String
) {
checkout(
id: $id
cartId: $cartId
paymentType: $paymentType
channelId: $channelId
languageId: $languageId
marketId: $marketId
) {
htmlSnippet
order {
orderId
}
}
}
{
"Accept": "application/json",
"X-ApiKey": "{MERCHANT_API_KEY}"
}
{
"id": "{EXTERNAL_ORDER_ID}",
"cartId": "{CART_ID}",
"paymentType": "{PAYMENT_TYPE}",
"channelId": "{CHANNEL_ID}",
"languageId": "{LANGUAGE_ID}",
"marketId": "{MARKET_ID}"
}
curl -X POST https://merchantapi.geins.io/graphql \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-ApiKey: {MERCHANT_API_KEY}" \
-d '{"query":"query checkout($id: String!, $cartId: String, $paymentType: PaymentType!, $channelId: String, $languageId: String, $marketId: String) { checkout(id: $id, cartId: $cartId, paymentType: $paymentType, channelId: $channelId, languageId: $languageId, marketId: $marketId) { htmlSnippet order { orderId publicId } } }","variables":{"id":"{EXTERNAL_ORDER_ID}","cartId":"{CART_ID}","paymentType":"{PAYMENT_TYPE}","channelId":"{CHANNEL_ID}","languageId":"{LANGUAGE_ID}","marketId":"{MARKET_ID}"}}'
Response example
200 OK{
"data": {
"checkout": {
"htmlSnippet": "<div id=\"klarna-checkout-confirmation\"><!-- Confirmation HTML --></div>",
"order": {
"orderId": "12345"
}
}
}
}
htmlSnippet field contains the confirmation frame HTML. Display this on your confirmation page. The paymentType should match the payment method used (e.g. GEINS_PAY, KLARNA, etc.).Complete the cart
After displaying the confirmation frame, mark the cart as completed to make it read-only.
mutation completeCart(
$id: String!
$channelId: String
$languageId: String
$marketId: String
) {
completeCart(
id: $id
channelId: $channelId
languageId: $languageId
marketId: $marketId
) {
id
completed
}
}
{
"Accept": "application/json",
"X-ApiKey": "{MERCHANT_API_KEY}"
}
{
"id": "{CART_ID}",
"channelId": "{CHANNEL_ID}",
"languageId": "{LANGUAGE_ID}",
"marketId": "{MARKET_ID}"
}
curl -X POST https://merchantapi.geins.io/graphql \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-ApiKey: {MERCHANT_API_KEY}" \
-d '{"query":"mutation completeCart($id: String!, $channelId: String, $languageId: String, $marketId: String) { completeCart(id: $id, channelId: $channelId, languageId: $languageId, marketId: $marketId) { id completed } }","variables":{"id":"{CART_ID}","channelId":"{CHANNEL_ID}","languageId":"{LANGUAGE_ID}","marketId":"{MARKET_ID}"}}'
Options
Session persistence
The checkout session is automatically maintained server-side. You can call createOrUpdateCheckout multiple times with the same cartId to update customer information, change shipping, or switch payment methods. The payment frame will be refreshed automatically.
Multi-market support
All mutations support optional parameters for multi-market functionality:
channelId: Target specific sales channelslanguageId: Set content languagemarketId: Target specific markets
Authenticated access
While authentication is not required for this mutation, including a JWT bearer token in the Authorization header can provide personalized results based on the authenticated user's context, for example personalized pricing.
To include authentication, add the JWT bearer token to your request headers:
"Authorization": "Bearer {JWT_TOKEN}"
Common pitfalls
- Ensure the payment provider is properly configured in your Geins backend
- Always use HTTPS when displaying payment frames for security
- The payment frame must be inserted into the DOM exactly as returned by the API
- Don't forget to call
completeCarton the confirmation page to finalize the order