#I'm extremely lost and don't know what are the steps to follow, I don't know how to use this process

1 messages · Page 1 of 1 (latest)

grizzled acornBOT
#

You’re mixing two different flows. For No‑Code Checkout, you do not initialize the JS processor.

Correct No‑Code Checkout (start → finish)

  1. Create a product

    • Type: One‑time payment
    • Set price, currency, tax category
    • Status must be Active
  2. Enable Payment Link

    • Open the product → Payment Link
    • Copy the generated link
  3. Use the payment link directly

    • Open the link in browser
    • Or embed it as a normal <a href="...">Buy</a>
    • ✅ No JS, no SDK, no processor initialization
  4. Make sure you’re in the right mode

Why you saw “products can not be purchased”

  • Product is Draft / Inactive
  • Using JS processor with a payment link
  • Test product opened in Live mode (or vice‑versa)

When to use JS instead

Only use JavaScript if you want:

  • Dynamic carts
  • Custom checkout sessions
  • Programmatic pricing

If you want, tell me:

  • Test or Live mode?
  • One‑time or subscription?
    And I’ll give the exact link format.

Helpful Links

Dodo Payments Documentation

Sell fixed‑price digital goods or services with a single upfront charge, no renewals.

Dodo Payments Documentation

This is the initial release of the Dodo Payments documentation, marking the Beta launch of our product. Explore the features and improvements designed to enhance your payment processing experience.

tight mica
grizzled acornBOT
# tight mica I used live mode,one time purchase, I actually have a code so I won't have to ha...

That approach is correct and supported.

Here’s the clean, correct setup for your use case (Live mode, one‑time, no‑code checkout, but dynamic links):

✅ Correct Flow (No‑Code Checkout + Your Own Button)

  1. Create product (Live mode)

    • Type: One‑time payment
    • Status: Active
    • Price + currency set
  2. Enable Payment Link

  3. Frontend button logic

    <button onclick="window.location.href=PAYMENT_LINK_FROM_DB">
      Buy
    </button>
    
    • ✅ No JS SDK
    • ✅ No processor initialization
    • ✅ Just a redirect
  4. Customer completes checkout

    • Hosted by Dodo
    • Payment + receipt handled automatically

❌ Do NOT do these

  • Initialize DodoPayments() JS
  • Create checkout sessions via API
  • Mix SDK + payment links
  • Use test links in live mode

When you would need JS/API

Only if you want:

  • Variable pricing
  • Multiple products in one checkout
  • Coupons applied programmatically
  • Subscriptions with custom logic

If you want, tell me:

  • Single product or multiple?
  • Do you need post‑payment webhook handling?

I’ll give the exact next steps.

tight mica
grizzled acornBOT
# tight mica One Single product per checkout, what's post-payment webhook handling?? Btw I u...

Post‑payment webhook handling (simple explanation)

A webhook is an HTTP endpoint on your server that Dodo calls after payment events.

You use it to:

  • Mark the order as paid
  • Grant access / unlock content
  • Store transaction details
  • Send confirmation emails (optional)

Common events

  • payment.succeeded
  • payment.failed
  • refund.processed

If you don’t need backend automation, you can skip webhooks entirely.


About your JS usage (important clarification)

✅ Your approach is fine as long as:

  • JS only reads the payment link from DB
  • JS only redirects the user
  • You are not initializing Dodo’s SDK or processor

✅ This is still No‑Code Checkout

❌ Not allowed in this flow:

  • new DodoPayments()
  • Checkout/session APIs
  • Any client‑side payment creation

Your dodo.js is fine if it’s just glue code, not payment logic.


When you actually need webhooks

You need them only if:

  • Digital product access after payment
  • License / role assignment
  • Order status tracking
  • Backend verification (recommended for paid content)

If you want, tell me:

  • Digital or physical product?
  • Backend language?

I’ll give you the exact webhook setup steps.


Helpful Links

Dodo Payments Documentation

Sell fixed‑price digital goods or services with a single upfront charge, no renewals.

tight mica
grizzled acornBOT
# tight mica Digital product, I'm using SUPABASE as a backend , should I get rid of any initi...

Short answer

Yes — remove all of that.

For No‑Code Checkout with payment links, you must not load or initialize the Dodo JS SDK.


What to remove

❌ Remove:

<script src="https://cdn.jsdelivr.net/npm/dodopayments-checkout@latest/dist/index.js"></script>

❌ Remove:

DodoPaymentsCheckout.DodoPayments.Initialize({ mode: "live" });

These are only for SDK / API‑based checkouts.


What your setup should look like

✅ Frontend:

  • Read payment link from DB
  • Redirect user to it

✅ Dodo:

  • Product = Active
  • One‑time payment
  • Live payment link

✅ Result:

  • Hosted checkout
  • Redirect after payment works

About webhooks (important for digital goods)

You’re technically correct — webhooks are optional
But recommended if the product has value.

Without webhooks:

  • Anyone can open the success URL
  • No payment verification
  • No fraud protection

With webhooks (Supabase):

  • Verify payment.succeeded
  • Unlock product securely
  • Prevent free access

If you want, I can give you:

  • Supabase webhook endpoint setup
  • Exact event to listen for
  • Minimal verification logic

Helpful Links

Dodo Payments Documentation

Sell fixed‑price digital goods or services with a single upfront charge, no renewals.

tight mica
grizzled acornBOT
# tight mica If you insist then yes a webhook might be helpful

Minimal webhook setup (Supabase + digital product)

You only need one event: payment.succeeded.


1️⃣ Create a webhook endpoint (Supabase)

  • Use Supabase Edge Functions (recommended)
  • Endpoint example:
POST https://<project>.supabase.co/functions/v1/dodo-webhook

2️⃣ Add webhook in Dodo Dashboard

  • Dashboard → Webhooks
  • URL: your Supabase function URL
  • Events: ✅ payment.succeeded
  • Copy the Webhook Secret

3️⃣ Verify webhook + unlock product

Inside your Edge Function:

  • Verify signature using the webhook secret
  • Read:
    • payment_id
    • customer_email
    • product_id
  • Mark order as paid
  • Grant access (DB flag, entitlement row, etc.)

👉 Only unlock content after webhook verification


4️⃣ Frontend flow (secure)

  • Payment link redirects to /success
  • /success page:
    • Shows “Processing…”
    • Polls backend → checks if order is marked paid
    • Then shows download / content

✅ Prevents direct URL abuse
✅ Works with No‑Code Checkout
✅ Fully secure for digital goods


What you still do NOT need

  • ❌ JS SDK
  • ❌ Checkout/session APIs
  • ❌ Processor initialization

If you want next:

  • Supabase Edge Function sample code
  • Signature verification logic
  • DB schema for entitlements

Tell me which one.

Helpful Links

Dodo Payments Documentation

Sell fixed‑price digital goods or services with a single upfront charge, no renewals.

tight mica
grizzled acornBOT
# tight mica I got rid of initialization and still same issue will webhook solve it eventuall...

No — webhooks will NOT fix this issue.

The error “products cannot be purchased at the moment” happens before payment, so webhooks are irrelevant here.


✅ What to check (this will fix it)

  1. Product status

    • Dashboard → Product
    • Must be Active (not Draft / Archived)
  2. Payment link regeneration

    • If product was Draft before:
      • Disable payment link
      • Re‑enable it
      • Use the new link
    • Old links stay invalid
  3. Live mode consistency

    • Product created in Live
    • Link domain must be:
      https://live.dodopayments.com/...
      
    • No test links anywhere
  4. Price + currency

    • Price > 0
    • Currency set
    • Tax category set (required in Live)
  5. No SDK remnants

    • Confirm:
      • No Dodo script loaded
      • No Initialize()
      • No checkout/session API calls

❌ What will NOT help

  • Adding webhooks
  • Changing redirect URLs
  • Frontend JS changes
  • Supabase changes

Fast sanity test (important)

Copy the payment link and:

  • Open it in incognito
  • Logged out of dashboard
  • If it still errors → it’s 100% a product/link config issue

If it still fails, tell me exactly:

  • Product status (Active/Draft)
  • Currency
  • Tax category
  • Whether link was regenerated

I’ll pinpoint it immediately.

tight mica
grizzled acornBOT
# tight mica I actually copied the payment link in chrome and it worked even recieved an emai...

Perfect — that confirms everything ✅
Your product and payment link are correct.

This means the issue is 100% in your frontend code, not Dodo.


✅ What this tells us

  • Product is Active
  • Live mode is correct
  • Payment link is valid
  • Dodo checkout works end‑to‑end

❌ Why it fails from your button

Common frontend mistakes that cause this exact behavior:

  1. Link is modified

    • Extra whitespace
    • Missing https://
    • URL encoded incorrectly
  2. Client‑side routing intercepts it

    • React / Next / Vue <Link> instead of full redirect
    • SPA router blocking external navigation
  3. Opened in iframe / embed

    • Payment links must open top‑level, not inside iframe

✅ Correct redirect (must be exact)

Use a hard browser redirect:

window.location.href = paymentLink;

Or:

<a href="PAYMENT_LINK" target="_self">Buy</a>

✅ No router
✅ No fetch
✅ No iframe
✅ No SDK


🔎 Quick debug test

Log the link right before redirect:

console.log(paymentLink);

Copy that value → paste into browser
If it works there, your redirect method is wrong.


Final rule

If pasting the link works, your code must redirect exactly to that string — no transformations.

If you want, paste:

  • How the link is stored
  • How the button click redirects

I’ll spot the bug instantly.

#

Found it. Your bug is not Dodo — it’s a variable mismatch.


❌ What’s wrong in your code

You validate one field but redirect using another:

if (!currentProduct?.dodo_buy_url) { ... }   // ✅ check
window.location.href = currentProduct.dodo_checkout_access; // ❌ redirect

If dodo_checkout_access is:

  • undefined
  • empty
  • different from the real payment link

→ Dodo shows “products cannot be purchased at the moment”.


✅ Fix (pick ONE field and use it consistently)

document.getElementById("product-pdf").addEventListener("click", () => {
  const link = currentProduct?.dodo_buy_url;

  if (!link) {
    alert("This product cannot be purchased at the moment.");
    return;
  }

  window.location.href = link;
});

🔎 Immediate debug check

Before redirect:

console.log(currentProduct.dodo_buy_url);

Copy‑paste that exact value into browser.
✅ If it works → fixed
❌ If not → DB value is wrong


Final note

  • No SDK ✅
  • No initialization ✅
  • No webhook needed to fix this ✅

This one line mismatch was the entire issue.

tight mica
grizzled acornBOT
# tight mica Oh it appeared!! But why when a payment failed it still redirected them to the p...

Because payment links always redirectsuccess and failure — and you’re trusting the redirect URL alone.

Redirect ≠ payment confirmation.


Why this happens

  • Dodo redirects after checkout regardless of outcome
  • Failed / canceled payments still hit your return URL
  • Your page doesn’t verify payment status

This is expected behavior.


✅ Correct fix (minimal, secure)

Use a webhook as the source of truth.

Flow

  1. Payment link → checkout
  2. Redirect → /success
  3. /success shows “Processing…”
  4. Backend checks:
    • Has payment.succeeded webhook been received?
  5. ✅ Yes → unlock product
    ❌ No → show failure message

❌ What NOT to do

  • Do not unlock on redirect
  • Do not trust query params
  • Do not trust frontend state

✅ What to implement (Supabase)

  • Listen for: payment.succeeded
  • Store:
    • payment_id
    • product_id
    • customer_email
  • Grant access only after webhook

One‑sentence rule

Redirects are UX only. Webhooks decide access.

If you want, I’ll give you:

  • Supabase Edge Function webhook code
  • Success page polling logic
  • Minimal DB schema

Just say which one.

tight mica
grizzled acornBOT
# tight mica Yes pls choose one that's easy to implement

✅ Easiest to implement: Success page polling logic

(No crypto verification, minimal backend work)

This works with No‑Code Checkout and fixes the “failed payment still unlocked” issue.


✅ Flow (simple + safe)

  1. Payment link → Dodo checkout
  2. Redirect → /success
  3. /success shows “Processing payment…”
  4. Page polls your backend
  5. Backend says:
    • ✅ Paid → unlock product
    • ❌ Not paid → show failure

1️⃣ Backend (Supabase — minimal)

You only need:

  • A table orders
  • A webhook that marks paid = true

orders table

id
email
product_id
paid (boolean)

Webhook logic (already discussed):

  • Event: payment.succeeded
  • Find order
  • Set paid = true

2️⃣ Frontend /success page (easy part)

async function checkPayment() {
  const res = await fetch("/api/check-payment");
  const data = await res.json();

  if (data.paid) {
    window.location.href = "/product-access";
  } else {
    setTimeout(checkPayment, 3000);
  }
}

checkPayment();

✅ No SDK
✅ No query params
✅ No trust in redirect


3️⃣ Backend check endpoint

Returns:

{ "paid": true }

or

{ "paid": false }

That’s it.


❌ What this avoids

  • No unlocking on redirect
  • No fake success URLs
  • No failed payments getting access

Golden rule

Redirect = UX
Webhook + backend = truth

If you want next:

  • The exact Supabase Edge Function webhook code
  • Or the check-payment endpoint

Tell me which one.

tight mica
grizzled acornBOT
# tight mica Can you explain this more by details so I don't mess up

Got it — here’s a clear, step‑by‑step explanation, with why each piece exists, so you don’t mess it up.


🧠 The core idea (remember this)

  • Redirect ≠ payment success
  • Webhook = truth
  • Your frontend must ask your backend if payment actually succeeded

✅ What you’re building (high level)

You will have 3 moving parts:

  1. Payment link (already works ✅)
  2. Webhook → marks payment as paid
  3. Success page → waits until backend confirms payment

Only when (2) happens do you unlock the product.


1️⃣ Database (Supabase)

You need a place to store payment state.

Table: orders

column purpose
id internal order id
email customer email
product_id which product
paid falsetrue after webhook

Important rule

  • Default: paid = false
  • Never set this from frontend

2️⃣ Webhook (this is the authority)

This runs server‑side only.

What happens here

  • Dodo sends payment.succeeded
  • Your webhook:
    1. Verifies it came from Dodo
    2. Extracts payment data
    3. Finds the matching order
    4. Sets paid = true

Why this matters

  • Webhook only fires after real money
  • Failed / canceled payments never trigger it
  • This is what blocks free access

✅ If webhook didn’t fire → user didn’t pay


3️⃣ Redirect page (/success)

This page does NOT unlock anything.

What it should do

  1. Show: “Processing your payment…”
  2. Call backend: “Is this order paid?”
  3. Repeat every few seconds

Why polling is needed

  • Webhooks are async
  • Redirect can happen before webhook arrives
  • Polling waits until backend confirms

4️⃣ Backend check endpoint

Example response:

{ "paid": false }

or

{ "paid": true }

Backend logic

  • Look up order
  • Read paid column
  • Return status only
#

❌ Do NOT calculate payment status here
✅ Just read what webhook already stored


5️⃣ Frontend logic (simple + safe)

async function checkPayment() {
  const res = await fetch("/api/check-payment");
  const data = await res.json();

  if (data.paid) {
    window.location.href = "/product-access";
  } else {
    setTimeout(checkPayment, 3000);
  }
}

checkPayment();

What this guarantees

  • Failed payment → never unlocks
  • Refreshing success page → still safe
  • Directly opening /product-access → blocked

❌ Common mistakes to avoid

  • Unlocking product on redirect
  • Trusting query params (?success=true)
  • Unlocking in frontend only
  • Skipping webhooks for digital goods

✅ Mental checklist

Before granting access, ask:

Did my backend receive payment.succeeded?

If no → deny access
If yes → unlock


If you want next (pick one):

  • Exact Supabase webhook Edge Function code
  • Exact /check-payment backend code
  • Product‑access page protection logic

Say which one and I’ll give it verbatim.

tight mica
grizzled acornBOT
# tight mica Can you explain the difference before I pick one

Here’s the difference between the three options, so you can choose safely.


1️⃣ Webhook Edge Function (payment truth)

What it does

  • Receives payment.succeeded from Dodo
  • Marks the order as paid = true in Supabase

Why it exists

  • This is the only reliable proof of payment
  • Redirects and frontend cannot be trusted

You need this no matter what
✅ Required
✅ One‑time setup
✅ No UI involved


2️⃣ /check-payment backend endpoint (status reader)

What it does

  • Frontend asks: “Is this order paid yet?”
  • Backend reads paid from DB and answers

Why it exists

  • Webhooks are async
  • Redirect may happen before webhook arrives

Optional but recommended
✅ Simple
✅ Prevents race conditions
❌ Not the authority — only reads data


3️⃣ Product‑access page protection (final gate)

What it does

  • Blocks /product-access unless paid = true
  • Runs on every page load

Why it exists

  • Prevents users from bookmarking or sharing the URL
  • Protects against manual navigation

Security layer
✅ Highly recommended
✅ Very small logic
❌ Doesn’t detect payment itself


🧠 How they work together (ideal setup)

Webhook  → sets paid = true   (authority)
Check API → reads paid state  (UX)
Access page → enforces access (security)

✅ If you want the minimum safe setup

Pick:

  • Webhook Edge Function
  • Product‑access page protection

✅ If you want the smoothest UX

Pick:

  • Webhook Edge Function
  • /check-payment endpoint

Simple rule

  • Webhook = money
  • Check endpoint = waiting
  • Access guard = protection

Tell me which one you want first and I’ll walk you through it step‑by‑step.

grizzled acornBOT
# tight mica Can these be done all together?

Yes — they’re meant to be done together.
You implement them once, and they work as a single secure flow.


✅ All‑together setup (clean + safe)

1️⃣ Webhook (authority)

  • Listens to: payment.succeeded
  • Sets orders.paid = true
  • Only source of truth

2️⃣ /check-payment endpoint (UX)

  • Reads orders.paid
  • Returns { paid: true | false }
  • Used only by /success page

3️⃣ Product‑access guard (final lock)

  • Runs on /product-access
  • If paid !== true → block access
  • Prevents bookmarking / sharing URL

✅ How the full flow works

Buy button
 → Dodo payment link
   → Redirect to /success
     → /success polls /check-payment
       → webhook flips paid=true
         → frontend unlocks product
           → access page double-checks paid=true

✅ Why this is safe

  • Failed payments ❌ never unlock
  • Redirects alone ❌ don’t grant access
  • Refreshing pages ✅ still safe
  • Users can’t fake success URLs ✅

✅ What you should build (order)

  1. Webhook Edge Function (required)
  2. orders table (paid boolean)
  3. /check-payment endpoint
  4. Access check on product page

Once done, you never touch this again.


Final reassurance

This is standard practice for digital goods and the correct way with Dodo payment links.

If you want, next I can give you:

  • All three pieces as copy‑paste Supabase code
  • ✅ Or start with Webhook only

Tell me which.

tight mica
grizzled acornBOT
# tight mica All three pieces as copy paste preferably

Below is a copy‑paste ready, minimal, and safe setup for all 3 pieces using Supabase.
This is the correct production pattern for Dodo + payment links.


0️⃣ Database (run once)

Create table orders:

create table orders (
  id uuid primary key default gen_random_uuid(),
  email text,
  product_id text,
  paid boolean default false
);

1️⃣ Webhook Edge Function (payment authority)

File: supabase/functions/dodo-webhook/index.ts

import { serve } from "https://deno.land/std/http/server.ts";

serve(async (req) => {
  const payload = await req.json();

  if (payload.event !== "payment.succeeded") {
    return new Response("ignored", { status: 200 });
  }

  const email = payload.data.customer_email;
  const productId = payload.data.product_id;

  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
  );

  await supabase
    .from("orders")
    .update({ paid: true })
    .eq("email", email)
    .eq("product_id", productId);

  return new Response("ok", { status: 200 });
});

✅ Add this URL in Dodo Dashboard → Webhooks
✅ Event: payment.succeeded


2️⃣ /check-payment Edge Function (UX polling)

File: supabase/functions/check-payment/index.ts

import { serve } from "https://deno.land/std/http/server.ts";

serve(async (req) => {
  const { email, product_id } = await req.json();

  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_ANON_KEY")!
  );

  const { data } = await supabase
    .from("orders")
    .select("paid")
    .eq("email", email)
    .eq("product_id", product_id)
    .single();

  return new Response(
    JSON.stringify({ paid: data?.paid === true }),
    { headers: { "Content-Type": "application/json" } }
  );
});