Örnek mağaza (Next.js + Prisma)
Sepet, sunucu tarafında siparişler, 3D Secure ve iadeler içeren çalıştırılabilir bir mağaza.
examples/next-shop bu
depodaki küçük bir mağazadır. App Router, SQLite ile Prisma ve workspace'teki better-payment
kullanır; varsayılan sağlayıcı MockProvider olduğu için kimlik bilgisi olmadan çalışır.
Örnekte neler var
- ürün listesi ve yalnızca ürün kimlikleri ile adetleri tutan bir çerez sepeti
- tutarı veritabanındaki fiyatlardan toplanan, sunucuda oluşturulan sipariş
- kart alanları ve
installmentInfo'dan gelen taksitlerle ödeme formu toNextJsHandlerüzerinden 3D Secure, doğrulanan callback vecallbackRedirect- yalnızca
payment.succeeded/payment.pending/payment.failedile güncellenen siparişler - ödenmiş siparişi ayrıcalıklı
refundroute'u üzerinden iade eden yönetici sayfası MockProviderile mutlu yol için bir Playwright testi
Akış beş adımda
app/page.tsxbir server action ile ürünü sepete ekler.lib/cart-actions.tssepetten siparişi oluşturur; tarayıcı hiçbir tutar göndermez.lib/payment.tsiçindekitransformRequestödeme isteğini sipariş satırından yeniden kurar.- Mock banka 3D Secure sonucunu handler'a gönderir; handler sonucu doğrular ve siparişi ödeme olayları üzerinden günceller.
callbackRedirecttarayıcıyı/orders/<id>sayfasına gönderir; sayfa veritabanındaki durumu gösterir.
Çalıştırma
corepack pnpm@10.33.0 install
corepack pnpm@10.33.0 --filter next-shop devhttp://localhost:3000 adresini açın. dev betiği Next.js başlamadan önce prisma generate ve
prisma db push çalıştırır, yani yeni bir checkout ek adım gerektirmez. Ödeme sayfasında
MockProvider başarı kartı 4242424242424242, ileri bir son kullanma tarihi ve herhangi bir CVC ile
ödeyin. Aynı sayfadaki diğer sihirli kartlar reddedilmeyi, başarısız 3D Secure'u ve kaybolan yanıtı
simüle eder.
Bunun yerine iyzico sandbox kullanmak için .env.example dosyasını .env.local olarak kopyalayın,
IYZICO_API_KEY ile IYZICO_SECRET_KEY değerlerini ayarlayın ve yeniden başlatın. Bu değerler boşken
örnek MockProvider kullanır; depoda hiçbir zaman kimlik bilgisi bulunmaz.
Ödeme nesnesi
Handler yalnızca bu mağazanın kullandığı eylemleri açar ve transformRequest istemciden gelen her
tutarı sipariş verisiyle değiştirir:
async function buildRequestFromDatabase(ctx: HandlerContext): Promise<object> {
if (ctx.action !== 'payment/init-3ds') return ctx.body ?? {};
const order = await getOrder(String(ctx.body?.orderId));
if (!order || !isPayable(order.status)) throw new Error('Order is not payable');
const total = formatKurus(order.amountKurus);
const paymentCard = readCard(ctx.body?.paymentCard);
// For installments > 1, ask the provider what it will charge.
const paidPrice = await quotedPaidPrice(paymentCard.cardNumber, total, order.id, order.installment);
return {
price: total, // never a browser value
paidPrice,
currency: order.currency,
installment: order.installment,
paymentCard,
buyer: demoBuyer(order.id),
basketItems: order.items.map(toBasketItem),
callbackUrl: `${new URL(ctx.request.url).origin}/api/pay/${PROVIDER_ID}/payment/complete-3ds`,
};
}Birden fazla taksit seçildiğinde örnek, sağlayıcıdan o taksit sayısının fiyat teklifini ister ve
teklif edilen toplamı paidPriceKurus olarak saklar; böylece iade tam olarak tahsil edilen tutarı
geri verir. Kart sağlayıcıya iletilir ve veritabanına asla yazılmaz; Prisma şemasında kart verisi
alanı yoktur.
Route handler
// app/api/pay/[...path]/route.ts
import { toNextJsHandler } from 'better-payment/next';
import { getBetterPayment } from '@/lib/payment';
export const runtime = 'nodejs'; // Prisma's SQLite client is not edge-compatible
export const { GET, POST } = toNextJsHandler(getBetterPayment);getBetterPayment tembel bir singleton'dır, bu yüzden next build hiçbir ortam değişkenine ihtiyaç
duymaz. Tam entegrasyon için Next.js rehberine bakın.
Olaylardan siparişler
Sipariş durumunu tarayıcının dönüşü değil, ödeme olayları yazar:
payment.on('payment.succeeded', async (event) => {
await prisma.order.updateMany({
where: { id: event.conversationId ?? '' },
data: { status: 'PAID', paymentId: event.paymentId ?? null },
});
});
payment.on('payment.failed', async (event) => {
await prisma.order.updateMany({
where: { id: event.conversationId ?? '', status: { notIn: ['PAID', 'REFUNDED'] } },
data: { status: 'FAILED', errorCode: event.code ?? 'UNKNOWN' },
});
});Aynı olaylar hem tarayıcıdaki 3D Secure dönüşü hem de sağlayıcı bildirimi için yayınlanır; bu yüzden iki yol çelişemez ve sahte bir callback hiçbir olay üretmez. Olaylara bakın.
İadeler ve authorize
refund ayrıcalıklı bir eylemdir; handler bir authorize kancası olmadan başlamayı reddeder. Örnek,
sağlayıcı callback'lerine ve müşteri ödemesine izin verir, iade için demo yönetici çerezini ister:
function authorize(ctx: HandlerContext): boolean {
// Signed provider callbacks and the customer checkout are public
if (CALLBACK_HANDLER_ACTIONS.includes(ctx.action)) return true;
if (ctx.action === 'payment/init-3ds' || ctx.action === 'installment') return true;
// Refunds need the shop's admin session (a demo cookie in the example)
if (ctx.action === 'refund') return hasDemoAdmin(ctx.request.headers);
return false;
}İade tutarı ve sağlayıcı ödeme kimliği transformRequest içinde siparişten okunur, yani yönetici
sayfası yalnızca sipariş kimliğini gönderir. Üretimde kullanmadan önce demo çerezini gerçek bir
oturum ve yönetici rolü kontrolüyle değiştirin.
Örnek öğrenmek içindir, üretim için değil. Paylaşılan bir idempotency store, gerçek kimlik doğrulama, HTTPS callback URL'leri, Prisma migration'ları ve sağlayıcının barındırılan kart alanlarını kullanın. README her maddeyi listeler.
Üretim için neleri değiştirmeli
- birden fazla instance çalıştırıyorsanız
handler.idempotency.storeiçin paylaşılan birIdempotencyStoreverin - demo yönetici çerezini oturumunuz ve
adminrolü kontrolüyle değiştirin callbackUrl'irequest.urlyerine yapılandırılmış bir HTTPS origin'inden türetinprisma db pushyerineprisma migrate deploykullanın- kart verisini sağlayıcının barındırılan alanlarıyla sunucunuzun dışında tutun
- ödemeyi yeniden denemeden önce
pendingsonucugetPaymentile doğrulayın
Testler
corepack pnpm@10.33.0 --filter next-shop test:e2ePlaywright testi sepete ürün ekler, başarı kartıyla öder, 3D Secure callback'ini bekler, siparişin
PAID olduğunu kontrol eder, yönetici sayfasından iade eder ve kaydedilen iadeyi doğrular. Kendi dev
sunucusunu 3310 portunda başlatır ve kimlik bilgisi gerektirmez.