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.