Harga itu hal yang paling sering salah ditampilin di theme. Di 14 Objects Produk dan Koleksi > Soal Harga: Subunit kita tau harga disimpan sebagai subunit, di 20 Filters Money, Metafield, Cart, Customer, dan Localization > Filter Uang kita tau filter buat ngeformatnya. Bab ini soal situasi harga yang lebih rumit: diskon, mata uang, per unit, cicilan, dan B2B.
Diskon
Diskon bisa diterapin di level line item (produk tertentu) atau di level cart/checkout/order (keseluruhan). Dua cara pemberiannya: otomatis (automatic discount yang diatur merchant) atau lewat kode diskon manual. Theme harus nampilin dua-duanya dengan benar, dan tempatnya beda.
1. Tiga Object
Udah dibahas propertinya di 16 Objects Cart, Checkout, Order, dan Customer > Object Diskon, ini perannya:
discount_application, satu diskon yang diterapin di level cart, checkout, atau order. Di template cart diakses lewatcart.discount_applications(semua) ataucart.cart_level_discount_applications(yang level cart aja). Catatan penting: kode diskon manual cuma bisa diterapin di checkout, jadi gak ada dicart.discount_applications. Yang kamu lihat di cart cuma diskon otomatis.discount_allocation, hubungan antara satudiscount_applicationdan satu line item: berapa dari diskon itu yang jatuh ke baris ini. Diakses lewatline_item.line_level_discount_allocations.line_item, punya atribut harga yang dibutuhin:original_price,original_line_price,final_price,final_line_price,line_level_total_discount.
Bedanya price dan line_price: price harga per unit, line_price harga dikali kuantitas. Dan original sebelum diskon, final sesudah.
2. Diskon di Line Item
Kalau diskon berlaku ke item tertentu, tampilin di item itu: harga asli dicoret plus harga baru, dan daftar diskonnya dengan jumlah masing-masing (atau totalnya lewat line_level_total_discount):
{% for line_item in cart.items %}
<!-- line item info -->
{% if line_item.original_price > line_item.final_price %}
<s>{{ line_item.original_price | money }}</s>
{% endif %}
{{ line_item.final_price | money }})
{% if line_item.line_level_discount_allocations.size > 0 %}
Discounts:
<ul>
{% for discount_allocation in line_item.line_level_discount_allocations %}
<li>
{{ discount_allocation.discount_application.title }}-{{ discount_allocation.amount | money }}
</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}Perhatiin discount_allocation.discount_application.title: dari alokasi kamu bisa naik ke aplikasinya buat dapat nama diskonnya (“Diskon 10% Sepatu”), dan discount_allocation.amount buat jumlah yang jatuh ke baris ini. Contoh Dawn di sections/main-cart-items.liquid.
❌ cuma nampilin final_price, pembeli gak tau dia dapat diskon
{{ line_item.final_price | money }}✅ harga asli dicoret
{% if line_item.original_price > line_item.final_price %}<s>{{ line_item.original_price | money }}</s>{% endif %}
{{ line_item.final_price | money }}3. Diskon di Cart
Definisinya: subtotal = total line item setelah diskon line item, total = total cart setelah diskon cart. Jadi diskon level cart tampil di antara subtotal dan total, lewat cart_level_discount_applications (ada juga di order):
Subtotal: {{ cart.items_subtotal_price | money }}
{% if cart.cart_level_discount_applications.size > 0 %}
Discounts:
<ul>
{% for discount_application in cart.cart_level_discount_applications %}
<li>
{{ discount_application.title }}-{{ discount_application.total_allocated_amount | money }}
</li>
{% endfor %}
</ul>
{% endif %}
Total: {{ cart.total_price | money }}total_allocated_amount itu jumlah total diskon ini di seluruh cart. Contoh Dawn di sections/main-cart-footer.liquid. Pola yang sama dipakai di halaman order pelanggan dengan object order.
Format Mata Uang Eksplisit
Banyak mata uang pakai simbol yang sama, $ dipakai USD, CAD, AUD, SGD. Pembeli dari Kanada yang lihat “50.00 CAD) lewat filter money_with_currency`. Format persisnya diatur merchant di admin (Settings > Store details > Currency formatting).
1. Bikin Setting
Jangan paksa semua toko nampilin kode mata uang, toko satu mata uang gak butuh. Kasih theme setting:
...
},
{
"name": "Currency format",
"settings": [
{
"type": "header",
"content": "Currency codes"
},
{
"type": "paragraph",
"content": "Note that cart and checkout totals will always show the currency code, and itemized, unit, and installment prices will not."
},
{
"type": "checkbox",
"id": "currency_code_enable",
"label": "Show currency codes",
"default": true
}
]
}Perhatiin paragraph-nya: ngasih tau merchant kalau setting ini gak berlaku di semua tempat, sesuai pedoman di bawah. Ini contoh bagus pemakaian sidebar setting dari 30 Settings dan Input Settings > Sidebar Settings.
2. Rujuk di Tampilan Harga
{% if settings.currency_code_enable %}
{{ product.price | money_with_currency }}
{% else %}
{{ product.price | money }}
{% endif %}Karena pola ini berulang di banyak tempat, bungkus di snippet price.liquid yang nerima parameter harga.
3. Pedoman: Di Mana Pakai, Di Mana Gak
Kebanyakan tampilan harga ngikutin setting. Tapi ada yang hardcode:
- Selalu eksplisit (tanpa peduli setting): total cart dan checkout, biar gak ada kebingungan waktu pembeli bayar.
- Gak pernah eksplisit (tanpa peduli setting), karena konteksnya udah cukup jelas dan format pendek lebih muat: harga cicilan di banner installments, harga per item di cart atau order (totalnya udah eksplisit), dan harga per unit.
❌ semua tampilan ngikutin setting, total cart bisa tanpa kode
Total: {% if settings.currency_code_enable %}{{ cart.total_price | money_with_currency }}{% else %}{{ cart.total_price | money }}{% endif %}✅ total cart selalu eksplisit
Total: {{ cart.total_price | money_with_currency }}Harga per Unit
Merchant yang jual produk dalam jumlah atau ukuran tertentu (500g, 1kg, 1,5kg) mungkin perlu nampilin harga per satuan (per 100g) biar pembeli bisa bandingin. Unit price ditambahin merchant di admin per varian, dan theme tinggal nampilinnya.
1. Object
unit_pricedivariantatauline_item, harganya (subunit).unit_price_measurementdari 14 Objects Produk dan Koleksi > 3. unit_price_measurement, satuannya:measured_type,quantity_value/quantity_unit(produknya 500 ml),reference_value/reference_unit(acuannya 100 ml).
Sumbernya tergantung konteks: di cart dan order lewat line_item, di product dan collection lewat variant.
2. Implementasi
Tambahin di semua tempat ada tampilan harga: collection, product, cart. Dua langkah: cek ada unit_price_measurement atau gak, kalau ada pakai filter unit_price_with_measurement dari 20 Filters Money, Metafield, Cart, Customer, dan Localization > 3. unit_price_with_measurement yang ngeformat sesuai setting HTML without currency toko:
{% if variant.unit_price_measurement %}
{{ variant.unit_price | unit_price_with_measurement: variant.unit_price_measurement }}
{% endif %}{% if line_item.unit_price_measurement %}
{{ line_item.unit_price | unit_price_with_measurement: line_item.unit_price_measurement }}
{% endif %}Hasilnya kaya $2.00 / 100ml. Buat format uang beda, rantai filter uang dulu baru unit_price_with_measurement:
{% if variant.unit_price_measurement %}
{{ variant.unit_price | money_with_currency | unit_price_with_measurement: variant.unit_price_measurement }}
{% endif %}Tapi ingat pedoman di atas: harga per unit sebaiknya gak pakai kode mata uang.
Shop Pay Installments
Shop Pay Installments bikin pembeli bisa bayar pesanan 50–3.000 USD dalam 4 cicilan tanpa bunga. Banner-nya ngasih tau pembeli soal opsi ini, dengan link Learn more yang buka pop-up berisi jumlah cicilan, info lebih lanjut, dan pengungkapan yang diwajibkan. Banner cuma muncul kalau fitur ini aktif di toko merchant (cuma tersedia buat toko AS dengan Shopify Payments), jadi kode kamu aman dipasang di theme manapun.
1. Yang Dibutuhin
Banner dipasang lewat filter payment_terms (dari 20 Filters Money, Metafield, Cart, Customer, dan Localization > 2. payment_terms) yang diterapin ke object form. Jadi kamu butuh form produk ({% form 'product' %}) atau form cart ({% form 'cart' %}) dari tag form. Kalau theme kamu pakai form HTML biasa, ubah ke tag form Liquid, atribut HTML apapun bisa dikasih lewat parameter tag-nya.
2. Di Form Produk
Taruh referensi form | payment_terms di antara tag form, di bawah harga produk biar info harga dan pembayaran ngumpul:
{% form 'product', product %}
<!-- product price -->
{{ form | payment_terms }}
...
{% endform %}Banner otomatis update ngikutin varian yang dipilih, yang dideteksi dari input name="id" di form. Ini alasan lain kenapa hidden input id dari 39 Produk Variants dan High-variant Products penting.
Kalau harga produk di theme kamu gak ada di dalam form produk, bikin form produk kedua di dekat harga khusus buat banner, dengan hidden input id yang diupdate waktu varian berubah:
<!-- product price -->
{% form 'product', product %}
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}" />
{{ form | payment_terms }}
{% endform %}3. Di Form Cart
Sama, di bawah subtotal cart:
{% form 'cart', cart %}
<!-- cart subtotal -->
{{ form | payment_terms }}
...
{% endform %}Buat ngikutin perubahan total cart, banner membaca subtotal dari elemen dengan atribut data-cart-subtotal. Theme gratis Shopify udah punya atribut ini. Kalau banner kamu gak update, tambahin:
<span data-cart-subtotal>{{ cart.total_price | money }}</span>B2B
Banyak merchant jual ke perusahaan lain (business-to-business). Buat ngelolanya, merchant bikin company (perusahaan) yang bisa punya banyak customer dan banyak location (lokasi/cabang). Buat tiap transaksi, pelanggan B2B harus milih lokasi yang transaksinya dikaitin, karena harga, katalog, dan syarat pembayaran bisa beda per lokasi.
1. Object
Dari 16 Objects Cart, Checkout, Order, dan Customer > 5. B2B dan 16 Objects Cart, Checkout, Order, dan Customer > Object B2B:
customer.b2b?,truekalau pelanggan yang login itu pelanggan B2B.customer.current_company, objectcompanyyang lagi aktif.customer.current_location, objectcompany_locationyang lagi aktif.customer.company_available_locations, semua lokasi yang bisa dipilih pelanggan ini.company_location.current?dancompany_location.url_to_set_as_current, buat ganti lokasi.
2. Location Picker
Kalau pelanggan B2B, kasih mereka cara milih lokasi yang mereka belanjain. Logikanya: cek customer.b2b?, tampilin perusahaan dan lokasi sekarang, dan kalau lokasinya lebih dari satu, tampilin daftar lokasi yang belum dipilih:
{% if customer.b2b? %}
<div>
<h2>Welcome, {{ customer.name }} from {{ customer.current_company.name }}!</h2>
<p>You are purchasing for the {{ customer.current_location.name }} location.</p>
{% if customer.company_available_locations.size > 1 %}
<h3>Select a different location:</h3>
<ul>
{% for location in customer.company_available_locations %}
{% unless location.current? %}
<li>
<a href="{{ location.url_to_set_as_current }}">{{ location.name }}</a>
</li>
{% endunless %}
{% endfor %}
</ul>
{% endif %}
</div>
{% endif %}url_to_set_as_current itu URL yang ngeganti lokasi aktif dan balik ke halaman sekarang, gak perlu form atau JavaScript. Tempat yang umum: header (dekat akun) dan halaman akun.
3. Konten Berdasarkan Perusahaan atau Lokasi
{% if customer.current_company.name == 'Shopify' %}
<h3>Delivery and installation included.</h3>
{% endif %}Lebih rapi kalau pakai metafield di company/location daripada bandingin nama, tapi polanya sama.
4. Konten Khusus B2B
Toko B2B khusus mungkin mau nyembunyiin sebagian konten dari pengunjung yang gak login sebagai B2B: harga produk, ikon cart di header, tombol add to cart, dan CTA lain di halaman produk. Ada dua cara: theme editor (store contextualization, ngustomisasi konten per konteks tanpa kode) atau Liquid dengan customer.b2b?:
{%- if customer.b2b? -%}
// code for price here
{%- endif -%}Di tempat harga biasanya tampil, kasih ajakan login:
{%- if customer.b2b? -%}
// code for price here
{%- else -%}
<a href="{{ routes.account_login_url }}">Log in to see the price</a>
{%- endif -%}Kebalikannya, konten cuma buat yang belum login B2B, misalnya form pertanyaan:
{%- unless customer.b2b? -%}
// code for inquiry form here
{%- endunless -%}Catatan buat merchant yang pakai theme dari Theme Store: ngedit kode theme bikin theme gak bisa auto-update lagi. Alternatifnya: sembunyiin section/block yang berisi konten itu, terus tambahin Custom Liquid section/block yang nampilin versi bersyaratnya (salin isi block harga ke Custom Liquid block dan bungkus customer.b2b?). Tapi beberapa area kaya ikon cart di header mungkin bukan section/block terpisah, jadi mau gak mau ngedit kode.
5. Quantity Rules dan Price Breaks
Katalog B2B bisa ngatur aturan jumlah dan harga bertingkat per varian, lewat object dari 14 Objects Produk dan Koleksi > Object B2B dan Unit Pricing:
variant.quantity_rule:min(default 1),max(nilkalau gak ada),increment(default 1). Pakai di input kuantitas:
<input type="number" name="quantity"
min="{{ variant.quantity_rule.min }}"
{% if variant.quantity_rule.max %}max="{{ variant.quantity_rule.max }}"{% endif %}
step="{{ variant.quantity_rule.increment }}"
value="{{ variant.quantity_rule.min }}">variant.quantity_price_breaks: arrayquantity_price_breakdenganminimum_quantitydanprice, dalam konteks pelanggan sekarang. Tampilin sebagai tabel harga grosir:
{% if variant.quantity_price_breaks.size > 0 %}
<table>
<tr><th>Quantity</th><th>Price</th></tr>
{% for break in variant.quantity_price_breaks %}
<tr><td>{{ break.minimum_quantity }}+</td><td>{{ break.price | money }}</td></tr>
{% endfor %}
</table>
{% endif %}Dua-duanya ngembaliin nilai default buat pelanggan non-B2B, jadi aman dipasang di semua theme. Dan variant.price sendiri udah menyesuaikan katalog pelanggan B2B yang login, kamu gak perlu ngitung apa-apa.
Ringkasan
- Diskon:
discount_application(satu diskon, level cart/checkout/order),discount_allocation(bagian diskon yang jatuh ke satu line item), dan atributline_itemoriginal_*/final_*. Kode diskon manual gak ada di cart, cuma di checkout. - Diskon line item tampil di itemnya:
original_pricedicoret kalau >final_price, daftarline_level_discount_allocationsdengandiscount_application.titledanamount. - Diskon cart tampil di antara subtotal (
items_subtotal_price) dan total (total_price) lewatcart_level_discount_applicationsdengantotal_allocated_amount. - Format mata uang eksplisit: setting
currency_code_enableyang milihmoney_with_currencyataumoney. Total cart/checkout selalu eksplisit, harga cicilan, per item, dan per unit gak pernah. - Harga per unit: cek
unit_price_measurement, pakaiunit_price | unit_price_with_measurement: unit_price_measurement, darivariant(product/collection) atauline_item(cart/order), di semua tampilan harga. - Shop Pay Installments:
{{ form | payment_terms }}di dalamform 'product'(di bawah harga, ngikutin inputname="id") atauform 'cart'(di bawah subtotal, bacadata-cart-subtotal). Cuma muncul kalau aktif di toko. - B2B:
customer.b2b?,current_company,current_location,company_available_locationsdengancurrent?danurl_to_set_as_currentbuat location picker. Sembunyiin harga/CTA dari non-B2B lewat Liquid atau store contextualization di editor, dan hormatiquantity_rule(min/max/increment) serta tampilinquantity_price_breaks.
Bab selanjutnya kita bahas tombol pembayaran cepat: dynamic checkout button (Shop Pay, Apple Pay, Google Pay, PayPal) lewat payment_button, aturan penempatannya, dan accelerated checkout buat banyak produk sekaligus di cart.