> ## Documentation Index
> Fetch the complete documentation index at: https://developers.ligdicash.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Common pitfalls — hosted payin

> Classic mistakes to avoid with LigdiCash hosted payin: blocked iframe, non-empty customer, blocked popup, client-side validation, and more.

Reference page listing the most frequent mistakes made when integrating hosted payin. Each pitfall describes the observed symptom, the cause, and the correction to apply.

<AccordionGroup>
  <Accordion title="customer not empty — operators hidden on the payment page">
    **Symptom**: the LigdiCash payment page only shows one operator, or none, while several are available in your region.

    **Cause**: if the `customer` field contains a phone number, LigdiCash filters the page to only show operators compatible with that number. The other operators are hidden.

    **Fix**: always send `customer: ""` in the invoice creation request.

    ```json theme={null}
    {
      "commande": {
        "invoice": {
          "customer": "",
          ...
        }
      }
    }
    ```

    <Note>
      The `customer_firstname`, `customer_lastname`, and `customer_email` fields can be filled without affecting the display of operators. Only `customer` (the phone number) is involved.
    </Note>
  </Accordion>

  <Accordion title="Blocked iframe — blank page or console error">
    **Symptom**: the iframe stays empty, or the browser console shows an error like `Refused to display … in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'`.

    **Cause**: LigdiCash deliberately blocks the loading of its payment page in an `<iframe>`. This is a security measure against clickjacking, not a bug.

    **Fix**: open the payment URL in a real navigation context.

    | Mode          | Implementation                    |
    | ------------- | --------------------------------- |
    | Same tab      | `window.location.href = url`      |
    | New tab       | `window.open(url, '_blank')`      |
    | Popup         | `about:blank` pattern (see below) |
    | Native mobile | Native iOS / Android WebView      |

    See [Redirect the customer](/en/payment-api/hosted-payin/redirect-customer) for the full implementations.
  </Accordion>

  <Accordion title="Popup blocked by the browser — window.open() returns null">
    **Symptom**: `window.open()` returns `null` and nothing opens. No visible error for the user.

    **Cause**: browsers block calls to `window.open()` that are not directly triggered by a user gesture. A call made after an `await fetch(...)` is considered asynchronous and blocked.

    **Fix**: open `about:blank` synchronously on click, then navigate to the URL once the API response is received.

    ```javascript JavaScript theme={null}
    element.addEventListener("click", async () => {
      // Synchronous — before any await
      const popup = window.open("about:blank", "payment", "width=500,height=700");

      const data = await createInvoice();

      if (data.response_code === "00") {
        popup.location.href = data.response_text;
      } else {
        popup.close();
      }
    });
    ```

    See [Redirect the customer](/en/payment-api/hosted-payin/redirect-customer) for the full pattern.
  </Accordion>

  <Accordion title="Validating the payment based on return_url or cancel_url only">
    **Symptom**: orders are fulfilled without a real payment, or the order state is wrong after cancellation.

    **Cause**: `return_url` and `cancel_url` are simply browser redirects. Anyone who knows the URL can navigate to it directly — they are not proof of payment status.

    **Fix**: in both cases (`return_url` and `cancel_url`), always verify the real transaction status by calling `confirm` server-side before making any business decision.

    ```javascript JavaScript theme={null}
    // Whether arriving via return_url or cancel_url
    const confirmation = await callBackend("/verify-payment", { token });

    if (confirmation.status === "completed") {
      showSuccess();
    } else {
      showFailure();
    }
    ```

    <Warning>
      Never infer payment status from the redirect URL. The only reliable status is the one returned by the `confirm` endpoint.
    </Warning>
  </Accordion>

  <Accordion title="Using the callback token to call confirm">
    **Symptom**: reconciliation with your order fails, or you confirm the wrong transaction.

    **Cause**: the `token` present in the callback payload is different from the token returned at invoice creation. Both let you call `confirm`, but only the creation token lets you link the response to your merchant-side order.

    **Fix**: store the token returned by the `create` endpoint in your database as soon as the invoice is created, and use it preferentially to call `confirm`.

    ```javascript JavaScript theme={null}
    // At creation
    const { token, response_text } = await createInvoice(order);
    await db.save({ orderId, token, paymentUrl: response_text });

    // In the callback handler or after return_url
    const { token } = await db.findByOrderId(orderId);
    const confirmation = await confirmPayment(token);
    ```

    See [The transaction\_id pattern](/en/concepts/transaction-id-pattern) for the recommended reconciliation strategy.
  </Accordion>

  <Accordion title="cancel_url and return_url swapped">
    **Symptom**: the user lands on the success page after cancelling, or on the cancellation page after a successful payment.

    **Cause**: the two fields are easily confused. The older LigdiCash documentation even had them swapped in its examples.

    **Fix**:

    * `return_url` → URL where LigdiCash redirects after a **successful payment**
    * `cancel_url` → URL where LigdiCash redirects after a **cancellation**

    ```json theme={null}
    {
      "actions": {
        "return_url": "https://myapp.com/payment/success",
        "cancel_url": "https://myapp.com/payment/cancel",
        "callback_url": "https://myapp.com/api/callback/ligdicash"
      }
    }
    ```

    <Warning>
      Whatever the landing URL, always verify the real status with `confirm` server-side before showing a final result to the user.
    </Warning>
  </Accordion>
</AccordionGroup>

## Related pages

* [Redirect the customer](/en/payment-api/hosted-payin/redirect-customer) — opening patterns and anti-blocker popup
* [Verify the status](/en/payment-api/hosted-payin/verify-status) — calling `confirm` correctly
* [Callback — security](/en/payment-api/callback/security) — re-verification and handler protection
* [The transaction\_id pattern](/en/concepts/transaction-id-pattern) — merchant-side reconciliation
