OctoWiki

Catalog Schema

Field-by-field reference for the three product catalogs — voucher (products + vendor_products), top-up (topup_products + topup_variants), and eSIM (esim_products + esim_variants) — with the discount model, fixed-vs-variable enforcement, and the dead columns.

Three independent catalogs that share no tables and no struct: voucher (products + vendor_products, denomination-range), top-up (topup_products + topup_variants, amount + input-fields), and eSIM (esim_products + esim_variants, data/validity). This is the field-by-field reference. See Products & Catalog for the conceptual model and Vendor Config for how variants map to a vendor SKU.

Three-catalog comparison

AspectVoucherTop-upeSIM
Master tableproductstopup_productsesim_products
SKU/variant tablevendor_productstopup_variantsesim_variants
Vendor pinned atvendor_product rowproduct row (vendor_id) + variantproduct row + variant
Vendor call keyvendor_products.vendor_product_idtopup_variants.vendor_variant_id (OCTO composite pid:vid)esim_variants.vendor_variant_id (DT One product_id)
Price modeldenomination range (min/max on VP)amount range OR fixedfixed amount only
Fixed flagis_fixed_denominationis_fixed_amountalways fixed (no flag)
Currency sourceparent Product.CurrencyIDtopup_variants.currency_codeesim_variants.currency_code
Client overrideclient_product_details (per vendor_product)client_topup_products (per product)client_esim_variants (per variant)
Extra inputnoneinput fields (player_id / mobile_number)none
Selection fnFindBestDiscountFindBestTopupVariantdirect variant lookup

Voucher: products

models.Product (product.go:8). Notable: country, currency, and denomination live on vendor_products, not here — there is no IsFixedDenomination/Min/Max on Product.

FieldColumnControls
IDidFK target of vendor_products, CPD, orders
Namenamedisplay + search
Terms / Details / HowToUseresp.legal / description / redemption instructions
Category / SubCategoryresp.free-text VARCHAR grouping (not enums), indexed
CountryID / CurrencyIDresp.product face-value country/currency (FK); drives forex/eligibility
OriginalVendorIDoriginal_vendor_idprovenance only (who first supplied it)
DeliveryMode / DeliveryTime / Validityresp.display strings
IsLinkEnabledis_link_enabledgates the reveal-via-link delivery path (was is_gv_link_enabled)
LinkTypelink_typePUBLIC / PROTECTED link access model
PreFetchEnabledpre_fetch_enabledpre-fetch vs on-demand stock
IsActiveis_activecatalog visibility (default TRUE)

Dropped columns (dead, restored only on down-migration): rank, delivery_type, classification, internal_reference_id, affiliate_link, store_name, is_barcode_enabled, min_kyc_enabled, gst_enabled, gst_fee. Note delivery_type still has a write branch (product.go:1252) though the column is gone — a dead write (only fires if a caller passes it). VendorProductSummary/VendorProductCount are computed, not columns.

Voucher: vendor_products

models.VendorProduct (vendor_product.go:9) — the product → vendor SKU mapping, carrier of denomination range + discount.

FieldColumnControls
ProductID / VendorIDresp.which master product / which vendor
VendorProductIDvendor_product_idexternal SKU — the call key when ordering
VendorProductSKU / VendorProductNameresp.secondary SKU / vendor's own name
MinDenomination / MaxDenominationresp.allowed face-value range (DECIMAL(15,2))
IsFixedDenominationis_fixed_denominationif true, only one denomination valid
Discount(computed, not stored)the client discount — calculated at read time
VendorDiscountvendor_discountdiscount the vendor gives us; base for margin
ClientProductDetailID(computed)set when a CPD override matched
PinMappedField / CodeMappedField / ClaimURLMappedField / VoucherReferenceCodeMappedFieldresp.which vendor-response field holds each secret
IsActiveis_activemapping enabled; skipped in FindBestDiscount if false

Dead/legacy: min_value/max_value still exist in DB but aren't in the model (superseded by min_denomination/max_denomination); display_discount + the stored discount column were dropped (discount is now purely computed); priority is unread. There is no currency column (currency comes from the parent Product) and no IsBulk/reference-support flags on this table. The (product_id, vendor_id) unique constraint was dropped, so one product can have multiple rows per vendor (different denomination bands).

The discount model

const DEFAULT_DISCOUNT = 1.0 (vendor_product.go:20). getDiscountForVendorProduct computes each VP's client Discount at read time:

  • CPD row exists for (client_id, vendor_product_id)Discount = cpd.Discount.
  • ElseDiscount = VendorDiscount − 1%. A negative result is a markup (client pays more) — intended.

FindBestDiscount(denomination, vps) picks the best VP for a denomination: skips inactive VPs and VPs whose [min,max] excludes it, tracks the max discount, and returns {VendorProductID, VendorID, Discount, VendorDiscount}. VendorProductID == 0 means "denomination out of range" — the range-validation gate.

Two divergent discount implementations

The live path is repo.getDiscountForVendorProduct + FindBestDiscount (−1%). A second, divergent utils.GetDiscountForVendorProduct (discount_calc.go:349) uses different rules and a hardcoded −4.0 surcharge — a parallel/legacy path. This is the discount divergence flagged in Client Config and Known Issues; verify which path each caller uses.

Top-up: topup_products + topup_variants

topup_products (topup_product.go:59) — natural key UNIQUE(vendor_id, vendor_category_id):

FieldColumnControls
VendorIDvendor_idpins the product to one vendor — resolution flows through this
VendorCategoryIDvendor_category_idvendor-side product id (DT One operator_id, SEAGM category_id)
TopupTypetopup_typeGAMING/MOBILE/UTILITY (Scan defaults GAMING)
CountryCode / CurrencyCoderesp.destination country/currency
Unitunitadmin override for the in-game unit label ("Diamonds"/"UC"); nil → inferred at read; not clobbered by re-sync
IsActiveis_activevisibility

topup_variants (topup_variant.go:9) — natural key UNIQUE(vendor_id, vendor_variant_id):

FieldColumnControls
VendorVariantIDvendor_variant_idvendor's variant id = the order call key. For OCTO a composite "productID:variantID" (split on : to recover the parent); SEAGM = type_id
Name / Categoryresp.display / UI grouping
CurrencyCodecurrency_codevariant price currency (default USD)
MinAmount / MaxAmountresp.variable-amount range (DECIMAL(15,4))
IsFixedAmountis_fixed_amountif true → baseAmount forced to MinAmount
VendorDiscountvendor_discountvendor margin
IsActive / LastSyncedAtresp.visibility / sync freshness

Input fields — two tables (a naming trap): topup_product_input_fields (product-level, the union across variants — this is what order validation reads, since orders carry product_id) and topup_variant_input_fields (per-variant, the vendor source-of-truth). Fields: field_name, field_type (text/number/select/phone), is_required, validation_regex, options in topup_product_input_options. See Top-up & eSIM Flows.

Supporting: topup_variant_fields (key-value: par_value, par_value_currency — server-internal, filtered from the public API and folded into unit), topup_variant_regions. Per-client discount is now client_topup_products (keyed per product) — the file client_topup_variant.go is stale-named but holds ClientTopupProduct.

eSIM: esim_products + esim_variants

esim_products (esim_product.go:9): VendorID, Code (legacy "DTONE_ESIM-4909", display-only), VendorCategoryID (operator_id), Country/CountryCode/CurrencyCode, SortOrder, IsActive.

esim_variants (esim_variant.go:9):

FieldColumnControls
VendorVariantIDvendor_variant_idDT One product_id = order call key
Amountamountfixed wholesale price (eSIM has no range) DECIMAL(12,4)
VendorDiscountvendor_discountmargin
DataAmountGB / ValidityDaysresp.plan quota / validity
IsActive / LastSyncedAtresp.visibility / freshness

Supporting: esim_variant_fields (wholesale/retail amount, fx_rate, service_id, operator_id), esim_variant_regions, client_esim_variants (per-variant discount).

Fixed vs variable — enforcement

IsFixedDenomination is mostly display, not a validator

For vouchers, the real amount check is the min/max range compare, not the boolean. The range gate is implicit via FindBestDiscount (out-of-range → VendorProductID==0 → rejected with "denomination out of range"), plus explicit compares in the order flow (create_voucher_order.go:2412/2609) and cart. IsFixedDenomination itself is largely set-and-displayed — the API-response layer even treats min==max as fixed regardless of the flag. So don't rely on the boolean for validation.

  • Top-up: IsFixedAmount && MinAmount!=nilbaseAmount forced to MinAmount (topup_order.go:161, topup.go:351); variable amounts range-match in FindBestTopupVariant.
  • eSIM: no range — amount is the fixed wholesale price, always charged as-is.

Catalog sync (reference)

  • Voucher: services/vendor_product_import.go (bulk import → temp table → enrich → upsert); CPD resolution during import uses FindBestDiscount.
  • Top-up: topup.go:1442 loops the vendor catalog, upserts TopupVariant on (vendor_id, vendor_variant_id), then variant fields + product input fields.
  • eSIM: esim_order.go catalog paths + admin_esim.go, upsert on (esim_product_id, vendor_variant_id).

LastSyncedAt records freshness (top-up/eSIM only). Admin-owned fields (topup_products.unit, is_active) are not clobbered by re-sync. Driven by the vendor-catalog-sync cron. See Products & Catalog → catalog sync.

Dead / computed / defaults summary

TableDead / computed
Productdelivery_type dead write; 10 dropped columns; VendorProductSummary computed
VendorProductmin_value/max_value/priority unread; display_discount+stored discount dropped; Discount/ClientProductDetailID computed; no currency/IsBulk
TopupProductdropped code/country/auto_delivery/sort_order; Unit admin-or-inferred
TopupVariantdropped description/image_url/auto_delivery; par_value_currency server-internal; two input-field tables; stale filename
EsimVariantCode legacy display-only; no fixed flag
Discount−1% live vs −4.0 legacy — divergent

Key files

  • Models: database/models/{product,vendor_product,topup_product,topup_variant,esim_product,esim_variant,client_product_detail}.go
  • Discount: database/repo/vendor_product.go:20,155, utils/discount_calc.go:249-389, utils/charges.go:44-83
  • Fixed enforcement: create_voucher_order.go:2412/2609, services/topup_order.go:161, http/handler/topup.go:351
  • Composite OCTO variant id: services/external_vendors/direct_topup/octopus/octopus_vendor.go:251,530

On this page