Skip to main content

Payments

Quark Commerce supports multiple payment providers configured per-store. The checkout flow handles payment initiation; this page covers the payment completion and webhook handling.

Supported Providers​

ProviderMarketsFeatures
StripeGlobalCredit cards, Apple Pay, Google Pay, 3D Secure
IyzicoTurkeyCredit cards, installments, local payment methods

Payment Flow​

Stripe Integration​

Client-Side (JavaScript/TypeScript)​

import { loadStripe } from '@stripe/stripe-js';

const stripe = await loadStripe('pk_live_...');

// After checkout confirm returns clientSecret
const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: { name: 'John Doe' }
}
});

if (error) {
// Show error to user
} else if (paymentIntent.status === 'succeeded') {
// Confirm with API
await fetch('/api/storefront/payments/confirm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionId: checkoutSessionId,
paymentIntentId: paymentIntent.id
})
});
}

Starting a Payment: payment/initialize​

Every provider is started with the same call. Note that it is addressed by basket id, unlike the other checkout endpoints:

POST /api/storefront/checkout/{basketId}/payment/initialize
{
"methodId": "<id from GET /checkout/payment-methods>",
"returnUrl": "myapp://checkout/payment-result",
"buyerName": "Ayşe", "buyerSurname": "Yılmaz", "buyerEmail": "ayse@example.com", "buyerPhone": "+905551112233",
"invoiceType": "individual", "identityNumber": "…"
}

There are no card fields: the shopper enters the card on the payment provider's own page, so card data never passes through your client or the API. The buyer fields are optional overrides of what the checkout session already knows. For a corporate invoice send invoiceType: "corporate" with invoiceCompanyName, a 10-digit taxNumber and taxOffice. The amount charged is always the checkout session's total (items − discounts + shipping) — the client never sends an amount.

The response tells the client what to do next:

nextActionProviderWhat the client does
redirectIyzico (Checkout Form), PaytenNavigate the browser or web view to redirectUrl — the provider's payment page
redirect_htmlProviders that return a ready pageShow htmlContent in a browser context or web view
redirect_form_postEstPOS (tr_est_v3)Post formFields to formAction (formMethod), e.g. from an auto-submitting form
bank_transferBank transfer / EFTNothing to redirect: the order exists already. Show orderNumber and bankDetails
client_integrationStripeContinue with the provider's client SDK (above); provider data, when any, is in clientData

This set is closed: a client that handles these values works with every payment provider, including ones added later.

3-D Secure and where the shopper ends up​

After the shopper confirms with their bank, the gateway sends the browser back to the API — /api/storefront/payments/return/{methodId} (Iyzico and EstPOS still use /api/storefront/payments/callback/{provider}). The API verifies the payment with the provider, server to server, checks that the amount charged matches the checkout total, creates the order once (a repeated callback returns the same order), and redirects the shopper to a result address with the outcome in the query string:

…?status=success&orderId=<guid>&orderNumber=ORD-20260921-482137
…?status=failure&error=<message or code>
…?status=pending&reason=<code>

pending means the money may have moved but there is no order yet — the gateway has not decided (payment_pending), or the charged amount did not match and the store will follow up (payment_under_review). Tell the shopper the payment is being checked; never that it failed.

That address is, in order of preference:

  1. the returnUrl sent with payment/initialize — for apps (myapp://…) and for storefronts on several domains;
  2. {Store.Url}/checkout/payment-result;
  3. {FrontendUrl}/checkout/payment-result from the API's configuration.

Because the API redirects to it, a returnUrl must be allow-listed or the call is refused with 400: anything under the store's own Url is allowed, and further prefixes are listed by the operator in Checkout:AllowedReturnUrls (see the configuration reference).

The callback always ends in that redirect — also when something goes wrong — so a client can rely on it. A mobile app loads the 3-D Secure page in a web view, watches navigations, and treats the first one to its returnUrl as the result instead of loading it.

caution

Never treat the redirect alone as proof of payment in a system of record. It tells the client what to show; the order and its paymentStatus in the API are the truth.

Bank transfer​

The order is created at once with paymentStatus: "PendingPayment"; the merchant marks it paid when the money arrives. bankDetails is the key–value list configured on the payment method (bank, account holder, IBAN, …) — show it as it comes.

Payment Statuses​

StatusDescription
PendingPayment initiated, awaiting completion
PaidPayment successfully received
FailedPayment failed or declined
RefundedPayment refunded (full or partial)

Webhook Handling​

Payment providers send webhooks to confirm payment status asynchronously. The API handles these at:

  • Gateways on the payments core (e.g. Payten): POST /api/storefront/payments/notify/{methodId} — register this address in the gateway's panel. It answers 200 once a notification is handled and 500 only when a retry may help.
  • Stripe: POST /api/storefront/payments/webhook
  • Iyzico: POST /api/storefront/payments/callback/iyzico
  • EstPOS: POST /api/storefront/payments/callback/tr_est_v3
info

Webhooks serve as a backup confirmation mechanism. The primary flow is the client-side confirmation call (POST /payments/confirm). Webhooks handle edge cases where the user closes the browser before confirming.