← Tutte le idee Negozi e community

100PercentMarket

Un mercato di grafiche e font originali per chi stampa su magliette e tazze

Incasso mensile: 2400 USD (fonte indicata nella scheda)

Cosa fa

Un sito dove i creativi vendono grafiche, font e clipart originali pensati per la stampa su richiesta (magliette, tazze, poster), e chi ha un negozio online li compra per i suoi prodotti.

Come guadagna

Ha entrate ricorrenti ogni mese, verificate su TrustMRR. Prezzi e piani esatti sono indicati sul sito del progetto.

Rendilo tuo

  • Specializzati su grafiche italiane: frasi in dialetto, città, squadre di quartiere.
  • Licenza chiara per l'uso commerciale, spiegata in modo semplice.

Le frasi da scrivere all'AI

1. Base del sito e grafica

Crea un'app web in italiano chiamata "Grafiche da Stampare": un marketplace dove i creativi vendono grafiche, font e illustrazioni con licenza commerciale per magliette, tazze e poster, e il sito trattiene una commissione su ogni vendita. Usa React, Vite, TypeScript e Tailwind CSS.

Pagine da creare:
1. Pagina iniziale con grafiche in evidenza, categorie e ricerca
2. Scheda del prodotto con anteprime su maglietta e tazza, licenza e prezzo
3. Pagina del venditore con i suoi prodotti
4. Area venditore: caricamento prodotti, vendite, incassi
5. I miei acquisti con i download

Stile: creativo e colorato: sfondo bianco, fucsia #DB2777, nero #18181B, carattere "Poppins", griglia di anteprime quadrate grandi. Nessuna immagine generica di stock.

Tutto deve funzionare perfettamente da telefono. Tutti i testi in italiano, chiari e senza parole tecniche.

2. Database, account e sicurezza

Collega il progetto a Supabase e attiva la registrazione con email e password.

Crea queste tabelle:
- "venditori": nome, biografia, account Stripe collegato, collegati all'utente
- "prodotti": titolo, descrizione, tag, prezzo, anteprima pubblica, file originale privato, licenza, stato (in revisione, pubblicato)
- "acquisti": prodotto, acquirente, prezzo pagato, commissione, pagato o no

Regole di sicurezza obbligatorie:
- tutti vedono i prodotti pubblicati e le anteprime, mai i file originali
- il file originale si scarica solo dopo un acquisto pagato, con un link temporaneo
- il venditore gestisce solo i suoi prodotti; la pubblicazione la decide un amministratore

Usa esattamente il codice SQL che trovi nei "Dettagli tecnici" di questa scheda: eseguilo nel SQL Editor di Supabase prima di continuare, poi collega le pagine alle tabelle.

3. Venditori e pagamenti divisi

I venditori si iscrivono e collegano il loro conto con Stripe Connect (account Express): Stripe verifica la loro identità e versa gli incassi direttamente a loro. Il sito non tiene mai i soldi dei venditori.

Il venditore carica il file originale (PNG ad alta risoluzione, SVG o font) nello Storage privato, un'anteprima con filigrana nello Storage pubblico, titolo, tag e prezzo. Il prodotto passa da un controllo dell'amministratore prima della pubblicazione (originalità, qualità, nessun marchio famoso o personaggio protetto).

Al pagamento, Stripe divide l'incasso: l'80% va al venditore e il 20% resta al sito (codice nei Dettagli tecnici).

4. Download sicuro e area venditore

Dopo il pagamento, nella pagina "I miei acquisti" il pulsante "Scarica" chiama una Edge Function che controlla che l'acquisto sia pagato e crea un link valido 60 secondi al file originale (codice nei Dettagli tecnici). Con il file si scarica un PDF con il testo della licenza e il nome dell'acquirente.

Area venditore: prodotti con visite e vendite, incassi del mese, e link alla dashboard Stripe per vedere i versamenti.

5. Pagamenti con Stripe

Aggiungi un pagamento singolo di il prezzo del prodotto deciso dal venditore. Il pagamento segna l'acquisto come pagato e sblocca il download del file originale.

A pagamento riuscito il webhook imposta "pagato" a true nella tabella "acquisti", sulla riga del acquisto indicata in client_reference_id.

Per la sessione di pagamento e il webhook segui la guida "Pagamenti con Stripe" di Fattibile.

6. Controlli specifici di questo progetto

Oltre ai controlli della guida "Controllo finale", verifica in particolare:
1. File protetti: prova ad aprire l'indirizzo di un file originale senza averlo comprato, o un link di download dopo 2 minuti: deve essere bloccato.
2. Commissione: fai un acquisto di prova e controlla nella dashboard Stripe di test che il venditore riceva l'80% e il sito il 20%.
Correggi quello che non va e scrivimi cosa hai cambiato.

Dettagli tecnici

Pagamento diviso con Stripe Connect

La Edge Function crea il pagamento: il prezzo lo legge dal database, Stripe trattiene la commissione del sito e versa il resto al venditore.

// Parte della Edge Function "compra"
const { data: prodotto } = await admin
  .from("prodotti")
  .select("id, titolo, prezzo_centesimi, venditori(stripe_account_id)")
  .eq("id", prodottoId)
  .eq("stato", "pubblicato")
  .single();
if (!prodotto) return new Response("Prodotto non disponibile", { status: 404 });

const { data: acquisto } = await admin.from("acquisti").insert({
  prodotto_id: prodotto.id,
  acquirente_id: user.id,
  prezzo_centesimi: prodotto.prezzo_centesimi,
}).select().single();

const sessione = await stripe.checkout.sessions.create({
  mode: "payment",
  line_items: [{
    quantity: 1,
    price_data: {
      currency: "eur",
      unit_amount: prodotto.prezzo_centesimi,
      product_data: { name: prodotto.titolo },
    },
  }],
  payment_intent_data: {
    application_fee_amount: Math.round(prodotto.prezzo_centesimi * 0.2),  // 20% al sito
    transfer_data: { destination: prodotto.venditori.stripe_account_id }, // resto al venditore
  },
  client_reference_id: acquisto.id,
  success_url: "https://tuosito.it/#/acquisti",
  cancel_url: "https://tuosito.it/#/prodotto/" + prodotto.id,
});
return Response.json({ url: sessione.url }, { headers: cors });

Download con link temporaneo

Il file originale sta in uno spazio privato: si scarica solo con un link che vale 60 secondi, creato dopo aver controllato che l'acquisto sia pagato.

// Parte della Edge Function "scarica"
const { data: acquisto } = await admin
  .from("acquisti")
  .select("pagato, prodotti(file_path)")
  .eq("id", acquistoId)
  .eq("acquirente_id", user.id)       // solo chi ha comprato
  .single();

if (!acquisto?.pagato) {
  return new Response("Acquisto non trovato o non pagato", { status: 403, headers: cors });
}

const { data: link } = await admin.storage
  .from("originali")                   // bucket privato
  .createSignedUrl(acquisto.prodotti.file_path, 60, { download: true });

return Response.json({ url: link?.signedUrl }, { headers: cors });

Tabelle e regole

Prodotti pubblici solo se approvati, acquisti visibili solo all'acquirente, "pagato" modificabile solo dal webhook.

create table venditori (
  utente_id uuid primary key references auth.users(id) on delete cascade,
  nome text not null,
  stripe_account_id text unique
);

create table prodotti (
  id uuid primary key default gen_random_uuid(),
  venditore_id uuid not null references venditori(utente_id) on delete cascade,
  titolo text not null,
  tag text[] not null default '{}',
  prezzo_centesimi int not null check (prezzo_centesimi >= 100),
  anteprima_url text not null,
  file_path text not null,             -- nello Storage privato "originali"
  stato text not null default 'in_revisione' check (stato in ('in_revisione', 'pubblicato'))
);

create table acquisti (
  id uuid primary key default gen_random_uuid(),
  prodotto_id uuid not null references prodotti(id),
  acquirente_id uuid not null references auth.users(id),
  prezzo_centesimi int not null,
  pagato boolean not null default false,
  creato_il timestamptz not null default now()
);

alter table venditori enable row level security;
alter table prodotti enable row level security;
alter table acquisti enable row level security;

create policy "venditori pubblici" on venditori for select using (true);
create policy "il mio profilo venditore" on venditori for update
  using (auth.uid() = utente_id) with check (auth.uid() = utente_id);

create policy "prodotti pubblicati" on prodotti
  for select using (stato = 'pubblicato' or auth.uid() = venditore_id);
create policy "carico i miei" on prodotti
  for insert with check (auth.uid() = venditore_id and stato = 'in_revisione');

-- Gli acquisti li crea e li segna pagati solo il server
create policy "i miei acquisti" on acquisti for select using (auth.uid() = acquirente_id);

Pezzi comuni a tutti i progetti

Glossario

Storage
Lo spazio di Supabase dove si salvano i file, come foto e audio, con le sue regole di accesso.
Stripe Connect
La funzione di Stripe per i marketplace: divide ogni pagamento tra il venditore e il sito, e paga i venditori al posto tuo.
Link firmato
Un link a un file privato che funziona solo per pochi secondi, creato dal server per chi ne ha diritto.