initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
examples
|
||||||
167
README.md
167
README.md
@@ -1,2 +1,169 @@
|
|||||||
# go-vchasno-kassa
|
# go-vchasno-kassa
|
||||||
|
|
||||||
|
Go SDK для работы с API кассы ВЧАСНО - украинской системы фискализации.
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get git.jeezft.xyz/rk/go-vchasno-kassa
|
||||||
|
```
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.jeezft.xyz/rk/go-vchasno-kassa"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := vchasno.NewClient(vchasno.Config{
|
||||||
|
Token: "your-api-token-here",
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Проверка подключения
|
||||||
|
if err := client.Ping(ctx); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создание фискального чека
|
||||||
|
receipt := vchasno.FiscalReceipt{
|
||||||
|
Cashier: "Иванов И.И.",
|
||||||
|
CashierTaxID: "1234567890",
|
||||||
|
Items: []vchasno.ReceiptItem{
|
||||||
|
{
|
||||||
|
Name: "Parking on obj1",
|
||||||
|
Code: "PARK001",
|
||||||
|
Price: vchasno.NewMoney(10.00),
|
||||||
|
Quantity: 2.0,
|
||||||
|
Amount: vchasno.NewMoney(20.00),
|
||||||
|
Tax: vchasno.NewTax(4.0, vchasno.NewMoney(0.80)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Payments: []vchasno.Payment{
|
||||||
|
{
|
||||||
|
Type: "cash",
|
||||||
|
Amount: vchasno.NewMoney(20.00),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Total: vchasno.NewMoney(20.00),
|
||||||
|
TaxTotal: vchasno.NewMoney(0.80),
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.CreateFiscalReceipt(ctx, receipt)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Fiscal receipt created: %s", response.FiscalNumber)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Функциональность
|
||||||
|
|
||||||
|
### Работа с фискальными чеками
|
||||||
|
- Создание фискальных чеков через единый API эндпоинт `/api/v3/fiscal/execute`
|
||||||
|
- Получение информации о фискализованном чеке
|
||||||
|
- Отмена фискальных чеков
|
||||||
|
- Поддержка различных типов оплаты (наличные, карта)
|
||||||
|
- Автоматический расчет НДС и налогов
|
||||||
|
|
||||||
|
### Отчеты
|
||||||
|
- X-отчет (промежуточный отчет без обнуления)
|
||||||
|
- Z-отчет (итоговый отчет с обнулением кассы)
|
||||||
|
|
||||||
|
### Валидация данных
|
||||||
|
- Проверка корректности сумм и количества
|
||||||
|
- Валидация обязательных полей
|
||||||
|
- Проверка соответствия общей суммы и оплат
|
||||||
|
|
||||||
|
## Примеры использования
|
||||||
|
|
||||||
|
Полные примеры использования находятся в папке `examples/`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd examples
|
||||||
|
go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## Валидация
|
||||||
|
|
||||||
|
SDK включает встроенную валидацию фискальных чеков:
|
||||||
|
|
||||||
|
```go
|
||||||
|
receipt := vchasno.FiscalReceipt{
|
||||||
|
Cashier: "Иванов И.И.",
|
||||||
|
CashierTaxID: "1234567890",
|
||||||
|
Items: []vchasno.ReceiptItem{...},
|
||||||
|
Payments: []vchasno.Payment{...},
|
||||||
|
Total: vchasno.NewMoney(100.00),
|
||||||
|
TaxTotal: vchasno.NewMoney(4.00),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := receipt.Validate(); err != nil {
|
||||||
|
log.Fatal("Validation error:", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обработка ошибок
|
||||||
|
|
||||||
|
```go
|
||||||
|
response, err := client.CreateFiscalReceipt(ctx, receipt)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, vchasno.ErrMissingToken):
|
||||||
|
log.Println("Отсутствует токен авторизации")
|
||||||
|
default:
|
||||||
|
log.Printf("API error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Работа с деньгами
|
||||||
|
|
||||||
|
SDK использует копейки для точных денежных расчетов:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Создание суммы в копейках
|
||||||
|
price := vchasno.NewMoney(10.50) // 1050 копеек
|
||||||
|
|
||||||
|
// Конвертация обратно в гривны
|
||||||
|
amount := price.ToFloat64() // 10.50
|
||||||
|
|
||||||
|
// Создание налога
|
||||||
|
tax := vchasno.NewTax(4.0, vchasno.NewMoney(0.42)) // 4% НДС
|
||||||
|
```
|
||||||
|
|
||||||
|
## Типы операций
|
||||||
|
|
||||||
|
Через эндпоинт `/api/v3/fiscal/execute` выполняются следующие команды:
|
||||||
|
|
||||||
|
- `create_receipt` - создание фискального чека
|
||||||
|
- `get_receipt` - получение информации о чеке
|
||||||
|
- `cancel_receipt` - отмена чека
|
||||||
|
- `x_report` - получение X-отчета
|
||||||
|
- `z_report` - получение Z-отчета
|
||||||
|
- `ping` - проверка соединения
|
||||||
|
|
||||||
|
## Конфигурация
|
||||||
|
|
||||||
|
```go
|
||||||
|
client := vchasno.NewClient(vchasno.Config{
|
||||||
|
BaseURL: "https://kasa.vchasno.ua", // необязательно, по умолчанию
|
||||||
|
Token: "your-token", // обязательно
|
||||||
|
Timeout: 30 * time.Second, // необязательно
|
||||||
|
HTTPClient: &http.Client{...}, // необязательно
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
Полная документация API ВЧАСНО доступна по адресу: https://documenter.getpostman.com/view/26351974/2s93shy9To
|
||||||
150
api/kasa.go
Normal file
150
api/kasa.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"resty.dev/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewKasaInstance(token string) *Kasa {
|
||||||
|
return &Kasa{
|
||||||
|
token: token,
|
||||||
|
resty: resty.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Kasa struct {
|
||||||
|
token string
|
||||||
|
resty *resty.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func createReceiptRow(name string, cnt int, price float64, comment string, disc float64, taxgrp string) ReceiptRow {
|
||||||
|
return ReceiptRow{
|
||||||
|
Name: name,
|
||||||
|
Cnt: cnt,
|
||||||
|
Price: price,
|
||||||
|
Disc: disc,
|
||||||
|
Taxgrp: taxgrp,
|
||||||
|
Comment: comment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createReceiptPayCash(PayType int, sum float64, comment string) ReceiptPay {
|
||||||
|
return ReceiptPay{
|
||||||
|
Type: PayType,
|
||||||
|
Sum: sum,
|
||||||
|
Comment: comment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createReceiptPayCard(PayType int, sum float64, comment string, cardmask string, bankID string) ReceiptPay {
|
||||||
|
return ReceiptPay{
|
||||||
|
Type: PayType,
|
||||||
|
Sum: sum,
|
||||||
|
Comment: comment,
|
||||||
|
Paysys: "parking_pos",
|
||||||
|
Cardmask: cardmask,
|
||||||
|
BankID: bankID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReceiptSum(rows []ReceiptRow, taxgrp string) float64 {
|
||||||
|
sum := 0.0
|
||||||
|
for _, row := range rows {
|
||||||
|
sum += (row.Price - row.Disc) * float64(row.Cnt)
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func createReceipt(PayType int, sum float64, comment string, cardmask string, bankID string, name string, cnt int, price float64, disc float64, taxgrp string) Receipt {
|
||||||
|
rows := []ReceiptRow{createReceiptRow(name, cnt, price, comment, disc, taxgrp)}
|
||||||
|
sum = getReceiptSum(rows, taxgrp)
|
||||||
|
|
||||||
|
r := Receipt{
|
||||||
|
Sum: sum,
|
||||||
|
Round: 0.00,
|
||||||
|
Disc: 0,
|
||||||
|
DiscType: 0,
|
||||||
|
Rows: rows,
|
||||||
|
Pays: []ReceiptPay{createReceiptPayCard(PayType, sum, comment, cardmask, bankID)},
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Receipt sum:", r.Sum)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// "fiscal": {
|
||||||
|
// "task": 1,
|
||||||
|
// "cashier": "Парковка",
|
||||||
|
// "receipt": {
|
||||||
|
// "sum": 40.00,
|
||||||
|
// "round": 0.00,
|
||||||
|
// "comment_up": "Квитанція за паркування",
|
||||||
|
// "comment_down": "Дякуємо за користування паркінгом!",
|
||||||
|
// "disc": 0.00,
|
||||||
|
// "disc_type": 0,
|
||||||
|
// "rows": [
|
||||||
|
// {
|
||||||
|
// "code": "PARK-3H",
|
||||||
|
// "pop": "Оплата за послуги паркування",
|
||||||
|
// "name": "Парковка, 3 години",
|
||||||
|
// "cnt": 1,
|
||||||
|
// "price": 40.00,
|
||||||
|
// "disc": 0.00,
|
||||||
|
// "taxgrp": "4",
|
||||||
|
// "comment": "Тариф: 3 години"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "pays": [
|
||||||
|
// {
|
||||||
|
// "type": 0,
|
||||||
|
// "sum": 40.00,
|
||||||
|
// "change": 0.00,
|
||||||
|
// "comment": "Оплата готівкою"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
func createFiscal(source string, cashier string, PayType int, sum float64, comment string, cardmask string, bankID string, name string, cnt int, price float64, disc float64, taxgrp string) FiskalCheck {
|
||||||
|
return FiskalCheck{
|
||||||
|
Source: source,
|
||||||
|
Fiscal: Fiscal{
|
||||||
|
Task: 1,
|
||||||
|
Cashier: cashier,
|
||||||
|
Receipt: createReceipt(PayType, sum, comment, cardmask, bankID, name, cnt, price, disc, taxgrp),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Kasa) NewSell(ctx context.Context, PayType int, sum float64, comment string, cardmask string, bankID string, name string, cnt int, price float64, disc float64, taxgrp string) (*KasaResponse, error) {
|
||||||
|
fiscal := createFiscal("kasa", "test", PayType, sum, comment, cardmask, bankID, name, cnt, price, disc, taxgrp)
|
||||||
|
|
||||||
|
// create a POST request to the kasa api https://kasa.vchasno.ua/api/v3/fiscal/execute with resty
|
||||||
|
request, err := k.resty.R().SetBody(fiscal).SetHeader("Authorization", k.token).Post("https://kasa.vchasno.ua/api/v3/fiscal/execute")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create a POST request to the kasa api: %w", err)
|
||||||
|
}
|
||||||
|
if request.IsError() {
|
||||||
|
return nil, fmt.Errorf("failed to create a POST request to the kasa api: %v", request.Error())
|
||||||
|
}
|
||||||
|
if request.StatusCode() != 200 {
|
||||||
|
return nil, fmt.Errorf("failed to create a POST request to the kasa api: %v", request.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(request.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(body))
|
||||||
|
var response KasaResponse
|
||||||
|
if err := json.Unmarshal(body, &response); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
93
api/structs.go
Normal file
93
api/structs.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
type FiskalCheck struct {
|
||||||
|
Source string `json:"source"`
|
||||||
|
Userinfo Userinfo `json:"userinfo"`
|
||||||
|
Fiscal Fiscal `json:"fiscal"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Userinfo struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fiscal struct {
|
||||||
|
Task int `json:"task"`
|
||||||
|
Cashier string `json:"cashier"`
|
||||||
|
Receipt Receipt `json:"receipt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Receipt struct {
|
||||||
|
Sum float64 `json:"sum"`
|
||||||
|
Round float64 `json:"round"`
|
||||||
|
CommentUp string `json:"comment_up"`
|
||||||
|
CommentDown string `json:"comment_down"`
|
||||||
|
Disc float64 `json:"disc"`
|
||||||
|
DiscType int `json:"disc_type"`
|
||||||
|
Rows []ReceiptRow `json:"rows"`
|
||||||
|
Pays []ReceiptPay `json:"pays"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReceiptRow struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Pop string `json:"pop,omitempty"`
|
||||||
|
Code1 string `json:"code1"`
|
||||||
|
Code2 string `json:"code2"`
|
||||||
|
CodeAa []string `json:"code_aa,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Cnt int `json:"cnt"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
|
Disc float64 `json:"disc"`
|
||||||
|
Taxgrp string `json:"taxgrp"`
|
||||||
|
Comment string `json:"comment"`
|
||||||
|
CodeA string `json:"code_a,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReceiptPay struct {
|
||||||
|
Type int `json:"type"`
|
||||||
|
Sum float64 `json:"sum"`
|
||||||
|
Change float64 `json:"change,omitempty"`
|
||||||
|
Comment string `json:"comment"`
|
||||||
|
Commission float64 `json:"commission,omitempty"`
|
||||||
|
Paysys string `json:"paysys,omitempty"`
|
||||||
|
Rrn string `json:"rrn,omitempty"`
|
||||||
|
OperType string `json:"oper_type,omitempty"`
|
||||||
|
Cardmask string `json:"cardmask,omitempty"`
|
||||||
|
TermID string `json:"term_id,omitempty"`
|
||||||
|
BankName string `json:"bank_name,omitempty"`
|
||||||
|
BankID string `json:"bank_id,omitempty"`
|
||||||
|
AuthCode string `json:"auth_code,omitempty"`
|
||||||
|
ShowAdditionalInfo bool `json:"show_additional_info,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KasaResponse struct {
|
||||||
|
Task int `json:"task"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
Ver int `json:"ver"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
Device string `json:"device"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Dt string `json:"dt"`
|
||||||
|
Res int `json:"res"`
|
||||||
|
ResAction int `json:"res_action"`
|
||||||
|
Errortxt string `json:"errortxt"`
|
||||||
|
Warnings []string `json:"warnings"`
|
||||||
|
Info Info `json:"info"`
|
||||||
|
ErrorExtra interface{} `json:"error_extra"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Info struct {
|
||||||
|
Task int `json:"task"`
|
||||||
|
Fisid string `json:"fisid"`
|
||||||
|
Dataid int `json:"dataid"`
|
||||||
|
Doccode string `json:"doccode"`
|
||||||
|
Dt string `json:"dt"`
|
||||||
|
Cashier string `json:"cashier"`
|
||||||
|
Dtype int `json:"dtype"`
|
||||||
|
Isprint int `json:"isprint"`
|
||||||
|
Isoffline bool `json:"isoffline"`
|
||||||
|
Safe float64 `json:"safe"`
|
||||||
|
ShiftLink int `json:"shift_link"`
|
||||||
|
Docno int `json:"docno"`
|
||||||
|
Cancelid string `json:"cancelid"`
|
||||||
|
}
|
||||||
7
go.mod
Normal file
7
go.mod
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module git.jeezft.xyz/rk/go-vchasno-kassa
|
||||||
|
|
||||||
|
go 1.23.5
|
||||||
|
|
||||||
|
require resty.dev/v3 v3.0.0-beta.3
|
||||||
|
|
||||||
|
require golang.org/x/net v0.33.0 // indirect
|
||||||
4
go.sum
Normal file
4
go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||||
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
|
resty.dev/v3 v3.0.0-beta.3 h1:3kEwzEgCnnS6Ob4Emlk94t+I/gClyoah7SnNi67lt+E=
|
||||||
|
resty.dev/v3 v3.0.0-beta.3/go.mod h1:OgkqiPvTDtOuV4MGZuUDhwOpkY8enjOsjjMzeOHefy4=
|
||||||
50
vchasno.go
Normal file
50
vchasno.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package vchasno
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.jeezft.xyz/rk/go-vchasno-kassa/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Vchasno struct {
|
||||||
|
api *api.Kasa
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
PayTypeCash = 0
|
||||||
|
PayTypeCard = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type SellParams struct {
|
||||||
|
PayType int
|
||||||
|
Sum float64
|
||||||
|
Comment string
|
||||||
|
Cardmask string
|
||||||
|
BankID string
|
||||||
|
Name string
|
||||||
|
Cnt int
|
||||||
|
Price float64
|
||||||
|
Disc float64
|
||||||
|
Taxgrp string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVchasno(token string) *Vchasno {
|
||||||
|
return &Vchasno{
|
||||||
|
api: api.NewKasaInstance(token),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vchasno) NewSell(params SellParams) (*api.KasaResponse, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
return v.api.NewSell(ctx, params.PayType, params.Sum, params.Comment, params.Cardmask, params.BankID, params.Name, params.Cnt, params.Price, params.Disc, params.Taxgrp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetDefaultParams() SellParams {
|
||||||
|
return SellParams{
|
||||||
|
PayType: PayTypeCard,
|
||||||
|
Taxgrp: "1",
|
||||||
|
Cnt: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user