diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..23f9009 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,100 @@ +stages: + - lint + - test + - clean + - build + - deploy + +lint: + image: golangci/golangci-lint:v1.52-alpine + stage: lint + before_script: + - go install github.com/vektra/mockery/v2@v2.26.0 + - go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4 + script: + - go generate ./... + - golangci-lint version + - golangci-lint run ./internal/... + +test: + image: golang:1.20.3-alpine + stage: test + coverage: /\(statements\)(?:\s+)?(\d+(?:\.\d+)?%)/ + script: + - CGO_ENABLED=0 go test ./internal/... -coverprofile=coverage.out + - go tool cover -html=coverage.out -o coverage.html + - go tool cover -func coverage.out + artifacts: + expire_in: "3 days" + paths: + - coverage.html + +clean-old: + stage: clean + image: + name: docker/compose:1.28.0 + entrypoint: [""] + allow_failure: true + variables: + PRODUCTION_BRANCH: main + STAGING_BRANCH: "staging" + DEPLOY_TO: "staging" + rules: + - if: $CI_COMMIT_BRANCH == $PRODUCTION_BRANCH || $CI_COMMIT_BRANCH == $STAGING_BRANCH + when: on_success + before_script: + - echo DEPLOY_TO = $DEPLOY_TO + script: + - docker-compose -f deployments/$DEPLOY_TO/docker-compose.yaml down --volumes --rmi local + +build-app: + stage: build + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + variables: + DOCKER_BUILD_PATH: "./Dockerfile" + STAGING_BRANCH: "staging" + PRODUCTION_BRANCH: "main" + rules: + - if: $CI_COMMIT_BRANCH == $PRODUCTION_BRANCH || $CI_COMMIT_BRANCH == $STAGING_BRANCH + when: on_success + before_script: + - echo PRODUCTION_BRANCH = $PRODUCTION_BRANCH + - echo STAGING_BRANCH = $STAGING_BRANCH + - echo CI_REGISTRY = $CI_REGISTRY + - echo CI_REGISTRY_USER = $CI_REGISTRY_USER + - echo CI_PROJECT_DIR = $CI_PROJECT_DIR + - echo CI_REGISTRY_IMAGE = $CI_REGISTRY_IMAGE + - echo CI_COMMIT_REF_SLUG = $CI_COMMIT_REF_SLUG + - echo DOCKER_BUILD_PATH = $DOCKER_BUILD_PATH + - echo CI_PIPELINE_ID = $CI_PIPELINE_ID + script: + - mkdir -p /kaniko/.docker + - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json + - | + /kaniko/executor --context $CI_PROJECT_DIR \ + --cache=true --cache-repo=$CI_REGISTRY_IMAGE \ + --dockerfile $CI_PROJECT_DIR/$DOCKER_BUILD_PATH --target production \ + --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + +deploy-to-staging: + stage: deploy + image: + name: docker/compose:1.28.0 + entrypoint: [""] + variables: + DEPLOY_TO: "staging" + BRANCH: "staging" + rules: + - if: $CI_COMMIT_BRANCH == $BRANCH + before_script: + - echo CI_PROJECT_NAME = $CI_PROJECT_NAME + - echo CI_REGISTRY = $CI_REGISTRY + - echo REGISTRY_USER = $REGISTRY_USER + - echo REGISTRY_TOKEN = $REGISTRY_TOKEN + - echo DEPLOY_TO = $DEPLOY_TO + - echo BRANCH = $BRANCH + script: + - docker login -u $REGISTRY_USER -p $REGISTRY_TOKEN $CI_REGISTRY + - docker-compose -f deployments/$DEPLOY_TO/docker-compose.yaml up -d diff --git a/README.md b/README.md index 9945f7f..3899038 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,11 @@ MONGO_DB_NAME - название базы данных для подключе MONGO_PASSWORD - пароль пользователя MongoDB MONGO_AUTH - имя базы данных Mongo, по которой будет производится авторизация +IS_MOCK - отвечает за инициализацию настроек моков в моковом сервисе (true | false) +MOCK_SERVICE_HOST - хост сервиса моков + YOOMONEY_STORE_ID - id магазина, зарегистрированного на yoomoney YOOMONEY_SECRET_KEY - секретный ключ yoomoney +YOOMONEY_WEBHOOKS_URL - адрес к обработчику вебхуков yoomoney +YOOMONEY_PAYMENTS_URL - адрес к обработчику платажей yoomoney ``` diff --git a/cmd/mock/mock_payments.go b/cmd/mock/mock_payments.go deleted file mode 100644 index cfb6a74..0000000 --- a/cmd/mock/mock_payments.go +++ /dev/null @@ -1,3 +0,0 @@ -package main - -func MockWebhooks() {} diff --git a/deployments/dev/docker-compose.yaml b/deployments/dev/docker-compose.yaml index 1be1425..0ec7e33 100644 --- a/deployments/dev/docker-compose.yaml +++ b/deployments/dev/docker-compose.yaml @@ -11,13 +11,18 @@ services: environment: - HTTP_HOST=0.0.0.0 - HTTP_PORT=8085 - - HTTP_DOMEN=http://localhost:8080 + - HTTP_DOMEN=http://localhost:8085 - GRPC_HOST=0.0.0.0 - GRPC_PORT=9085 + - IS_MOCK=true + - MOCK_SERVICE_HOST=http://treasurer-mock:8080 + - YOOMONEY_STORE_ID=id - YOOMONEY_SECRET_KEY=secret + - YOOMONEY_WEBHOOKS_URL=http://treasurer-mock:8080/webhooks + - YOOMONEY_PAYMENTS_URL=http://treasurer-mock:8080/payments - MONGO_HOST=mongo - MONGO_PORT=27017 diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml new file mode 100644 index 0000000..fcdc325 --- /dev/null +++ b/deployments/staging/docker-compose.yaml @@ -0,0 +1,56 @@ +version: "3.3" + +services: + treasurer-app: + container_name: treasurer-service + tty: true + build: + context: ../../. + dockerfile: Dockerfile + target: production + environment: + - HTTP_HOST=0.0.0.0 + - HTTP_PORT=8085 + - HTTP_DOMEN=http://localhost:8085 + + - GRPC_HOST=0.0.0.0 + - GRPC_PORT=9085 + + - IS_MOCK=true + - MOCK_SERVICE_HOST=http://treasurer-mock:8080 + + - YOOMONEY_STORE_ID=id + - YOOMONEY_SECRET_KEY=secret + - YOOMONEY_WEBHOOKS_URL=http://treasurer-mock:8080/webhooks + - YOOMONEY_PAYMENTS_URL=http://treasurer-mock:8080/payments + + - MONGO_HOST=mongo + - MONGO_PORT=27017 + - MONGO_USER=test + - MONGO_PASSWORD=test + - MONGO_DB_NAME=admin + - MONGO_AUTH=admin + ports: + - 8085:8085 + - 9085:9085 + networks: + - backend_external + - default + depends_on: + - treasurer-mock + + treasurer-mock: + container_name: treasurermock-container + image: 'wiremock/wiremock:2.35.0' + ports: + - 8080:8080 + networks: + - backend_external + - default + +networks: + backend_external: + driver: bridge + attachable: true + internal: true + diff --git a/deployments/test/docker-compose.yaml b/deployments/test/docker-compose.yaml index 204991f..fd0f0d8 100644 --- a/deployments/test/docker-compose.yaml +++ b/deployments/test/docker-compose.yaml @@ -8,26 +8,31 @@ services: target: test environment: - HTTP_HOST=0.0.0.0 - - HTTP_PORT=8080 - - HTTP_DOMEN=http://localhost:8080 + - HTTP_PORT=8085 + - HTTP_DOMEN=http://localhost:8085 - GRPC_HOST=0.0.0.0 - - GRPC_PORT=8081 + - GRPC_PORT=9085 - - YOOMONEY_STORE_ID=storeid + - IS_MOCK=false + - MOCK_SERVICE_HOST=http://mock:8080 + + - YOOMONEY_STORE_ID=id - YOOMONEY_SECRET_KEY=secret + - YOOMONEY_WEBHOOKS_URL=http://mock:8080/webhooks + - YOOMONEY_PAYMENTS_URL=http://mock:8080/payments - MONGO_HOST=mongo - MONGO_PORT=27017 - - MONGO_USER=admin - - MONGO_PASSWORD=admin + - MONGO_USER=test + - MONGO_PASSWORD=test - MONGO_DB_NAME=admin - MONGO_AUTH=admin ports: - 8080:8080 - 8081:8081 networks: - - dev + - integartion_test depends_on: - mongo @@ -39,16 +44,16 @@ services: ports: - '27017:27017' networks: - - dev + - integartion_test mock: image: 'wiremock/wiremock:2.35.0' ports: - 8000:8080 networks: - - dev + - integartion_test depends_on: - treasurer-app networks: - dev: + integartion_test: diff --git a/internal/app/app.go b/internal/app/app.go index 605b256..caf1d3e 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -10,6 +10,7 @@ import ( "penahub.gitlab.yandexcloud.net/external/treasurer/internal/interface/swagger" "penahub.gitlab.yandexcloud.net/external/treasurer/internal/models" "penahub.gitlab.yandexcloud.net/external/treasurer/internal/server" + "penahub.gitlab.yandexcloud.net/external/treasurer/internal/worker" "penahub.gitlab.yandexcloud.net/external/treasurer/pkg/closer" "penahub.gitlab.yandexcloud.net/external/treasurer/pkg/mongo" ) @@ -60,6 +61,15 @@ func Run(ctx context.Context, config *models.Config, logger *zap.Logger) error { return err.Wrap("failed to initialize services") } + workers, err := initialize.NewWorkers(initialize.WorkersDeps{ + Logger: logger, + Services: *services, + }) + + if err != nil { + return err.Wrap("failed to initialize workers") + } + controllers, err := initialize.NewControllers(initialize.ControllersDeps{ Logger: logger, Services: *services, @@ -94,6 +104,7 @@ func Run(ctx context.Context, config *models.Config, logger *zap.Logger) error { go httpServer.Run(&config.HTTP) go grpcServer.Run(&config.GRPC) + go worker.Run(ctx, workers) closer.Add(mongoDB.Client().Disconnect) closer.Add(httpServer.Stop) diff --git a/internal/initialize/services.go b/internal/initialize/services.go index a880a26..d08dbca 100644 --- a/internal/initialize/services.go +++ b/internal/initialize/services.go @@ -5,6 +5,7 @@ import ( "penahub.gitlab.yandexcloud.net/external/treasurer/internal/errors" "penahub.gitlab.yandexcloud.net/external/treasurer/internal/models" "penahub.gitlab.yandexcloud.net/external/treasurer/internal/service/callback" + "penahub.gitlab.yandexcloud.net/external/treasurer/internal/service/mock" "penahub.gitlab.yandexcloud.net/external/treasurer/internal/service/payment" "penahub.gitlab.yandexcloud.net/external/treasurer/internal/service/status" "penahub.gitlab.yandexcloud.net/external/treasurer/internal/service/webhook" @@ -15,6 +16,7 @@ type ServicesDeps struct { Repositories Repositories Clients Clients ConfigurationHTTP *models.ConfigurationHTTP + Configuration *models.ServiceConfiguration } type Services struct { @@ -23,6 +25,7 @@ type Services struct { YandexPayment *payment.Yandex Status *status.Service YandexWebhook *webhook.Yandex + Mock *mock.Service } func NewServices(deps ServicesDeps) (*Services, errors.Error) { @@ -69,11 +72,30 @@ func NewServices(deps ServicesDeps) (*Services, errors.Error) { return nil, err } + yoomoneyMockService, err := mock.NewYoomoneyMockService(mock.YoomoneyMockServiceDeps{ + Logger: deps.Logger, + YoomoneyURL: &deps.Configuration.MockConfiguration.YoomomeyURL, + }) + if err != nil { + return nil, err + } + + mockService, err := mock.New(mock.Deps{ + Logger: deps.Logger, + Enabled: deps.Configuration.MockConfiguration.IsMock, + MockServiceHost: deps.Configuration.MockConfiguration.Host, + YoomoneyMockService: yoomoneyMockService, + }) + if err != nil { + return nil, err + } + return &Services{ Callback: callbackService, Payment: paymentService, YandexPayment: yandexPaymentService, Status: statusService, YandexWebhook: yandexWebhookService, + Mock: mockService, }, nil } diff --git a/internal/initialize/workers.go b/internal/initialize/workers.go new file mode 100644 index 0000000..7262680 --- /dev/null +++ b/internal/initialize/workers.go @@ -0,0 +1,30 @@ +package initialize + +import ( + "go.uber.org/zap" + "penahub.gitlab.yandexcloud.net/external/treasurer/internal/errors" + "penahub.gitlab.yandexcloud.net/external/treasurer/internal/worker/mock" +) + +type WorkersDeps struct { + Logger *zap.Logger + Services Services +} + +type Workers struct { + Mock *mock.Worker +} + +func NewWorkers(deps WorkersDeps) (*Workers, errors.Error) { + mockWorker, err := mock.New(mock.WorkerDeps{ + Logger: deps.Logger, + MockService: deps.Services.Mock, + }) + if err != nil { + return nil, err + } + + return &Workers{ + Mock: mockWorker, + }, nil +} diff --git a/internal/interface/client/yandex.go b/internal/interface/client/yandex.go index d6adc7f..85d10f5 100644 --- a/internal/interface/client/yandex.go +++ b/internal/interface/client/yandex.go @@ -13,19 +13,6 @@ import ( "penahub.gitlab.yandexcloud.net/external/treasurer/pkg/client" ) -const ( - yandexPaymentsMockURL = "http://treasurer-mock:8080/payments" - yandexPaymentsProdURL = "https://api.yookassa.ru/v3/payments" - - yandexWebhooksMockURL = "http://treasurer-mock:8080/webhooks" - yandexWebhooksProdURL = "https://api.yookassa.ru/v3/webhooks" -) - -const ( - yandexPaymentsURL = yandexPaymentsMockURL - yandexWebhooksURL = yandexWebhooksMockURL -) - type YandexClientDeps struct { Logger *zap.Logger Configuration *models.YoomomeyConfiguration @@ -53,7 +40,7 @@ func NewYandexClient(deps YandexClientDeps) (*YandexClient, errors.Error) { func (receiver *YandexClient) CreatePayment(ctx context.Context, idempotenceKey string, request *yandex.CreatePaymentRequest[yandex.PaymentMethodType]) (*yandex.Payment, errors.Error) { response, err := client.Post[yandex.Payment, any](ctx, &client.RequestSettings{ - URL: yandexPaymentsURL, + URL: receiver.configuration.URL.Payments, Body: request, Headers: map[string]string{ "Content-Type": "application/json", @@ -75,7 +62,7 @@ func (receiver *YandexClient) CreatePayment(ctx context.Context, idempotenceKey func (receiver *YandexClient) CreatePaymentB2B(ctx context.Context, idempotenceKey string, request *yandex.CreatePaymentRequest[yandex.PaymentMethodB2B]) (*yandex.Payment, errors.Error) { response, err := client.Post[yandex.Payment, any](ctx, &client.RequestSettings{ - URL: yandexPaymentsURL, + URL: receiver.configuration.URL.Payments, Body: request, Headers: map[string]string{ "Content-Type": "application/json", @@ -97,7 +84,7 @@ func (receiver *YandexClient) CreatePaymentB2B(ctx context.Context, idempotenceK func (receiver *YandexClient) CreatePaymentBankCard(ctx context.Context, idempotenceKey string, request *yandex.CreatePaymentRequest[yandex.PaymentMethodBankCard]) (*yandex.Payment, errors.Error) { response, err := client.Post[yandex.Payment, any](ctx, &client.RequestSettings{ - URL: yandexPaymentsURL, + URL: receiver.configuration.URL.Payments, Body: request, Headers: map[string]string{ "Content-Type": "application/json", @@ -119,7 +106,7 @@ func (receiver *YandexClient) CreatePaymentBankCard(ctx context.Context, idempot func (receiver *YandexClient) CreatePaymentLogin(ctx context.Context, idempotenceKey string, request *yandex.CreatePaymentRequest[yandex.PaymentMethodLogin]) (*yandex.Payment, errors.Error) { response, err := client.Post[yandex.Payment, any](ctx, &client.RequestSettings{ - URL: yandexPaymentsURL, + URL: receiver.configuration.URL.Payments, Body: request, Headers: map[string]string{ "Content-Type": "application/json", @@ -141,7 +128,7 @@ func (receiver *YandexClient) CreatePaymentLogin(ctx context.Context, idempotenc func (receiver *YandexClient) CreatePaymentPhone(ctx context.Context, idempotenceKey string, request *yandex.CreatePaymentRequest[yandex.PaymentMethodPhone]) (*yandex.Payment, errors.Error) { response, err := client.Post[yandex.Payment, any](ctx, &client.RequestSettings{ - URL: yandexPaymentsURL, + URL: receiver.configuration.URL.Payments, Body: request, Headers: map[string]string{ "Content-Type": "application/json", @@ -162,11 +149,11 @@ func (receiver *YandexClient) CreatePaymentPhone(ctx context.Context, idempotenc } func (receiver *YandexClient) DeleteWebhook(ctx context.Context, webhookID string) errors.Error { - url, err := url.JoinPath(yandexWebhooksURL, webhookID) + url, err := url.JoinPath(receiver.configuration.URL.Webhooks, webhookID) if err != nil { receiver.logger.Error("failed to join path url on of ", zap.Error(err), - zap.String("yandex webhook url", yandexWebhooksURL), + zap.String("yandex webhook url", receiver.configuration.URL.Webhooks), zap.String("webhookID", webhookID), ) @@ -200,7 +187,7 @@ func (receiver *YandexClient) DeleteWebhook(ctx context.Context, webhookID strin func (receiver *YandexClient) GetWebhookEvents(ctx context.Context) ([]yandex.Webhook, errors.Error) { response, err := client.Delete[[]yandex.Webhook, any](ctx, &client.RequestSettings{ - URL: yandexWebhooksURL, + URL: receiver.configuration.URL.Webhooks, Headers: map[string]string{ "Content-Type": "application/json", @@ -215,7 +202,7 @@ func (receiver *YandexClient) GetWebhookEvents(ctx context.Context) ([]yandex.We if response.Error != nil { receiver.logger.Error("failed to delete webhook on of ", zap.Any("response", response.Error), - zap.String("url", yandexWebhooksURL), + zap.String("url", receiver.configuration.URL.Webhooks), ) return []yandex.Webhook{}, errors.NewWithMessage("failed to create payment", errors.ErrInternalError) @@ -226,7 +213,7 @@ func (receiver *YandexClient) GetWebhookEvents(ctx context.Context) ([]yandex.We func (receiver *YandexClient) SetWebhookEvent(ctx context.Context, idempotenceKey string, request *yandex.CreateWebhookRequest) (string, errors.Error) { response, err := client.Post[yandex.Webhook, any](ctx, &client.RequestSettings{ - URL: yandexWebhooksURL, + URL: receiver.configuration.URL.Webhooks, Body: request, Headers: map[string]string{ "Content-Type": "application/json", @@ -243,7 +230,7 @@ func (receiver *YandexClient) SetWebhookEvent(ctx context.Context, idempotenceKe if response.Error != nil { receiver.logger.Error("failed to delete webhook on of ", zap.Any("response", response.Error), - zap.String("url", yandexWebhooksURL), + zap.String("url", receiver.configuration.URL.Webhooks), ) return "", errors.NewWithMessage("failed to create payment", errors.ErrInternalError) diff --git a/internal/interface/swagger/api.gen.go b/internal/interface/swagger/api.gen.go index 2dca525..6a1cbf7 100644 --- a/internal/interface/swagger/api.gen.go +++ b/internal/interface/swagger/api.gen.go @@ -18,6 +18,7 @@ import ( // ServerInterface represents all server handlers. type ServerInterface interface { + // (GET /available) GetAvailablePayments(ctx echo.Context) error @@ -123,32 +124,32 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xYzW7bRhB+FWLbQwIwlpw2DaBb0kMR9NDARlEUSWBsyJXD1CIZknIiGAL0kzRBHTRI", - "0EMPRYq0PReybNm0fuhXmH2FPkkxs0tJlLaOVSC3XBKJImdnvm++b4beY05QCwNf+EnMKnssdh6IGqeP", - "N2pB3U/wUxgFoYgST9B1px5Fwnca+NkVsRN5YeIFPqsw+EO2YCCfwYHswhD6MIAJTOQ+nFowhAyOLOhD", - "D0byZ9mR+xb0LfkUMtmCMfRkBwbWrc1vrnx+df06s5l4wmvhjmAVtvHtTWazpBHilziJPH+bNW22y3fq", - "wpDDO9mFMUbE+NCX+3AgW9DDTCDDTKYpwKBwznp5rVxePqlps0g8qnuRcFnljj7WnqFwb/pEcP+hcBLM", - "7TZv1ISfbCY8qRNmwq/X8OnH3Es8f3urGkRbDg+TeoSxQuG7eJbN4rrjCOEKF0/gviN2hDt3wqz6DeF4", - "oSdM/HDHQeK2PNeAzq9whKzIDqTyKaQwJOQz2bIQMjiEHpxAChMN399f8zjmJvi3eSIe88aqp8i27MIB", - "9GBIF7t0U++9qM/VVDjaBP6mSAr4b4hHdREbgBK7Gr/FDoIMDuQ+Zg8D24KMulcVABmMLdml3j7CLzCS", - "r2AgO3NYzVrKDxKv6jmcIhtA1ElX9tinkaiyCvukNJNjSWux9D33XfFE14SPqTjL6oMUzixK/icYwBCR", - "XbPgLyRAtiGl7PtTKQwsOEGi5XPSaQoD65/WL9Z8ztalYqn5jZdXqHKBSfrV1thPETDxWCx7uc2n9nQe", - "dtrEmignkpu7xU2cv0HngrF8ZcEZOpXsYN2yBceqlSFVP42UU8GxqWlt5gR+1YtqCgrTKXNuRL4o23ie", - "bCNr/+foGVxOJHjy/vLoqBM4ohwo8gmpEXVvLKkQaSnwWziDVLZVtAVDNfy4FF08Cb1IxO/J2rawA4sy", - "PMQLfRwjY8jgGDWILX0AA8JUoTXBm/CZsUayI19akMII0kW0j/Jf55A2ZbyqrRaIK5rqDCoeu9XrPP4C", - "/71WvYZfjWf7TlATW6u2fsiNSf8uW5AqD4Ah+oZOFTE1NsX9INgR3FchSZZbNZE8CFzjGO7IFq4BsiO7", - "2PLIE3KUYe8twnJpRqLiawSpfE7JvJRt7NJMdgpiWYpx2SSKaH5MngfWbJ7SU9W67/L7OyaXfUP6UelO", - "MBvdNdpdB3gBu6tP9/WxdCzqDDLrxu1bRjDVeajdFYmNpwvGefcXtxGcH3ocntsPiCvVklEpp6pBBlTP", - "j5BCaihlwetpWusc7dyw50kpuJbOS7drgYXl8dAkNVQDoyUdUF9oYjKt8Lb6Xw0/TZJsU0F95VLWFeUJ", - "I9mVz3M3UJuQfEbrK20vYxjLrj1nRrjR0I3KpFA61A00czvypW2po5QZZqg1dItTnVIKp4VotC5Po+EC", - "S0nl9mWTh+AhKu0UezFvOq2S6f5CypnpGjH2EmXO72alX6HN75Dc9nDJueRr7HS5L59ZtN6/0FvAKbPZ", - "rohiBfv6WnmtTDtNKHweeqzCPlsrr60Tn8kDatIS3+XeTq6rbWFqwqlJIEijfB4rSvJhOMVyiOQcaRF2", - "4UyniUsO9XKPLDlDwHSwIhi4T9CsvuWyCvtKJDfyBLVmYurDOAz8WG0dV8tlegcK/ES7Cg/DHb38lB7G", - "akgq5dGOmVv8HfbIe+whZJ5aqD3/h6Baxdb2ElGj4Et+ry/wKOIN1fNFtBab6iJAzCOAERO+HWN6IW+w", - "e3ih1KDFq6QtvqQEXJq+juAmFhgN5LdF7mSXGrNH+2YfhqQDvEBDob08B+4yeJtPa/kaJnfZEkubIils", - "hsrWvsyzUw4k4uRm4DZWouo8C/2vd4pmU3necocsQPMnUYXiwY3bgExvARmk/5ox1BuYyC5OV/16/UqN", - "xxeQwgEGWonV2QvnB6Z1HoDswrRuzr0Pf+R1BV71Hxo+uFjhGNL8ZQIGF+b1O53eR1bPZ1XtQB9arIVV", - "dUW5blCGH9V6EV5nV/aYz2s42vGX5r3mvwEAAP//ghXolIcVAAA=", + "H4sIAAAAAAAC/+xYzW7bRhB+FWLbQwIwlpw2DeBbUhRF0EMDG0VRJIaxoVYOU4tkSMqJEQjQT9IEddAg", + "QQ89FCnS9lxIsmXT+qFfYfYV+iTFzC4lUtrYVoHccmnlJbkz+33zfTObJ8zxa4HvCS+O2NoTFjn3RY3T", + "zxs1v+7F+CsI/UCEsSto3amHofCcPfxdEZETukHs+h5bY/CnbMJAPoOe7MAQ+jCACUzkPpxYMIQUDi3o", + "QxdG8hfZlvsW9C35FFLZhDF0ZRsG1q2Nb698fnX1OrOZeMxrwY5ga2z9u5vMZvFegH9Eceh626xhs12+", + "UxeGHN7JDoxxR9wf+nIferIJXcwEUsxkmgIMCnFWyyvl8mKkhs1C8bDuhqLC1u7osPYMhc3pF/69B8KJ", + "MbevdoVCTnj1Gn4V8L2a8OKVR9yNXW97q+qHWw4P4nqIe2VPo7rjCFERldyawz1H7NBSKKp1r5J7a9MA", + "y2313UbM43qUT+E9oYVXwS9tlg8+DWqKsC4cN3CFqTS442DNbLkVAzG/wSEWhGxDIp9CAkMiPZVNC9mC", + "A+jCMSQw0cz98w2PIm5ifpvH4hHfWzaKbMkO9KALQ1rs0EvdcwnPnakQ2sT7hogL+K+Lh3URGYASWYF8", + "GooqW2OflGYyLGkNllQVNewswDmv/8C9inis4+NnKr1FkUICpxak0JM/wwCGiMKKBX8jWLIFiWxCSiJR", + "ihlYcIykyOck5wQG1r/NXy3Pj92q63Dc1bokO6T2Q0hhDKPsxcsFfeW/OBd1emprnKYImDAvHnuxJKcu", + "dhZ22usaWPokjcoWjw3YvUGDg7F8ZcEpGpps47llE45U2UGiHo2UocGRqcBs5vhe1Q1rCgpTlJxpkX3K", + "FsaTLWTt/4SeweWEgsfnH49CHcMh5UA7H5NyUKPGIxV2Wtj4LZxCIltqtznfNTxc2F08DtxQROdkbVtY", + "gdRrlOYhhQNc6GO3GUMKRzCgRgM9GBCmCq0JvoTfjDWSbfnSggRGkMyjfZg9zSFtynhZCywQVzTAGVQ8", + "qlSv8+gL/O+16jX80xjbc/ya2Fq29ANuTPoP2YREeQAM0Td0qoipsSju+f6O4J7akmS5VRPxfb9i7NZt", + "2cRpQbZlB0seeUKOUqy9eVguzUhUfI0gkc8pmZeyhVWaynZBLAt7XDaJIsy3tLPAmvW+RtaP+b0dk8u+", + "If2odCeYja4a7a4DXMDq6tN7fTw6HuoUUuvG7VtGMFU81O6SxEbTYeCs94uTA/YP3brOrAfElc6S0lFO", + "VIEM6Dw/QQKJ4ShzXk+dVedoZ4adJ6XgWjovXa4FFhbbQ4PUUPWNltSjutDEpFrhLfV/1fw0SbJFB+or", + "l7KuKE8YyY58nrmBmlrkM5pyadIYw1h27JwZ4fRBLyqTQulQNVDPbcuXtqVCKTNMUWvoFic6pQROCrvR", + "VD3dDedcSiqzL5s8BIOotBOsxazotEpwBNgnM3pV0DVi7MbKnN/Njn6FprQDctuDBeeSr7HS5b58ZtEt", + "4IWeAk6YzXZFGCnYV1fKK2WaaQLh8cBla+yzlfLKKvEZ36ciLfFd7u5kutoWpiKcmgSCNMr6saIka4ZT", + "LIdIzqEWYQdOdZo45FAtd8mSUwRMb1YEA+cJ6tW3KmyNfS3iG1mCWjMR1WEU+F6kpo6r5TJdlXwv1q7C", + "g2BHDz+lB5Fqkkp5NA9mFn+HPXQfuQiZq4Zf1/vRr1axtN1Y1GjzBb/XCzwM+Z6q+SJa80V1ESDyCOCO", + "Md+O9E2GbeJCaY8Gr5K2+JIScGl6dcBJzDcayO/z3MkOFWaX5s0+DEkHuEBNobXYB+4yeJt1a/kaJnfZ", + "AksbIi5MhsrWvszfpmg2v+lX9pai6iwLfd/832goz1uskDlo/iKqUDw4cRuQ6c4hg/RfM271Biayg91V", + "38Jfqfb4AhLo4UZLsTq7HH5gWvMApBemdSN3d/3I6xK86n8U+OBihSNIsssEDC7M6/c6vY+sns2qmoE+", + "tFgLo+qScl2nDD+q9SK8zlaeMI/XsLXjk8Zm478AAAD//0NwNQGuFQAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/interface/swagger/api.go b/internal/interface/swagger/api.go index 060e9fb..b673e32 100644 --- a/internal/interface/swagger/api.go +++ b/internal/interface/swagger/api.go @@ -5,8 +5,8 @@ import ( "penahub.gitlab.yandexcloud.net/external/treasurer/internal/errors" ) -//go:generate oapi-codegen --config api.yaml ../../openapi.yaml -//go:generate oapi-codegen --config models.yaml ../../openapi.yaml +//go:generate oapi-codegen --config api.yaml ../../../openapi.yaml +//go:generate oapi-codegen --config models.yaml ../../../openapi.yaml type commonController interface { // (GET /available) diff --git a/internal/interface/swagger/models.gen.go b/internal/interface/swagger/models.gen.go index 1c3615b..dbc6065 100644 --- a/internal/interface/swagger/models.gen.go +++ b/internal/interface/swagger/models.gen.go @@ -3,6 +3,14 @@ // Code generated by github.com/deepmap/oapi-codegen version v1.12.4 DO NOT EDIT. package swagger +// Defines values for Event. +const ( + PaymentCanceled Event = "payment.canceled" + PaymentSucceeded Event = "payment.succeeded" + PaymentWaitingForCapture Event = "payment.waiting_for_capture" + RefundSucceeded Event = "refund.succeeded" +) + // Defines values for PaymentStatus. const ( Canceled PaymentStatus = "canceled" @@ -20,6 +28,9 @@ type Amount struct { Value string `json:"value"` } +// Event defines model for Event. +type Event string + // PaymentStatus defines model for PaymentStatus. type PaymentStatus string @@ -34,8 +45,7 @@ type Recipient struct { // SetPaymentStatusRequest defines model for SetPaymentStatusRequest. type SetPaymentStatusRequest struct { - // Event Событие, о котором уведомляет ЮKassa - Event string `json:"event"` + Event Event `json:"event"` Object YandexPayment `json:"object"` // Type Тип объекта. Фиксированное значение — notification (уведомление) diff --git a/internal/models/config.go b/internal/models/config.go index 3396fe4..ce7cf86 100644 --- a/internal/models/config.go +++ b/internal/models/config.go @@ -24,9 +24,22 @@ type ConfigurationGRPC struct { type ServiceConfiguration struct { YoomomeyConfiguration YoomomeyConfiguration + MockConfiguration MockConfiguration +} + +type MockConfiguration struct { + IsMock bool `env:"IS_MOCK,default=false"` + Host string `env:"MOCK_SERVICE_HOST,default=http://localhost:8080"` + YoomomeyURL YoomomeyURL } type YoomomeyConfiguration struct { StoreID string `env:"YOOMONEY_STORE_ID,required"` SecretKey string `env:"YOOMONEY_SECRET_KEY,required"` + URL YoomomeyURL +} + +type YoomomeyURL struct { + Webhooks string `env:"YOOMONEY_WEBHOOKS_URL,required"` + Payments string `env:"YOOMONEY_PAYMENTS_URL,required"` } diff --git a/internal/service/mock/mock.go b/internal/service/mock/mock.go new file mode 100644 index 0000000..589a048 --- /dev/null +++ b/internal/service/mock/mock.go @@ -0,0 +1,60 @@ +package mock + +import ( + "go.uber.org/zap" + "penahub.gitlab.yandexcloud.net/external/treasurer/internal/errors" + "penahub.gitlab.yandexcloud.net/external/treasurer/pkg/validate" +) + +type MockStrategyService interface { + Register(host string) errors.Error +} + +type Deps struct { + Logger *zap.Logger + MockServiceHost string + Enabled bool + YoomoneyMockService MockStrategyService +} + +type Service struct { + logger *zap.Logger + mockServiceHost string + enabled bool + yoomoneyMockService MockStrategyService +} + +func New(deps Deps) (*Service, errors.Error) { + if deps.Logger == nil { + return nil, errors.NewWithMessage("logger is nil on ", errors.ErrInvalidArgs) + } + + if deps.YoomoneyMockService == nil { + return nil, errors.NewWithMessage("YoomoneyMockService is nil on ", errors.ErrInvalidArgs) + } + + if validate.IsStringEmpty(deps.MockServiceHost) { + return nil, errors.NewWithMessage("mock service host is empty on ", errors.ErrInvalidArgs) + } + + return &Service{ + logger: deps.Logger, + mockServiceHost: deps.MockServiceHost, + yoomoneyMockService: deps.YoomoneyMockService, + enabled: deps.Enabled, + }, nil +} + +func (receiver *Service) Register() bool { + if !receiver.enabled { + receiver.logger.Info("register mocks is disabled") + return false + } + + if err := receiver.yoomoneyMockService.Register(receiver.mockServiceHost); err != nil { + receiver.logger.Error("failed to register yoomoney mocks", zap.Error(err), zap.String("host", receiver.mockServiceHost)) + return false + } + + return true +} diff --git a/internal/service/mock/yoomoney.go b/internal/service/mock/yoomoney.go new file mode 100644 index 0000000..1571c4d --- /dev/null +++ b/internal/service/mock/yoomoney.go @@ -0,0 +1,96 @@ +package mock + +import ( + "fmt" + "net/url" + + "github.com/walkerus/go-wiremock" + "go.uber.org/zap" + "penahub.gitlab.yandexcloud.net/external/treasurer/internal/errors" + "penahub.gitlab.yandexcloud.net/external/treasurer/internal/models" + "penahub.gitlab.yandexcloud.net/external/treasurer/internal/models/yandex" +) + +type YoomoneyMockServiceDeps struct { + Logger *zap.Logger + YoomoneyURL *models.YoomomeyURL +} + +type YoomoneyMockService struct { + logger *zap.Logger + yoomoneyURL *models.YoomomeyURL +} + +func NewYoomoneyMockService(deps YoomoneyMockServiceDeps) (*YoomoneyMockService, errors.Error) { + if deps.Logger == nil { + return nil, errors.NewWithMessage("logger is nil on ", errors.ErrInvalidArgs) + } + + if deps.YoomoneyURL == nil { + return nil, errors.NewWithMessage("yoomoney urls is nil on ", errors.ErrInvalidArgs) + } + + return &YoomoneyMockService{ + logger: deps.Logger, + yoomoneyURL: deps.YoomoneyURL, + }, nil +} + +func (receiver *YoomoneyMockService) Register(host string) errors.Error { + return receiver.RegisterPayments(host) +} + +func (receiver *YoomoneyMockService) RegisterPayments(host string) errors.Error { + url, parseErr := url.Parse(receiver.yoomoneyURL.Payments) + if parseErr != nil { + receiver.logger.Error("failed to parse payment url on of ", + zap.Error(parseErr), + zap.String("url", receiver.yoomoneyURL.Payments), + ) + + return errors.NewWithError(fmt.Errorf("failed to parse payment url: %w", parseErr), errors.ErrInternalError) + } + + if url.Host != host { + return errors.NewWithMessage("yoomoney hosts not equal", errors.ErrInternalError) + } + + mockClient := wiremock.NewClient(host) + + rule := wiremock. + Post(wiremock.URLPathEqualTo(url.Path)). + WillReturnJSON( + &yandex.Payment{ + ID: "f32d71f4-214b-464b-94bb-f7b4d5299af0", + Status: yandex.PaymentStatusPending, + Amount: yandex.Amount{ + Value: "150.00", + Currency: "RUB", + }, + Confirmation: &yandex.ConfirmationRedirect{ + Type: yandex.ConfirmationTypeRedirect, + ConfirmationURL: "https://yandex-redirect-url.com", + }, + }, + map[string]string{"Content-Type": "application/json"}, + 200, + ). + AtPriority(1) + + if err := mockClient.StubFor(rule); err != nil { + receiver.logger.Error("failed to init payment mock handler on of ", zap.Error(err)) + return errors.NewWithError(fmt.Errorf("failed to init payment mock handler: %w", err), errors.ErrInternalError) + } + + isVerified, err := mockClient.Verify(wiremock.NewRequest("post", wiremock.URLPathEqualTo(url.Path)), 0) + if err != nil { + receiver.logger.Error("failed to verify payment mock handler on of ", zap.Error(err)) + return errors.NewWithError(fmt.Errorf("failed to verify payment mock handler: %w", err), errors.ErrInternalError) + } + + if !isVerified { + return errors.NewWithMessage("payment mock handler not verified", errors.ErrInternalError) + } + + return nil +} diff --git a/internal/swagger/api.gen.go b/internal/swagger/api.gen.go deleted file mode 100644 index 2dca525..0000000 --- a/internal/swagger/api.gen.go +++ /dev/null @@ -1,224 +0,0 @@ -// Package swagger provides primitives to interact with the openapi HTTP API. -// -// Code generated by github.com/deepmap/oapi-codegen version v1.12.4 DO NOT EDIT. -package swagger - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - "fmt" - "net/url" - "path" - "strings" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/labstack/echo/v4" -) - -// ServerInterface represents all server handlers. -type ServerInterface interface { - // (GET /available) - GetAvailablePayments(ctx echo.Context) error - - // (POST /yandex/payment/status/canceled) - SetYandexPaymentStatusCanceled(ctx echo.Context) error - - // (POST /yandex/payment/status/succeeded) - SetYandexPaymentStatusSucceeded(ctx echo.Context) error - - // (POST /yandex/payment/status/waiting) - SetYandexPaymentStatusWaiting(ctx echo.Context) error - - // (POST /yandex/refund/status/succeeded) - SetYandexRefundStatusSucceeded(ctx echo.Context) error -} - -// ServerInterfaceWrapper converts echo contexts to parameters. -type ServerInterfaceWrapper struct { - Handler ServerInterface -} - -// GetAvailablePayments converts echo context to params. -func (w *ServerInterfaceWrapper) GetAvailablePayments(ctx echo.Context) error { - var err error - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.GetAvailablePayments(ctx) - return err -} - -// SetYandexPaymentStatusCanceled converts echo context to params. -func (w *ServerInterfaceWrapper) SetYandexPaymentStatusCanceled(ctx echo.Context) error { - var err error - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.SetYandexPaymentStatusCanceled(ctx) - return err -} - -// SetYandexPaymentStatusSucceeded converts echo context to params. -func (w *ServerInterfaceWrapper) SetYandexPaymentStatusSucceeded(ctx echo.Context) error { - var err error - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.SetYandexPaymentStatusSucceeded(ctx) - return err -} - -// SetYandexPaymentStatusWaiting converts echo context to params. -func (w *ServerInterfaceWrapper) SetYandexPaymentStatusWaiting(ctx echo.Context) error { - var err error - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.SetYandexPaymentStatusWaiting(ctx) - return err -} - -// SetYandexRefundStatusSucceeded converts echo context to params. -func (w *ServerInterfaceWrapper) SetYandexRefundStatusSucceeded(ctx echo.Context) error { - var err error - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.SetYandexRefundStatusSucceeded(ctx) - return err -} - -// This is a simple interface which specifies echo.Route addition functions which -// are present on both echo.Echo and echo.Group, since we want to allow using -// either of them for path registration -type EchoRouter interface { - CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route -} - -// RegisterHandlers adds each server route to the EchoRouter. -func RegisterHandlers(router EchoRouter, si ServerInterface) { - RegisterHandlersWithBaseURL(router, si, "") -} - -// Registers handlers, and prepends BaseURL to the paths, so that the paths -// can be served under a prefix. -func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { - - wrapper := ServerInterfaceWrapper{ - Handler: si, - } - - router.GET(baseURL+"/available", wrapper.GetAvailablePayments) - router.POST(baseURL+"/yandex/payment/status/canceled", wrapper.SetYandexPaymentStatusCanceled) - router.POST(baseURL+"/yandex/payment/status/succeeded", wrapper.SetYandexPaymentStatusSucceeded) - router.POST(baseURL+"/yandex/payment/status/waiting", wrapper.SetYandexPaymentStatusWaiting) - router.POST(baseURL+"/yandex/refund/status/succeeded", wrapper.SetYandexRefundStatusSucceeded) - -} - -// Base64 encoded, gzipped, json marshaled Swagger object -var swaggerSpec = []string{ - - "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", -} - -// GetSwagger returns the content of the embedded swagger specification file -// or error if failed to decode -func decodeSpec() ([]byte, error) { - zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) - if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) - } - zr, err := gzip.NewReader(bytes.NewReader(zipped)) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) - } - var buf bytes.Buffer - _, err = buf.ReadFrom(zr) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) - } - - return buf.Bytes(), nil -} - -var rawSpec = decodeSpecCached() - -// a naive cached of a decoded swagger spec -func decodeSpecCached() func() ([]byte, error) { - data, err := decodeSpec() - return func() ([]byte, error) { - return data, err - } -} - -// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. -func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) - if len(pathToFile) > 0 { - res[pathToFile] = rawSpec - } - - return res -} - -// GetSwagger returns the Swagger specification corresponding to the generated code -// in this file. The external references of Swagger specification are resolved. -// The logic of resolving external references is tightly connected to "import-mapping" feature. -// Externally referenced files must be embedded in the corresponding golang packages. -// Urls can be supported but this task was out of the scope. -func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") - - loader := openapi3.NewLoader() - loader.IsExternalRefsAllowed = true - loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() - pathToFile = path.Clean(pathToFile) - getSpec, ok := resolvePath[pathToFile] - if !ok { - err1 := fmt.Errorf("path not found: %s", pathToFile) - return nil, err1 - } - return getSpec() - } - var specData []byte - specData, err = rawSpec() - if err != nil { - return - } - swagger, err = loader.LoadFromData(specData) - if err != nil { - return - } - return -} diff --git a/internal/swagger/api.go b/internal/swagger/api.go deleted file mode 100644 index 060e9fb..0000000 --- a/internal/swagger/api.go +++ /dev/null @@ -1,77 +0,0 @@ -package swagger - -import ( - "github.com/labstack/echo/v4" - "penahub.gitlab.yandexcloud.net/external/treasurer/internal/errors" -) - -//go:generate oapi-codegen --config api.yaml ../../openapi.yaml -//go:generate oapi-codegen --config models.yaml ../../openapi.yaml - -type commonController interface { - // (GET /available) - GetAvailablePayments(ctx echo.Context) error -} - -type yandexStatusController interface { - // (POST /yandex/payment/status/canceled) - SetPaymentStatusCanceled(ctx echo.Context) error - - // (POST /yandex/payment/status/succeeded) - SetPaymentStatusSucceeded(ctx echo.Context) error - - // (POST /yandex/payment/status/waiting) - SetPaymentStatusWaiting(ctx echo.Context) error - - // (POST /yandex/refund/status/succeeded) - SetRefundStatusSucceeded(ctx echo.Context) error -} - -type Deps struct { - CommonController commonController - YandexStatusController yandexStatusController -} - -type API struct { - commonController commonController - yandexStatusController yandexStatusController -} - -func New(deps Deps) (*API, errors.Error) { - if deps.CommonController == nil { - return nil, errors.NewWithMessage("CommonController in nil on ", errors.ErrInvalidArgs) - } - - if deps.YandexStatusController == nil { - return nil, errors.NewWithMessage("YandexStatusController in nil on ", errors.ErrInvalidArgs) - } - - return &API{ - commonController: deps.CommonController, - yandexStatusController: deps.YandexStatusController, - }, nil -} - -// Common - -func (receiver *API) GetAvailablePayments(ctx echo.Context) error { - return receiver.commonController.GetAvailablePayments(ctx) -} - -// Status (Yandex) - -func (receiver *API) SetYandexPaymentStatusCanceled(ctx echo.Context) error { - return receiver.yandexStatusController.SetPaymentStatusCanceled(ctx) -} - -func (receiver *API) SetYandexPaymentStatusSucceeded(ctx echo.Context) error { - return receiver.yandexStatusController.SetPaymentStatusSucceeded(ctx) -} - -func (receiver *API) SetYandexPaymentStatusWaiting(ctx echo.Context) error { - return receiver.yandexStatusController.SetPaymentStatusWaiting(ctx) -} - -func (receiver *API) SetYandexRefundStatusSucceeded(ctx echo.Context) error { - return receiver.yandexStatusController.SetRefundStatusSucceeded(ctx) -} diff --git a/internal/swagger/api.yaml b/internal/swagger/api.yaml deleted file mode 100644 index a2634dd..0000000 --- a/internal/swagger/api.yaml +++ /dev/null @@ -1,6 +0,0 @@ -output: api.gen.go -package: swagger -generate: - echo-server: true - models: false - embedded-spec: true diff --git a/internal/swagger/models.gen.go b/internal/swagger/models.gen.go deleted file mode 100644 index 1c3615b..0000000 --- a/internal/swagger/models.gen.go +++ /dev/null @@ -1,94 +0,0 @@ -// Package swagger provides primitives to interact with the openapi HTTP API. -// -// Code generated by github.com/deepmap/oapi-codegen version v1.12.4 DO NOT EDIT. -package swagger - -// Defines values for PaymentStatus. -const ( - Canceled PaymentStatus = "canceled" - Pending PaymentStatus = "pending" - Succeeded PaymentStatus = "succeeded" - WaitingForCapture PaymentStatus = "waiting_for_capture" -) - -// Amount defines model for Amount. -type Amount struct { - // Currency Трехбуквенный код валюты в формате ISO-4217 - Currency string `json:"currency"` - - // Value Сумма в выбранной валюте - Value string `json:"value"` -} - -// PaymentStatus defines model for PaymentStatus. -type PaymentStatus string - -// Recipient defines model for Recipient. -type Recipient struct { - // AccountId Идентификатор магазина в ЮKassa - AccountId string `json:"account_id"` - - // GatewayId Идентификатор субаккаунта - GatewayId string `json:"gateway_id"` -} - -// SetPaymentStatusRequest defines model for SetPaymentStatusRequest. -type SetPaymentStatusRequest struct { - // Event Событие, о котором уведомляет ЮKassa - Event string `json:"event"` - Object YandexPayment `json:"object"` - - // Type Тип объекта. Фиксированное значение — notification (уведомление) - Type string `json:"type"` -} - -// YandexPayment defines model for YandexPayment. -type YandexPayment struct { - Amount Amount `json:"amount"` - - // CapturedAt Время подтверждения платежа - CapturedAt *string `json:"captured_at,omitempty"` - - // Confirmation Выбранный способ подтверждения платежа - Confirmation *map[string]interface{} `json:"confirmation,omitempty"` - - // CreatedAt Время создания заказа - CreatedAt string `json:"created_at"` - - // Description Описание - Description *string `json:"description,omitempty"` - - // ExpiresAt Время, до которого вы можете бесплатно отменить или подтвердить платеж - ExpiresAt *string `json:"expires_at,omitempty"` - - // Id Идентификатор платежа в ЮKassa - Id string `json:"id"` - IncomeAmount *Amount `json:"income_amount,omitempty"` - - // Paid Признак оплаты заказа - Paid bool `json:"paid"` - - // PaymentMethod Структура метода платежа (может отличаться от способа платежа) - PaymentMethod *map[string]interface{} `json:"payment_method,omitempty"` - Recipient Recipient `json:"recipient"` - - // Refundable Возможность провести возврат по API - Refundable bool `json:"refundable"` - RefundedAmount *Amount `json:"refunded_amount,omitempty"` - Status PaymentStatus `json:"status"` - - // Test Признак тестовой операции - Test bool `json:"test"` -} - -// SetYandexPaymentStatusCanceledJSONRequestBody defines body for SetYandexPaymentStatusCanceled for application/json ContentType. -type SetYandexPaymentStatusCanceledJSONRequestBody = SetPaymentStatusRequest - -// SetYandexPaymentStatusSucceededJSONRequestBody defines body for SetYandexPaymentStatusSucceeded for application/json ContentType. -type SetYandexPaymentStatusSucceededJSONRequestBody = SetPaymentStatusRequest - -// SetYandexPaymentStatusWaitingJSONRequestBody defines body for SetYandexPaymentStatusWaiting for application/json ContentType. -type SetYandexPaymentStatusWaitingJSONRequestBody = SetPaymentStatusRequest - -// SetYandexRefundStatusSucceededJSONRequestBody defines body for SetYandexRefundStatusSucceeded for application/json ContentType. -type SetYandexRefundStatusSucceededJSONRequestBody = SetPaymentStatusRequest diff --git a/internal/swagger/models.yaml b/internal/swagger/models.yaml deleted file mode 100644 index 9ef06be..0000000 --- a/internal/swagger/models.yaml +++ /dev/null @@ -1,4 +0,0 @@ -output: models.gen.go -package: swagger -generate: - models: true diff --git a/internal/utils/echotools/bind.go b/internal/utils/echotools/bind.go deleted file mode 100644 index 50bfef9..0000000 --- a/internal/utils/echotools/bind.go +++ /dev/null @@ -1,13 +0,0 @@ -package echotools - -import "github.com/labstack/echo/v4" - -func Bind[T any](ctx echo.Context) (*T, error) { - item := new(T) - - if err := ctx.Bind(item); err != nil { - return nil, err - } - - return item, nil -} diff --git a/internal/worker/mock/mock.go b/internal/worker/mock/mock.go new file mode 100644 index 0000000..54309c4 --- /dev/null +++ b/internal/worker/mock/mock.go @@ -0,0 +1,60 @@ +package mock + +import ( + "context" + "time" + + "go.uber.org/zap" + "penahub.gitlab.yandexcloud.net/external/treasurer/internal/errors" +) + +type mockService interface { + Register() bool +} + +type WorkerDeps struct { + Logger *zap.Logger + MockService mockService +} + +type Worker struct { + logger *zap.Logger + mockService mockService +} + +func New(deps WorkerDeps) (*Worker, errors.Error) { + if deps.Logger == nil { + return nil, errors.NewWithMessage("logger is nil on ", errors.ErrInvalidArgs) + } + + if deps.MockService == nil { + return nil, errors.NewWithMessage("MockService urls is nil on ", errors.ErrInvalidArgs) + } + + return &Worker{ + logger: deps.Logger, + mockService: deps.MockService, + }, nil +} + +func (receiver *Worker) Run(ctx context.Context) { + after := time.After(10 * time.Second) + + select { + case <-ctx.Done(): + receiver.logger.Info("register mocks operations canceled ") + return + case <-after: + receiver.logger.Info("register yoomoney mocks ") + + isSuccess := receiver.mockService.Register() + + if isSuccess { + receiver.logger.Info("register yoomoney mocks complete ") + } + + if !isSuccess { + receiver.logger.Info("register yoomoney mocks failure ") + } + } +} diff --git a/internal/worker/run.go b/internal/worker/run.go new file mode 100644 index 0000000..e1f7f0e --- /dev/null +++ b/internal/worker/run.go @@ -0,0 +1,11 @@ +package worker + +import ( + "context" + + "penahub.gitlab.yandexcloud.net/external/treasurer/internal/initialize" +) + +func Run(ctx context.Context, workers *initialize.Workers) { + go workers.Mock.Run(ctx) +} diff --git a/migrations/test/001_amocrm_insert.down.json b/migrations/test/001_amocrm_insert.down.json deleted file mode 100644 index 7c39d90..0000000 --- a/migrations/test/001_amocrm_insert.down.json +++ /dev/null @@ -1 +0,0 @@ -[{ "delete": "amocrm", "deletes": [{ "q": {} }] }] diff --git a/migrations/test/001_amocrm_insert.up.json b/migrations/test/001_amocrm_insert.up.json deleted file mode 100644 index cd07e84..0000000 --- a/migrations/test/001_amocrm_insert.up.json +++ /dev/null @@ -1,85 +0,0 @@ -[ - { - "insert": "amocrm", - "ordered": true, - "documents": [ - { - "amocrmId": "1", - "userId": "1", - "information": { - "id": 30228997, - "name": "ООО ПЕНА", - "subdomain": "penadigital", - "created_at": 1683680509, - "created_by": 0, - "updated_at": 1683680509, - "updated_by": 0, - "current_user_id": 8110726, - "country": "RU", - "customers_mode": "disabled", - "is_unsorted_on": true, - "is_loss_reason_enabled": true, - "is_helpbot_enabled": false, - "is_technical_account": true, - "contact_name_display_order": 1, - "amojo_id": "", - "uuid": "", - "version": 0, - "_links": { "self": { "href": "https://penadigital.amocrm.ru/api/v4/account" } }, - "_embedded": { - "amojo_rights": { "can_direct": false, "can_create_groups": false }, - "users_groups": null, - "task_types": null, - "entity_names": { - "leads": { - "ru": { - "gender": "", - "plural_form": { - "dative": "", - "default": "", - "genitive": "", - "accusative": "", - "instrumental": "", - "prepositional": "" - }, - "singular_form": { - "dative": "", - "default": "", - "genitive": "", - "accusative": "", - "instrumental": "", - "prepositional": "" - } - }, - "en": { - "singular_form": { "default": "" }, - "plural_form": { "default": "" }, - "gender": "" - }, - "es": { - "singular_form": { "default": "" }, - "plural_form": { "default": "" }, - "gender": "" - } - } - }, - "datetime_settings": { - "date_pattern": "", - "short_date_pattern": "", - "short_time_pattern": "", - "date_formant": "", - "time_format": "", - "timezone": "", - "timezone_offset": "" - } - } - }, - "audit": { - "createdAt": "2022-12-31T00:00:00.000Z", - "updatedAt": "2022-12-31T00:00:00.000Z", - "deleted": false - } - } - ] - } -] diff --git a/pkg/validate/string.go b/pkg/validate/string.go new file mode 100644 index 0000000..ad98ee4 --- /dev/null +++ b/pkg/validate/string.go @@ -0,0 +1,7 @@ +package validate + +import "strings" + +func IsStringEmpty(text string) bool { + return strings.TrimSpace(text) == "" +} diff --git a/pkg/validate/string_test.go b/pkg/validate/string_test.go new file mode 100644 index 0000000..b2f372e --- /dev/null +++ b/pkg/validate/string_test.go @@ -0,0 +1,16 @@ +package validate_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "penahub.gitlab.yandexcloud.net/external/treasurer/pkg/validate" +) + +func TestIsStringEmpty(t *testing.T) { + assert.True(t, validate.IsStringEmpty("")) + assert.True(t, validate.IsStringEmpty(" ")) + assert.True(t, validate.IsStringEmpty(" ")) + assert.False(t, validate.IsStringEmpty("tett")) + assert.False(t, validate.IsStringEmpty(" t ")) +} diff --git a/pkg/validate/url.go b/pkg/validate/url.go new file mode 100644 index 0000000..04fb19a --- /dev/null +++ b/pkg/validate/url.go @@ -0,0 +1,28 @@ +package validate + +import ( + "net" + "net/url" + "regexp" + "strings" +) + +func URL(text string) bool { + url, err := url.Parse(text) + if err != nil { + return false + } + + address := net.ParseIP(url.Host) + + if address == nil { + regex := regexp.MustCompile(`\b\w+:\d+\b`) + return strings.Contains(url.Host, ".") || regex.MatchString(url.Host) + } + + if IsStringEmpty(url.Scheme) || IsStringEmpty(url.Host) { + return false + } + + return true +} diff --git a/pkg/validate/url_test.go b/pkg/validate/url_test.go new file mode 100644 index 0000000..9170548 --- /dev/null +++ b/pkg/validate/url_test.go @@ -0,0 +1,25 @@ +package validate_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "penahub.gitlab.yandexcloud.net/external/treasurer/pkg/validate" +) + +func TestValidateURL(t *testing.T) { + assert.True(t, validate.URL("http://google.com")) + assert.True(t, validate.URL("http://w.com/cn")) + assert.True(t, validate.URL("http://192.158.0.1:90")) + assert.False(t, validate.URL("192.158.0.1:9090")) + assert.False(t, validate.URL("http://w")) + assert.False(t, validate.URL("fsw")) + assert.True(t, validate.URL("http://192.158.1/1")) + assert.True(t, validate.URL("https://www.googleapis.com/oauth2/v3")) + assert.True(t, validate.URL("http://localhost:8080/google/callback")) + assert.True(t, validate.URL("https://oauth.pena.digital/amocrm/callback")) + assert.True(t, validate.URL("https://www.amocrm.ru/oauth")) + assert.True(t, validate.URL("https://www.amocrm.ru/oauth/access_token")) + assert.True(t, validate.URL("http://localhost:8080/vk/callback")) + assert.True(t, validate.URL("http://mock:8000/api/v4/account")) +}