Initial
commit
54856057c2
|
@ -0,0 +1,18 @@
|
||||||
|
# Database config
|
||||||
|
MYSQL_USER=example
|
||||||
|
MYSQL_PASSWORD=example
|
||||||
|
MYSQL_HOST=127.0.0.1
|
||||||
|
MYSQL_DATABASE=example
|
||||||
|
|
||||||
|
|
||||||
|
# Redis config
|
||||||
|
REDIS_ADDRESS=127.0.0.1:6379
|
||||||
|
REDIS_PASSWORD=
|
||||||
|
REDIS_DATABASE=0
|
||||||
|
|
||||||
|
# EXTERANL BLOCK
|
||||||
|
|
||||||
|
BITRIX_API_EP=https://bitrix.address/rest/auth/string
|
||||||
|
|
||||||
|
YOOKASSA_ACCOUNT_ID=1234
|
||||||
|
YOOKASSA_ACCOUNT_SECRET=YOUR_PWD
|
|
@ -0,0 +1,4 @@
|
||||||
|
server
|
||||||
|
./build/
|
||||||
|
.env
|
||||||
|
.idea/
|
|
@ -0,0 +1,367 @@
|
||||||
|
package bitrix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var EP = os.Getenv("BITRIX_API_EP")
|
||||||
|
|
||||||
|
type ClientsResource struct {
|
||||||
|
EntityId string `json:"entityId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderResource struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Clients []ClientsResource `json:"clients"`
|
||||||
|
Lid string `json:"lid,omitempty"`
|
||||||
|
DateInsert time.Time `json:"dateInsert,omitempty"`
|
||||||
|
DateUpdate time.Time `json:"dateUpdate,omitempty"`
|
||||||
|
PersonTypeID string `json:"personTypeId,omitempty"`
|
||||||
|
StatusID string `json:"statusId,omitempty"`
|
||||||
|
DateStatus time.Time `json:"dateStatus,omitempty"`
|
||||||
|
EmpStatusID any `json:"empStatusId,omitempty"`
|
||||||
|
Marked string `json:"marked,omitempty"`
|
||||||
|
DateMarked any `json:"dateMarked,omitempty"`
|
||||||
|
EmpMarkedID any `json:"empMarkedId,omitempty"`
|
||||||
|
ReasonMarked any `json:"reasonMarked,omitempty"`
|
||||||
|
Price float64 `json:"price,omitempty"`
|
||||||
|
DiscountValue string `json:"discountValue,omitempty"`
|
||||||
|
TaxValue string `json:"taxValue,omitempty"`
|
||||||
|
UserDescription string `json:"userDescription,omitempty"`
|
||||||
|
AdditionalInfo any `json:"additionalInfo,omitempty"`
|
||||||
|
Comments string `json:"comments,omitempty"`
|
||||||
|
CompanyID string `json:"companyId,omitempty"`
|
||||||
|
ResponsibleID any `json:"responsibleId,omitempty"`
|
||||||
|
StatGid any `json:"statGid,omitempty"`
|
||||||
|
DatePayBefore any `json:"datePayBefore,omitempty"`
|
||||||
|
DateBill any `json:"dateBill,omitempty"`
|
||||||
|
RecurringID any `json:"recurringId,omitempty"`
|
||||||
|
LockedBy string `json:"lockedBy,omitempty"`
|
||||||
|
DateLock time.Time `json:"dateLock,omitempty"`
|
||||||
|
RecountFlag string `json:"recountFlag,omitempty"`
|
||||||
|
AffiliateID any `json:"affiliateId,omitempty"`
|
||||||
|
DeliveryDocNum any `json:"deliveryDocNum,omitempty"`
|
||||||
|
DeliveryDocDate any `json:"deliveryDocDate,omitempty"`
|
||||||
|
Updated1C string `json:"updated1c,omitempty"`
|
||||||
|
OrderTopic any `json:"orderTopic,omitempty"`
|
||||||
|
XMLID any `json:"xmlId,omitempty"`
|
||||||
|
ID1C any `json:"id1c,omitempty"`
|
||||||
|
Version1C any `json:"version1c,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
ExternalOrder string `json:"externalOrder,omitempty"`
|
||||||
|
StoreID any `json:"storeId,omitempty"`
|
||||||
|
Canceled string `json:"canceled,omitempty"`
|
||||||
|
EmpCanceledID any `json:"empCanceledId,omitempty"`
|
||||||
|
DateCanceled any `json:"dateCanceled,omitempty"`
|
||||||
|
ReasonCanceled any `json:"reasonCanceled,omitempty"`
|
||||||
|
BasketItems []BasketResource `json:"basketItems,omitempty"`
|
||||||
|
Properties []propertyValueResource `json:"properties,omitempty"`
|
||||||
|
Payments []PaymentResource `json:"payments,omitempty"`
|
||||||
|
Shipments []any `json:"shipments,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type propertyValueResource struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value []string `json:"value"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
OrderPropsId string `json:"orderPropsId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaymentResource struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
OrderId string `json:"orderId"`
|
||||||
|
Paid string `json:"paid"`
|
||||||
|
DatePaid interface{} `json:"datePaid"`
|
||||||
|
EmpPaidId interface{} `json:"empPaidId"`
|
||||||
|
PaySystemId string `json:"paySystemId"`
|
||||||
|
PsStatus interface{} `json:"psStatus"`
|
||||||
|
PsStatusCode interface{} `json:"psStatusCode"`
|
||||||
|
PsInvoiceId interface{} `json:"psInvoiceId"`
|
||||||
|
PsStatusDescription interface{} `json:"psStatusDescription"`
|
||||||
|
PsStatusMessage interface{} `json:"psStatusMessage"`
|
||||||
|
PsSum interface{} `json:"psSum"`
|
||||||
|
PsCurrency interface{} `json:"psCurrency"`
|
||||||
|
PsResponseDate interface{} `json:"psResponseDate"`
|
||||||
|
PayVoucherNum string `json:"payVoucherNum"`
|
||||||
|
PayVoucherDate interface{} `json:"payVoucherDate"`
|
||||||
|
DatePayBefore interface{} `json:"datePayBefore"`
|
||||||
|
DateBill time.Time `json:"dateBill"`
|
||||||
|
XmlId interface{} `json:"xmlId"`
|
||||||
|
Sum string `json:"sum"`
|
||||||
|
PriceCod string `json:"priceCod"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
PaySystemName string `json:"paySystemName"`
|
||||||
|
ResponsibleId interface{} `json:"responsibleId"`
|
||||||
|
EmpResponsibleId interface{} `json:"empResponsibleId"`
|
||||||
|
DateResponsibleId interface{} `json:"dateResponsibleId"`
|
||||||
|
Comments interface{} `json:"comments"`
|
||||||
|
CompanyId string `json:"companyId"`
|
||||||
|
PayReturnNum string `json:"payReturnNum"`
|
||||||
|
PayReturnDate interface{} `json:"payReturnDate"`
|
||||||
|
EmpReturnId interface{} `json:"empReturnId"`
|
||||||
|
PayReturnComment string `json:"payReturnComment"`
|
||||||
|
IsReturn string `json:"isReturn"`
|
||||||
|
Marked string `json:"marked"`
|
||||||
|
DateMarked interface{} `json:"dateMarked"`
|
||||||
|
EmpMarkedId interface{} `json:"empMarkedId"`
|
||||||
|
ReasonMarked interface{} `json:"reasonMarked"`
|
||||||
|
Updated1C string `json:"updated1c"`
|
||||||
|
Id1C interface{} `json:"id1c"`
|
||||||
|
Version1C interface{} `json:"version1c"`
|
||||||
|
ExternalPayment string `json:"externalPayment"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasketResource struct {
|
||||||
|
Module string `json:"module"`
|
||||||
|
ProductId string `json:"productId"`
|
||||||
|
Id string `json:"id"`
|
||||||
|
Lid string `json:"lid"`
|
||||||
|
Quantity string `json:"quantity"`
|
||||||
|
Weight string `json:"weight"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
|
CustomPrice string `json:"customPrice"`
|
||||||
|
BasePrice string `json:"basePrice"`
|
||||||
|
ProductPriceId string `json:"productPriceId"`
|
||||||
|
PriceTypeId string `json:"priceTypeId"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
BarcodeMulti string `json:"barcodeMulti"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CatalogXmlId string `json:"catalogXmlId"`
|
||||||
|
VatRate string `json:"vatRate"`
|
||||||
|
Notes string `json:"notes"`
|
||||||
|
DiscountPrice string `json:"discountPrice"`
|
||||||
|
ProductProviderClass string `json:"productProviderClass"`
|
||||||
|
Dimensions string `json:"dimensions"`
|
||||||
|
Type interface{} `json:"type"`
|
||||||
|
SetParentId interface{} `json:"setParentId"`
|
||||||
|
DetailPageUrl string `json:"detailPageUrl"`
|
||||||
|
MeasureCode string `json:"measureCode"`
|
||||||
|
MeasureName string `json:"measureName"`
|
||||||
|
OrderId string `json:"orderId"`
|
||||||
|
ProductXmlId string `json:"productXmlId"`
|
||||||
|
Subscribe string `json:"subscribe"`
|
||||||
|
Recommendation interface{} `json:"recommendation"`
|
||||||
|
VatIncluded string `json:"vatIncluded"`
|
||||||
|
Sort string `json:"sort"`
|
||||||
|
DiscountName interface{} `json:"discountName"`
|
||||||
|
DiscountValue interface{} `json:"discountValue"`
|
||||||
|
DiscountCoupon interface{} `json:"discountCoupon"`
|
||||||
|
Properties []struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
BasketId string `json:"basketId"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Sort string `json:"sort"`
|
||||||
|
} `json:"properties"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type bitrix struct{}
|
||||||
|
|
||||||
|
type Bitrix interface {
|
||||||
|
CreateAnonymousUser() (int, error)
|
||||||
|
CreateOrder(userId int) (int, error)
|
||||||
|
ApprovePayment(paymentId int, paySystemId int) error
|
||||||
|
CancelOrder(orderId int) error
|
||||||
|
GetOrderInfo(orderId int) (*OrderResource, error)
|
||||||
|
CreatePayment(orderId int, sum float64) error
|
||||||
|
AddProductToOrder(orderId int, productId int, price float64, quantity int) error
|
||||||
|
UpdateContact(contactId int, email string, name string, phone string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type createAnonymousUserRequest struct {
|
||||||
|
Email string `json:"EMAIL"`
|
||||||
|
Name string `json:"NAME"`
|
||||||
|
Password string `json:"PASSWORD"`
|
||||||
|
ConfirmPassword string `json:"CONFIRM_PASSWORD"`
|
||||||
|
UFDepartment []int `json:"UF_DEPARTMENT"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type createAnonymousUserResponse struct {
|
||||||
|
Result int `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ bitrix) CreateAnonymousUser() (int, error) {
|
||||||
|
uid, _ := uuid.NewUUID()
|
||||||
|
req := createAnonymousUserRequest{
|
||||||
|
Email: fmt.Sprintf("anonymous%s@anonym.ru", uid.String()),
|
||||||
|
Name: "Анонимный пользователь",
|
||||||
|
Password: uid.String(),
|
||||||
|
ConfirmPassword: uid.String(),
|
||||||
|
UFDepartment: []int{0},
|
||||||
|
}
|
||||||
|
|
||||||
|
query, _ := json.Marshal(req)
|
||||||
|
|
||||||
|
resp, err := http.Post(EP+"/user.add", "application/json", bytes.NewBuffer(query))
|
||||||
|
|
||||||
|
result := createAnonymousUserResponse{}
|
||||||
|
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||||
|
|
||||||
|
return result.Result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type createOrderRequestWrapper struct {
|
||||||
|
Fields createOrderRequest `json:"fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type createOrderRequest struct {
|
||||||
|
Lid string `json:"lid"`
|
||||||
|
PersonTypeId int `json:"personTypeId"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
UserId int `json:"userId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type createOrderResponse struct {
|
||||||
|
Result struct {
|
||||||
|
Order struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
} `json:"order"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ bitrix) CreateOrder(userId int) (int, error) {
|
||||||
|
req := createOrderRequestWrapper{createOrderRequest{
|
||||||
|
Lid: "s2",
|
||||||
|
PersonTypeId: 5,
|
||||||
|
Currency: "RUB",
|
||||||
|
UserId: userId,
|
||||||
|
}}
|
||||||
|
|
||||||
|
query, _ := json.Marshal(req)
|
||||||
|
|
||||||
|
resp, _ := http.Post(EP+"/sale.order.add", "application/json", bytes.NewBuffer(query))
|
||||||
|
result := createOrderResponse{}
|
||||||
|
|
||||||
|
err := json.NewDecoder(resp.Body).Decode(&result)
|
||||||
|
|
||||||
|
return result.Result.Order.Id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ bitrix) ApprovePayment(paymentId int, paySystemId int) error {
|
||||||
|
req := map[string]interface{}{
|
||||||
|
"id": paymentId,
|
||||||
|
"fields": map[string]interface{}{"paid": "Y", "paySystemId": paySystemId},
|
||||||
|
}
|
||||||
|
|
||||||
|
query, _ := json.Marshal(req)
|
||||||
|
|
||||||
|
_, err := http.Post(EP+"/sale.payment.update", "application/json", bytes.NewBuffer(query))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ bitrix) CancelOrder(orderId int) error {
|
||||||
|
req := map[string]interface{}{
|
||||||
|
"id": orderId,
|
||||||
|
"fields": map[string]interface{}{"canceled": "Y"},
|
||||||
|
}
|
||||||
|
|
||||||
|
query, _ := json.Marshal(req)
|
||||||
|
|
||||||
|
_, err := http.Post(EP+"/sale.order.update", "application/json", bytes.NewBuffer(query))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ bitrix) GetOrderInfo(orderId int) (*OrderResource, error) {
|
||||||
|
req := map[string]interface{}{
|
||||||
|
"id": orderId,
|
||||||
|
}
|
||||||
|
|
||||||
|
query, _ := json.Marshal(req)
|
||||||
|
|
||||||
|
response, err := http.Post(EP+"/sale.order.get", "application/json", bytes.NewBuffer(query))
|
||||||
|
|
||||||
|
result := struct {
|
||||||
|
Result struct {
|
||||||
|
Order map[string]interface{} `json:"order"`
|
||||||
|
} `json:"result"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
err = json.NewDecoder(response.Body).Decode(&result)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Error: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
resultParsed := OrderResource{}
|
||||||
|
mapstructure.Decode(result.Result.Order, &resultParsed)
|
||||||
|
return &resultParsed, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ bitrix) CreatePayment(orderId int, sum float64) error {
|
||||||
|
req := map[string]interface{}{
|
||||||
|
"fields": map[string]interface{}{
|
||||||
|
"orderId": orderId,
|
||||||
|
"sum": sum,
|
||||||
|
"paid": "N",
|
||||||
|
"paySystemId": 8,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
query, _ := json.Marshal(req)
|
||||||
|
|
||||||
|
_, err := http.Post(EP+"/sale.payment.add", "application/json", bytes.NewBuffer(query))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ bitrix) AddProductToOrder(orderId int, productId int, price float64, quantity int) error {
|
||||||
|
req := map[string]interface{}{
|
||||||
|
"fields": map[string]interface{}{
|
||||||
|
"orderId": orderId,
|
||||||
|
"productId": productId,
|
||||||
|
"quantity": quantity,
|
||||||
|
"currency": "RUB",
|
||||||
|
"price": price,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
query, _ := json.Marshal(req)
|
||||||
|
|
||||||
|
_, err := http.Post(EP+"/sale.basketitem.addCatalogProduct", "application/json", bytes.NewBuffer(query))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ bitrix) UpdateContact(contactId int, email string, name string, phone string) error {
|
||||||
|
req := map[string]interface{}{
|
||||||
|
"id": contactId,
|
||||||
|
"fields": map[string]interface{}{
|
||||||
|
"EMAIL": append([]map[string]interface{}{}, map[string]interface{}{
|
||||||
|
"VALUE_TYPE": "other",
|
||||||
|
"VALUE": email,
|
||||||
|
"TYPE_ID": "EMAIL",
|
||||||
|
}),
|
||||||
|
"NAME": strings.Split(name, " ")[1],
|
||||||
|
"LAST_NAME": strings.Split(name, " ")[0],
|
||||||
|
"SECOND_NAME": strings.Split(name, " ")[2],
|
||||||
|
"PHONE": append([]map[string]interface{}{}, map[string]interface{}{
|
||||||
|
"VALUE_TYPE": "other",
|
||||||
|
"VALUE": phone,
|
||||||
|
"TYPE_ID": "PHONE",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
query, _ := json.Marshal(req)
|
||||||
|
|
||||||
|
_, err := http.Post(EP+"/crm.contact.update", "application/json", bytes.NewBuffer(query))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Initialize() Bitrix {
|
||||||
|
return &bitrix{}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package AgentType
|
||||||
|
|
||||||
|
type AgentType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
BANKING_PAYMENT_AGENT = "banking_payment_agent" // Банковский платежный агент
|
||||||
|
BANKING_PAYMENT_SUBAGENT = "banking_payment_subagent" // Банковский платежный субагент
|
||||||
|
PAYMENT_AGENT = "payment_agent" // Платежный агент
|
||||||
|
PAYMENT_SUBAGENT = "payment_subagent" // Платежный субагент
|
||||||
|
ATTORNEY = "attorney" // Поверенный
|
||||||
|
COMMISSIONER = "commissioner" // Комиссионер
|
||||||
|
AGENT = "agent" // Агент
|
||||||
|
)
|
|
@ -0,0 +1,30 @@
|
||||||
|
package Measure
|
||||||
|
|
||||||
|
type Measure string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PIECE = "piece" // Штука, единица товара
|
||||||
|
GRAM = "gram" // Грамм
|
||||||
|
KILOGRAM = "kilogram" // Килограмм
|
||||||
|
TON = "ton" // Тонна
|
||||||
|
CENTIMETER = "centimeter" // Сантиметр
|
||||||
|
DECIMETER = "decimeter" // Дециметр
|
||||||
|
METER = "meter" // Метр
|
||||||
|
SQUARE_CENTIMETER = "square_centimeter" // Квадратный сантиметр
|
||||||
|
SQUARE_DECIMETER = "square_decimeter" // Квадратный дециметр
|
||||||
|
SQUARE_METER = "square_meter" // Квадратный метр
|
||||||
|
MILLILITER = "milliliter" // Миллилитр
|
||||||
|
LITER = "liter" // Литр
|
||||||
|
CUBIC_METER = "cubic_meter" // Кубический метр
|
||||||
|
KILOWATT_HOUR = "kilowatt_hour" // Килловат-час
|
||||||
|
GIGACALORIE = "gigacalorie" // Гигакалория
|
||||||
|
DAY = "day" // Сутки
|
||||||
|
HOUR = "hour" // Час
|
||||||
|
MINUTE = "minute" // Минута
|
||||||
|
SECOND = "second" // Секунда
|
||||||
|
KILOBYTE = "kilobyte" // Килобайт
|
||||||
|
MEGABYTE = "megabyte" // Мегабайт
|
||||||
|
GIGABYTE = "gigabyte" // Гигабайт
|
||||||
|
TERABYTE = "terabyte" // Терабайт
|
||||||
|
ANOTHER = "another" // Другое
|
||||||
|
)
|
|
@ -0,0 +1,13 @@
|
||||||
|
package PaymentMode
|
||||||
|
|
||||||
|
type PaymentMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FULL_PREPAYMENT = "full_prepayment" // Полная предоплата
|
||||||
|
PARTIAL_PREPAYMENT = "partial_prepayment" // Частичная предоплата
|
||||||
|
ADVANCE = "advance" // Аванс
|
||||||
|
FULL_PAYMENT = "full_payment" // Полный расчет
|
||||||
|
PARTIAL_PAYMENT = "partial_payment" // Частичный расчет и кредит
|
||||||
|
CREDIT = "credit" // Кредит
|
||||||
|
CREDIT_PAYMENT = "credit_payment" // Выплата по кредиту
|
||||||
|
)
|
|
@ -0,0 +1,25 @@
|
||||||
|
package PaymentSubject
|
||||||
|
|
||||||
|
type PaymentSubject string
|
||||||
|
|
||||||
|
const (
|
||||||
|
COMMODITY = "commodity" //Товар Товар
|
||||||
|
EXCISE = "excise" //Подакцизный товар
|
||||||
|
JOB = "job" //Работа
|
||||||
|
SERVICE = "service" //Услуга Услуга
|
||||||
|
PAYMENT = "payment" //Платеж Платеж
|
||||||
|
CASINO = "casino" // Платеж казино
|
||||||
|
GAMBLING_BET = "gambling_bet" //Ставка в азартной игре
|
||||||
|
GAMBLING_PRIZE = "gambling_prize" // Выигрыш азартной игры
|
||||||
|
LOTTERY = "lottery" // Лотерейный билет
|
||||||
|
LOTTERY_PRIZE = "lottery_prize" // Выигрыш в лотерею
|
||||||
|
INTELLECTUAL_ACTIVITY = "intellectual_activity" //Результаты интеллектуальной деятельности
|
||||||
|
AGENT_COMMISSION = "agent_commission" //Агентское вознаграждение
|
||||||
|
PROPERTY_RIGHT = "property_right" //Имущественное право
|
||||||
|
NON_OPERATING_GAIN = "non_operating_gain" //Внереализационный доход
|
||||||
|
INSURANCE_PREMIUM = "insurance_premium" //Страховой сбор
|
||||||
|
SALES_TAX = "sales_tax" //Торговый сбор
|
||||||
|
RESORT_FEE = "resort_fee" // Курортный сбор
|
||||||
|
COMPOSITE = "composite" // Несколько вариантов
|
||||||
|
ANOTHER = "another" // Другое
|
||||||
|
)
|
|
@ -0,0 +1,10 @@
|
||||||
|
package Settlements
|
||||||
|
|
||||||
|
type Settlements string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CASHLESS = "cashless" // Безналичный расчет
|
||||||
|
PREPAYMENT = "prepayment" // Предоплата (аванс)
|
||||||
|
POSTPAYMENT = "postpayment" // Постоплата (кредит)
|
||||||
|
CONSIDERATION = "consideration" // Встречное предоставление
|
||||||
|
)
|
|
@ -0,0 +1,12 @@
|
||||||
|
package TaxSystemCode
|
||||||
|
|
||||||
|
type TaxSystemCode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
GENERAL = iota + 1
|
||||||
|
USN_INCOME
|
||||||
|
USN_INCOME_MINUS_EXPENCES
|
||||||
|
ENVD
|
||||||
|
ESN
|
||||||
|
PATENT
|
||||||
|
)
|
|
@ -0,0 +1,12 @@
|
||||||
|
package VatCodes
|
||||||
|
|
||||||
|
type VatCode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
WithoutNDS = iota + 1
|
||||||
|
NDS_0
|
||||||
|
NDS_10
|
||||||
|
NDS_20
|
||||||
|
NDS_10_FOR_110
|
||||||
|
NDS_20_FOR_120
|
||||||
|
)
|
|
@ -0,0 +1,9 @@
|
||||||
|
package kassa
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ACCOUNT_ID = os.Getenv("YOOKASSA_ACCOUNT_ID")
|
||||||
|
PASSWORD = os.Getenv("YOOKASSA_ACCOUNT_SECRET")
|
||||||
|
BASE_URL = "https://api.yookassa.ru/v3/payments"
|
||||||
|
)
|
|
@ -0,0 +1,123 @@
|
||||||
|
package kassa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"net/http"
|
||||||
|
"relynolli-server/external/kassa/Measure"
|
||||||
|
"relynolli-server/external/kassa/PaymentMode"
|
||||||
|
"relynolli-server/external/kassa/PaymentSubject"
|
||||||
|
"relynolli-server/external/kassa/TaxSystemCode"
|
||||||
|
"relynolli-server/external/kassa/VatCodes"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var once sync.Once
|
||||||
|
|
||||||
|
type KassaAmount struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KassaReceiptItems struct {
|
||||||
|
Description string `json:"description"`
|
||||||
|
Amount KassaAmount `json:"amount"`
|
||||||
|
VatCode VatCodes.VatCode `json:"vat_code"`
|
||||||
|
Quantity string `json:"quantity"`
|
||||||
|
Measure Measure.Measure `json:"measure"`
|
||||||
|
PaymentSubject PaymentSubject.PaymentSubject `json:"payment_subject"`
|
||||||
|
PaymentMode PaymentMode.PaymentMode `json:"payment_mode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KassaCustomer struct {
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KassaReceipt struct {
|
||||||
|
Customer KassaCustomer `json:"customer"`
|
||||||
|
Items []KassaReceiptItems `json:"items"`
|
||||||
|
TaxSystemCode TaxSystemCode.TaxSystemCode `json:"tax_system_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KassaConfirmation struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
ReturnUrl string `json:"return_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KassaPaymentReq struct {
|
||||||
|
Amount KassaAmount `json:"amount"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Receipt KassaReceipt `json:"receipt"`
|
||||||
|
Confirmation KassaConfirmation `json:"confirmation"`
|
||||||
|
Capture bool `json:"capture"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KassaResult struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Amount KassaAmount `json:"amount"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Recipient struct {
|
||||||
|
AccountId string `json:"account_id"`
|
||||||
|
GatewayId string `json:"gateway_id"`
|
||||||
|
} `json:"recipient"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
Confirmation struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
ConfirmationUrl string `json:"confirmation_url"`
|
||||||
|
} `json:"confirmation"`
|
||||||
|
Paid bool `json:"paid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicAuth(username, password string) string {
|
||||||
|
auth := username + ":" + password
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreatePayment(orderId int, sum float64, fullName string, email string, phone string, items []KassaReceiptItems) (map[string]interface{}, error) {
|
||||||
|
req := KassaPaymentReq{
|
||||||
|
Amount: KassaAmount{Value: fmt.Sprintf("%f", sum), Currency: "RUB"},
|
||||||
|
Description: fmt.Sprintf("Заказ №%d", orderId),
|
||||||
|
Confirmation: KassaConfirmation{
|
||||||
|
Type: "redirect",
|
||||||
|
ReturnUrl: "https://relynolli.ru",
|
||||||
|
},
|
||||||
|
Capture: true,
|
||||||
|
Receipt: KassaReceipt{
|
||||||
|
Customer: KassaCustomer{
|
||||||
|
FullName: fullName,
|
||||||
|
Email: email,
|
||||||
|
Phone: phone,
|
||||||
|
},
|
||||||
|
Items: items,
|
||||||
|
TaxSystemCode: TaxSystemCode.GENERAL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
query, _ := json.Marshal(req)
|
||||||
|
uid, err := uuid.NewUUID()
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
request, _ := http.NewRequest(http.MethodPost, BASE_URL, bytes.NewBuffer(query))
|
||||||
|
request.Header.Set("Authorization", "Basic "+basicAuth(ACCOUNT_ID, PASSWORD))
|
||||||
|
request.Header.Set("Idempotence-Key", uid.String())
|
||||||
|
request.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
response, err := client.Do(request)
|
||||||
|
|
||||||
|
result := map[string]interface{}{}
|
||||||
|
json.NewDecoder(response.Body).Decode(&result)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package kassa
|
|
@ -0,0 +1,42 @@
|
||||||
|
module relynolli-server
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.11.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/gin-contrib/cors v1.5.0 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.19.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||||
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||||
|
github.com/redis/go-redis/v9 v9.5.1 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
golang.org/x/arch v0.7.0 // indirect
|
||||||
|
golang.org/x/crypto v0.20.0 // indirect
|
||||||
|
golang.org/x/net v0.21.0 // indirect
|
||||||
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.32.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,111 @@
|
||||||
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
|
github.com/bytedance/sonic v1.11.1 h1:JC0+6c9FoWYYxakaoa+c5QTtJeiSZNeByOBhXtAFSn4=
|
||||||
|
github.com/bytedance/sonic v1.11.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
|
github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk=
|
||||||
|
github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
|
||||||
|
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||||
|
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||||
|
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
|
||||||
|
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||||
|
github.com/rvinnie/yookassa-sdk-go v0.0.0-20230904104101-ff7e5be5530c h1:m6dxe045lJQ1tkJeCBwseulCwppUDcdZk+RIxzBjQXQ=
|
||||||
|
github.com/rvinnie/yookassa-sdk-go v0.0.0-20230904104101-ff7e5be5530c/go.mod h1:flatybkcu+7YLaB7mMnj9JTNKeim4jZ+ZrXNFjVA0pA=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||||
|
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
|
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
|
||||||
|
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||||
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||||
|
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
@ -0,0 +1,34 @@
|
||||||
|
package endpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"relynolli-server/models"
|
||||||
|
"relynolli-server/services"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *handlers) GetCartItems(c *gin.Context) {
|
||||||
|
|
||||||
|
fuserId := c.Query("fuserId")
|
||||||
|
if fuserId == "" {
|
||||||
|
c.JSON(400, models.Response{Status: 400, Info: "\"fuserId\" should be provided"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, err := strconv.Atoi(fuserId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, models.Response{Status: 400, Info: "\"fuserId should be an integer number\""})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, services.GetCartItems(idx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handlers) CreateFUser(c *gin.Context) {
|
||||||
|
lastInsertId := services.CreateFuser()
|
||||||
|
|
||||||
|
c.JSON(201, gin.H{
|
||||||
|
"fuserId": lastInsertId,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package endpoints
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
type handlers struct{}
|
||||||
|
|
||||||
|
type Handlers interface {
|
||||||
|
GetCartItems(c *gin.Context)
|
||||||
|
CreateFUser(c *gin.Context)
|
||||||
|
|
||||||
|
CreateCartItem(c *gin.Context)
|
||||||
|
UpdateCartItem(c *gin.Context)
|
||||||
|
DeleteCartItem(c *gin.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHandlers() Handlers {
|
||||||
|
return &handlers{}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package endpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
"relynolli-server/models"
|
||||||
|
"relynolli-server/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type createCartItemRequest struct {
|
||||||
|
FuserId int `json:"fuserId"`
|
||||||
|
PriceTypeId int `json:"priceTypeId,omitempty"`
|
||||||
|
ProductId int `json:"productId"`
|
||||||
|
Quantity int `json:"quantity,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type updateCartRequest struct {
|
||||||
|
FuserId int `json:"fuserId"`
|
||||||
|
ProductId int `json:"productId"`
|
||||||
|
Quantity int `json:"quantity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type deleteCartRequest struct {
|
||||||
|
FuserId int `json:"fuserId"`
|
||||||
|
ProductId int `json:"productId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handlers) CreateCartItem(c *gin.Context) {
|
||||||
|
req := createCartItemRequest{}
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
services.AddItemToCart(req.FuserId, req.ProductId)
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, models.Response{Status: http.StatusCreated, Info: fmt.Sprintf("Item %d has added to cart", req.ProductId)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handlers) UpdateCartItem(c *gin.Context) {
|
||||||
|
req := updateCartRequest{}
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = services.UpdateCartItem(req.FuserId, req.ProductId, req.Quantity)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, models.Response{Status: http.StatusOK})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handlers) DeleteCartItem(c *gin.Context) {
|
||||||
|
|
||||||
|
req := deleteCartRequest{}
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, models.Response{Status: 400, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
services.DeleteCartItem(req.FuserId, req.ProductId)
|
||||||
|
|
||||||
|
c.JSON(http.StatusNoContent, models.Response{Status: http.StatusNoContent})
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package cart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"relynolli-server/handlers/cart/endpoints"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleRoutes(parent *gin.RouterGroup) {
|
||||||
|
h := endpoints.GetHandlers()
|
||||||
|
cart := parent.Group("/cart")
|
||||||
|
itemRouter := cart.Group("/item")
|
||||||
|
{
|
||||||
|
cart.GET("", h.GetCartItems)
|
||||||
|
cart.POST("", h.CreateFUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
itemRouter.POST("", h.CreateCartItem)
|
||||||
|
itemRouter.PATCH("", h.UpdateCartItem)
|
||||||
|
itemRouter.DELETE("", h.DeleteCartItem)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package endpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"relynolli-server/models"
|
||||||
|
"relynolli-server/services"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *handlers) GetCatalogItems(c *gin.Context) {
|
||||||
|
|
||||||
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||||
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
|
||||||
|
c.JSON(200, services.GetCatalogItems(limit, offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handlers) GetCatalogItem(c *gin.Context) {
|
||||||
|
code := c.Param("code")
|
||||||
|
if code == "" {
|
||||||
|
c.JSON(400, models.Response{Status: 400, Info: "product \"Code\" should be provided"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := services.GetCatalogItem(code)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(404, models.Response{Status: 404, Info: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, resp)
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package endpoints
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
type handlers struct{}
|
||||||
|
|
||||||
|
type Handlers interface {
|
||||||
|
GetFilters(c *gin.Context)
|
||||||
|
GetCatalogItems(c *gin.Context)
|
||||||
|
GetCatalogItem(c *gin.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHandlers() Handlers {
|
||||||
|
return &handlers{}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package endpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"relynolli-server/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type filterValues struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type filterStruct struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Values []filterValues `json:"values"`
|
||||||
|
valuesString []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handlers) GetFilters(c *gin.Context) {
|
||||||
|
stmt := "select * from api_filter;"
|
||||||
|
var responseData []filterStruct
|
||||||
|
|
||||||
|
db := internal.InitDatabase()
|
||||||
|
rows := db.Query(stmt)
|
||||||
|
for rows.Next() {
|
||||||
|
filter := filterStruct{}
|
||||||
|
// grab data from db
|
||||||
|
rows.Scan(&filter.Id, &filter.Code, &filter.Name, &filter.valuesString)
|
||||||
|
json.Unmarshal(filter.valuesString, &filter.Values)
|
||||||
|
// parse data as json
|
||||||
|
responseData = append(responseData, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, responseData)
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package catalog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"relynolli-server/handlers/catalog/endpoints"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleRoutes(parent *gin.RouterGroup) {
|
||||||
|
h := endpoints.GetHandlers()
|
||||||
|
catalog := parent.Group("/catalog")
|
||||||
|
catalog.GET("/filters", h.GetFilters)
|
||||||
|
catalog.GET("/", h.GetCatalogItems)
|
||||||
|
catalog.GET("/:code", h.GetCatalogItem)
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package endpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"net/http"
|
||||||
|
"relynolli-server/models"
|
||||||
|
"relynolli-server/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type handlers struct{}
|
||||||
|
|
||||||
|
type getTotalRequest struct {
|
||||||
|
FuserId int `json:"fuserId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type getTotalResponse struct {
|
||||||
|
TotalProductPrice float64 `json:"total_product_price"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type makeOrderRequest struct {
|
||||||
|
FuserId int `json:"fuserId" validate:"required"`
|
||||||
|
PhoneNumber string `json:"phoneNumber" validate:"required"`
|
||||||
|
FullName string `json:"fullName" validate:"required"`
|
||||||
|
Email string `json:"email" validate:"required,email"`
|
||||||
|
DeliveryType string `json:"deliveryType,omitempty"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h handlers) GetTotal(c *gin.Context) {
|
||||||
|
req := getTotalRequest{}
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: "fuserId is not provided"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
total := services.GetTotal(req.FuserId)
|
||||||
|
c.JSON(http.StatusOK, getTotalResponse{TotalProductPrice: total})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h handlers) MakeOrder(c *gin.Context) {
|
||||||
|
// VALIDATION
|
||||||
|
validate := validator.New(validator.WithRequiredStructEnabled())
|
||||||
|
req := makeOrderRequest{}
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("ERROR: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validationErr := validate.Struct(req)
|
||||||
|
|
||||||
|
if validationErr != nil {
|
||||||
|
responseErr := validationErr.(validator.ValidationErrors)[0]
|
||||||
|
c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Validation Error: Field %s should be %s", responseErr.Field(), responseErr.Tag())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kassaResult, serviceErr := services.MakeOrder(req.FuserId, req.Email, req.FullName, req.PhoneNumber)
|
||||||
|
|
||||||
|
if serviceErr != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, models.Response{Status: http.StatusInternalServerError, Info: fmt.Sprintf("Error: %s", serviceErr.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, kassaResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handlers interface {
|
||||||
|
GetTotal(c *gin.Context)
|
||||||
|
MakeOrder(c *gin.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHandlers() Handlers {
|
||||||
|
return &handlers{}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package order
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"relynolli-server/handlers/order/endpoints"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleRoutes(parent *gin.RouterGroup) {
|
||||||
|
h := endpoints.GetHandlers()
|
||||||
|
order := parent.Group("/order")
|
||||||
|
order.POST("/make", h.MakeOrder)
|
||||||
|
order.POST("/total", h.GetTotal)
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"relynolli-server/handlers/cart"
|
||||||
|
"relynolli-server/handlers/catalog"
|
||||||
|
"relynolli-server/handlers/order"
|
||||||
|
"relynolli-server/handlers/validate"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitializeRouter(router *gin.Engine) {
|
||||||
|
APIV1Router := router.Group("/api/v1")
|
||||||
|
catalog.HandleRoutes(APIV1Router)
|
||||||
|
cart.HandleRoutes(APIV1Router)
|
||||||
|
order.HandleRoutes(APIV1Router)
|
||||||
|
validate.HandleRoutes(APIV1Router)
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package endpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
"relynolli-server/models"
|
||||||
|
"relynolli-server/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type handlers struct{}
|
||||||
|
|
||||||
|
type ValidateReq struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Event string `json:"event"`
|
||||||
|
Object struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ handlers) Validate(c *gin.Context) {
|
||||||
|
req := ValidateReq{}
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest,
|
||||||
|
Info: fmt.Sprintf("Error: %s", err.Error())})
|
||||||
|
}
|
||||||
|
|
||||||
|
services.YookassaValidate(req.Object.Id, req.Object.Status)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handlers interface {
|
||||||
|
Validate(c *gin.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHandlers() Handlers {
|
||||||
|
return &handlers{}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package validate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"relynolli-server/handlers/validate/endpoints"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleRoutes(parent *gin.RouterGroup) {
|
||||||
|
h := endpoints.GetHandlers()
|
||||||
|
validate := parent.Group("/yookassa-validate")
|
||||||
|
validate.POST("", h.Validate)
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type database struct {
|
||||||
|
instance *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type Database interface {
|
||||||
|
GetInstance() *sqlx.DB
|
||||||
|
Close()
|
||||||
|
Query(stmt string) *sqlx.Rows
|
||||||
|
Execute(stmt string) sql.Result
|
||||||
|
FetchRows(stmt string, dest interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
instance *database = nil
|
||||||
|
once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func initialize() {
|
||||||
|
db, err := sqlx.Open("mysql", fmt.Sprintf(
|
||||||
|
"%s:%s@tcp(%s)/%s",
|
||||||
|
os.Getenv("MYSQL_USER"),
|
||||||
|
os.Getenv("MYSQL_PASSWORD"),
|
||||||
|
os.Getenv("MYSQL_HOST"),
|
||||||
|
os.Getenv("MYSQL_DATABASE")))
|
||||||
|
|
||||||
|
db.SetConnMaxLifetime(time.Minute * 3)
|
||||||
|
db.SetMaxOpenConns(3)
|
||||||
|
db.SetMaxIdleConns(3)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if db.Ping() != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Connection to db succeded")
|
||||||
|
instance = &database{instance: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitDatabase() Database {
|
||||||
|
if instance == nil {
|
||||||
|
once.Do(initialize)
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *database) GetInstance() *sqlx.DB {
|
||||||
|
return db.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *database) Close() {
|
||||||
|
defer log.Println("Connection to database was closed")
|
||||||
|
err := db.instance.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *database) Query(stmt string) *sqlx.Rows {
|
||||||
|
rows, err := db.instance.Queryx(stmt)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *database) Execute(stmt string) sql.Result {
|
||||||
|
result, err := db.instance.Exec(stmt)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
type FetchRowStruct []interface{}
|
||||||
|
|
||||||
|
func (db *database) FetchRows(stmt string, dest interface{}) {
|
||||||
|
err := db.instance.Select(dest, stmt)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
redisInstance *redis.Client = nil
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cache interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitRedis() *redis.Client {
|
||||||
|
if redisInstance == nil {
|
||||||
|
redis_db_num, err := strconv.Atoi(os.Getenv("REDIS_DATABASE"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("REDIS_DATABASE should be integer")
|
||||||
|
}
|
||||||
|
redisInstance = redis.NewClient(&redis.Options{Addr: os.Getenv("REDIS_ADDRESS"), Password: os.Getenv("REDIS_PASSWORD"), DB: redis_db_num})
|
||||||
|
_, conError := redisInstance.Ping(context.Background()).Result()
|
||||||
|
if conError != nil {
|
||||||
|
log.Fatalln("Cannot connect to redis host")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return redisInstance
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/cors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"relynolli-server/handlers"
|
||||||
|
"relynolli-server/internal"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
loadEnvironment()
|
||||||
|
server := gin.Default()
|
||||||
|
crs := cors.New(cors.Config{
|
||||||
|
AllowHeaders: []string{"*"},
|
||||||
|
AllowAllOrigins: true,
|
||||||
|
AllowMethods: []string{"GET, POST, PATCH, DELETE"},
|
||||||
|
})
|
||||||
|
|
||||||
|
server.Use(crs)
|
||||||
|
|
||||||
|
db := internal.InitDatabase()
|
||||||
|
rdb := internal.InitRedis()
|
||||||
|
handlers.InitializeRouter(server)
|
||||||
|
defer db.Close()
|
||||||
|
defer rdb.Close()
|
||||||
|
|
||||||
|
gracefullyShutDown := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(gracefullyShutDown, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
go server.Run("0.0.0.0:8000")
|
||||||
|
|
||||||
|
<-gracefullyShutDown
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadEnvironment() {
|
||||||
|
err := godotenv.Load(".env")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error loading .env file")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type CatalogStruct struct {
|
||||||
|
Id int
|
||||||
|
Code string
|
||||||
|
Name string
|
||||||
|
IsActive int `json:"is_active" db:"is_active"`
|
||||||
|
Properties []byte
|
||||||
|
DetailText string `json:"detailText" db:"detailText"`
|
||||||
|
Price []byte
|
||||||
|
AvailableQuantity int `json:"availableQuantity,omitempty" db:"available_quantity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CatalogStructWeb struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
IsActive int `json:"is_active" db:"is_active"`
|
||||||
|
Properties map[string]interface{} `json:"properties"`
|
||||||
|
DetailText string `json:"detailText" db:"detailText"`
|
||||||
|
Price map[string]interface{} `json:"price"`
|
||||||
|
AvailableQuantity int `json:"availableQuantity,omitempty" db:"available_quantity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CatalogWithQuantityWeb struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
IsActive int `json:"is_active"`
|
||||||
|
Properties map[string]interface{} `json:"properties"`
|
||||||
|
DetailText string `json:"detailText"`
|
||||||
|
Price map[string]interface{} `json:"price"`
|
||||||
|
Quantity int `json:"quantity"`
|
||||||
|
AvailableQuantity int `json:"available_quantity" db:"available_quantity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CatalogWithQuantity struct {
|
||||||
|
Id int
|
||||||
|
Code string
|
||||||
|
Name string
|
||||||
|
IsActive int `json:"is_active" db:"is_active"`
|
||||||
|
Properties []byte
|
||||||
|
DetailText string `json:"detailText" db:"detailText"`
|
||||||
|
Price []byte
|
||||||
|
Quantity int `json:"quantity"`
|
||||||
|
AvailableQuantity int `json:"available_quantity" db:"available_quantity"`
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
Info string `json:"info,omitempty"`
|
||||||
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"relynolli-server/internal"
|
||||||
|
"relynolli-server/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetCartItems(fuserId int) []models.CatalogWithQuantityWeb {
|
||||||
|
rdb := internal.InitRedis()
|
||||||
|
keys, _ := rdb.Keys(context.Background(), fmt.Sprintf("api.api_cart.%d.*", fuserId)).Result()
|
||||||
|
|
||||||
|
result := []models.CatalogWithQuantityWeb{}
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
str, _ := rdb.Get(context.Background(), key).Result()
|
||||||
|
item := models.CatalogWithQuantityWeb{}
|
||||||
|
json.Unmarshal([]byte(str), &item)
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
//cartList := []models.CatalogWithQuantity{}
|
||||||
|
//returnedList := []models.CatalogWithQuantityWeb{}
|
||||||
|
//
|
||||||
|
//stmt := fmt.Sprintf(`select t1.*, t2.quantity, t3.QUANTITY as available_quantity from api_catalog t1
|
||||||
|
// join api_cart t2 on t1.id = t2.product_id
|
||||||
|
// left join b_catalog_product t3 on t1.id = t3.ID where t2.fuser_id = %d;`, fuserId)
|
||||||
|
//
|
||||||
|
//retrieveItems(stmt, &cartList)
|
||||||
|
//
|
||||||
|
//for _, item := range cartList {
|
||||||
|
// itemProd := models.CatalogWithQuantityWeb{
|
||||||
|
// Id: item.Id,
|
||||||
|
// Code: item.Code,
|
||||||
|
// Name: item.Name,
|
||||||
|
// IsActive: item.IsActive,
|
||||||
|
// DetailText: item.DetailText,
|
||||||
|
// Quantity: item.Quantity,
|
||||||
|
// AvailableQuantity: item.AvailableQuantity,
|
||||||
|
// }
|
||||||
|
// json.Unmarshal(item.Price, &itemProd.Price)
|
||||||
|
// json.Unmarshal(item.Properties, &itemProd.Properties)
|
||||||
|
// returnedList = append(returnedList, itemProd)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//return returnedList
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateFuser() int64 {
|
||||||
|
stmt := "insert into b_sale_fuser (DATE_INSERT, DATE_UPDATE, CODE) values (now(), now(), md5(rand()));"
|
||||||
|
|
||||||
|
db := internal.InitDatabase()
|
||||||
|
result := db.Execute(stmt)
|
||||||
|
lastInsertId, _ := result.LastInsertId()
|
||||||
|
return lastInsertId
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddItemToCart(fuserId int, productId int) {
|
||||||
|
|
||||||
|
rdb := internal.InitRedis()
|
||||||
|
item, _ := GetCatalogItemById(productId)
|
||||||
|
|
||||||
|
itemWithQuantity := models.CatalogWithQuantityWeb{
|
||||||
|
Id: item.Id,
|
||||||
|
Code: item.Code,
|
||||||
|
Name: item.Name,
|
||||||
|
IsActive: item.IsActive,
|
||||||
|
Properties: item.Properties,
|
||||||
|
DetailText: item.DetailText,
|
||||||
|
Price: item.Price,
|
||||||
|
Quantity: 1,
|
||||||
|
AvailableQuantity: item.AvailableQuantity,
|
||||||
|
}
|
||||||
|
|
||||||
|
marshaled, _ := json.Marshal(itemWithQuantity)
|
||||||
|
|
||||||
|
err := rdb.Set(context.Background(), fmt.Sprintf("api.api_cart.%d.%d", fuserId, productId), string(marshaled), 0).Err()
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateCartItem(fuserId int, productId int, quantity int) error {
|
||||||
|
if quantity <= 0 {
|
||||||
|
DeleteCartItem(fuserId, productId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
item, _ := GetCatalogItemById(productId)
|
||||||
|
if item.AvailableQuantity < quantity {
|
||||||
|
return fmt.Errorf("Available quantity is less than requested. Available %d, requested %d", item.AvailableQuantity, quantity)
|
||||||
|
}
|
||||||
|
|
||||||
|
itemWithQuantity := models.CatalogWithQuantityWeb{
|
||||||
|
Id: item.Id,
|
||||||
|
Code: item.Code,
|
||||||
|
Name: item.Name,
|
||||||
|
IsActive: item.IsActive,
|
||||||
|
Properties: item.Properties,
|
||||||
|
DetailText: item.DetailText,
|
||||||
|
Price: item.Price,
|
||||||
|
Quantity: quantity,
|
||||||
|
AvailableQuantity: item.AvailableQuantity,
|
||||||
|
}
|
||||||
|
|
||||||
|
marshaled, _ := json.Marshal(itemWithQuantity)
|
||||||
|
|
||||||
|
rdb := internal.InitRedis()
|
||||||
|
|
||||||
|
rdb.Set(context.Background(), fmt.Sprintf("api.api_cart.%d.%d", fuserId, productId), string(marshaled), 0)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
//var availableQunatity int
|
||||||
|
//stmtQuantity := fmt.Sprintf("select QUANTITY as q from b_catalog_product where ID = %d;", productId)
|
||||||
|
//updateStmt := fmt.Sprintf("update api_cart set quantity = %d where product_id = %d and fuser_id = %d", quantity, productId, fuserId)
|
||||||
|
|
||||||
|
//db := internal.InitDatabase()
|
||||||
|
//rows := db.Query(stmtQuantity)
|
||||||
|
//rows.Next()
|
||||||
|
//rows.Scan(&availableQunatity)
|
||||||
|
//if quantity > availableQunatity {
|
||||||
|
// return fmt.Errorf("Available quantity is less than requested. Available %d, requested %d", availableQunatity, quantity)
|
||||||
|
//}
|
||||||
|
//db.Execute(updateStmt)
|
||||||
|
//return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteCartItem(fuserId int, productId int) {
|
||||||
|
rdb := internal.InitRedis()
|
||||||
|
rdb.Del(context.Background(), fmt.Sprintf("api.api_cart.%d.%d", fuserId, productId)).Err()
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"relynolli-server/internal"
|
||||||
|
"relynolli-server/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func retrieveItems(stmt string, structure interface{}) {
|
||||||
|
db := internal.InitDatabase()
|
||||||
|
db.FetchRows(stmt, structure)
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieveCatalogItems(stmt string) []models.CatalogStructWeb {
|
||||||
|
var catalogList []models.CatalogStruct
|
||||||
|
var returnedList []models.CatalogStructWeb
|
||||||
|
|
||||||
|
retrieveItems(stmt, &catalogList)
|
||||||
|
for _, item := range catalogList {
|
||||||
|
itemProd := models.CatalogStructWeb{
|
||||||
|
Id: item.Id,
|
||||||
|
Code: item.Code,
|
||||||
|
Name: item.Name,
|
||||||
|
IsActive: item.IsActive,
|
||||||
|
DetailText: item.DetailText,
|
||||||
|
AvailableQuantity: item.AvailableQuantity,
|
||||||
|
}
|
||||||
|
json.Unmarshal(item.Price, &itemProd.Price)
|
||||||
|
json.Unmarshal(item.Properties, &itemProd.Properties)
|
||||||
|
returnedList = append(returnedList, itemProd)
|
||||||
|
}
|
||||||
|
return returnedList
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCatalogItems(limit int, offset int) []models.CatalogStructWeb {
|
||||||
|
stmt := fmt.Sprintf("select * from api_catalog order by id DESC limit %d offset %d;", limit, offset)
|
||||||
|
return retrieveCatalogItems(stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCatalogItem(code string) (models.CatalogStructWeb, error) {
|
||||||
|
stmt := fmt.Sprintf("select * from api_catalog where code = '%s';", code)
|
||||||
|
items := retrieveCatalogItems(stmt)
|
||||||
|
if len(items) == 0 {
|
||||||
|
return models.CatalogStructWeb{}, fmt.Errorf("Not founded catalog item with given code")
|
||||||
|
}
|
||||||
|
return retrieveCatalogItems(stmt)[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCatalogItemById(id int) (models.CatalogStructWeb, error) {
|
||||||
|
stmt := fmt.Sprintf("select * from api_catalog where id = %d;", id)
|
||||||
|
items := retrieveCatalogItems(stmt)
|
||||||
|
if len(items) == 0 {
|
||||||
|
return models.CatalogStructWeb{}, fmt.Errorf("Not founded catalog item with given code")
|
||||||
|
}
|
||||||
|
return retrieveCatalogItems(stmt)[0], nil
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"relynolli-server/external/bitrix"
|
||||||
|
"relynolli-server/external/kassa"
|
||||||
|
"relynolli-server/external/kassa/Measure"
|
||||||
|
"relynolli-server/external/kassa/PaymentMode"
|
||||||
|
"relynolli-server/external/kassa/PaymentSubject"
|
||||||
|
"relynolli-server/external/kassa/VatCodes"
|
||||||
|
"relynolli-server/internal"
|
||||||
|
"relynolli-server/models"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetTotal(fuserId int) float64 {
|
||||||
|
rdb := internal.InitRedis()
|
||||||
|
keys, _ := rdb.Keys(context.Background(), fmt.Sprintf("api.api_cart.%d.*", fuserId)).Result()
|
||||||
|
|
||||||
|
result := []models.CatalogWithQuantityWeb{}
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
str, _ := rdb.Get(context.Background(), key).Result()
|
||||||
|
item := models.CatalogWithQuantityWeb{}
|
||||||
|
|
||||||
|
json.Unmarshal([]byte(str), &item)
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := float64(0)
|
||||||
|
|
||||||
|
for _, catalogItem := range result {
|
||||||
|
sum = sum + catalogItem.Price["BASE"].(float64)*float64(catalogItem.Quantity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
type addProductsToOrderReq struct {
|
||||||
|
ProductId int `db:"product_id"`
|
||||||
|
PriceTypeId int `db:"price_type_id"`
|
||||||
|
Quantity int `db:"quantity"`
|
||||||
|
Price float64 `db:"price"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func addProductsToOrder(api bitrix.Bitrix, fuserId int, orderId int) error {
|
||||||
|
//Получаем данные из корзины
|
||||||
|
|
||||||
|
cartItems := GetCartItems(fuserId)
|
||||||
|
|
||||||
|
rdb := internal.InitRedis()
|
||||||
|
rdb.Keys(context.Background(), "")
|
||||||
|
|
||||||
|
for _, product := range cartItems {
|
||||||
|
err := api.AddProductToOrder(orderId, product.Id, product.Price["BASE"].(float64), product.Quantity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeOrder(fuserId int, email string, fullName string, phone string) (map[string]interface{}, error) {
|
||||||
|
|
||||||
|
// Инициализируем api
|
||||||
|
|
||||||
|
api := bitrix.Initialize()
|
||||||
|
|
||||||
|
// 1. Создаем анонимного пользователя
|
||||||
|
|
||||||
|
userId, _ := api.CreateAnonymousUser()
|
||||||
|
|
||||||
|
// 2. Создаем заказ
|
||||||
|
orderId, _ := api.CreateOrder(userId)
|
||||||
|
|
||||||
|
// --- обновляем контакт пользователя
|
||||||
|
order, orderErr := api.GetOrderInfo(orderId)
|
||||||
|
if orderErr != nil {
|
||||||
|
return nil, orderErr
|
||||||
|
}
|
||||||
|
clientId, _ := strconv.Atoi(order.Clients[0].EntityId)
|
||||||
|
api.UpdateContact(clientId, email, fullName, phone)
|
||||||
|
|
||||||
|
// 3. Добавляем элементы в корзину
|
||||||
|
addProductErr := addProductsToOrder(api, fuserId, orderId)
|
||||||
|
if addProductErr != nil {
|
||||||
|
return nil, addProductErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Получаем обновленный ресурс заказа
|
||||||
|
order, _ = api.GetOrderInfo(orderId)
|
||||||
|
|
||||||
|
// 5. Добавляем способ оплаты товара
|
||||||
|
createPaymentError := api.CreatePayment(orderId, order.Price)
|
||||||
|
|
||||||
|
if createPaymentError != nil {
|
||||||
|
return nil, createPaymentError
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Получаем ресурс оплаты и url для нее
|
||||||
|
paymentData, _ := kassa.CreatePayment(orderId, order.Price, fullName, email, phone, getItemsForPayment(order))
|
||||||
|
|
||||||
|
insPaymentDataStmt := fmt.Sprintf(`
|
||||||
|
insert into api_youkassa_payment (payment_id, order_id, link, status)
|
||||||
|
values ('%s',
|
||||||
|
'%s',
|
||||||
|
'%s',
|
||||||
|
'%s');
|
||||||
|
`, paymentData["id"].(string),
|
||||||
|
orderId,
|
||||||
|
paymentData["confirmation"].(map[string]interface{})["confirmation_url"].(string),
|
||||||
|
paymentData["status"].(string),
|
||||||
|
)
|
||||||
|
|
||||||
|
db := internal.InitDatabase()
|
||||||
|
|
||||||
|
db.Execute(insPaymentDataStmt)
|
||||||
|
|
||||||
|
return paymentData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getItemsForPayment(order *bitrix.OrderResource) []kassa.KassaReceiptItems {
|
||||||
|
result := []kassa.KassaReceiptItems{}
|
||||||
|
|
||||||
|
for _, basketItem := range order.BasketItems {
|
||||||
|
quantity, _ := strconv.Atoi(strings.Split(basketItem.Quantity, ".")[0])
|
||||||
|
item := kassa.KassaReceiptItems{
|
||||||
|
Description: basketItem.Name,
|
||||||
|
Amount: kassa.KassaAmount{
|
||||||
|
Value: fmt.Sprintf("%f", basketItem.Price),
|
||||||
|
Currency: "RUB",
|
||||||
|
},
|
||||||
|
VatCode: VatCodes.NDS_20,
|
||||||
|
Quantity: fmt.Sprintf("%d", quantity),
|
||||||
|
Measure: Measure.PIECE,
|
||||||
|
PaymentSubject: PaymentSubject.COMMODITY,
|
||||||
|
PaymentMode: PaymentMode.FULL_PAYMENT,
|
||||||
|
}
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"relynolli-server/external/bitrix"
|
||||||
|
"relynolli-server/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func YookassaValidate(paymentId string, status string) {
|
||||||
|
stmt := fmt.Sprintf(`select t1.order_id as order_id, t2.ID as payment_id from api_youkassa_payment t1 join b_sale_order_payment t2 on t1.order_id = t2.ORDER_ID where t1.payment_id = '%s';`, paymentId)
|
||||||
|
db := internal.InitDatabase()
|
||||||
|
rows := db.Query(stmt)
|
||||||
|
|
||||||
|
var (
|
||||||
|
orderId int
|
||||||
|
paymentIdBitrix int
|
||||||
|
)
|
||||||
|
|
||||||
|
rows.Next()
|
||||||
|
rows.Scan(&orderId, &paymentIdBitrix)
|
||||||
|
|
||||||
|
api := bitrix.Initialize()
|
||||||
|
if status == "succeeded" {
|
||||||
|
api.ApprovePayment(paymentIdBitrix, 8)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if status == "canceled" {
|
||||||
|
api.CancelOrder(orderId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
Loading…
Reference in New Issue