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 need | Example | Note |
|---|---|---|
| EuniPay API service address | https://pay.example.com | Replace with your actual service address |
| This Integration's Test API key | Get it from the EuniPay dashboard | Keep it on the backend, never in the page |
| Your website address | https://merchant.example | Open the payment page from this address |
| Your backend's payment notification address | https://merchant.example/webhooks/eunipay | A 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 item | What to do with it |
|---|---|
| data.checkout_id | EuniPay's payment ID; save it against your own order_123 |
| data.checkout_token | The temporary token needed to display the form in the next step |
| data.settlement | This 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:
- In script, replace
https://pay.example.comwith the actual EuniPay service address. - 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.
- Open Transactions in EuniPay's Test environment, select the correct Integration, and find the payment.
- Check whether your backend received a checkout.paid notification.
- 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:
| When | What your website should do |
|---|---|
| A customer saves billing details | The backend performs step 2 to save or update the customer |
| A customer clicks Pay | The 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 token | It performs step 5 to display the form |
| The backend receives a payment-success notification | It verifies the notification, matches the payment ID, order, and amounts, and marks the order as paid once |
| The customer opens the result page | It 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:
- Compare the complete
v1=<hex digest>value in constant time and reject stale timestamps. - Match data.checkout_id, data.external_order_id, data.status, and payment amounts against the saved order and settlement.
- 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.