Kit de tienda online para Next.js: catálogo, carrito, pago con Stripe, pedidos, envíos, cupones, reseñas y cuentas
Instalar
genpm add @core/kit-storeQué obtienes
- Código en src/app/(store)/, 27 archivos. (57,2 kB)
- Reglas de IA en src/app/(store)/AGENTS.md, más archivos de reglas para tu IDE.
- Variables añadidas a .env.example: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, CART_COOKIE_SECRET, ORDER_LINK_SECRET, STORE_CURRENCY.
- Resuelve @core/cart, @core/catalog, @core/checkout, @core/discounts, @core/kit-cms, @core/orders, @core/reviews, @core/shipping por ti.
README
Este paquete no tiene README.
Esto es exactamente lo que lee tu IA cuando trabaja en src/app/(store). No se añade nada más a su contexto.
@core/kit-store — rules for AI agents
Purpose
An online store on top of @core/kit-cms, managed from the same /admin: products with variants and categories,
cart, Stripe Checkout, orders (created only by the Stripe webhook), shipping zones and rates, discount codes,
verified reviews, customer orders page, transactional emails and SEO (Product JSON-LD, sitemap). Every storefront
form is a Next.js Server Action, so it works without JavaScript and never accepts prices from the browser.
Map
_templates/genpm.md— the exact lines this kit adds to the project'ssrc/genpm/*.ts(generated, always current)._lib/maintenance.ts— dailystore.maintenancejob (deletes expired carts).shop/page.tsx,categories/[slug],collections/[slug]— listings.products/[slug]/page.tsx— product page.cart/page.tsx— cart, discount code, shipping country/method, totals and payment.checkout/success— real status.orders/[number]/page.tsx— order for its owner or with the signed email link.account/page.tsx— customer orders.api/stripe/webhook/route.ts— Stripe webhook (@core/stripe)._lib/actions.ts— server actions (add, update, code, shipping, checkout, review)._lib/store.ts— helpers._components/— product card/grid, add to cart,CartLinkfor the header, reviews, order summary,store.css._blocks/products.tsx— "Product grid" and "Buy now" blocks._lib/widgets.ts— sales, orders to ship, low stock._lib/legal.ts+_scripts/seed-legal.ts— legal pages as drafts pending review, with a dashboard warning.
Integration
- @core/kit-cms must be integrated first.
- Env:
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET,CART_COOKIE_SECRETandORDER_LINK_SECRET(≥ 32 chars),STORE_CURRENCY(e.g.EUR),SITE_URL,EMAIL_FROM. Use Stripe test mode until the end-to-end check passes. If the Stripe MCP server is available and the person agrees, use it to inspect the test account (never live keys). - Migrations for catalog, cart, checkout, orders, shipping, discounts, reviews, comments and stripe.
- Add every line of
_templates/genpm.mdto the project'ssrc/genpm/*.ts(it says which file and where): it createssrc/genpm/commerce.ts(imports that register webhooks, cart steps and the dailystore.maintenancejob), the product sitemap and search sources, theproductGrid/buyNowblocks, and the admin resources and widgets (including comments, where product reviews are moderated). Then run the kit-cmssetup-schedules.tsagain. - Stripe webhook: endpoint
https://<site>/api/stripe/webhookwithcheckout.session.completed,checkout.session.async_payment_succeededandcheckout.session.expired(refunds are started from the admin); locallystripe listen --forward-to localhost:3000/api/stripe/webhook. - Create shipping zones and rates in the admin; put
<CartLink />in the site header and "Shop" in the menu. - The person runs
npx tsx "src/app/(store)/_scripts/seed-legal.ts": terms, shipping, returns/withdrawal, privacy and cookies are created as drafts. They must complete them and have them reviewed before publishing. - Verify in test mode: buy with card 4242 4242 4242 4242, the webhook creates the order, the email arrives, stock decreases, the order page opens from the email link and not without it.
Conventions
- Prices are integers in minor units; the client only sends variant ids, quantities, codes and the shipping choice.
- Orders exist only after Stripe confirms payment (webhook), never from the success page.
- The "before" price is hidden: in the EU it must be the lowest price of the previous 30 days (Omnibus directive).
With @core/pricing installed, show it only through
honestCompareAt.
Don't
- Don't publish legal pages the person has not reviewed, and never present generated legal text as final.
- Don't show fake reviews, ratings, stock counts or countdowns; review JSON-LD only with real approved reviews.
- Don't expose order pages without the owner session or the signed token; don't put tokens in analytics.
# @core/kit-store — rules for AI agents
## Purpose
An online store on top of @core/kit-cms, managed from the same `/admin`: products with variants and categories,
cart, Stripe Checkout, orders (created only by the Stripe webhook), shipping zones and rates, discount codes,
verified reviews, customer orders page, transactional emails and SEO (Product JSON-LD, sitemap). Every storefront
form is a Next.js Server Action, so it works without JavaScript and never accepts prices from the browser.
## Map
- `_templates/genpm.md` — the exact lines this kit adds to the project's `src/genpm/*.ts` (generated, always current).
- `_lib/maintenance.ts` — daily `store.maintenance` job (deletes expired carts).
- `shop/page.tsx`, `categories/[slug]`, `collections/[slug]` — listings. `products/[slug]/page.tsx` — product page.
- `cart/page.tsx` — cart, discount code, shipping country/method, totals and payment. `checkout/success` — real status.
- `orders/[number]/page.tsx` — order for its owner or with the signed email link. `account/page.tsx` — customer orders.
- `api/stripe/webhook/route.ts` — Stripe webhook (@core/stripe).
- `_lib/actions.ts` — server actions (add, update, code, shipping, checkout, review). `_lib/store.ts` — helpers.
- `_components/` — product card/grid, add to cart, `CartLink` for the header, reviews, order summary, `store.css`.
- `_blocks/products.tsx` — "Product grid" and "Buy now" blocks. `_lib/widgets.ts` — sales, orders to ship, low stock.
- `_lib/legal.ts` + `_scripts/seed-legal.ts` — legal pages as drafts pending review, with a dashboard warning.
## Integration
1. @core/kit-cms must be integrated first.
2. Env: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `CART_COOKIE_SECRET` and `ORDER_LINK_SECRET` (≥ 32 chars),
`STORE_CURRENCY` (e.g. `EUR`), `SITE_URL`, `EMAIL_FROM`. Use Stripe test mode until the end-to-end check passes.
If the Stripe MCP server is available and the person agrees, use it to inspect the test account (never live keys).
3. Migrations for catalog, cart, checkout, orders, shipping, discounts, reviews, comments and stripe.
4. Add every line of `_templates/genpm.md` to the project's `src/genpm/*.ts` (it says which file and where): it creates
`src/genpm/commerce.ts` (imports that register webhooks, cart steps and the daily `store.maintenance` job), the
product sitemap and search sources, the `productGrid` / `buyNow` blocks, and the admin resources and widgets
(including comments, where product reviews are moderated). Then run the kit-cms `setup-schedules.ts` again.
5. Stripe webhook: endpoint `https://<site>/api/stripe/webhook` with `checkout.session.completed`,
`checkout.session.async_payment_succeeded` and `checkout.session.expired` (refunds are started from the admin); locally `stripe listen --forward-to localhost:3000/api/stripe/webhook`.
6. Create shipping zones and rates in the admin; put `<CartLink />` in the site header and "Shop" in the menu.
7. The person runs `npx tsx "src/app/(store)/_scripts/seed-legal.ts"`: terms, shipping, returns/withdrawal, privacy
and cookies are created as drafts. They must complete them and have them reviewed before publishing.
8. Verify in test mode: buy with card 4242 4242 4242 4242, the webhook creates the order, the email arrives, stock
decreases, the order page opens from the email link and not without it.
## Conventions
- Prices are integers in minor units; the client only sends variant ids, quantities, codes and the shipping choice.
- Orders exist only after Stripe confirms payment (webhook), never from the success page.
- The "before" price is hidden: in the EU it must be the lowest price of the previous 30 days (Omnibus directive).
With @core/pricing installed, show it only through `honestCompareAt`.
## Don't
- Don't publish legal pages the person has not reviewed, and never present generated legal text as final.
- Don't show fake reviews, ratings, stock counts or countdowns; review JSON-LD only with real approved reviews.
- Don't expose order pages without the owner session or the signed token; don't put tokens in analytics.
El árbol exacto que se inyectará, tras aplicar .genpmignore. Anclado a
// Carrito: cantidades, cupón, país y método de envío, totales del servidor y pago con Stripe Checkout.
import type { Metadata } from 'next';
import { computeTotals, getCart } from '@/lib/cart';
import { getDb } from '@/lib/db';
import { imageFor } from '@/lib/media';
import { format, money } from '@/lib/money';
import { ratesFor, shippingZones } from '@/lib/shipping';
import '../_components/store.css';
import { applyCode, checkout, setShipping, updateLine } from '../_lib/actions';
import { cookieHeader, currentUserId, one, price } from '../_lib/store';
import Link from 'next/link';
export const dynamic = 'force-dynamic';
export const metadata: Metadata = { title: 'Cart', robots: { index: false, follow: false } };
const ERRORS: Record<string, string> = {
insufficient_stock: 'There is not enough stock for that quantity.',
unavailable: 'That product is no longer available.',
needs_shipping: 'Choose your country and a shipping method first.',
issues: 'Please review the notices in your cart.',
empty: 'Your cart is empty.',
invalid: 'Please enter a valid email address.',
};
export default async function CartPage({ searchParams }: { searchParams: Promise<Record<string, string | string[] | undefined>> }) {
const sp = await searchParams;
const cart = await getCart(await cookieHeader(), await currentUserId());
const t = cart ? await computeTotals(cart) : null;
const error = one(sp.cart_error) ?? one(sp.checkout_error);
if (!t || !t.lines.length)
return (
<main className="blk store">
<h1>Cart</h1>
<p>Your cart is empty.</p>
<p>
<Link className="ui-button ui-button--primary" href="/shop">
Continue shopping
</Link>
</p>
</main>
);
const zones = await getDb().select({ countries: shippingZones.countries }).from(shippingZones);
const names = new Intl.DisplayNames(['en'], { type: 'region' });
const countries = [...new Set(zones.flatMap((z) => z.countries))].sort().map((c) => ({ code: c, name: names.of(c) ?? c }));
const country = t.context.meta.country;
const rates = country ? await ratesFor(t, country) : [];
const images = await Promise.all(t.lines.map((l) => imageFor(l.mediaId, { sizes: '96px', alt: '' })));
return (
<main className="blk store store-cart">
<h1>Cart</h1>
{one(sp.added) && (
<p role="status" className="ui-alert ui-alert--success">
Added to your cart.
</p>
)}
{error && (
<p role="alert" className="ui-alert ui-alert--error">
{ERRORS[error] ?? 'Something went wrong. Please try again.'}
</p>
)}
{t.issues.length > 0 && (
<ul className="ui-alert ui-alert--warning">
{t.issues.map((i) => (
<li key={`${i.code}:${i.variantId ?? ''}`}>{i.message}</li>
))}
</ul>
)}
<table className="ui-table">
<caption className="ui-visually-hidden">Products in your cart</caption>
<thead>
<tr>
<th scope="col">Product</th>
<th scope="col">Price</th>
<th scope="col">Quantity</th>
<th scope="col">Total</th>
</tr>
</thead>
<tbody>
{t.lines.map((l, i) => (
<tr key={l.variantId}>
<th scope="row">
{images[i] && <img {...images[i]} alt="" width={64} height={64} className="store-cart__thumb" />}
<a href={`/products/${l.slug}`}>{l.name}</a>
{l.title && <span className="blog-meta"> · {l.title}</span>}
</th>
<td>{price(l.unitPrice)}</td>
<td>
<form action={updateLine} className="store-inline">
<input type="hidden" name="variantId" value={l.variantId} />
<label className="ui-visually-hidden" htmlFor={`qty-${l.variantId}`}>
Quantity of {l.name}
</label>
<input id={`qty-${l.variantId}`} name="quantity" type="number" min={0} max={99} defaultValue={l.quantity} className="ui-input store-qty" />
<button type="submit" className="ui-button ui-button--sm">
Update
</button>
</form>
<form action={updateLine} className="store-inline">
<input type="hidden" name="variantId" value={l.variantId} />
<input type="hidden" name="quantity" value="0" />
<button type="submit" className="ui-button ui-button--sm ui-button--ghost" aria-label={`Remove ${l.name}`}>
Remove
</button>
</form>
</td>
<td>{price(l.subtotal)}</td>
</tr>
))}
</tbody>
</table>
<div className="store-cart__side">
<form action={applyCode} className="store-inline">
<label htmlFor="code">Discount code</label>
<input id="code" name="code" className="ui-input" defaultValue={t.context.discountCodes[0] ?? ''} maxLength={40} autoComplete="off" />
<button type="submit" className="ui-button ui-button--sm">
Apply
</button>
</form>
<form action={setShipping} className="store-shipping">
<div className="ui-field">
<label className="ui-field__label" htmlFor="country">
Ship to
</label>
<select id="country" name="country" className="ui-select" defaultValue={country ?? ''} required>
<option value="" disabled>
Choose a country…
</option>
{countries.map((c) => (
<option key={c.code} value={c.code}>
{c.name}
</option>
))}
</select>
</div>
{rates.length > 0 && (
<fieldset className="ui-field">
<legend className="ui-field__label">Shipping method</legend>
{rates.map((r) => (
<label key={r.id} className="ui-checkbox">
<input type="radio" name="shippingRateId" value={r.id} defaultChecked={t.shipping?.code === r.id} /> {r.name} — {r.free ? 'Free' : format(money(r.amount, r.currency))}
{r.minDays !== null && r.maxDays !== null && ` (${r.minDays}–${r.maxDays} days)`}
</label>
))}
</fieldset>
)}
<button type="submit" className="ui-button ui-button--sm">
Update shipping
</button>
</form>
<dl className="store-totals">
<div>
<dt>Subtotal</dt>
<dd>{price(t.subtotal)}</dd>
</div>
{t.discounts.map((d) => (
<div key={`${d.source}:${d.code ?? d.label}`}>
<dt>{d.label}</dt>
<dd>−{price(d.amount)}</dd>
</div>
))}
{t.shipping && (
<div>
<dt>Shipping ({t.shipping.label})</dt>
<dd>{price(t.shipping.amount)}</dd>
</div>
)}
{t.tax && (
<div>
<dt>{t.tax.label}</dt>
<dd>{price(t.tax.amount)}</dd>
</div>
)}
<div className="store-totals__total">
<dt>Total</dt>
<dd>{price(t.total)}</dd>
</div>
</dl>
<form action={checkout} className="store-checkout">
<div className="ui-field">
<label className="ui-field__label" htmlFor="email">
Email for your receipt <span aria-hidden="true">*</span>
</label>
<input id="email" name="email" type="email" required autoComplete="email" className="ui-input" />
</div>
<button type="submit" className="ui-button ui-button--primary">
Pay securely with Stripe
</button>
<p className="blog-meta">
By paying you accept the <Link href="/terms">terms of sale</Link>. You have 14 days to withdraw (<Link href="/returns">returns</Link>).
</p>
</form>
</div>
</main>
);
}
Este paquete no declara servidores MCP.
| Versión | Commit | Publicado | Escaneo |
|---|---|---|---|
| 1.0.1 | 6a45dae | hace 5 horas | escaneo superado |
- genpm
- @core/cart ^1.0.0@core/catalog ^1.0.0@core/checkout ^1.0.0@core/discounts ^1.0.0@core/kit-cms ^1.0.0@core/orders ^1.0.0@core/reviews ^1.0.0@core/shipping ^1.0.0
- propuesta
- GenPM propone el comando npm y solo lo ejecuta si dices que sí.
- Usado por (1)
- @core/kit-dropshipping ^1.0.0
- escaneo
- escaneo superado · 0 hallazgos
- commit
- v1.0.1 → 6a45dae248ce17fbd95b5f3b2b61c68f66236041 · verificado tras la descarga
- scripts
- Ninguno. GenPM nunca ejecuta código del paquete.
- licencia
- MIT
- Calidad
- 100/100
- Licencia reconocidacumplido
- AGENTS.md explica su propósitocumplido
- AGENTS.md tiene pasos de integracióncumplido
- AGENTS.md lista convenciones o prohibicionescumplido
- Incluye testscumplido
- Escaneo de seguridad superadocumplido
- Publicado en los últimos 6 mesescumplido
- Publicador verificadocumplido
- Resumen y palabras clavecumplido
- reporte
- ¿Ves algo raro?