Skip to content

Developer notes

app/addons/kv_vendor_coupons/
addon.xml settings, 2 tables, 2 privileges
init.php / func.php hooks, promotion condition/bonus callbacks, settings handlers, licence helpers
controllers/backend/kv_vendor_coupons.php coupon list, editor, approval, usage report (admin + vendor)
controllers/backend/promotions.pre.php core promotion editor: redirects coupon promotions
controllers/backend/kv_vendor_coupons_license.php "Check now" and licence cron
schemas/promotions/schema.post.php condition "kv_vc_coupon" and bonus "kv_vc_discount"
schemas/menu/*.post.php Marketing menu (admin, vendor panel, vendor panel configurator)
schemas/permissions/*.post.php privileges, vendor access
Tygh/Addons/KvVendorCoupons/
Config.php settings, vendor/plan check
Coupons.php storage, validation, shadow promotion sync
Engine.php condition, bonus (discount lines), free shipping, funding adjustment
Usage.php usage rows, limits, first-order check, report
License/, Upgrade/ licence client, self-healing upgrades
design/backend/templates/addons/kv_vendor_coupons/ views (manage, update, usage), components, licence UI
design/backend/css/addons/kv_vendor_coupons/ styles.less, license.less
js/addons/kv_vendor_coupons/backend.js coupon editor
var/langs/en/addons/kv_vendor_coupons.po

Every coupon owns one core cart promotion (?:promotions, company_id = vendor, zone = cart, priority 9000000 so it runs after marketplace promotions, status A only while the coupon is active and approved). Its conditions are coupon_code eq CODE and the add-on condition kv_vc_coupon eq <coupon_id>; its bonus is kv_vc_discount. The core then handles the coupon field and messages, cart.coupons, the order’s promotions data and the display of applied promotions.

  • The condition (Engine::check()) checks licence, status/approval, dates, vendor/plan, the vendor’s items, the minimum, total/per-customer uses and the first-order rule. While Multi-Vendor suborders are placed, core evaluates conditions against the parent order; the condition then simply returns whether the parent order has the coupon.
  • The bonus (Engine::apply()) calculates per-unit discounts for the vendor’s lines and applies them with core fn_promotion_apply_discount() (by_fixed per unit), like the core “discount on products” bonus. In suborders the per-unit values stored in the parent order are re-used, so parent and vendor order always agree. The coupon data (kv_vc: coupon, vendor, code, discount, item subtotal, lines, free shipping, funding) is stored in the bonus, i.e. in orders.promotions.
  • With the add-on disabled, the condition and bonus are missing from the promotion schema, so core never applies the promotions.
  • The promotions are hidden on the core promotion pages (get_promotions while runtime.controller is promotions and the search is not the cart calculation) and the core editor redirects to the coupon (promotions.pre.php).
  • Percent: round(price × percent / 100, decimals) per unit, on the price after marketplace catalog/product promotions; capped by the maximum percent.
  • Fixed amount: integer minor units; proportional shares per line, floored per unit; the rest is added one minor unit per item, lines with the smallest quantity first, as long as it fits. The total never exceeds min(amount, cap, items).
  • Free shipping: calculate_cart_content_after_shipping_calculation sets every rate of the vendor’s product group to 0; the original rate is kept in kv_vc_rate and restored when the coupon goes away.
  • Funding (mve_place_order, add-on priority 2700): fn_calculate_commission_for_payout() runs again with the coupon added back to the order total, payout amount and lines (recursion-guarded). Vendor: commission = that value, capped at the order amount. Marketplace: that value minus the discount. Shared: unchanged. marketplace_profit, commission_amount and extra.kv_vendor_coupons of the payout are updated before core saves it.
Table Contents
?:kv_vc_coupons coupon_id, company_id, promotion_id, code (unique), name, discount_type (P/F/N), discount_value, free_shipping, min_subtotal, from_date, to_date, uses_total, uses_per_customer, first_order, status (A/D), approval (A/P/R), admin_note, created_at, updated_at, user_id
?:kv_vc_usages usage_id, coupon_id, company_id, order_id, parent_order_id, user_id, email, code, items_subtotal, discount, shipping_saved, funding, commission_delta, created_at; unique (order_id, coupon_id)

A use is counted while the order status is not N, F, D or I.

All hooks have identical arguments in 4.17.2 and 4.21.2.

Hook Use
calculate_cart_items resets the free-shipping marks before promotions are re-applied
calculate_cart_content_after_shipping_calculation free shipping of the vendor’s product group
promotion_apply_before_get_promotions drops the per-request promotion cache after a coupon changed in the same request
pre_place_order stops the order once when a coupon the customer saw became invalid during checkout
mve_place_order funding adjustment of the vendor payout
place_order usage row of each vendor order
get_promotions hides coupon promotions on core promotion pages
delete_promotions_post a coupon promotion deleted elsewhere disables its coupon
delete_order, delete_company cleanup

No storefront output and no storefront writes on GET requests. Customers use the core coupon field on the cart and checkout pages, which are not cached.

fn_kv_vendor_coupons_is_licensed() (License\Client::isActive()) gates new coupons in carts (in the condition; a coupon already applied in the previous calculation keeps working, so a started checkout is never broken) and saving coupons. Placed orders, the funding adjustment of orders with an applied coupon, lists and reports are never gated. The licence check itself runs only in the admin panel or from cron (php admin.php --dispatch=kv_vendor_coupons_license.check).

  • The applied coupons of a cart or order: \Tygh\Addons\KvVendorCoupons\Engine::appliedCoupons($cart['promotions']).
  • The payout extra kv_vendor_coupons holds the commission with and without the coupon and the adjustment.
  • Commission add-ons that use vendor_plans_calculate_commission_for_payout_* are included in the “without coupon” calculation automatically.