Your first test payment

Use a 100.00 SEK test payment to display a payment form on your website and confirm that EuniPay received the payment. A Checkout is a payment attempt here, not proof that an order has been paid.

First walk through it manually, then have your website automate the same steps. A developer needs to take part; these commands do not build your website's order system.

Before you start: prepare four things

Complete the dashboard setup in Quickstart: select Test, save business details and Stripe test credentials, and enable SEK with a configured rate for your Integration. Stripe notification setup is explained at the end.

What you needExampleNote
EuniPay API service addresshttps://pay.example.comReplace with your actual service address
This Integration's Test API keyGet it from the EuniPay dashboardKeep it on the backend, never in the page
Your website addresshttps://merchant.exampleOpen the payment page from this address
Your backend's payment notification addresshttps://merchant.example/webhooks/eunipayA developer must implement it first; EuniPay must be able to reach it

Both domains are examples. Your page does not need to be hosted by EuniPay: the page displays the form; your backend manages customers, orders, and payment results.

A developer runs steps 1–4 in a backend terminal. The HTML in step 5 belongs on your website. Step 6 checks the payment result.

1. Check the service address and key

Where: a backend terminal. Replace the two values below and run the command. Use the same terminal for later commands, which reuse the address and key. Keep the key out of screenshots, logs, and source control.

export EUNIPAY_BASE_URL="https://pay.example.com"
export EUNIPAY_API_KEY="replace-with-your-test-key"

curl -i "$EUNIPAY_BASE_URL/v1/auth-check" \
  -H "Authorization: Bearer $EUNIPAY_API_KEY"

What success looks like: HTTP 204. An empty response body is normal. For a 401, check that the key belongs to the current Test Integration and that the service address is correct.

2. Tell EuniPay who is paying

Where: the backend terminal. Save a test customer's billing details first. customer_123 is the ID you assign to your customer; use it again when creating the payment.

curl --fail-with-body -sS "$EUNIPAY_BASE_URL/v1/buyers/customer_123" \
  -X PUT \
  -H "Authorization: Bearer $EUNIPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "expected_version": 0,
    "entity_type": "personal",
    "name": "Test Customer",
    "email": "customer@example.com",
    "country": "SE",
    "postal_code": "11120",
    "city": "Stockholm",
    "line_1": "Example Street 1"
  }'

What success looks like: a response containing the customer profile and version. expected_version 0 means first creation. If the customer already exists, read the current profile and use its current version to update it instead of creating it again.

The example name and address are for Test only. In your application, these details come from the customer's billing information.

3. Tell EuniPay where to send the payment result

Where: the backend terminal. Replace url with the notification address your backend already provides. Registering an address tells EuniPay where to send notifications; it does not create the receiving endpoint for you.

curl --fail-with-body -sS "$EUNIPAY_BASE_URL/v1/webhook-endpoint" \
  -X PUT \
  -H "Authorization: Bearer $EUNIPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: webhook:merchant-app" \
  -d '{
    "url": "https://merchant.example/webhooks/eunipay",
    "rotate_signing_secret": false
  }'

What success looks like: the response shows the configured receiving address. First registration also returns signing_secret. Save it immediately on your backend to verify later notifications.

Register one active callback per Integration and environment. A new API key in the same Integration does not need separate setup. Registering the same address again without rotation keeps the existing signing secret.

This is EuniPay notifying your website, separate from Stripe notifying EuniPay. Do not mix up their signing secrets.

4. Create a payment for this customer

Where: the backend terminal. The request below creates a payment for order_123, using customer_123 and a payment amount of 100.00 SEK, including VAT.

Before running it, understand these inputs:

  • amount uses the smallest currency unit, so 10000 means 100.00 SEK.
  • Replace browser_origin with the payment page's protocol, domain, and port, without a page path.
  • Replace return_url with the page the customer returns to after payment. It must use the same protocol, domain, and port.
  • location_fixture simulates Sweden in this example. Use it only in Test; remove it in Live and provide the real customer IP.
curl --fail-with-body -sS "$EUNIPAY_BASE_URL/v1/checkouts" \
  -X POST \
  -H "Authorization: Bearer $EUNIPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: checkout:order_123" \
  -d '{
    "external_order_id": "order_123",
    "external_customer_id": "customer_123",
    "payment_currency": "SEK",
    "amount": 10000,
    "tax_semantics": "inclusive",
    "description": "Service purchase",
    "buyer_ip": "203.0.113.10",
    "location_fixture": { "country": "SE" },
    "browser_origin": "https://merchant.example",
    "return_url": "https://merchant.example/orders/order_123"
  }'

What success looks like: a response containing data. Find these three items first:

Response itemWhat to do with it
data.checkout_idEuniPay's payment ID; save it against your own order_123
data.checkout_tokenThe temporary token needed to display the form in the next step
data.settlementThis payment's amounts and tax; save the complete value on the backend for verification

The token is the actual string returned by the API, not the API key or payment ID. Successful creation means the customer can start paying, not that you have received payment.

When retrying the same request after a network failure, keep the order ID and Idempotency-Key. For another new test order, change both together.

5. Display the payment form on your website

Where: your website's payment page. Add the HTML below to the website address you specified in step 4 and open it in a browser. Do not open a local file directly.

Replace only two things:

  1. In script, replace https://pay.example.com with the actual EuniPay service address.
  2. Replace PASTE_CHECKOUT_TOKEN_HERE with the actual data.checkout_token returned in the previous step.

This is a one-off Test demonstration: paste the temporary token manually to see the payment form first. Never paste an API key or commit a page containing a real token. The automated website flow is explained at the end.

<div id="eunipay-checkout"></div>
<button id="confirm-payment" type="button" disabled>Pay</button>
<p id="payment-message" role="status"></p>
<script src="https://pay.example.com/sdk/v1/checkout.js"></script>
<script type="module">
  const button = document.querySelector("#confirm-payment");
  const message = document.querySelector("#payment-message");

  try {
    const checkout = await window.EuniPayCheckout.mount({
      element: document.querySelector("#eunipay-checkout"),
      token: "PASTE_CHECKOUT_TOKEN_HERE",
      locale: "en", // Match your page language. Omit to use the browser language.
    });
    button.disabled = false;

    button.addEventListener("click", async () => {
      button.disabled = true;
      message.textContent = "Submitting payment…";
      try {
        const result = await checkout.confirm();
        const submitted = result?.type === "success";
        message.textContent = submitted
          ? "Payment submitted. Check the server-side payment result."
          : "Payment not submitted. Check the form and try again.";
        button.disabled = submitted;
      } catch {
        message.textContent = "Unable to submit payment. Check the form and try again.";
        button.disabled = false;
      }
    });
  } catch {
    message.textContent = "Unable to load payment form. Check the service address, page origin, and token.";
  }
</script>

What success looks like: payment methods and a Pay button appear. Enter test payment details and click Pay. If the payment method redirects, follow its instructions and return to the page you configured.

This example does not call an unimplemented website endpoint. The token belongs to the payment already created in step 4; you do not need to create another payment just to display its form.

If the form does not load, check the service address, payment-page origin, and whether the token is correct and unexpired. Never replace the payment token with an API key.

6. Confirm receipt of payment, not just browser completion

Where: the EuniPay dashboard and your backend. A browser submission message only means the payment action was submitted. Use EuniPay's confirmed result to determine whether payment succeeded.

  1. Open Transactions in EuniPay's Test environment, select the correct Integration, and find the payment.
  2. Check whether your backend received a checkout.paid notification.
  3. To check this payment's state, replace eco_01J4EXAMPLE below with the actual checkout_id saved in step 4:
curl --fail-with-body -sS "$EUNIPAY_BASE_URL/v1/checkouts/eco_01J4EXAMPLE" \
  -H "Authorization: Bearer $EUNIPAY_API_KEY"

What success looks like: data.status is paid and the response includes data.paid payment details. A manually created test payment does not automatically update an order on your website; your backend must handle the notification to do that.

If the state remains open or processing, check whether Stripe confirmed this actual payment and delivered its notification to EuniPay. If EuniPay shows success but your website has no notification, check the receiving address and handler from step 3. See Troubleshooting for other cases.

Connect the website: automate the manual steps

After the test succeeds, have your developer connect the same flow to your website so that nobody runs commands or copies tokens manually:

WhenWhat your website should do
A customer saves billing detailsThe backend performs step 2 to save or update the customer
A customer clicks PayThe backend authenticates the customer, reads its own order amount, performs step 4, saves the payment ID and settlement, and returns only the temporary token to the page
The payment page receives the tokenIt performs step 5 to display the form
The backend receives a payment-success notificationIt verifies the notification, matches the payment ID, order, and amounts, and marks the order as paid once
The customer opens the result pageIt reads the order state from your backend, without treating a browser redirect or submission message as payment confirmation

Never trust an amount sent directly by the browser. Keep your website's login and request protections. Configure step 3's notification address once per Integration and environment, not for every order.

Call checkout.destroy() when leaving the payment page. If retrying payment creation returns a new token, use checkout.replaceToken() to replace the existing form's token.

This completes the website connection. Without backend order handling, EuniPay can confirm a payment but will not ship goods, add credit, or enable services for you. Before going live, complete the Test and Live checklist.

Developer detail: verify payment notifications

EuniPay includes these headers in a payment-success notification:

EuniPay-Timestamp: <unix timestamp>
EuniPay-Signature: v1=<hex digest>
EuniPay-Event-ID: <stable event id>

On the backend, use the EuniPay signing secret saved in step 3 to calculate HMAC-SHA256 over:

timestamp + "." + raw_body

raw_body must be the original request body as received, not JSON that has been parsed and serialized again. Process it in this order:

  1. Compare the complete v1=<hex digest> value in constant time and reject stale timestamps.
  2. Match data.checkout_id, data.external_order_id, data.status, and payment amounts against the saved order and settlement.
  3. Use EuniPay-Event-ID to identify repeated notifications and fulfil the order once; retries must not credit it again.

Do not use Stripe's signing secret for this notification, or mark an order as paid just because a request arrived. Open the API reference when you need the full field details.

Developer detail: configure Stripe notifications

In EuniPay's Stripe settings, save the Test Secret Key, Publishable Key, and enabled payment methods. Copy the displayed Stripe Webhook URL, register that address in Stripe, and subscribe to these events:

checkout.session.completed
checkout.session.async_payment_succeeded
checkout.session.async_payment_failed
checkout.session.expired

Then copy Stripe's whsec_... signing secret into EuniPay's Webhook Signing Secret field. EuniPay does not create this notification address in Stripe for you.

The Stripe notification tells EuniPay whether payment succeeded. The notification in step 3 delivers EuniPay's confirmed result to your website. Connect both, then check that the full order flow completes.