WooCommerce

Az egyik leghasznosabb ármódosító WooCommerce metódus, a wc_price()

Az egyik leghasznosabb ármódosító WooCommerce metódus, a wc_price() 1600 700 bacsoa

Minden héten szembejön valami, de amivel ma találkoztam, azt muszáj megosztanom, mert ugyan nekem új volt (mint a múltkori Chrome Easter Egg), de attól még lehet, hogy másnak is az lesz és az egyik leggyakrabban használt dologgal kapcsolatos: a termékek árával.

A feladat, hogy változtassunk meg egy termék árát bizonyos feltételek mentén, majd írjuk ki az eredeti és a módosított árat is. A mostani példában annyi a feladat, hogy a bejelentkezett felhasználók kapjanak 10% kedvezményt.

Ez a bejegyzés nyilván nem a pluginokról fog szólni, habár biztos meg lehetne oldani legalább két különbözővel 🙂

A következő két WooCommerce hookra van szükségünk:

  1. woocommerce_get_price
  2. woocommerce_get_price_html

Az első magát az árat, mint numerikus értéket állítja, a 2. pedig a megjelenített, tehát lerenderelt HTML kimenetet. Igazából ha csak a desszert érdekel, akkor a 2. pontot elhagyhatod, de azért olvass tovább, hátha érdekel a köret is.

Először nézzük az 1. példát, állítsuk be az árat:

add_filter('woocommerce_get_price', 'exibio_return_custom_price', 10, 2);
function exibio_return_custom_price( $price, $product ) {

    $post_id = $product->get_id(); # a termék azonosító (post_id)
    $user_id = get_current_user_id(); # a felhasználói azonosító (user_id)
    $original_price = $price; # az eredeti ár

    if ( is_user_logged_in() ) {
        $discount = $price / 100 * 10; # a kedvezmény mértéke: 10%
        $price -= $discount; # a kedvezménnyel csökkentett új ár
    }

    return $price;

}

Igazából a post_id és  a user_id nem kell ehhez a feladathoz, de odaírtam, hátha valaki csak egy konkrét termékre, vagy felhasználóra akarja megadni a kedvezményt. A fenti hook módosítja a WooCommerce terméknek admin felületen beállított árát. A megoldás ennyi, viszont nem ez volt a heti újdonság, hanem az, hogy szeretném kiírni az új árat úgy, hogy mellé/fölé kiírom az eredeti árat.

Ehhez kell a 2. hook, a woocommerce_get_price_html.

A probléma azzal van, hogy az árat numerikus (float azaz lebegőpontos) formátumban kapjuk meg, majd  formázottan szeretnénk megjeleníteni, pl. ezres elválasztóval, devizanem megjelenítéssel, stb. Tehát bejön ez:

15000

És ezt szeretnénk kiírni:

15 000 Ft

Ekkor jön az, hogy az a számot elkezdjük a jól ismert number_format függvénnyel alakítgatni, majd hozzátesszük a devizanemet, de ez elég macerás (újabb WooCommerce függvény kell hozzá, amivel a beállított devizanemet is le kell kérni a get_woocommerce_currency() metódussal). Plusz, ha esetleg kétnyelvű a webáruházunk és az egyikben HUF-nak, a másikban Ft-nak hívjuk a devizanemet, vagy esetleg eleve két különböző devizanem van, akkor még tovább kell bonyolítani. Arról nem is beszéltem, hogy néha £400 kell kiírnunk, nem pedig 400 £, ugye.

Ezt az egész problémát megoldja egy lépésben a wc_price nevű metódus. Egyetlen paramétert vár, ez pedig az ár numerikus (lebegőpontos vagy integer) formátumban, majd szépen visszaadja a WooCommerce-ben beállított formátumú devizában, ezres elválasztó karakterrel, szépen HTML formátumban.

add_filter( 'woocommerce_get_price_html', 'exibio_return_custom_price_html', 10, 2 );
function exibio_return_custom_price_html( $price, $product ) {
    
    $post_id = $product->get_id();
    $user_id = get_current_user_id();
    $original_price = (float) get_post_meta( $post_id, '_price', true ); // az eredeti ár numerikus formátumban

    if ( is_user_logged_in() ) {
        
        // az eredeti adatbázisban tárolt ár formázva, mielőtt még a 10% kedvezményt megadtuk volna
        $output = "<abbr class='original_price_label'>Eredeti ár:</abbr> <del class='original_price' aria-hidden='true'>" . wc_price($original_price) .  "</del>";
        
        $output .= "<abbr class='discount_price_label'>Akciós ár 10% kedvezménnyel:</abbr>";

        // ez pedig már a 10%-kal csökkentett / módosított ár        
        $output .= $price;
        $price = $output;
    }

    return $price;

}

A woocommerce_get_price előbb fut le, mint a woocommerce_get_price_html hook. Az eredeti ötletet innen nyúltam.

Összegezve, röviden így működik. Nézzük ezt a számot, mint eredeti ár:

15000

Hívjuk meg a wc_price metódust:

echo wc_price( 15000 );

Majd megkapjuk a generált WooCommerce formátumú HTML kimenetet:

<span class="woocommerce-Price-amount amount">
	<bdi>15 000&nbsp;<span class="woocommerce-Price-currencySymbol">Ft</span></bdi>
</span>

Örülünk Vincent. Rövid és eredményes kódolást!

WooCommerce rendelés utáni item meta adatok mentése, hook különbségek

WooCommerce rendelés utáni item meta adatok mentése, hook különbségek 1440 960 bacsoa

Ma futottam bele egy érdekes problémába, gondoltam megosztom másokkal is, mert szerintem elég nagy a zavar a fejekben – legalábbis az internet népét és a Stackoverflow felhasználóit tekintve.

Adott egy WooCommerce (4.8, de amúgy szerintem majdnem mindegy). A feladat, hogy a WooCommerce-ben leadott rendelés után hajtsunk végre valamit.

Erre több lehetőségünk is van. Mivel a WooCommerce rendelés gyakorlatilag egy egyedi WordPress bejegyzés (shop_order), ezért van neki post_status mezője. Ezeknek a szabványos WordPress bejegyzésekhez hasonlóan többféle státusza van, sőt lehet neki egyedit is adni. Általában alapértelmezetten a wc-processing (feldolgozás alatti) státuszt kapja meg. Egy SQL lekérdezéssel ellenőrizhetjük, hogy egy adott WooCommerce webáruházunkban hány fajta státuszú rendelés létezik:

SELECT post_status
FROM `wp_posts` 
WHERE `post_type` = 'shop_order' 
GROUP BY post_status

A Woocommerce rendelés státuszokról és magáról a Order objektumról itt olvashatsz bővebben.

Azért kellett ez a rövid bevezető, mert nem mindegy, hogy a rendelések mely státuszánál, mi fusson le. A Woocommerce erre többféle beépített hook-ot kínál. Mi az a hook? Ez gyakorlatilag egy callback. Mi az a callback? Na igen, ez az amikor 15 dolgot el kell magyarázni, hogy a végén egy szót megérts 🙂 A Callback-ről magyarul itt olvashatsz bővebben. A WordPress hook-okról pedig itt.

A rendelés leadása utáni tranzakciókezelésre a Woocommerce többféle hook megoldást is nyújt, például ezeket:

  1. woocommerce_checkout_order_processed
  2. woocommerce_payment_complete
  3. woocommerce_thankyou

A teljes listát itt találod.

Több, eltérő leírás van erről a neten, sőt a Woocommerce verziók között is vannak eltérések. Arra is kell vigyázni és ezt sokan elrontják, hogy minden hook-nak eltérő mennyiségű paramétere van. Ha többet adsz át az nem gond, de ha kevesebbet, akkor le sem fut. Sajnos vannak olyan hook-ok, amik nem dobnak hibát, csak egyszerűen nem működnek. Az alábbi példában például a 10, 3 a végén azt jelenti, hogy 3 paramétert vár és 10 prioritással fusson le. Mivel többet is egymásba ágyazhatsz, és az egyik felülírhatja a másikat, hogy ne legyen olyan egyszerű az élet. Főleg akkor jó ez, ha egy másik plugin már használja valami unintelligens 99999 prioritással és te meg nem jössz rá, hogy a tiéd miért nem működik… 😀

Amibe ma belefutottam, az egy elég egyszerűnek tűnő probléma. Én általában a woocommerce_checkout_order_processed hook-ot használom. Ez három paramétert vár. Az 1. a Woocommerce rendelés azonosító (wc_order_id), a 2. az elküldött személyes adatok (posted_data), a 3. pedig maga a rendelés objektum (order class):


add_action( 'woocommerce_checkout_order_processed', 'exibio_woocommerce_order_processed', 10, 3 );
function exibio_woocommerce_order_processed( $wc_order_id, $posted_data, $order ) {
    // ez fut le a rendelés leadása után
}

Ez a hook akkor fut le, ha a rendelés a processing státuszára már átváltott (tehát a wc-processed után). Sajnos vannak olyan bővítmények, amelyek rosszul használják a rendeléshez tartozó és elmentett item meta data, azaz a rendelés tétel meta adatok (ezek általában a rendeléshez tartozó egyedi mezők, paraméterek, akár a felhasználók által átadott változók, stb.) elmentését és nem a woocommerce_checkout_order_processed hook-ot használják, hanem a woocommerce_thankyou hook-ot. Nem ugyanarra való, de sajnos sokan összekeverik vagy helytelenül használják.

 

Emiatt hiába vártam egy plugin által összegyűjtött mezőit a rendelés meta adatait között, az csak a rendelés leadása utáni pillanatban vált elérhetővé. Ez azért volt, mert a plugin nem a processed utáni pillanatban mentette el a meta adatokat, hanem a thank you endpoint utáni pillanatban. Ez azt okozta, hogy a hook utáni iterálásnál on-the-fly hiába kérdeztem le az order_itemmeta táblát, nyoma sem volt a plugin által elmentett adatoknak.

Először a woocommerce_checkout_order_processed hook után egy sima SQL lekérdezést használtam (az item_id egyébként az order objektumon belüli változó). Az $items objektum iterálása SQL lekérdezéssel:

global $wpdb;
$items = $order->get_items();
foreach ($items as $item_id => $item) {
$sql = "
	SELECT meta_value
	FROM wp_woocommerce_order_itemmeta
	WHERE `order_item_id` = {$item_id}
	AND (meta_key = '_sumo_pp_payment_id')
";

$result = $wpdb->get_results($sql, ARRAY_A);
if ( !empty($result) ) {
	$sumo_pp_payment_obj = $result[0];
	$sumo_pp_payment_id = (int) $sumo_pp_payment_obj['meta_value'];
}

}

Ugyanez, dedikált Woocommerce függvénnyel lényegesen egyszerűbb, de egyik sem adott vissza találatot.

$items = $order->get_items();
foreach ($items as $item_id => $item) {
	$sumo_pp_payment_id = (int) wc_get_order_item_meta( $item_id, '_sumo_pp_payment_id', true );
}

Sehol semmi. Aztán amikor már lezajlott a rendelésleadás (és lefutott a woocommerce_thankyou hook filtere is, ami kb 1 mp múlva már megtörtént), akkor már ott volt a _sumo_pp_payment_id nevű rekord tartalma. Tanulság: ha legközelebb belefutsz egy ilyenbe és nem találod az adott meta mezőt, próbáld ki mindhárom hook-ot, hátha az adott megoldásban az lesz a nyerő.

Ajánlott irodalom:

  1. Egy WooCommerce hiba, amikor a woocommerce_checkout_order_processed hook előbb lefut, mint kéne: https://github.com/woocommerce/woocommerce/issues/28113
  2. A woocommerce-checkout-order-processed miért nem működik? https://stackoverflow.com/questions/45997729/any-idea-why-woocommerce-checkout-order-processed-not-firing-when-user-is-not-lo
  3. A woocommerce_thankyou hook használata https://stackoverflow.com/questions/43301940/woocommerce-thankyou-hook-not-working
  4. A woocommerce_payment_complete értelme, vagyis amikor az a fontos, hogy mikor van kifizetve egy rendelés: https://stackoverflow.com/questions/46686075/woocommerce-checkout-order-processed-hook-executing-function-twice