AnnouncementsIntroducing Arkoren

    Menu

    Mollie Callbacks

    Bound unsigned callback hints, verify saved merchant ownership, and reconcile authoritative provider reads durably.

    What's on this page
    1. Parse bounded unsigned hints without treating their IDs as payment evidence.
    2. Check persisted merchant ownership before reading and durably reconciling provider state.
    3. Reconcile saved refund handles and test duplicate, malformed, and failed callback paths.
    On this page

    Mollie’s classic callback is an unsigned form POST containing a payment ID. Anyone can send one. Treat it as a hint to read provider state, never as evidence that an order was paid. Use a plain application handler, not webhook.Handler: there is no shipped molliewebhook or authenticated Mollie Source.

    Configure the reachable HTTPS callback URL on the Mollie driver before creating checkout. The following fragments assume payments *payment.Client for one fixed merchant and environment, mux *http.ServeMux, and application-owned durable storage. Apply route-level rate limits before mounting the handler; a body limit does not limit request frequency.

    Binding Ownership

    Save each returned payment ID with its order, application account, merchant, and test/live environment. For multiple merchants, select credentials from this persisted association, never from callback input. A valid-looking ID alone must not cause an authenticated lookup under arbitrary credentials.

    The handler uses two application functions, not Arkoren APIs:

    • ownsPayment(context.Context, payment.ID) (bool, error) checks that the ID is saved under the fixed client’s merchant/environment.
    • reconcile(context.Context, payment.Payment) error checks the returned identity, order association, expected amount/currency, and permitted state transition; it commits processing idempotently and durably. Only a matching succeeded payment can satisfy its collection condition.

    An unknown ID is acknowledged without revealing order data. A known ID still requires an authoritative provider read before reconciliation.

    Parsing Hints

    Use a small parser to require exactly one body form field with one valid ID. Import context, errors, net/http, and arkoren.dev/core/service/payment. The caller applies a 1024-byte limit before invoking this helper:

    func readMollieHint(r *http.Request) (payment.ID, error) {
        if err := r.ParseForm(); err != nil {
            return "", err
        }
        ids := r.PostForm["id"]
        if len(r.PostForm) != 1 || len(ids) != 1 {
            return "", errors.New("expected one payment id")
        }
        id := payment.ID(ids[0])
        if err := id.Validate(r.Context()); err != nil {
            return "", err
        }
        return id, nil
    }

    This validates the hint’s shape, not its authenticity. Query parameters do not choose the payment: only the parsed POST body is used.

    Reconciling State

    With the application functions above supplied, mount the handler and reject unusable input before any provider request:

    mux.HandleFunc("POST /payments/mollie", func(w http.ResponseWriter, r *http.Request) {
        r.Body = http.MaxBytesReader(w, r.Body, 1024)
        id, err := readMollieHint(r)
        if err != nil {
            w.WriteHeader(http.StatusBadRequest)
            return
        }
        known, err := ownsPayment(r.Context(), id)
        if err != nil {
            w.WriteHeader(http.StatusServiceUnavailable)
            return
        }
        if !known {
            w.WriteHeader(http.StatusOK)
            return
        }
        reconcileMollieHint(w, r, id, payments, reconcile)
    })

    Only a saved association reaches the authenticated lookup. The next helper uses the same fixed merchant’s payments client and application reconcile:

    func reconcileMollieHint(
        w http.ResponseWriter,
        r *http.Request,
        id payment.ID,
        payments *payment.Client,
        reconcile func(context.Context, payment.Payment) error,
    ) {
        observed, err := payments.GetPayment(r.Context(), id)
        if err != nil {
            w.WriteHeader(http.StatusServiceUnavailable)
            return
        }
        if err := reconcile(r.Context(), observed); err != nil {
            w.WriteHeader(http.StatusServiceUnavailable)
            return
        }
        w.WriteHeader(http.StatusOK)
    }

    These helpers show application collaborators; bind the client and reconciliation function in your handler’s own dependency wiring. A 200 acknowledges durable handling or an intentionally ignored unknown ID, not successful collection. Provider read/reconciliation failures return 503 to permit retries. A malformed or unusable successful GET response wraps payment.ErrUnavailable; it cannot supply payment evidence or resolve an earlier uncertain mutation.

    Duplicate and delayed hints must not repeat fulfillment. Commit the order transition and fulfillment handoff atomically, or use a durable outbox. A callback can fail after a partial application effect; acknowledgement alone is not a transaction boundary.

    Following Refunds

    Refund callbacks contain the payment ID, not a compound refund handle. Read the refund IDs you already saved with GetRefund to reconcile returned funds; do not reconstruct them from the hint. Payment Refunds distinguishes pending progress from confirmed refund success. Next-gen Mollie webhooks are not implemented.

    Test malformed/repeated IDs, unknown IDs, wrong merchant/environment, outages, malformed successful GET bodies, unusable snapshots, duplicates, and recording failures. None may grant fulfillment without a matching succeeded payment. Use Testing for provider outcomes and net/http/httptest for parsing and HTTP responses; no callback signature should be expected.

    For callback configuration and snapshot behavior, see the Mollie payment API.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive