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