commit 54856057c259a3328f06d48313bdab7be59de0a5 Author: Ernest Litvinenko Date: Fri Mar 15 21:27:45 2024 +0300 Initial diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..9319170 --- /dev/null +++ b/.env.sample @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c59d4c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +server +./build/ +.env +.idea/ \ No newline at end of file diff --git a/external/bitrix/api.go b/external/bitrix/api.go new file mode 100644 index 0000000..086bc82 --- /dev/null +++ b/external/bitrix/api.go @@ -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{} +} diff --git a/external/kassa/AgentType/enums.go b/external/kassa/AgentType/enums.go new file mode 100644 index 0000000..0241765 --- /dev/null +++ b/external/kassa/AgentType/enums.go @@ -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" // Агент +) diff --git a/external/kassa/Measure/enums.go b/external/kassa/Measure/enums.go new file mode 100644 index 0000000..ab2da6d --- /dev/null +++ b/external/kassa/Measure/enums.go @@ -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" // Другое +) diff --git a/external/kassa/PaymentMode/enums.go b/external/kassa/PaymentMode/enums.go new file mode 100644 index 0000000..1c9ac0c --- /dev/null +++ b/external/kassa/PaymentMode/enums.go @@ -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" // Выплата по кредиту +) diff --git a/external/kassa/PaymentSubject/enums.go b/external/kassa/PaymentSubject/enums.go new file mode 100644 index 0000000..e639f21 --- /dev/null +++ b/external/kassa/PaymentSubject/enums.go @@ -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" // Другое +) diff --git a/external/kassa/Settlements/enums.go b/external/kassa/Settlements/enums.go new file mode 100644 index 0000000..c76c5a7 --- /dev/null +++ b/external/kassa/Settlements/enums.go @@ -0,0 +1,10 @@ +package Settlements + +type Settlements string + +const ( + CASHLESS = "cashless" // Безналичный расчет + PREPAYMENT = "prepayment" // Предоплата (аванс) + POSTPAYMENT = "postpayment" // Постоплата (кредит) + CONSIDERATION = "consideration" // Встречное предоставление +) diff --git a/external/kassa/TaxSystemCode/enums.go b/external/kassa/TaxSystemCode/enums.go new file mode 100644 index 0000000..e0c61b0 --- /dev/null +++ b/external/kassa/TaxSystemCode/enums.go @@ -0,0 +1,12 @@ +package TaxSystemCode + +type TaxSystemCode int + +const ( + GENERAL = iota + 1 + USN_INCOME + USN_INCOME_MINUS_EXPENCES + ENVD + ESN + PATENT +) diff --git a/external/kassa/VatCodes/enums.go b/external/kassa/VatCodes/enums.go new file mode 100644 index 0000000..a2f1f3c --- /dev/null +++ b/external/kassa/VatCodes/enums.go @@ -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 +) diff --git a/external/kassa/general.go b/external/kassa/general.go new file mode 100644 index 0000000..760a104 --- /dev/null +++ b/external/kassa/general.go @@ -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" +) diff --git a/external/kassa/kassa.go b/external/kassa/kassa.go new file mode 100644 index 0000000..2d0564b --- /dev/null +++ b/external/kassa/kassa.go @@ -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 + +} diff --git a/external/kassa/types.go b/external/kassa/types.go new file mode 100644 index 0000000..9707ab8 --- /dev/null +++ b/external/kassa/types.go @@ -0,0 +1 @@ +package kassa diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..45928eb --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1292dd5 --- /dev/null +++ b/go.sum @@ -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= diff --git a/handlers/cart/endpoints/cart.go b/handlers/cart/endpoints/cart.go new file mode 100644 index 0000000..28d5de4 --- /dev/null +++ b/handlers/cart/endpoints/cart.go @@ -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, + }) +} diff --git a/handlers/cart/endpoints/ep.go b/handlers/cart/endpoints/ep.go new file mode 100644 index 0000000..5833d09 --- /dev/null +++ b/handlers/cart/endpoints/ep.go @@ -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{} +} diff --git a/handlers/cart/endpoints/item.go b/handlers/cart/endpoints/item.go new file mode 100644 index 0000000..6f610cc --- /dev/null +++ b/handlers/cart/endpoints/item.go @@ -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}) +} diff --git a/handlers/cart/routes.go b/handlers/cart/routes.go new file mode 100644 index 0000000..ebe00c8 --- /dev/null +++ b/handlers/cart/routes.go @@ -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) + } +} diff --git a/handlers/catalog/endpoints/catalog.go b/handlers/catalog/endpoints/catalog.go new file mode 100644 index 0000000..cce3555 --- /dev/null +++ b/handlers/catalog/endpoints/catalog.go @@ -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) +} diff --git a/handlers/catalog/endpoints/ep.go b/handlers/catalog/endpoints/ep.go new file mode 100644 index 0000000..8cb2605 --- /dev/null +++ b/handlers/catalog/endpoints/ep.go @@ -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{} +} diff --git a/handlers/catalog/endpoints/filters.go b/handlers/catalog/endpoints/filters.go new file mode 100644 index 0000000..9f44366 --- /dev/null +++ b/handlers/catalog/endpoints/filters.go @@ -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) +} diff --git a/handlers/catalog/routes.go b/handlers/catalog/routes.go new file mode 100644 index 0000000..e917827 --- /dev/null +++ b/handlers/catalog/routes.go @@ -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) +} diff --git a/handlers/order/endpoints/ep.go b/handlers/order/endpoints/ep.go new file mode 100644 index 0000000..f97c39b --- /dev/null +++ b/handlers/order/endpoints/ep.go @@ -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{} +} diff --git a/handlers/order/routes.go b/handlers/order/routes.go new file mode 100644 index 0000000..cc10a66 --- /dev/null +++ b/handlers/order/routes.go @@ -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) +} diff --git a/handlers/routers.go b/handlers/routers.go new file mode 100644 index 0000000..c6778ad --- /dev/null +++ b/handlers/routers.go @@ -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) +} diff --git a/handlers/validate/endpoints/validate.go b/handlers/validate/endpoints/validate.go new file mode 100644 index 0000000..a85f124 --- /dev/null +++ b/handlers/validate/endpoints/validate.go @@ -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{} +} diff --git a/handlers/validate/routes.go b/handlers/validate/routes.go new file mode 100644 index 0000000..7a9097a --- /dev/null +++ b/handlers/validate/routes.go @@ -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) +} diff --git a/internal/database.go b/internal/database.go new file mode 100644 index 0000000..a3cdb4f --- /dev/null +++ b/internal/database.go @@ -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) + } +} diff --git a/internal/redis.go b/internal/redis.go new file mode 100644 index 0000000..d08cab2 --- /dev/null +++ b/internal/redis.go @@ -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 +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..22f3a8e --- /dev/null +++ b/main.go @@ -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") + } +} diff --git a/models/catalog.go b/models/catalog.go new file mode 100644 index 0000000..0acd42c --- /dev/null +++ b/models/catalog.go @@ -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"` +} diff --git a/models/response.go b/models/response.go new file mode 100644 index 0000000..8148836 --- /dev/null +++ b/models/response.go @@ -0,0 +1,7 @@ +package models + +type Response struct { + Status int `json:"status"` + Info string `json:"info,omitempty"` + Data interface{} `json:"data,omitempty"` +} diff --git a/services/cart.go b/services/cart.go new file mode 100644 index 0000000..dc781c0 --- /dev/null +++ b/services/cart.go @@ -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() +} diff --git a/services/catalog.go b/services/catalog.go new file mode 100644 index 0000000..0a45c5d --- /dev/null +++ b/services/catalog.go @@ -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 +} diff --git a/services/order.go b/services/order.go new file mode 100644 index 0000000..352469b --- /dev/null +++ b/services/order.go @@ -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 +} diff --git a/services/validate.go b/services/validate.go new file mode 100644 index 0000000..a7acee5 --- /dev/null +++ b/services/validate.go @@ -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 +}