Closed source
Avgoro
A marketplace for local egg producers in Greece, on the web and as a React Native app.
- period:
- 2026
- status:
- in development
- PostgreSQL
- SQL
- Supabase
- Stripe Connect
- TypeScript
- JavaScript
- React Native
- Firebase
- Google Sign-In
- Deno
- Netlify
- Resend
- Plausible
- ipapi.co
On Avgoro, producers set their own prices, buyers search by area, and Stripe Connect pays each farm.
How it’s built
The website is plain HTML, JavaScript and CSS, with no framework. The app is React Native 0.87 with 14 screens, including KYC and courier tracking. Both talk to Supabase with a public key, so PostgreSQL decides what each user can do.
(full size)
(full size)
(full size)
(full size)
(full size)
(full size)
(full size)How security works
The rules are in 44 SQL migrations. The server works out every price from the database, and admin actions need two-factor authentication.
Interactive demo
Try to cheat the checkout
Edit the checkout request or try an attack, and see what the server does with it. This runs a copy of the checkout rules, tested against the real code (below, underPrices worked out on the server).
catalogue and platform settings
- Κοτέτσι Πλάτανος: Free-range eggs, €3.20 per 6 eggs, delivery platform defaultb7a1c2d3-1e2f-4a5b-8c6d-7e8f9a0b1c2d
- Φάρμα Λιθαράκι: Organic eggs, €3.90 per 6 eggs, delivery €3.00 (farm's own)c3d4e5f6-2a3b-4c5d-9e8f-1a2b3c4d5e6f
- Αγρόκτημα Ανατολή: Wholesale eggs, €38.00 per 180 eggs, delivery platform default, 4 in stockd8e9f0a1-3b4c-4d5e-a6b7-c8d9e0f1a2b3
commission 10% (b2c) or 6% (b2b). Default delivery €2.50. Per product: 200 as a guest,1,000 signed in
what the server does with it
What went wrong
Two migrations both defined recompute_order, the function that works out an order’s fees, and the one that ran last won. One copy ignored each farm’s own delivery fee and charged the platform default. The other had lost its pending check, so it could rewrite a paid order’s amounts. Now there is one definition, and the tests below check both cases.
How checkout works
- 01 The public API key
- 02 Row-level security
- 03 Prices worked out on the server
- 04 Stock locks in a fixed order
- 05 Fees in the database
- 06 Testing the fees
- 07 Order status through one function
The public API key
Both clients connect to Supabase with the anon key, which anyone can read from the app, so row-level security has to guard the data. This lock stops two tabs refreshing the session at once and signing the user out.
async function avgAuthLock(name, acquireTimeout, fn) {
if (!(navigator.locks && navigator.locks.request)) return await fn();
let started = false;
try {
return await navigator.locks.request(name, { mode: 'exclusive' }, async () => {
started = true;
return await fn();
});
} catch (e) {
if (started) throw e; // fn itself failed: don't run it twice
return await fn(); // the lock could not be acquired
}
}Row-level security
Only the producer who owns a KYC row, or an admin, can read it. The table has no write policies, so writes can only go through the submit_kyc function.
CREATE POLICY "Owner or admin reads producer_kyc"
ON public.producer_kyc FOR SELECT TO authenticated
USING (
public.is_admin()
OR producer_id IN (SELECT id FROM public.producer_profiles WHERE user_id = auth.uid())
);Prices worked out on the server
The request only names products and quantities. create-checkout reads prices and fees from the database (byId holds those rows) and never reads a price the client sends.
const groups = new Map<string, Grp>();
for (const [pid, qty] of requested) {
const p = byId.get(pid)!;
const g = groups.get(p.producer_id) ?? { lines: [], subtotalCents: 0, deliveryCents: feeFor(p.producer_profiles) };
g.lines.push({
product_id: p.id, producer_id: p.producer_id, farm_name: p.producer_profiles.farm_name,
type: p.type, unit: p.unit, price: Number(p.price), qty,
});
g.subtotalCents += cents(Number(p.price)) * qty;
groups.set(p.producer_id, g);
}On mobile: Stripe payment sheet
The app calls the same function, then opens Stripe's native payment sheet with the client secret it gets back.
const {error: initError} = await initPaymentSheet({
paymentIntentClientSecret: out.clientSecret,
});
if (initError) {
showToast('Σφάλμα: ' + initError.message);
setSubmitting(false);
return;
}
const {error: presentError} = await presentPaymentSheet();
setSubmitting(false);Stock locks in a fixed order
Each order line locks its product's row to check stock. Two checkouts locking the same products in opposite orders could deadlock, so the lines are sorted by product id first.
// Sorted by product_id so concurrent checkouts take stock locks in the
// same order (no deadlocks). The enforce_order_item_price trigger
// re-derives price, producer, farm name, type and unit from products.
const sortedLines = [...g.lines].sort((a, b) => (a.product_id < b.product_id ? -1 : 1));
const { error: itemsErr } = await supabaseAdmin
.from("order_items").insert(sortedLines.map((l) => ({ ...l, order_id: order.id })));
if (itemsErr) {
if (/insufficient_stock/.test(itemsErr.message)) {
throw Object.assign(new Error("insufficient_stock"), { userFacing: true });
}
throw new Error(`items insert: ${itemsErr.message}`);
}Fees in the database
A trigger runs recompute_order when a pending order's lines change, using the farm's own delivery fee if it has one. Paid orders are left alone, because refunds use the stored total.
CREATE OR REPLACE FUNCTION public.recompute_order(p_order_id uuid)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public, pg_temp
AS $$
DECLARE
s public.platform_settings;
o public.orders;
sub numeric;
rate numeric;
fee numeric;
BEGIN
SELECT * INTO o FROM public.orders WHERE id = p_order_id;
IF o.id IS NULL OR o.status IS DISTINCT FROM 'pending' THEN
RETURN;
END IF;
SELECT * INTO s FROM public.platform_settings WHERE id = 1;
SELECT COALESCE(SUM(line_price), 0) INTO sub FROM public.order_items WHERE order_id = p_order_id;
rate := CASE WHEN o.order_type = 'b2b' THEN COALESCE(s.commission_b2b, 0) ELSE COALESCE(s.commission_b2c, 0) END;
-- Per-farm flat delivery fee (producer-ships: the farm pays its own courier).
-- create-checkout makes one order per producer, so every line in this order
-- belongs to the same farm and the first match is it. NULL = platform default.
SELECT pp.delivery_fee INTO fee
FROM public.order_items oi
JOIN public.producer_profiles pp ON pp.id = oi.producer_id
WHERE oi.order_id = p_order_id
LIMIT 1;
fee := COALESCE(fee, s.delivery_fee, 0);
UPDATE public.orders SET
subtotal = sub,
commission = round(sub * rate, 2),
delivery_fee = fee,
total = sub + fee
WHERE id = p_order_id;
END;
$$;Testing the fees
The test for the bug above. It sets an odd €7.13 fee on a farm and checks that a pending order uses it, then marks the order paid, changes the fee and checks nothing moves.
-- An odd fee no platform setting would hold, so a match can only come from the farm.
UPDATE public.producer_profiles SET delivery_fee = 7.13 WHERE id = v_prod.producer_id;
INSERT INTO public.order_items (order_id, product_id, qty) VALUES (v_order, v_prod.id, 1);
SELECT * INTO o FROM public.orders WHERE id = v_order;
PERFORM pg_temp.expect(o.delivery_fee = 7.13 AND o.total = o.subtotal + 7.13,
'a pending order charges the farm''s own delivery fee');
UPDATE public.orders SET status = 'paid' WHERE id = v_order;
UPDATE public.producer_profiles SET delivery_fee = 2.00 WHERE id = v_prod.producer_id;
PERFORM public.recompute_order(v_order);
SELECT * INTO o FROM public.orders WHERE id = v_order;
PERFORM pg_temp.expect(o.delivery_fee = 7.13, 'recompute_order leaves a paid order''s amounts alone');Order status through one function
Clients can't write the status column. set_order_status checks each change: only the payment webhook marks an order paid, and a paid order is cancelled by refunding it.
IF v_current = p_status THEN
RETURN; -- idempotent no-op
END IF;
IF p_status = 'cancelled' AND v_current <> 'pending' THEN
RAISE EXCEPTION 'paid_order_needs_refund'
USING ERRCODE = 'check_violation',
HINT = 'A paid order is cancelled by refunding it (create-refund).';
END IF;
-- Admins may correct mistakes between fulfilment steps, but never mark an
-- order paid (only the payment webhook does), reopen a cancelled order
-- (its stock was already released) or send it back to pending.
IF public.is_admin() THEN
IF v_current = 'cancelled' OR p_status IN ('pending', 'paid') THEN
RAISE EXCEPTION 'Illegal status change: % -> %', v_current, p_status;
END IF;
UPDATE public.orders SET status = p_status WHERE id = p_order_id;
RETURN;
END IF;
IF NOT public._is_legal_transition(v_current, p_status) THEN
RAISE EXCEPTION 'Illegal status transition: % -> %', v_current, p_status;
END IF;On mobile: Push notifications
Status changes reach the app as Firebase push messages. Each carries the order number and its new status. When a parcel ships, the buyer's message also gives the courier and the tracking number. A tap opens the farm's dashboard or the buyer's tracking screen, which reads the order with the user's own session.
export function setupForegroundHandler(onNotification) {
if (!fbMessaging || !fbFns) return () => {};
try {
return fbFns.onMessage(fbMessaging, async remoteMessage => {
if (onNotification) onNotification(remoteMessage);
});
} catch {
return () => {};
}
}