Fiyatlandırma ve teklif

WooCommerce'te fiyatı giriş yapılana kadar gizleme

Ürün sayfasındaki fiyat filtresi tek başına yetmez: varyasyon JSON'u, Store API, yapılandırılmış veri ve REST anahtarları aynı sayıyı yayımlamayı sürdürür. Sızıntıların her birini kapatan filtreyi ve önbellek katmanının bütün işi nasıl boşa çıkardığını tek tek ele alıyoruz.

Toptancılara satıyorsunuz, ya da her anlaşmayı ayrı ayrı pazarlık ediyorsunuz, ya da distribütörlük sözleşmeniz liste fiyatlarının herkese açık görünmemesini şart koşuyor. Sebep ne olursa olsun gereksinim aynı: anonim bir ziyaretçi hiçbir rakam görmesin, giriş yapan onaylı bir müşteri ise fiyatları görsün ve normal şekilde siparişini tamamlasın. WooCommerce’te giriş yapılana kadar fiyatı gizleme kalıbı budur ve bir B2B mağazasının WooCommerce’ten beklediği ama WooCommerce’in kendi başına yapmadığı en yaygın şeylerden biridir.

Aratın, on saniye içinde dört satırlık bir kod parçası bulursunuz. O kod parçası, kapsadığı kadarıyla doğrudur. Neredeyse kimsenin söylemediği şey şu: ürün sayfasındaki fiyat, WooCommerce’in o sayıyı yayımladığı en az yarım düzine yerden yalnızca biridir. Üstelik bir sayfa önbelleği, giriş yapmış bir kullanıcı için üretilmiş çıktıyı bir misafire sunup tek bir istekte bunların hepsini boşa çıkarabilir.

WooCommerce’te “fiyat” aslında nedir#

Ürün sayfasında gördüğünüz biçimlendirilmiş fiyat WC_Product::get_price_html() fonksiyonundan gelir. Bu, sunum katmanıdır. Sayının kendisi _price meta alanında ve ürün lookup tablosunda durur; WooCommerce onu, get_price_html() fonksiyonunu hiç çağırmayan birkaç bağımsız kod yolu üzerinden tekrar tekrar yayımlar.

  • Klasik şablonlar. Tekil ürün, mağaza döngüsü, ilgili ürünler, gruplu ürün satırları, üst satış ürünleri.
  • Varyasyon JSON’u. Varyasyonlu bir üründe WooCommerce, form istek atmadan seçim değiştirebilsin diye her varyasyonun fiyatını bir data-product_variations niteliğine basar.
  • Store API. Blok temalar ile sepet/ödeme blokları /wp-json/wc/store/v1/products adresinden veri çeker; bu uç nokta bir prices nesnesi ve bir price_html dizesi döndürür, koleksiyon verisi uç noktası da toplulaştırılmış bir fiyat aralığı döndürür.
  • Yapılandırılmış veri. WooCommerce, offers.price alanı içeren JSON-LD üretir. Rank Math, Yoast, AIOSEO ve şema eklentilerinin çoğu da kendi kodlarından aynısını yapar.
  • REST API ve GraphQL. wc/v3/products da WPGraphQL de fiyat alanlarını, kimlik bilgilerini elinde tutan herkese açar.
  • Sepet, mini sepet, ödeme, sipariş e-postaları ve Hesabım. Bunlar satır toplamlarını get_price_html() üzerinden değil, doğrudan biçimlendirir.

Bunlardan birini filtrelemek diğerlerine dokunmaz. Neredeyse her “fiyatı gizledim ama Google hâlâ gösteriyor” destek konusunun açıklaması tek başına bu gerçektir.

is_user_logged_in() ile değil, rol kontrolüyle başlayın#

Çoğu eğitim, fiyat filtresini is_user_logged_in() içine sarar. Mağazanızda kayıt kapalıysa ve her hesabı elle siz açıyorsanız bunda sorun yok. Ama kayıt açıksa — WooCommerce › Ayarlar › Hesaplar & Gizlilik altındaki, epeyce mağazanın açtığı bir onay kutusu — “giriş yapmış” demek “e-posta adresini yazan herkes” demektir; toptan fiyatları göstermeyi düşündüğünüz kitle ise bu değildir.

Bu soruyu yanıtlayan tek bir fonksiyon yazın ve her yerde onu çağırın. Böylece politikayı sonradan değiştirmek, functions.php dosyasını baştan sona taramak yerine tek satırlık bir düzenlemeye dönüşür.

<?php
/**
 * The single source of truth for price visibility.
 */
function trade_can_see_prices() {
	if ( ! is_user_logged_in() ) {
		return false;
	}

	$allowed = array( 'administrator', 'shop_manager', 'trade_customer' );
	$user    = wp_get_current_user();

	return (bool) array_intersect( $allowed, (array) $user->roles );
}

trade_customer rolünü bir kez, bir etkinleştirme kancası içinde add_role() ile ekleyin; her kaydı elle onaylıyorsanız varsayılan customer rolünü de kullanabilirsiniz. Her iki durumda da onay, kayıt olmanın yan etkisi olmaktan çıkıp açık bir eyleme dönüşür.

Şimdi görüntüleme filtresi:

<?php
add_filter( 'woocommerce_get_price_html', 'trade_price_html', 100, 2 );
add_filter( 'woocommerce_variable_price_html', 'trade_price_html', 100, 2 );
add_filter( 'woocommerce_variable_empty_price_html', 'trade_price_html', 100, 2 );

function trade_price_html( $price_html, $product ) {
	if ( trade_can_see_prices() ) {
		return $price_html;
	}

	return '<span class="price-on-request">'
		. esc_html__( 'Price on request', 'trade' )
		. '</span>';
}

100 önceliği önemlidir. Geç çalışın ki para birimi değiştiriciler, vergi görüntüleme filtreleri ve indirim eklentileri sıralarını çoktan kullanmış olsun ve son sözü siz söyleyin.

Varyasyonlu ürünlerde, kod parçalarının genelde ima ettiği bir noktayı düzeltmek gerekiyor. WC_Product_Variable::get_price_html() aralığı oluşturur, bunu woocommerce_variable_price_html filtresinden (hiçbir varyasyonun fiyatı yoksa woocommerce_variable_empty_price_html filtresinden) geçirir ve sonucu, tıpkı diğer bütün ürün tiplerinde olduğu gibi woocommerce_get_price_html filtresine devreder. Yani aralığı tek başına ilk filtre zaten kapsar. Yukarıdaki iki varyasyona özel kanca bir zorunluluk değil, bu yolları doğrudan çağıran tema ve eklentilere karşı fazladan bir güvencedir. Varyasyonlu ürün hikâyesinin geri kalanı varyasyonlu ürünlerde fiyat aralığını gizleme yazısında.

Fiyatı gizlemek, ödemeyi kapatmak değildir#

Yalnızca yukarıdaki filtreyle bir misafir, “Price on request” yazısını ve hâlâ çalışan bir Sepete Ekle düğmesini görür. Ürünü sepete ekleyebilir, sepete gidebilir ve satır toplamında fiyatın tamını okuyabilir. Sepet, get_price_html() fonksiyonundan hiçbir zaman izin istemedi.

Kaba çözüm, ürünü misafirler için satın alınamaz hâle getirmektir. Bu tek bir filtredir ve klasik şablonlar, blok tabanlı Sepete Ekle ve Store API’nin sepete yazma uç noktaları bu filtreye uyar. Düğmeyi CSS ile gizlemekten üstün olmasının sebebi de budur:

<?php
add_filter( 'woocommerce_is_purchasable', function ( $purchasable, $product ) {
	return trade_can_see_prices() ? $purchasable : false;
}, 100, 2 );

Mağazanın misafirler için gezilebilir bir katalog olarak çalışmayı sürdürmesini, sepet yerine bir iletişim yolu sunmasını istiyorsanız bunun uzun anlatımı eklentisiz WooCommerce katalog modu ve Sepete Ekle düğmesini gizleme yazılarında.

Giriş yapılana kadar fiyat gizleme neden tek bir filtre değildir#

Sızıntılar, genelde keşfedilme sıralarıyla ve her birini kapatan filtresiyle birlikte aşağıda. Her birini, çıkış yapıp işlenmiş sayfaya değil ham yanıta bakarak test edin.

Varyasyon JSON’u#

Çıkış yapmış hâlde bir varyasyonlu ürün sayfasının kaynağını görüntüleyin ve display_price ifadesini arayın. Varyasyon formunun bu değere istemci tarafında ihtiyacı olduğu için her varyasyonun fiyatı HTML’in içinde duruyordur.

<?php
add_filter( 'woocommerce_available_variation', function ( $data ) {
	if ( trade_can_see_prices() ) {
		return $data;
	}

	$data['price_html']            = '';
	$data['display_price']         = 0;
	$data['display_regular_price'] = 0;

	return $data;
} );

İki sayısal anahtar için boş dize yerine 0 kullanın. Bazı temalar değeri bir JavaScript sayı biçimlendiricisinden geçirir ve boş dizede konsol hatası fırlatır; bu da varyasyon formunun tamamını bozar.

Yapılandırılmış veri#

WooCommerce çekirdeği, bir offers düğümü içeren Product JSON-LD bloku basar. Arama motorlarının okuduğu şey budur ve görünen fiyattan bağımsız olarak üretilir.

<?php
add_filter( 'woocommerce_structured_data_product', function ( $markup ) {
	if ( trade_can_see_prices() ) {
		return $markup;
	}

	unset( $markup['offers'] );

	return $markup;
} );

Bu yalnızca çekirdeği kapsar. Rank Math, Yoast, All in One SEO ya da işi şemaya adanmış bir eklenti kullanıyorsanız her biri kendi grafiğini kendi filtre adıyla kurar; her birini ayrı ayrı kapatmanız gerekir. Çıkış yapmış bir sayfanın işlenmiş kaynağında "price" dizesini arayın ve kaç kez geçtiğini sayın.

Store API ve REST API#

Blok temaya geçenleri asıl şaşırtan madde budur. Product Collection ile sepet ve ödeme blokları fiyatları PHP tarafında işlemez; /wp-json/wc/store/v1/ adresinden JSON çeker ve tarayıcıda işler. woocommerce_get_price_html filtreniz hiç çalışmaz.

wc/v3 ise farklı biçimde bir risktir ve bu konuda net olmakta fayda var. O rota herkese açık değildir: kimliği doğrulanmamış bir GET /wp-json/wc/v3/products isteği 401 döndürür. Ama bugüne kadar oluşturduğunuz her tüketici anahtarı — bir stok senkronizasyonuna, bir muhasebe köprüsüne, bir mobil uygulamaya, projeyi iki yıl önce bitirmiş bir ajansa verdiğiniz her anahtar — price, regular_price ve sale_price alanlarının tamamını okur ve anahtarlar çoğu zaman üretildikleri entegrasyondan uzun yaşar. Mağaza yüzünde fiyatları gizlerken WooCommerce > Ayarlar > Gelişmiş > REST API ekranında beş canlı anahtar bırakmak bir politika kararıdır; o hâlde bunu bilerek verin.

Store API’yi de REST API’yi de tek bir yerden, bir WordPress çekirdek filtresiyle yakalayabilirsiniz:

<?php
add_filter( 'rest_post_dispatch', function ( $response, $server, $request ) {
	if ( ! $response instanceof WP_REST_Response || trade_can_see_prices() ) {
		return $response;
	}

	$prefixes = array( '/wc/store/', '/wc/v3/products', '/wc/v2/products' );
	$route    = $request->get_route();
	$match    = false;

	foreach ( $prefixes as $prefix ) {
		if ( 0 === strpos( $route, $prefix ) ) {
			$match = true;
			break;
		}
	}

	if ( ! $match ) {
		return $response;
	}

	$response->set_data( trade_blank_prices( $response->get_data() ) );

	return $response;
}, 10, 3 );

function trade_blank_prices( $data ) {
	$keys = array(
		'price', 'regular_price', 'sale_price',
		'price_html', 'min_price', 'max_price',
	);

	foreach ( (array) $data as $key => $value ) {
		if ( is_array( $value ) ) {
			$data[ $key ] = trade_blank_prices( $value );
		} elseif ( in_array( $key, $keys, true ) ) {
			$data[ $key ] = '';
		}
	}

	return $data;
}

Bu kod bilerek kabadır. Yanıtın tamamını dolaşır ve fiyata benzeyen her şeyi, fiyat filtresi bloklarının kullandığı toplulaştırılmış fiyat aralığı dâhil, boşaltır. Mutlaka test edin, çünkü blok editörünün beklediği bir alanı boşaltmak, blokun fiyatsız işlenmesi yerine bomboş işlenmesine yol açabilir. Rotanın mühürlendiğini varsaymadan önce bilmeniz gereken iki şey var: bir tüketici anahtarı, sahibi olan kullanıcı kimliğiyle doğrulanır; yani bir yöneticiye ait anahtar trade_can_see_prices() kontrolünden yine geçer ve gerçek sayıları yine döndürür. Bu genelde istediğiniz şeydir, arada bir değildir. İkincisi, WPGraphQL’i de dışarı açıyorsanız orası kendi çözümleyicileri olan üçüncü bir şemadır ve ayrı bir geçiş ister.

Beş filtreyi ve üstüne bir şema eklentisi taramasını functions.php içinde sürdürmek istediğinizden fazlaysa, PriceVeil for WooCommerce tam olarak bu yüzeyleri sunucu tarafında kapatır. Bu yazı açısından önemli olan bir ayrım var: ücretsiz sürümdeki kitle ayarı Everyone veya Guests only sunar ve Guests only, düz giriş kontrolüdür; giriş yapmış her müşteri fiyatları görür ve normal şekilde satın alır. Bu, yukarıdaki rol kontrolü değildir; fiyat görünürlüğünü seçtiğiniz rollerle sınırlamak bir Pro hedefleme kuralıdır. Ücretsiz sürümde kimin hesap sahibi olacağını, tıpkı kod parçasındaki gibi yine sizin kayıt politikanız belirler. Ücretsiz sürümün kapsadığı şey yüzey listesidir: mağaza yüzü, varyasyon JSON’u, Store API, çekirdek ve üçüncü taraf yapılandırılmış verisi (Rank Math, Yoast, All in One SEO, Schema & Structured Data for WP), WooCommerce REST API ve GraphQL. Belgeler, neyin otomatik olarak hâlledildiğini ve neyi hâlâ kendinizin denetlemesi gerektiğini ortaya koyar; bunların bir kısmını elle yamadıysanız işinize yarayacak yarısı da budur.

Önbellek tuzağı#

Yukarıdaki filtrelerin hepsi wp_get_current_user() fonksiyonuna bağlıdır; yani bir ürün sayfasının çıktısı artık onu kimin istediğine bağlıdır. Bunu bilmeyen bir tam sayfa önbelleği tek bir çıktıyı saklar ve herkese onu sunar.

Arızanın iki yönü var ve bunlardan yalnızca biri zararsız. Misafir çıktısının önbelleğe alınıp giriş yapmış bir toptan müşteriye sunulması can sıkıcıdır: müşteri sepete gelene kadar “Price on request” görür. Giriş yapmış kullanıcı çıktısının önbelleğe alınıp misafirlere sunulması ise, giriş yapılana kadar fiyat gizlemeyi tamamen çökerten arızadır; çünkü gerçek fiyatlarınız artık uzun bir TTL ile bir CDN uç düğümünde duruyordur.

Pratikte buna üç yapılandırma yol açar:

  • Çerez atlaması olmayan Cache Everything. HTML’i önbelleğe alan bir Cloudflare sayfa kuralı ya da dönüşümü, wordpress_logged_in_* çerezi için bir atlama kuralıyla birlikte kurulmalıdır. Bu olmadan, kimliği doğrulanmış ilk istek herkese açık önbelleği doldurur.
  • Kullanıcı olarak çalışan ısıtma tarayıcıları. Bazı önbellek ön yükleme araçları giriş yapılmış bir oturuma yönlendirilebilir. Sizinki bunu yapabiliyorsa, yapmadığından emin olun.
  • Fiyat HTML’inin parça ya da nesne önbelleğine alınması. Bir tema veya optimizasyon eklentisi mağaza döngüsü işaretlemesini, kullanıcının rolünü içermeyen bir anahtar altında önbelleğe alıyorsa, sayfanın kendisi paylaşılmasa bile döngü kitleler arasında paylaşılır.

En güvenli düzen en yalın olanıdır: misafir sürümünü agresif biçimde önbelleğe alın, giriş çerezi taşıyan yanıtları asla önbelleğe almayın ve Store API ile REST rotalarının HTML ve CDN önbelleklemesinin tamamen dışında tutulduğundan emin olun. Doğrulamayı sıkıcı yoldan yapın: gizli bir pencere açın, ürün sayfasını isteyin ve işlenmiş sayfaya güvenmek yerine ham HTML’i okuyun. Ardından aynı pencerede /wp-json/wc/store/v1/products?per_page=100 adresini isteyin ve JSON içinde rakam arayın. WooCommerce eklentilerini nasıl sızıntı testinden geçiriyoruz yazımız aynı kontrol listesini daha ayrıntılı anlatıyor; PriceVeil bunun bir kısmını wp priceveil selftest komutuyla otomatikleştirir. Bu komut her ürünü dolaşır ve fiyat HTML’inin, yapılandırılmış verinin, Store API ürün yanıtının ve koleksiyon fiyat aralığının boş döndüğünü doğrular; herhangi bir sızıntıda sıfırdan farklı bir kodla çıkar.

Tek bakışta kapsam#

YüzeyTek başına price_html filtresi kapsıyor muNeyle kapanır
Tekil ürün, mağaza döngüsü, ilgili ürünlerEvetwoocommerce_get_price_html
Varyasyonlu ürün fiyat aralığıEvetwoocommerce_get_price_html (varyasyon kancaları isteğe bağlı)
Satır içi varyasyon JSON’uHayırwoocommerce_available_variation
Store API ürünleri ve fiyat aralığıHayırREST yanıt filtresi
Store API sepet yazmalarıHayırwoocommerce_is_purchasable
Çekirdek JSON-LD offers düğümüHayırwoocommerce_structured_data_product
SEO eklentisi şemasıHayırEklenti başına bir filtre
REST wc/v3 ve wc/v2 (anahtar sahipleri)HayırREST yanıt filtresi ve anahtar hijyeni
WPGraphQL / WooGraphQLHayırÇözümleyici düzeyinde filtreleme
Sepet, ödeme, sipariş e-postalarıHayırSatın almayı engellemek ya da her toplamı filtrelemek
Yanlış kitleye sunulan önbelleklenmiş HTMLHayırPHP değil, önbellek kuralları

Bu yaklaşımın çözmediği şeyler#

Sınırlar konusunda dürüst olmak, uzun bir özellik listesinden daha faydalıdır.

  • Ürün akışları. CTX Feed, Google Product Feed ve merchant center entegrasyonları dosyalarını sizin filtrelerinizden değil, ürün nesnesinden üretir. Gerçek fiyatları dışa aktarmayı sürdürürler. Ya bunları kapatın ya da kataloğunuzun o kanaldan herkese açık olduğunu kabul edin.
  • Tema ve özel kod. $product->get_price() çağırıp sonucu ekrana basan her kod, tasarımı gereği bütün görüntüleme filtrelerini atlar. Alt temanızda get_price için bir grep bir dakika sürer ve yapmaya değer.
  • Zaten dizine alınmış sayfalar. Filtreler yalnızca bundan sonra sunulanı değiştirir. Google’ın geçen ay taradığı fiyatlar, yeniden tarama yapılana kadar hem Google’ın önbelleğinde hem de üçüncü taraf fiyat karşılaştırma kazıyıcılarında kalır.
  • Üçüncü taraf uç önbellekleri. Bunları etkinleştirmek, bir CDN’de hâlihazırda duran bir nesneyi temizlemez. Değişiklikten sonra bir kez, elle temizleyin.
  • Kayıt politikası. Rol kontrolü, ancak rolü kimin alacağını siz belirliyorsanız bir anlam taşır. Herkes kayıt olup otomatik onaylanabiliyorsa fiyatları yalnızca tarayıcı botlarından gizlemiş olursunuz, başka kimseden değil.

Bundan sonra ne yapmalı#

Şu sırayla ilerleyin. trade_can_see_prices() yardımcı fonksiyonunu ve görüntüleme filtresini ekleyin. Misafirlerin satın alabilmesi gerekip gerekmediğine karar verin; gerekmiyorsa CSS yerine woocommerce_is_purchasable filtresini ekleyin. Varyasyon JSON’unu, yapılandırılmış veriyi ve REST rotalarını kapatın, ardından canlı REST anahtarlarınızı gözden geçirin. Sonra önbellek yapılandırmanızı denetleyin; çünkü giriş yapılana kadar fiyat gizleme kurulumu, ancak önündeki katman kadar güvenilirdir.

Son olarak, bir misafirin satın almak yerine ne yapması gerektiğine karar verin. Sonraki adımı göstermeden “Price on request” yazan bir ürün sayfası, ziyaretçiye gidecek hiçbir yer bırakmaz. Sepete Ekle düğmesinin eskiden durduğu yere bir teklif isteme akışı koymak alışılmış yanıttır; üstelik bu, hesabı onaylamak için zaten ihtiyaç duyduğunuz e-posta adresini size verir.

Okumaya devam

İlgili yazılar