package yookassa // Fiscal receipt support for «Чеки от ЮKassa» (54-ФЗ). YooKassa registers a receipt itself — no cash // register to rent, no OFD contract — but only if the payment and refund requests carry a receipt // object. // // It is DORMANT: the merchant runs on НПД, which is outside 54-ФЗ, so no receipt is sent and the // merchant reports each operation to «Мой налог» instead. The code stays because the switch back is // foreseeable — НПД has an annual income ceiling, and losing the regime puts 54-ФЗ back in force — // and turning it on is then setting one deploy variable rather than writing this again. Everything // here is inert while BACKEND_YOOKASSA_VAT_CODE is unset (see ReceiptEnabled). Reference: // https://yookassa.ru/developers/payment-acceptance/receipts/54fz/yoomoney/parameters-values const ( // PaymentSubjectService is the settlement subject (54-ФЗ tag 1212) — what the money is taken // for. Chips are sold as a service ("Услуга"). PaymentSubjectService = "service" // PaymentModeFullPayment is the settlement method (54-ФЗ tag 1214) — the payer pays and receives // the goods at once ("Полный расчет"). «Чеки от ЮKassa» accept only this and full_prepayment; // partial prepayment, advance and credit are not supported. PaymentModeFullPayment = "full_payment" // VatCodeNone is the VAT rate code (54-ФЗ tag 1199) for «Без НДС» — the rate a sole proprietor on // УСН or ПСН charges. The deployed value is configurable because the rate is the one receipt // attribute that genuinely changes (Russia raised the main rate on 1 Jan 2026, adding codes 11 // and 12). VatCodeNone = 1 // VatCodeOff is the configured value that means "send no receipt at all" — the state a merchant // outside 54-ФЗ runs in. It is the default, so receipts stay off until a rate is deliberately set. VatCodeOff = 0 // VatCodeMax is the highest VAT rate code the API accepts; codes run 1..12. VatCodeMax = 12 ) // Customer is the payer's contact data for delivering the receipt. «Чеки от ЮKassa» deliver only by // email (no SMS), so Email is required — the direct rail's confirmed email anchor supplies it. type Customer struct { Email string `json:"email,omitempty"` } // ReceiptItem is one line of a fiscal receipt: what was sold, how much of it, for how much, and the // three fiscal attributes that classify it. type ReceiptItem struct { Description string `json:"description"` Quantity float64 `json:"quantity"` Amount Amount `json:"amount"` VatCode int `json:"vat_code"` PaymentSubject string `json:"payment_subject"` PaymentMode string `json:"payment_mode"` } // Receipt is the fiscal receipt data sent alongside a payment or a refund. tax_system_code is // deliberately absent: YooKassa ignores it for «Чеки от ЮKassa». type Receipt struct { Customer Customer `json:"customer"` Items []ReceiptItem `json:"items"` } // maxDescriptionRunes is the API's limit for both a payment description and a receipt line // description (54-ФЗ tag 1030). A longer title is truncated rather than rejected, so an over-long // product name cannot block a purchase. const maxDescriptionRunes = 128 // SingleItemReceipt builds a one-line receipt for selling title at amount to email. Chip packs are // always a single line bought once, so quantity is 1 and the line amount is the whole payment. func SingleItemReceipt(email, title string, amount Amount, vatCode int) *Receipt { return &Receipt{ Customer: Customer{Email: email}, Items: []ReceiptItem{{ Description: TruncateDescription(title), Quantity: 1, Amount: amount, VatCode: vatCode, PaymentSubject: PaymentSubjectService, PaymentMode: PaymentModeFullPayment, }}, } } // TruncateDescription shortens s to the API's description limit, counting runes rather than bytes so // a Cyrillic title is not cut mid-character. func TruncateDescription(s string) string { r := []rune(s) if len(r) <= maxDescriptionRunes { return s } return string(r[:maxDescriptionRunes]) } // ValidVatCode reports whether code is a rate the API accepts (1..12) or VatCodeOff, which disables // receipts altogether. func ValidVatCode(code int) bool { return code == VatCodeOff || (code >= 1 && code <= VatCodeMax) } // ReceiptEnabled reports whether a configured VAT rate code asks for fiscal receipts. A merchant // outside 54-ФЗ leaves the code unset and no receipt is built or sent. func ReceiptEnabled(vatCode int) bool { return vatCode != VatCodeOff }