İçeriğe geç

Oturum açmış müşteri

Markdown

Sitenizde oturum açmış müşteri için yorum formunun sipariş numarası sorulmadan açılması, ad ve e-posta yazmadan soru sorulması ve sipariş detayı sayfasında ürünlerin değerlendirilmesi.

Müşteri sitenizde oturum açmışsa, sipariş numarası ve e-posta adresi sorulmadan yorum yazması sağlanabilir. Bunun için sunucunuz, oturumdaki e-posta adresiyle müşterinin bu ürüne yorum yazıp yazamayacağını Sayproof’a sorar ve yanıtı script’e iletir.

Soru-cevap açıksa oturum açmış müşteri soru sorarken de adını ve e-posta adresini yazmaz: Soru-cevap. Sipariş detayı sayfasında ise siparişin ürünleri sayfanın içinde değerlendirilebilir: Sipariş detayı sayfası.

Temel kural değişmez: yorum yazma hakkını tarayıcı değil, Sayproof’taki sipariş kaydı belirler. Oturum yalnızca müşterinin kim olduğunu belirtir.

  1. Container’a data-review-viewer="pending" attribute’u eklenir. Yanıt gelene kadar script yorum yazma alanını göstermez.
  2. Tarayıcı, sizin sitenizdeki bir endpoint’e (örneğin /api/sayproof/viewer) ürün için müşterinin durumunu sorar.
  3. Sunucunuz oturumdaki e-posta adresini okur ve sunucu anahtarıyla Sayproof’a sorar: GET /v1/viewer.
  4. Yanıt tarayıcıya döner; sayfa bu yanıtı Sayproof.update(container, { viewer }) ile script’e iletir.

Müşterinin durumu sayfanın HTML’ine eklenmez; kişiye özeldir ve her seferinde önbellek kullanılmadan sorgulanır. Böylece ürün sayfasının kendisi önbellekte tutulabilir.

state Anlamı Müşterinin gördüğü
can_write Teslim edilmiş ve henüz yorum yazılmamış bir satın alması var “Yorum yaz” düğmesi; form sipariş sorulmadan açılır
written Bu ürün için yorum yazmış Kendi yorumunun durumu: yayında, incelemede, reddedildi ya da kaldırıldı
none Yorum yazabileceği bir satın alması yok Oturum açmamış ziyaretçiyle aynı doğrulama alanı
anonymous Oturum yok ya da yanıt alınamadı Oturum açmamış ziyaretçiyle aynı doğrulama alanı

Yalnızca kanalın pazarındaki siparişler dikkate alınır. E-posta adresi küçük harfe dönüştürülerek birebir eşleştirilir.

Sayproof’a gönderilen istek:

GET https://api.sayproof.example/v1/viewer?product=URUN-KODU&scope=variant&lang=tr&channel=web
Authorization: Bearer SUNUCU-ANAHTARINIZ
X-Sayproof-Customer: ayse@ornek.com
  • E-posta adresi yalnızca X-Sayproof-Customer header’ında gönderilir; URL’e eklenmez.
  • Bu endpoint yalnızca sunucu anahtarıyla çalışır. Tarayıcıdan site anahtarıyla gönderilen istek 403 refused / server_only yanıtı alır.
  • Bir sunucu anahtarıyla 5 dakikada 6.000 istek gönderilebilir. Tüm müşterilerin istekleri sizin sunucunuz üzerinden geldiği için müşteriler birbirinin sınırını etkilemez.
  • Yanıt Cache-Control: no-store header’ını taşır; kendi tarafınızda da önbelleğe almayın.

Next.js Route Handler örneği. Oturum bilgisini kendi kimlik doğrulama kütüphanenizden alın; buradaki auth() yalnızca örnektir:

import "server-only";
import { auth } from "@/auth";
export const dynamic = "force-dynamic";
const base = process.env.SAYPROOF_BASE || process.env.NEXT_PUBLIC_SAYPROOF_BASE;
const noStore = { "Cache-Control": "no-store" };
const anonymous = () => Response.json({ state: "anonymous" }, { headers: noStore });
export async function GET(request: Request) {
const asked = new URL(request.url).searchParams;
const product = asked.get("product");
const email = (await auth())?.user?.email;
if (!product || !email) return anonymous();
const query = new URLSearchParams({
product,
scope: asked.get("scope") === "group" ? "group" : "variant",
lang: asked.get("locale") ?? "tr",
channel: process.env.SAYPROOF_CHANNEL ?? "web",
});
try {
const answer = await fetch(`${base}/v1/viewer?${query}`, {
headers: {
Authorization: `Bearer ${process.env.SAYPROOF_SSR_KEY}`,
"X-Sayproof-Customer": email,
},
cache: "no-store",
signal: AbortSignal.timeout(3000),
});
return answer.ok ? Response.json(await answer.json(), { headers: noStore }) : anonymous();
} catch {
return anonymous();
}
}

Başka bir sunucu dilinde de aynı üç kural geçerlidir: e-posta adresini tarayıcıdan değil oturumdan okuyun; hata durumunda anonymous döndürün; yanıtı önbelleğe almayın.

<div id="reviews"
data-review-mount
data-review-product="URUN-KODU"
data-review-viewer="pending"></div>
function whenSayproofReady(callback) {
if (window.Sayproof) callback(window.Sayproof);
else window.addEventListener("sayproof:ready", () => callback(window.Sayproof), { once: true });
}
async function askViewer(container) {
const query = new URLSearchParams({
product: container.dataset.reviewProduct,
scope: container.dataset.reviewScope ?? "variant",
locale: document.documentElement.lang || "tr",
});
try {
const answer = await fetch(`/api/sayproof/viewer?${query}`, {
credentials: "same-origin",
cache: "no-store",
signal: AbortSignal.timeout(8000),
});
return answer.ok ? await answer.json() : { state: "anonymous" };
} catch {
return { state: "anonymous" };
}
}
const container = document.querySelector("#reviews");
askViewer(container).then((viewer) => whenSayproofReady((sayproof) => sayproof.update(container, { viewer })));

Yanıt hiç gelmese bile script’e bir değer iletin: { state: "anonymous" } ya da viewer: null. Aksi hâlde yorum yazma alanı bekleme durumunda kalır.

Gönderim başarılı olduğunda script müşterinin durumunu kendisi written olarak günceller; aynı sayfada form yeniden açılmaz. Kendi listenizi yenilemek isterseniz container’ın yaydığı sayproof:submitted event’ini dinleyin: JavaScript API.

Marka soru-cevabı açtıysa GET /v1/viewer yanıtı state değeri ne olursa olsun bir asker alanı da taşır. Script bu alanı görünce soru formunda ad ve e-posta alanlarını göstermez, yerine “Ayşe K. olarak soruyorsunuz. Cevap gelince e-postanıza haber veririz.” satırını gösterir. Soru oturumdaki e-posta adresiyle kaydedilir ve cevap yayına çıktığında bu adrese haber verilir.

{
"state": "none",
"reviews": [],
"limits": { "…": "…" },
"asker": { "token": "…", "name": "Ayşe K." }
}
  • asker.token: markanıza bağlı, imzalı ve 1 saat geçerli bir token. Script soruyu bu token’la gönderir; Sayproof e-posta adresini ve adı token’dan okur, tarayıcı e-posta adresini göndermez. Token’ın içeriğini kendi kodunuzda okumayın ve değiştirmeyin.
  • asker.name: sorunun yanında görünecek ad. İsteğe bağlı X-Sayproof-Customer-Name header’ıyla gönderilir; Türkçe karakterler için değeri UTF-8 olarak percent-encode edin (JavaScript’te encodeURIComponent). Boşluklar sadeleştirilir, en fazla 120 karakter alınır. Header gönderilmezse name boş gelir, formda “Hesabınızla soruyorsunuz. Cevap gelince e-postanıza haber veririz.” yazar ve soru “Anonim” olarak görünür. Ad sayfada herkese görünür; tam ad yerine “Ayşe K.” gibi kısa bir biçim gönderin.
  • Token’ın süresi dolduysa ya da geçersizse soru 403 token_refused / asker_expired ya da asker_invalid yanıtı alır. Script bu durumda formu, yazılan soruyu koruyarak oturum açmamış ziyaretçinin formuna çevirir.
GET https://api.sayproof.example/v1/viewer?product=URUN-KODU&scope=variant&lang=tr&channel=web
Authorization: Bearer SUNUCU-ANAHTARINIZ
X-Sayproof-Customer: ayse@ornek.com
X-Sayproof-Customer-Name: Ay%C5%9Fe%20K.

Yukarıdaki Next.js örneğinde header’lara "X-Sayproof-Customer-Name": encodeURIComponent(name) satırını eklemeniz yeterlidir; adı e-posta adresi gibi oturumdan okuyun.

Hesap bölümünüzdeki sipariş detayı sayfasında, siparişin her ürünü için sayfanın içinde bir puan ve yorum formu gösterilebilir. Dialog açılmaz ve sipariş numarası sorulmaz; her ürünün kendi Gönder düğmesi vardır. Sunucunuz oturumdaki e-posta adresi ve sipariş numarasıyla Sayproof’a sorar, yanıtı sayfanın HTML’ine ekler; script bölümü bu yanıttan kurar.

GET https://api.sayproof.example/v1/viewer/order?order=100245&channel=web&lang=tr
Authorization: Bearer SUNUCU-ANAHTARINIZ
X-Sayproof-Customer: ayse@ornek.com

Yanıttaki her satır bir sipariş ürünüdür ve state değeri taşır:

state Anlamı Müşterinin gördüğü
can_write Ürün teslim edilmiş ve henüz yorum yazılmamış Yıldızlar, ürün için tanımlıysa değerlendirme başlıkları, yorum metni, fotoğraf ve video alanı ve satırın Gönder düğmesi
pending Yorum gönderilmiş, incelemede “Yorumunuz alındı; incelemeden sonra yayınlanacak.”
published Yorum yayında “Bu ürün için yorumunuz yayında. Teşekkürler!”
rejected Yorum yayınlanmadı “Yorumunuz yayınlanmadı. Size gönderdiğimiz e-postadaki bağlantıyla düzeltip yeniden gönderebilirsiniz.”
removed Yorum şu anda yayında değil “Bu ürün için yorumunuz şu an yayında değil.”
waiting Teslimat henüz doğrulanmadı “Ürün size ulaştıktan sonra değerlendirebilirsiniz.”
closed Bu ürün için artık yorum yazılamıyor (örneğin yazma süresi dolmuş ya da ürün iptal veya iade edilmiş) “Bu ürün için yorum yazılamıyor.”

Sipariş bulunamazsa ya da başka bir müşteriye aitse yanıt 200 olur ve lines boş döner; script bu durumda bölümü hiç çizmez ve container boş kalır. Ürün sayfasındaki isteğin kuralları burada da geçerlidir: e-posta adresini oturumdan okuyun, isteği yalnızca sunucu anahtarıyla gönderin (tarayıcıdan gönderilen istek 403 refused / server_only alır), yanıtı önbelleğe almayın. Rate limit GET /v1/viewer ile ortaktır.

Sunucu tarafında yanıtı bir <script type="application/json"> etiketine güvenle yazan fonksiyon:

import "server-only";
const base = process.env.SAYPROOF_BASE || process.env.NEXT_PUBLIC_SAYPROOF_BASE;
const unsafe: Record<string, string> = { "<": "\\u003c", ">": "\\u003e", "&": "\\u0026", "
": "\\u2028", "
": "\\u2029" };
export async function sayproofOrderState(order: string, email: string | null | undefined, locale: string) {
if (!order || !email) return null;
const query = new URLSearchParams({ order, channel: process.env.SAYPROOF_CHANNEL ?? "web", lang: locale });
try {
const answer = await fetch(`${base}/v1/viewer/order?${query}`, {
headers: { Authorization: `Bearer ${process.env.SAYPROOF_SSR_KEY}`, "X-Sayproof-Customer": email },
cache: "no-store",
signal: AbortSignal.timeout(3000),
});
if (!answer.ok) return null;
const state = await answer.json();
if (!state.lines.length) return null;
const json = JSON.stringify(state).replace(/[<>&

]/g, (character) => unsafe[character]);
return `<script type="application/json" data-review-order-state>${json}</script>`;
} catch {
return null;
}
}

Sayfada etiket, değeri sipariş numarası olan bir data-review-order öğesinin içine yazılır; sayfada Sayproof script’i de bulunmalıdır. Script öğeyi kendisi bulur:

<div data-review-order="100245">
<script type="application/json" data-review-order-state>{"order":"100245","token":"…","lines":[…]}</script>
</div>

Sayfa tarayıcıda oluşturuluyorsa aynı yanıt window.Sayproof.mountOrder(öğe, yanıt) ile verilir: JavaScript API. Her ürün ayrı gönderilir; gönderim başarılı olduğunda satır pending durumuna geçer, öğe sayproof:submitted event’ini detail: { order, line } ile yayar. Yıldız verilmeden gönderilmek istenirse “Göndermeden önce yıldız verin.” mesajı gösterilir. Öğeler ve durumlar için: DOM attribute’ları › Sipariş detayında değerlendirme.