Skip to main content

Accept an Online Payment

Securely accept payments online.

Build a payment form or use a prebuilt checkout page to start accepting online payments.

Qashier-hosted page​

This is a Qashier-hosted page for when platform is web. It looks like this image below.

Online Payment Checkout Session

The payment methods in this image are illustrative. What your customer sees is whatever the merchant has enabled, which varies by account and country — see Payment methods.

Base URL​

https://api.qashier.com/v3/payment/qpay

Authentication​

API requests are authenticated using HTTP Basic Auth. Provide your API key as the username and leave the password empty:

curl https://api.qashier.com/v3/payment/qpay/checkout/sessions \
-u YOUR_API_KEY:

Alternatively, construct the header manually:

Authorization: Basic <base64(YOUR_API_KEY:)>

Rate Limits​

  • 100 requests per second per API key

If you exceed the rate limit, the API returns a 429 Too Many Requests response with a rate_limit_error.

Error Handling​

Error responses follow this structure:

{
"error": {
"type": "invalid_request_error",
"code": "parameter_missing",
"message": "Missing required parameter: return_url",
"param": "return_url"
}
}
TypeDescription
api_errorA temporary problem with Qashier's servers
authentication_errorFailure to properly authenticate the request
idempotency_errorAn Idempotency-Key was re-used with different parameters
invalid_request_errorThe request has invalid or missing parameters
rate_limit_errorToo many requests sent too quickly

Redirect your customer to Qashier Checkout [Client-side] [Server-side]​

Add a checkout button to your website that calls a server-side endpoint to create a Checkout Session.

<html>
<head>
<title>Buy cool new product</title>
</head>
<body>
<form action="/create-checkout-session" method="POST">
<button type="submit">Checkout</button>
</form>
</body>
</html>

A Checkout Session is the programmatic representation of what your customer sees when they're redirected to the payment form. You can configure it with options such as:

  • Line items to charge
  • Currency to use (ISO 4217 currency code format, e.g., "SGD" for Singapore Dollar)

You must populate success_url with the URL value of a page on your website that Checkout returns your customer to after they complete the payment.

Checkout Sessions expire 24 hours after creation by default.

After creating a Checkout Session, redirect your customer to the url of CheckoutSession returned in the response.

// This example sets up an endpoint using the Express framework.

const express = require('express');
const fetch = require('node-fetch');
const app = express();
const url = "https://api.qashier.com/v3/payment/qpay/checkout/sessions";

app.post('/create-checkout-session', async (req, res) => {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + Buffer.from('YOUR_API_KEY:').toString('base64')
},
body: JSON.stringify({
currency: 'SGD', // ISO 4217 currency code (e.g., 'SGD', 'MYR', 'INR')
line_items: [
{
unit_amount: 2000,
quantity: 1,
},
],
expires_at: Math.floor(Date.now() / 1000) + 86400, // 24 hours from now (max allowed)
return_url: 'http://localhost:4242/return',
success_url: 'http://localhost:4242/success',
})
});

const data = await response.json();
res.redirect(303, data.url);
});

app.listen(4242, () => console.log(`Listening on port ${4242}!`));

Payment methods​

You do not choose the payment methods. Qashier Checkout renders whatever the merchant has enabled on their account, and the available set differs by country — a Singapore merchant and a Thai one are offered different methods, and both change over time as we add support for new ones.

This means your integration does not need to change when a method is added or removed: you create the session, we render what applies, and the same checkout.session.completed tells you when the attempt has finished.

Completed does not mean paid. A declined payment also completes the session and emits checkout.session.completed — the two outcomes differ only in data.object.payment_status, which is paid on success and unpaid on a decline. Fulfil on payment_status, never on the event name alone.

Abandonment produces no event at all. A session that reaches expires_at without a payment expires silently — checkout.session.expired is only sent when you expire a session yourself through the API. To react to abandoned checkouts, retrieve the session after expires_at and confirm status: expired before treating it as abandoned. Do not infer abandonment from webhook silence alone: a delayed checkout.session.completed could otherwise misclassify a paid order.

To see what a specific account will offer, create a test Checkout Session against it and open the returned URL — the checkout renders exactly what that account has enabled, which no list here could stay true to. If a method you expect is missing, it is an account setting rather than an integration problem; contact your Qashier representative to have it enabled.

Confirm your endpoint​

Confirm your endpoint is accessible by starting your web server (for example, localhost:4242) and running the following command:

curl -X POST -is "http://localhost:4242/create-checkout-session" -d ""

You should see a response in your terminal that looks like this:

HTTP/1.1 303 See Other
Location: https://qpay.qashier.com/c/pay/cs_test_...
...

Testing​

You should now have a working checkout button that redirects your customer to Qashier Checkout.

  1. Click the checkout button.
  2. You're redirected to the Qashier Checkout payment form.

If your integration isn't working:

  1. Open the Network tab in your browser's developer tools.
  2. Click the checkout button and confirm it sent an XHR request to your server-side endpoint (POST /create-checkout-session).
  3. Verify the request is returning a 200 status.
  4. Use console.log(data) inside your button click listener to confirm the correct data returned.

Show a success page [Client-side] [Server-side]​

It's important for your customer to see a success page after they successfully submit the payment form. Host this success page on your site.

Create a minimal success page:

<html>
<head><title>Thanks for your order!</title></head>
<body>
<h1>Thanks for your order!</h1>
<p>
We appreciate your business!
If you have any questions, please email
<a href="mailto:orders@example.com">orders@example.com</a>.
</p>
</body>
</html>

Next, find the new transaction in the Qashier HQ Dashboard. Successful payments appear in the Dashboard's Transaction. When you click a payment, it takes you to the payment details page.

Handle post-payment events​

Qashier sends a checkout.session.completed event when a payment attempt on a Checkout Session finishes — on success and on decline alike. Check data.object.payment_status before acting on it: paid means money moved, unpaid means the attempt failed. On a paid event it might trigger you to:

  • Send an order confirmation email to your customer.
  • Log the sale in a database.
  • Start a shipping workflow.

Listen for these events rather than waiting for your customer to be redirected back to your website. Triggering fulfilment only from your Checkout landing page is unreliable. Setting up your integration to listen for asynchronous events.

Handle the following events when collecting payments with the Checkout:

EventDescriptionAction
checkout.session.completedSent when a payment attempt finishes, whether it succeeded or was declined. data.object.payment_status is paid on success and unpaid on a decline.Only when payment_status is paid: send the customer an order confirmation and fulfill (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) their order.
checkout.session.expiredSent when you expire a session through the expire API. A session that reaches expires_at on its own expires without emitting this event.

Test your integration​

To test your Qashier-hosted payment form integration:

  1. Create a Checkout Session.
  2. Fill out the payment details with a method from the following table, using the CVV and Expiry that row specifies.
    • Enter any billing postal code.
  3. Click Pay. You're redirected to your success_url.
  4. Go to Qashier HQ and look for the payment on the Transactions page. If your payment succeeded, you'll see it in that list.
  5. Click your payment to see more details, like a Checkout summary with billing information and the list of purchased items. You can use this information to fulfil the order.

Cards​

Test card numbers are specific to the account you are testing against, because the card processor behind an account differs by country. Using a number from the wrong row fails at the processor, not in your integration — so check the country column before you conclude something is broken.

CountryCard NumberCVVExpiryBrandDescription
SG, MY400552000000012900210/30VisaSuccessful transaction
SG, MY541333008901064000210/30MastercardSuccessful transaction
SG, MY4147463011110083AnyAnyVisa3DS frictionless — authenticated
SG, MY5239290700000028AnyAnyMastercard3DS frictionless — authenticated
SG, MY4147463011110091AnyAnyVisa3DS frictionless — not authenticated
SG, MY5239290700000036AnyAnyMastercard3DS frictionless — not authenticated
SG, MY4147463011110117AnyAnyVisa3DS frictionless — attempted
SG, MY5239290700000044AnyAnyMastercard3DS frictionless — attempted
SG, MY4147463011110042AnyAnyVisa3DS frictionless — rejected
SG, MY5239290700000051AnyAnyMastercard3DS frictionless — rejected
SG, MY4147463011110109AnyAnyVisa3DS challenge — authenticated
SG, MY4147463011110034AnyAnyVisa3DS challenge — rejected
SG, MY5239290700000010AnyAnyMastercard3DS challenge — rejected
TH4111111111111111123AnyVisaSuccessful transaction
TH5372074248113841123AnyMastercardSuccessful transaction
TH4953261730509988123AnyVisa3DS challenge before it completes
TH4943129900084541123AnyVisaDeclined — insufficient funds
TH4111111111000025123AnyVisaDeclined by the issuer

Any means any value works — any future expiry, any CVV of the right length.

The 3DS rows exercise authentication only and are not built to carry a payment through to settlement. Frictionless means the issuer decides without asking the customer anything; challenge puts a step in front of them, and is the path worth running because it is the only one where your success_url is reached later than you might assume. The Thailand 3DS challenge row is the equivalent.

The two Declined rows still emit checkout.session.completed — with payment_status: unpaid, as described in Handle post-payment events. They are the rows to use when testing that your fulfilment logic reads payment_status rather than the event name.

Cards are only one method. Testing a QR or wallet method does not involve a card number at all: complete the payment in the app the checkout hands you to. The webhook behaves the same for every method — checkout.session.completed fires when the attempt finishes, and payment_status says whether it was paid.

Disclose Qashier to your customers​

Qashier collects information on customer interactions with Elements to provide services to you, prevent fraud, and improve its services. This includes using cookies and IP addresses to identify which Elements a customer saw during a single checkout session. You're responsible for disclosing and obtaining all rights and consents necessary for Qashier to use data in these ways. For more information, visit our TBD privacy center.


API Reference​

For full endpoint details, request/response schemas, and webhook specifications, see the Online Payments API Reference.

Was this page helpful?