diff --git a/.woodpecker/bff.yml b/.woodpecker/bff.yml index 25c29c7a..63cef64a 100644 --- a/.woodpecker/bff.yml +++ b/.woodpecker/bff.yml @@ -12,6 +12,9 @@ when: path: include: - api/server/** + - api/payments/methods/client/** + - api/payments/methods/go.mod + - api/payments/methods/go.sum - api/proto/** - api/pkg/** ignore_message: '[rebuild]' diff --git a/.woodpecker/payments_methods.yml b/.woodpecker/payments_methods.yml new file mode 100644 index 00000000..7c237649 --- /dev/null +++ b/.woodpecker/payments_methods.yml @@ -0,0 +1,79 @@ +matrix: + include: + - PAYMENTS_METHODS_IMAGE_PATH: payments/methods + PAYMENTS_METHODS_DOCKERFILE: ci/prod/compose/payments_methods.dockerfile + PAYMENTS_METHODS_MONGO_SECRET_PATH: sendico/db + PAYMENTS_METHODS_ENV: prod + +when: + - event: push + branch: main + path: + include: + - api/payments/methods/** + - api/proto/** + - api/pkg/** + ignore_message: '[rebuild]' + +steps: + - name: version + image: alpine:latest + commands: + - set -euo pipefail 2>/dev/null || set -eu + - apk add --no-cache git + - GIT_REV="$(git rev-parse --short HEAD)" + - BUILD_BRANCH="$(git rev-parse --abbrev-ref HEAD)" + - APP_V="$(cat version)" + - BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + - BUILD_USER="${WOODPECKER_MACHINE:-woodpecker}" + - printf "GIT_REV=%s\nBUILD_BRANCH=%s\nAPP_V=%s\nBUILD_DATE=%s\nBUILD_USER=%s\n" \ + "$GIT_REV" "$BUILD_BRANCH" "$APP_V" "$BUILD_DATE" "$BUILD_USER" | tee .env.version + + - name: proto + image: golang:alpine + depends_on: [ version ] + commands: + - set -eu + - apk add --no-cache bash git build-base protoc protobuf-dev + - go install google.golang.org/protobuf/cmd/protoc-gen-go@latest + - go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + - export PATH="$(go env GOPATH)/bin:$PATH" + - bash ci/scripts/proto/generate.sh + + - name: secrets + image: alpine:latest + depends_on: [ version ] + environment: + VAULT_ADDR: { from_secret: VAULT_ADDR } + VAULT_ROLE_ID: { from_secret: VAULT_APP_ROLE } + VAULT_SECRET_ID: { from_secret: VAULT_SECRET_ID } + commands: + - set -euo pipefail + - apk add --no-cache bash coreutils openssh-keygen curl sed python3 + - mkdir -p secrets + - ./ci/vlt kv_to_file kv ops/deploy/ssh_key private_b64 secrets/SSH_KEY.b64 600 + - base64 -d secrets/SSH_KEY.b64 > secrets/SSH_KEY + - chmod 600 secrets/SSH_KEY + - ssh-keygen -y -f secrets/SSH_KEY >/dev/null + - ./ci/vlt kv_get kv registry user > secrets/REGISTRY_USER + - ./ci/vlt kv_get kv registry password > secrets/REGISTRY_PASSWORD + + - name: build-image + image: gcr.io/kaniko-project/executor:debug + depends_on: [ proto, secrets ] + commands: + - sh ci/scripts/payments_methods/build-image.sh + + - name: deploy + image: alpine:latest + depends_on: [ secrets, build-image ] + environment: + VAULT_ADDR: { from_secret: VAULT_ADDR } + VAULT_ROLE_ID: { from_secret: VAULT_APP_ROLE } + VAULT_SECRET_ID: { from_secret: VAULT_SECRET_ID } + commands: + - set -euo pipefail + - apk add --no-cache bash openssh-client rsync coreutils curl sed python3 + - mkdir -p /root/.ssh + - install -m 600 secrets/SSH_KEY /root/.ssh/id_rsa + - sh ci/scripts/payments_methods/deploy.sh diff --git a/Makefile b/Makefile index 6ec216d7..8c5a17ab 100644 --- a/Makefile +++ b/Makefile @@ -218,6 +218,8 @@ services-up: dev-billing-documents \ dev-ledger \ dev-payments-orchestrator \ + dev-payments-quotation \ + dev-payments-methods \ dev-chain-gateway \ dev-tron-gateway \ dev-mntx-gateway \ @@ -245,6 +247,8 @@ list-services: @echo " - dev-billing-documents :50061, :9409 (Billing Documents)" @echo " - dev-ledger :50052, :9401 (Double-Entry Ledger)" @echo " - dev-payments-orchestrator :50062, :9403 (Payment Orchestration)" + @echo " - dev-payments-quotation :50064, :9414 (Payment Quotation)" + @echo " - dev-payments-methods :50066, :9416 (Payment Methods)" @echo " - dev-chain-gateway :50070, :9404 (EVM Blockchain Gateway)" @echo " - dev-tron-gateway :50071, :9408 (TRON Blockchain Gateway)" @echo " - dev-mntx-gateway :50075, :9405, :8084 (Card Payouts)" @@ -273,7 +277,7 @@ build-fx: build-payments: @echo "$(GREEN)Building payment services...$(NC)" - @$(COMPOSE) build dev-payments-orchestrator + @$(COMPOSE) build dev-payments-orchestrator dev-payments-quotation dev-payments-methods build-gateways: @echo "$(GREEN)Building gateway services...$(NC)" diff --git a/README.md b/README.md index 6b2175fc..31d33598 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Financial services platform providing payment orchestration, ledger accounting, | Ledger | `api/ledger/` | Double-entry accounting | | Orchestrator | `api/payments/orchestrator/` | Payment orchestration | | Quotation | `api/payments/quotation/` | Payment quotation | +| Payment Methods | `api/payments/methods/` | Payment methods | | Billing Fees | `api/billing/fees/` | Fee calculation | | Billing Documents | `api/billing/documents/` | Billing documents | | FX Oracle | `api/fx/oracle/` | FX quote provider | @@ -24,7 +25,7 @@ Financial services platform providing payment orchestration, ledger accounting, | Gateway Chain | `api/gateway/chain/` | EVM blockchain gateway | | Gateway TRON | `api/gateway/tron/` | TRON blockchain gateway | | Gateway MNTX | `api/gateway/mntx/` | Card payouts | -| Gateway TGSettle | `api/gateway/tgsettle/` | Settlements | +| Gateway TGSettle | `api/gateway/tgsettle/` | Telegram settlements with MNTX | | Notification | `api/notification/` | Notifications | | BFF | `api/server/` | Backend for frontend | | Frontend | `frontend/pweb/` | Flutter web UI | diff --git a/api/billing/documents/go.mod b/api/billing/documents/go.mod index 347d3098..23467825 100644 --- a/api/billing/documents/go.mod +++ b/api/billing/documents/go.mod @@ -15,7 +15,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/billing/documents/go.sum b/api/billing/documents/go.sum index 3bd6cadc..b2fa29f2 100644 --- a/api/billing/documents/go.sum +++ b/api/billing/documents/go.sum @@ -201,16 +201,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -260,8 +260,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/billing/fees/go.mod b/api/billing/fees/go.mod index 6096ab2e..7e99f7ee 100644 --- a/api/billing/fees/go.mod +++ b/api/billing/fees/go.mod @@ -10,7 +10,7 @@ require ( github.com/tech/sendico/fx/oracle v0.0.0 github.com/tech/sendico/pkg v0.1.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/billing/fees/go.sum b/api/billing/fees/go.sum index e9ad2722..35825f0b 100644 --- a/api/billing/fees/go.sum +++ b/api/billing/fees/go.sum @@ -152,16 +152,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/discovery/go.mod b/api/discovery/go.mod index bcda6b99..c1005c0b 100644 --- a/api/discovery/go.mod +++ b/api/discovery/go.mod @@ -44,6 +44,6 @@ require ( golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/grpc v1.78.0 // indirect + google.golang.org/grpc v1.79.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/api/discovery/go.sum b/api/discovery/go.sum index e9ad2722..35825f0b 100644 --- a/api/discovery/go.sum +++ b/api/discovery/go.sum @@ -152,16 +152,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/fx/ingestor/go.mod b/api/fx/ingestor/go.mod index e6db5199..a9defa7e 100644 --- a/api/fx/ingestor/go.mod +++ b/api/fx/ingestor/go.mod @@ -48,6 +48,6 @@ require ( golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/grpc v1.78.0 // indirect + google.golang.org/grpc v1.79.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/api/fx/ingestor/go.sum b/api/fx/ingestor/go.sum index e9ad2722..35825f0b 100644 --- a/api/fx/ingestor/go.sum +++ b/api/fx/ingestor/go.sum @@ -152,16 +152,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/fx/ingestor/internal/ingestor/service_test.go b/api/fx/ingestor/internal/ingestor/service_test.go index 16f9b2e1..7087a467 100644 --- a/api/fx/ingestor/internal/ingestor/service_test.go +++ b/api/fx/ingestor/internal/ingestor/service_test.go @@ -200,7 +200,7 @@ type repositoryStub struct { func (r *repositoryStub) Ping(context.Context) error { return nil } func (r *repositoryStub) Rates() storage.RatesStore { return r.rates } -func (r *repositoryStub) Quotes() storage.QuotesStore { return nil } +func (r *repositoryStub) Quotes() quotestorage.QuotesStore { return nil } func (r *repositoryStub) Pairs() storage.PairStore { return nil } func (r *repositoryStub) Currencies() storage.CurrencyStore { return nil } diff --git a/api/fx/oracle/go.mod b/api/fx/oracle/go.mod index d8ab34bc..b7ea0e59 100644 --- a/api/fx/oracle/go.mod +++ b/api/fx/oracle/go.mod @@ -13,7 +13,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/fx/oracle/go.sum b/api/fx/oracle/go.sum index e9ad2722..35825f0b 100644 --- a/api/fx/oracle/go.sum +++ b/api/fx/oracle/go.sum @@ -152,16 +152,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/fx/oracle/internal/service/oracle/service_test.go b/api/fx/oracle/internal/service/oracle/service_test.go index be573e63..7857f608 100644 --- a/api/fx/oracle/internal/service/oracle/service_test.go +++ b/api/fx/oracle/internal/service/oracle/service_test.go @@ -19,16 +19,16 @@ import ( type repositoryStub struct { rates storage.RatesStore - quotes storage.QuotesStore + quotes quotestorage.QuotesStore pairs storage.PairStore currencies storage.CurrencyStore pingErr error } -func (r *repositoryStub) Ping(ctx context.Context) error { return r.pingErr } -func (r *repositoryStub) Rates() storage.RatesStore { return r.rates } -func (r *repositoryStub) Quotes() storage.QuotesStore { return r.quotes } -func (r *repositoryStub) Pairs() storage.PairStore { return r.pairs } +func (r *repositoryStub) Ping(ctx context.Context) error { return r.pingErr } +func (r *repositoryStub) Rates() storage.RatesStore { return r.rates } +func (r *repositoryStub) Quotes() quotestorage.QuotesStore { return r.quotes } +func (r *repositoryStub) Pairs() storage.PairStore { return r.pairs } func (r *repositoryStub) Currencies() storage.CurrencyStore { return r.currencies } diff --git a/api/fx/storage/mongo/repository.go b/api/fx/storage/mongo/repository.go index fd469fde..5d02793f 100644 --- a/api/fx/storage/mongo/repository.go +++ b/api/fx/storage/mongo/repository.go @@ -21,7 +21,7 @@ type Store struct { txFactory transaction.Factory rates storage.RatesStore - quotes storage.QuotesStore + quotes quotestorage.QuotesStore pairs storage.PairStore currencies storage.CurrencyStore } @@ -92,7 +92,7 @@ func (s *Store) Rates() storage.RatesStore { return s.rates } -func (s *Store) Quotes() storage.QuotesStore { +func (s *Store) Quotes() quotestorage.QuotesStore { return s.quotes } diff --git a/api/fx/storage/mongo/store/quotes.go b/api/fx/storage/mongo/store/quotes.go index 22201768..a97026f4 100644 --- a/api/fx/storage/mongo/store/quotes.go +++ b/api/fx/storage/mongo/store/quotes.go @@ -23,7 +23,7 @@ type quotesStore struct { txFactory transaction.Factory } -func NewQuotes(logger mlogger.Logger, db *mongo.Database, txFactory transaction.Factory) (storage.QuotesStore, error) { +func NewQuotes(logger mlogger.Logger, db *mongo.Database, txFactory transaction.Factory) (quotestorage.QuotesStore, error) { repo := repository.CreateMongoRepository(db, model.QuotesCollection) indexes := []*ri.Definition{ { diff --git a/api/gateway/chain/go.mod b/api/gateway/chain/go.mod index 70ac074a..bdf94ce7 100644 --- a/api/gateway/chain/go.mod +++ b/api/gateway/chain/go.mod @@ -15,7 +15,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/gateway/chain/go.sum b/api/gateway/chain/go.sum index ac0b2ebb..0adbce5a 100644 --- a/api/gateway/chain/go.sum +++ b/api/gateway/chain/go.sum @@ -298,16 +298,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -362,8 +362,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/gateway/mntx/go.mod b/api/gateway/mntx/go.mod index e09985e9..6bdedf08 100644 --- a/api/gateway/mntx/go.mod +++ b/api/gateway/mntx/go.mod @@ -11,7 +11,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/gateway/mntx/go.sum b/api/gateway/mntx/go.sum index 0d812dcc..546641fd 100644 --- a/api/gateway/mntx/go.sum +++ b/api/gateway/mntx/go.sum @@ -154,16 +154,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -212,8 +212,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/gateway/tgsettle/go.mod b/api/gateway/tgsettle/go.mod index 0f738c0d..991c6fff 100644 --- a/api/gateway/tgsettle/go.mod +++ b/api/gateway/tgsettle/go.mod @@ -8,7 +8,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/gateway/tgsettle/go.sum b/api/gateway/tgsettle/go.sum index e9ad2722..35825f0b 100644 --- a/api/gateway/tgsettle/go.sum +++ b/api/gateway/tgsettle/go.sum @@ -152,16 +152,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/gateway/tron/go.mod b/api/gateway/tron/go.mod index f78dcb1b..aaa406d1 100644 --- a/api/gateway/tron/go.mod +++ b/api/gateway/tron/go.mod @@ -17,7 +17,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/gateway/tron/go.sum b/api/gateway/tron/go.sum index 4e88971e..21f0b502 100644 --- a/api/gateway/tron/go.sum +++ b/api/gateway/tron/go.sum @@ -313,16 +313,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -383,8 +383,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1: google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/ledger/go.mod b/api/ledger/go.mod index 41a81fc6..4c30bd16 100644 --- a/api/ledger/go.mod +++ b/api/ledger/go.mod @@ -11,7 +11,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/ledger/go.sum b/api/ledger/go.sum index 88b3b986..80161577 100644 --- a/api/ledger/go.sum +++ b/api/ledger/go.sum @@ -154,16 +154,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -212,8 +212,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/notification/go.mod b/api/notification/go.mod index ea61d3ca..ef296a3f 100644 --- a/api/notification/go.mod +++ b/api/notification/go.mod @@ -51,6 +51,6 @@ require ( golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/grpc v1.78.0 // indirect + google.golang.org/grpc v1.79.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/api/notification/go.sum b/api/notification/go.sum index fb432038..8246be3b 100644 --- a/api/notification/go.sum +++ b/api/notification/go.sum @@ -167,16 +167,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -227,8 +227,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/payments/methods/.air.toml b/api/payments/methods/.air.toml new file mode 100644 index 00000000..16f8c34b --- /dev/null +++ b/api/payments/methods/.air.toml @@ -0,0 +1,46 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + entrypoint = "./tmp/main" + cmd = "go build -o ./tmp/main ." + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go", "_templ.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/api/payments/methods/client/client.go b/api/payments/methods/client/client.go new file mode 100644 index 00000000..b87d0564 --- /dev/null +++ b/api/payments/methods/client/client.go @@ -0,0 +1,131 @@ +package client + +import ( + "context" + "crypto/tls" + "fmt" + "strings" + "time" + + "github.com/tech/sendico/pkg/merrors" + methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" +) + +// Client exposes typed helpers around the payment methods gRPC API. +type Client interface { + CreatePaymentMethod(ctx context.Context, req *methodsv1.CreatePaymentMethodRequest) (*methodsv1.CreatePaymentMethodResponse, error) + GetPaymentMethod(ctx context.Context, req *methodsv1.GetPaymentMethodRequest) (*methodsv1.GetPaymentMethodResponse, error) + UpdatePaymentMethod(ctx context.Context, req *methodsv1.UpdatePaymentMethodRequest) (*methodsv1.UpdatePaymentMethodResponse, error) + DeletePaymentMethod(ctx context.Context, req *methodsv1.DeletePaymentMethodRequest) (*methodsv1.DeletePaymentMethodResponse, error) + SetPaymentMethodArchived(ctx context.Context, req *methodsv1.SetPaymentMethodArchivedRequest) (*methodsv1.SetPaymentMethodArchivedResponse, error) + ListPaymentMethods(ctx context.Context, req *methodsv1.ListPaymentMethodsRequest) (*methodsv1.ListPaymentMethodsResponse, error) + Close() error +} + +type grpcPaymentMethodsClient interface { + CreatePaymentMethod(ctx context.Context, in *methodsv1.CreatePaymentMethodRequest, opts ...grpc.CallOption) (*methodsv1.CreatePaymentMethodResponse, error) + GetPaymentMethod(ctx context.Context, in *methodsv1.GetPaymentMethodRequest, opts ...grpc.CallOption) (*methodsv1.GetPaymentMethodResponse, error) + UpdatePaymentMethod(ctx context.Context, in *methodsv1.UpdatePaymentMethodRequest, opts ...grpc.CallOption) (*methodsv1.UpdatePaymentMethodResponse, error) + DeletePaymentMethod(ctx context.Context, in *methodsv1.DeletePaymentMethodRequest, opts ...grpc.CallOption) (*methodsv1.DeletePaymentMethodResponse, error) + SetPaymentMethodArchived(ctx context.Context, in *methodsv1.SetPaymentMethodArchivedRequest, opts ...grpc.CallOption) (*methodsv1.SetPaymentMethodArchivedResponse, error) + ListPaymentMethods(ctx context.Context, in *methodsv1.ListPaymentMethodsRequest, opts ...grpc.CallOption) (*methodsv1.ListPaymentMethodsResponse, error) +} + +type paymentMethodsClient struct { + cfg Config + conn *grpc.ClientConn + client grpcPaymentMethodsClient +} + +// New dials the payment methods endpoint and returns a ready client. +func New(ctx context.Context, cfg Config, opts ...grpc.DialOption) (Client, error) { + cfg.setDefaults() + if strings.TrimSpace(cfg.Address) == "" { + return nil, merrors.InvalidArgument("payment-methods: address is required") + } + + dialCtx, cancel := context.WithTimeout(ctx, cfg.DialTimeout) + defer cancel() + + dialOpts := make([]grpc.DialOption, 0, len(opts)+1) + dialOpts = append(dialOpts, opts...) + if cfg.Insecure { + dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } else { + dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))) + } + + conn, err := grpc.DialContext(dialCtx, cfg.Address, dialOpts...) + if err != nil { + return nil, merrors.InternalWrap(err, fmt.Sprintf("payment-methods: dial %s", cfg.Address)) + } + + return &paymentMethodsClient{ + cfg: cfg, + conn: conn, + client: methodsv1.NewPaymentMethodsServiceClient(conn), + }, nil +} + +// NewWithClient injects a pre-built payment methods client (useful for tests). +func NewWithClient(cfg Config, c grpcPaymentMethodsClient) Client { + cfg.setDefaults() + return &paymentMethodsClient{ + cfg: cfg, + client: c, + } +} + +func (c *paymentMethodsClient) Close() error { + if c.conn != nil { + return c.conn.Close() + } + return nil +} + +func (c *paymentMethodsClient) callContext(ctx context.Context) (context.Context, context.CancelFunc) { + timeout := c.cfg.CallTimeout + if timeout <= 0 { + timeout = 3 * time.Second + } + return context.WithTimeout(ctx, timeout) +} + +func (c *paymentMethodsClient) CreatePaymentMethod(ctx context.Context, req *methodsv1.CreatePaymentMethodRequest) (*methodsv1.CreatePaymentMethodResponse, error) { + callCtx, cancel := c.callContext(ctx) + defer cancel() + return c.client.CreatePaymentMethod(callCtx, req) +} + +func (c *paymentMethodsClient) GetPaymentMethod(ctx context.Context, req *methodsv1.GetPaymentMethodRequest) (*methodsv1.GetPaymentMethodResponse, error) { + callCtx, cancel := c.callContext(ctx) + defer cancel() + return c.client.GetPaymentMethod(callCtx, req) +} + +func (c *paymentMethodsClient) UpdatePaymentMethod(ctx context.Context, req *methodsv1.UpdatePaymentMethodRequest) (*methodsv1.UpdatePaymentMethodResponse, error) { + callCtx, cancel := c.callContext(ctx) + defer cancel() + return c.client.UpdatePaymentMethod(callCtx, req) +} + +func (c *paymentMethodsClient) DeletePaymentMethod(ctx context.Context, req *methodsv1.DeletePaymentMethodRequest) (*methodsv1.DeletePaymentMethodResponse, error) { + callCtx, cancel := c.callContext(ctx) + defer cancel() + return c.client.DeletePaymentMethod(callCtx, req) +} + +func (c *paymentMethodsClient) SetPaymentMethodArchived(ctx context.Context, req *methodsv1.SetPaymentMethodArchivedRequest) (*methodsv1.SetPaymentMethodArchivedResponse, error) { + callCtx, cancel := c.callContext(ctx) + defer cancel() + return c.client.SetPaymentMethodArchived(callCtx, req) +} + +func (c *paymentMethodsClient) ListPaymentMethods(ctx context.Context, req *methodsv1.ListPaymentMethodsRequest) (*methodsv1.ListPaymentMethodsResponse, error) { + callCtx, cancel := c.callContext(ctx) + defer cancel() + return c.client.ListPaymentMethods(callCtx, req) +} diff --git a/api/payments/methods/client/config.go b/api/payments/methods/client/config.go new file mode 100644 index 00000000..272bd4ab --- /dev/null +++ b/api/payments/methods/client/config.go @@ -0,0 +1,20 @@ +package client + +import "time" + +// Config captures connection settings for the payment methods gRPC service. +type Config struct { + Address string + DialTimeout time.Duration + CallTimeout time.Duration + Insecure bool +} + +func (c *Config) setDefaults() { + if c.DialTimeout <= 0 { + c.DialTimeout = 5 * time.Second + } + if c.CallTimeout <= 0 { + c.CallTimeout = 3 * time.Second + } +} diff --git a/api/payments/methods/config.dev.yml b/api/payments/methods/config.dev.yml new file mode 100644 index 00000000..ee32ba01 --- /dev/null +++ b/api/payments/methods/config.dev.yml @@ -0,0 +1,49 @@ +runtime: + shutdown_timeout_seconds: 15 + +grpc: + network: tcp + address: ":50066" + advertise_host: "dev-payments-methods" + enable_reflection: true + enable_health: true + +metrics: + address: ":9416" + +messaging: + driver: NATS + settings: + url_env: NATS_URL + host_env: NATS_HOST + port_env: NATS_PORT + username_env: NATS_USER + password_env: NATS_PASSWORD + broker_name: Payments Methods Service + max_reconnects: 10 + reconnect_wait: 5 + buffer_size: 1024 + +database: + driver: mongodb + settings: + host_env: PAYMENTS_MONGO_HOST + port_env: PAYMENTS_MONGO_PORT + database_env: PAYMENTS_MONGO_DATABASE + user_env: PAYMENTS_MONGO_USER + password_env: PAYMENTS_MONGO_PASSWORD + auth_source_env: PAYMENTS_MONGO_AUTH_SOURCE + replica_set_env: PAYMENTS_MONGO_REPLICA_SET + +permissions_database: + driver: mongodb + settings: + host_env: MONGO_HOST + port_env: MONGO_PORT + database_env: MONGO_DATABASE + user_env: MONGO_USER + password_env: MONGO_PASSWORD + auth_source_env: MONGO_AUTH_SOURCE + replica_set_env: MONGO_REPLICA_SET + enforcer: + driver: native diff --git a/api/payments/methods/config.yml b/api/payments/methods/config.yml new file mode 100644 index 00000000..966a7890 --- /dev/null +++ b/api/payments/methods/config.yml @@ -0,0 +1,49 @@ +runtime: + shutdown_timeout_seconds: 15 + +grpc: + network: tcp + address: ":50066" + advertise_host: "sendico_payments_methods" + enable_reflection: true + enable_health: true + +metrics: + address: ":9416" + +messaging: + driver: NATS + settings: + url_env: NATS_URL + host_env: NATS_HOST + port_env: NATS_PORT + username_env: NATS_USER + password_env: NATS_PASSWORD + broker_name: Payments Methods Service + max_reconnects: 10 + reconnect_wait: 5 + buffer_size: 1024 + +database: + driver: mongodb + settings: + host_env: PAYMENTS_MONGO_HOST + port_env: PAYMENTS_MONGO_PORT + database_env: PAYMENTS_MONGO_DATABASE + user_env: PAYMENTS_MONGO_USER + password_env: PAYMENTS_MONGO_PASSWORD + auth_source_env: PAYMENTS_MONGO_AUTH_SOURCE + replica_set_env: PAYMENTS_MONGO_REPLICA_SET + +permissions_database: + driver: mongodb + settings: + host_env: MONGO_HOST + port_env: MONGO_PORT + database_env: MONGO_DATABASE + user_env: MONGO_USER + password_env: MONGO_PASSWORD + auth_source_env: MONGO_AUTH_SOURCE + replica_set_env: MONGO_REPLICA_SET + enforcer: + driver: native diff --git a/api/payments/methods/env/.gitignore b/api/payments/methods/env/.gitignore new file mode 100644 index 00000000..f2a8cbe3 --- /dev/null +++ b/api/payments/methods/env/.gitignore @@ -0,0 +1 @@ +.env.api diff --git a/api/payments/methods/go.mod b/api/payments/methods/go.mod new file mode 100644 index 00000000..75040980 --- /dev/null +++ b/api/payments/methods/go.mod @@ -0,0 +1,52 @@ +module github.com/tech/sendico/payments/methods + +go 1.25.7 + +replace github.com/tech/sendico/pkg => ../../pkg + +replace github.com/tech/sendico/payments/storage => ../storage + +require ( + github.com/tech/sendico/payments/storage v0.0.0-00010101000000-000000000000 + github.com/tech/sendico/pkg v0.1.0 + go.mongodb.org/mongo-driver/v2 v2.5.0 + go.uber.org/zap v1.27.1 + google.golang.org/grpc v1.79.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect + github.com/casbin/casbin/v2 v2.135.0 // indirect + github.com/casbin/govaluate v1.10.0 // indirect + github.com/casbin/mongodb-adapter/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/go-chi/chi/v5 v5.2.5 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.18.4 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nats-io/nats.go v1.48.0 // indirect + github.com/nats-io/nkeys v0.4.15 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.19.2 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.2.0 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) diff --git a/api/payments/methods/go.sum b/api/payments/methods/go.sum new file mode 100644 index 00000000..35825f0b --- /dev/null +++ b/api/payments/methods/go.sum @@ -0,0 +1,221 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/casbin/casbin/v2 v2.135.0 h1:6BLkMQiGotYyS5yYeWgW19vxqugUlvHFkFiLnLR/bxk= +github.com/casbin/casbin/v2 v2.135.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18= +github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0= +github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/casbin/mongodb-adapter/v4 v4.3.0 h1:yYXky9v1by6vj/0QK7OyHyd/xpz4vzh0lCi7JKrS4qQ= +github.com/casbin/mongodb-adapter/v4 v4.3.0/go.mod h1:bOTSYZUjX7I9E0ExEvgq46m3mcDNRII7g8iWjrM1BHE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +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/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U= +github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4= +github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +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.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw= +github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8= +github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0 h1:iXVA84s5hKMS5gn01GWOYHE3ymy/2b+0YkpFeTxB2XY= +github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0/go.mod h1:R6tMjTojRiaoo89fh/hf7tOmfzohdqSU17R9DwSVSog= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= +github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/api/payments/methods/internal/appversion/version.go b/api/payments/methods/internal/appversion/version.go new file mode 100644 index 00000000..88e7ee1e --- /dev/null +++ b/api/payments/methods/internal/appversion/version.go @@ -0,0 +1,28 @@ +package appversion + +import ( + "github.com/tech/sendico/pkg/version" + vf "github.com/tech/sendico/pkg/version/factory" +) + +// Build information populated via ldflags. +var ( + Version string + Revision string + Branch string + BuildUser string + BuildDate string +) + +// Create returns a printer configured for the payment methods service. +func Create() version.Printer { + vi := version.Info{ + Program: "Sendico Payment Methods Service", + Revision: Revision, + Branch: Branch, + BuildUser: BuildUser, + BuildDate: BuildDate, + Version: Version, + } + return vf.Create(&vi) +} diff --git a/api/payments/methods/internal/server/internal/config.go b/api/payments/methods/internal/server/internal/config.go new file mode 100644 index 00000000..40b42677 --- /dev/null +++ b/api/payments/methods/internal/server/internal/config.go @@ -0,0 +1,62 @@ +package serverimp + +import ( + "os" + "strings" + + "github.com/tech/sendico/pkg/api/routers" + "github.com/tech/sendico/pkg/db" + "github.com/tech/sendico/pkg/server/grpcapp" + "go.uber.org/zap" + "gopkg.in/yaml.v3" +) + +type config struct { + *grpcapp.Config `yaml:",inline"` + + // PermissionsDatabase points to the authorization store (policies/roles/assignments). + // If omitted, startup falls back to Database for backward compatibility. + PermissionsDatabase *db.Config `yaml:"permissions_database"` +} + +func (i *Imp) loadConfig() (*config, error) { + data, err := os.ReadFile(i.file) + if err != nil { + i.logger.Error("Could not read configuration file", zap.String("config_file", i.file), zap.Error(err)) + return nil, err + } + + cfg := &config{Config: &grpcapp.Config{}} + if err := yaml.Unmarshal(data, cfg); err != nil { + i.logger.Error("Failed to parse configuration", zap.Error(err)) + return nil, err + } + + if cfg.Runtime == nil { + cfg.Runtime = &grpcapp.RuntimeConfig{ShutdownTimeoutSeconds: 15} + } + + if cfg.GRPC == nil { + cfg.GRPC = &routers.GRPCConfig{ + Network: "tcp", + Address: ":50066", + EnableReflection: true, + EnableHealth: true, + } + } else { + if strings.TrimSpace(cfg.GRPC.Address) == "" { + cfg.GRPC.Address = ":50066" + } + if strings.TrimSpace(cfg.GRPC.Network) == "" { + cfg.GRPC.Network = "tcp" + } + } + + if cfg.Metrics == nil { + cfg.Metrics = &grpcapp.MetricsConfig{Address: ":9416"} + } else if strings.TrimSpace(cfg.Metrics.Address) == "" { + cfg.Metrics.Address = ":9416" + } + + return cfg, nil +} diff --git a/api/payments/methods/internal/server/internal/discovery.go b/api/payments/methods/internal/server/internal/discovery.go new file mode 100644 index 00000000..7a0cb157 --- /dev/null +++ b/api/payments/methods/internal/server/internal/discovery.go @@ -0,0 +1,88 @@ +package serverimp + +import ( + "strings" + + "github.com/tech/sendico/payments/methods/internal/appversion" + "github.com/tech/sendico/pkg/discovery" + msg "github.com/tech/sendico/pkg/messaging" + "go.uber.org/zap" +) + +const methodsDiscoverySender = "payment_methods" + +func (i *Imp) initDiscovery(cfg *config) { + if i == nil || cfg == nil || cfg.Messaging == nil || cfg.Messaging.Driver == "" { + return + } + + logger := i.logger.Named("discovery") + broker, err := msg.CreateMessagingBroker(logger.Named("bus"), cfg.Messaging) + if err != nil { + i.logger.Warn("Failed to initialise discovery broker", zap.Error(err)) + return + } + + registry := discovery.NewRegistry() + watcher, err := discovery.NewRegistryWatcher(logger, broker, registry) + if err != nil { + i.logger.Warn("Failed to initialise discovery registry watcher", zap.Error(err)) + return + } + if err := watcher.Start(); err != nil { + i.logger.Warn("Failed to start discovery registry watcher", zap.Error(err)) + return + } + + i.discoveryWatcher = watcher + i.discoveryReg = registry + i.logger.Info("Discovery registry watcher started") +} + +func (i *Imp) startDiscoveryAnnouncer(cfg *config, producer msg.Producer) { + if i == nil || cfg == nil || producer == nil || cfg.GRPC == nil { + return + } + + invokeURI := strings.TrimSpace(cfg.GRPC.DiscoveryInvokeURI()) + if invokeURI == "" { + i.logger.Warn("Skipping discovery announcement: missing advertise host/port in gRPC config") + return + } + + announce := discovery.Announcement{ + Service: "PAYMENTS_METHODS", + Operations: []string{ + "payment_methods.manage", + "payment_methods.read", + }, + InvokeURI: invokeURI, + Version: appversion.Create().Short(), + } + + i.discoveryAnnouncer = discovery.NewAnnouncer(i.logger, producer, methodsDiscoverySender, announce) + i.discoveryAnnouncer.Start() + i.logger.Info("Discovery announcer started", + zap.String("service", announce.Service), + zap.String("invoke_uri", announce.InvokeURI)) +} + +func (i *Imp) stopDiscoveryAnnouncer() { + if i == nil || i.discoveryAnnouncer == nil { + return + } + i.discoveryAnnouncer.Stop() + i.discoveryAnnouncer = nil +} + +func (i *Imp) stopDiscovery() { + if i == nil { + return + } + i.stopDiscoveryAnnouncer() + if i.discoveryWatcher != nil { + i.discoveryWatcher.Stop() + i.discoveryWatcher = nil + } + i.discoveryReg = nil +} diff --git a/api/payments/methods/internal/server/internal/lifecycle.go b/api/payments/methods/internal/server/internal/lifecycle.go new file mode 100644 index 00000000..b28f155f --- /dev/null +++ b/api/payments/methods/internal/server/internal/lifecycle.go @@ -0,0 +1,16 @@ +package serverimp + +import "context" + +func (i *Imp) shutdownApp() { + if i == nil || i.app == nil { + return + } + + timeout := i.config.Runtime.ShutdownTimeout() + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + i.app.Shutdown(ctx) + i.app = nil +} diff --git a/api/payments/methods/internal/server/internal/serverimp.go b/api/payments/methods/internal/server/internal/serverimp.go new file mode 100644 index 00000000..e52563c3 --- /dev/null +++ b/api/payments/methods/internal/server/internal/serverimp.go @@ -0,0 +1,117 @@ +package serverimp + +import ( + "context" + + "github.com/tech/sendico/payments/methods/internal/service/methods" + "github.com/tech/sendico/payments/storage" + mongostorage "github.com/tech/sendico/payments/storage/mongo" + "github.com/tech/sendico/pkg/db" + "github.com/tech/sendico/pkg/merrors" + msg "github.com/tech/sendico/pkg/messaging" + mb "github.com/tech/sendico/pkg/messaging/broker" + "github.com/tech/sendico/pkg/mlogger" + "github.com/tech/sendico/pkg/mservice" + "github.com/tech/sendico/pkg/server/grpcapp" + "go.uber.org/zap" +) + +func Create(logger mlogger.Logger, file string, debug bool) (*Imp, error) { + return &Imp{ + logger: logger.Named("server"), + file: file, + debug: debug, + }, nil +} + +func (i *Imp) Shutdown() { + i.stopDiscovery() + if i.service != nil { + i.service.Shutdown() + } + i.shutdownApp() + if i.dbFactory != nil { + i.dbFactory.CloseConnection() + i.dbFactory = nil + } +} + +func (i *Imp) Start() error { + cfg, err := i.loadConfig() + if err != nil { + return err + } + i.config = cfg + + i.initDiscovery(cfg) + + if cfg.Database == nil { + return merrors.InvalidArgument("database configuration is required") + } + + permissionsDB := cfg.PermissionsDatabase + if permissionsDB == nil { + i.logger.Info("permissions_database is not configured, falling back to database settings") + permissionsDB = cfg.Database + } + + i.dbFactory, err = db.NewConnection(i.logger, permissionsDB) + if err != nil { + return err + } + + policy, err := i.dbFactory.Permissions().GetPolicyDescription(context.Background(), mservice.PaymentMethods) + if err != nil { + i.dbFactory.CloseConnection() + i.dbFactory = nil + return err + } + + var broker mb.Broker + if cfg.Messaging != nil && cfg.Messaging.Driver != "" { + broker, err = msg.CreateMessagingBroker(i.logger, cfg.Messaging) + if err != nil { + i.logger.Warn("Failed to create recipient notifications broker", zap.Error(err)) + } + } + + repoFactory := func(logger mlogger.Logger, conn *db.MongoConnection) (storage.Repository, error) { + return mongostorage.New( + logger, + conn, + mongostorage.WithPaymentMethodsAuth(i.dbFactory.Permissions().Enforcer(), policy.ID), + ) + } + + serviceFactory := func(logger mlogger.Logger, repo storage.Repository, producer msg.Producer) (grpcapp.Service, error) { + opts := []methods.Option{} + if broker != nil { + opts = append(opts, methods.WithRecipientEventsBroker(broker)) + } + + i.startDiscoveryAnnouncer(cfg, producer) + svc, err := methods.NewService(logger, repo, opts...) + if err != nil { + return nil, err + } + i.service = svc + return svc, nil + } + + app, err := grpcapp.NewApp(i.logger, "payments_methods", cfg.Config, i.debug, repoFactory, serviceFactory) + if err != nil { + i.dbFactory.CloseConnection() + i.dbFactory = nil + return err + } + i.app = app + + if err := i.app.Start(); err != nil { + if i.dbFactory != nil { + i.dbFactory.CloseConnection() + i.dbFactory = nil + } + return err + } + return nil +} diff --git a/api/payments/methods/internal/server/internal/types.go b/api/payments/methods/internal/server/internal/types.go new file mode 100644 index 00000000..d68b2351 --- /dev/null +++ b/api/payments/methods/internal/server/internal/types.go @@ -0,0 +1,29 @@ +package serverimp + +import ( + "github.com/tech/sendico/payments/storage" + "github.com/tech/sendico/pkg/db" + "github.com/tech/sendico/pkg/discovery" + "github.com/tech/sendico/pkg/mlogger" + "github.com/tech/sendico/pkg/server/grpcapp" +) + +type methodsService interface { + grpcapp.Service + Shutdown() +} + +type Imp struct { + logger mlogger.Logger + file string + debug bool + + config *config + app *grpcapp.App[storage.Repository] + service methodsService + dbFactory db.Factory + + discoveryWatcher *discovery.RegistryWatcher + discoveryReg *discovery.Registry + discoveryAnnouncer *discovery.Announcer +} diff --git a/api/payments/methods/internal/server/server.go b/api/payments/methods/internal/server/server.go new file mode 100644 index 00000000..5b926c60 --- /dev/null +++ b/api/payments/methods/internal/server/server.go @@ -0,0 +1,12 @@ +package server + +import ( + serverimp "github.com/tech/sendico/payments/methods/internal/server/internal" + "github.com/tech/sendico/pkg/mlogger" + "github.com/tech/sendico/pkg/server" +) + +// Create initialises the payment methods server implementation. +func Create(logger mlogger.Logger, file string, debug bool) (server.Application, error) { + return serverimp.Create(logger, file, debug) +} diff --git a/api/payments/methods/internal/service/methods/archive.go b/api/payments/methods/internal/service/methods/archive.go new file mode 100644 index 00000000..536709e7 --- /dev/null +++ b/api/payments/methods/internal/service/methods/archive.go @@ -0,0 +1,36 @@ +package methods + +import ( + "context" + + "github.com/tech/sendico/pkg/merrors" + methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1" +) + +func (s *Service) SetPaymentMethodArchived(ctx context.Context, req *methodsv1.SetPaymentMethodArchivedRequest) (*methodsv1.SetPaymentMethodArchivedResponse, error) { + if req == nil { + return autoError[methodsv1.SetPaymentMethodArchivedResponse](ctx, s.logger, merrors.InvalidArgument("request is required")) + } + if s.pmstore == nil { + return autoError[methodsv1.SetPaymentMethodArchivedResponse](ctx, s.logger, errStoreUnavailable) + } + + accountRef, err := parseObjectID(req.GetAccountRef(), "account_ref") + if err != nil { + return autoError[methodsv1.SetPaymentMethodArchivedResponse](ctx, s.logger, err) + } + organizationRef, err := parseObjectID(req.GetOrganizationRef(), "organization_ref") + if err != nil { + return autoError[methodsv1.SetPaymentMethodArchivedResponse](ctx, s.logger, err) + } + methodRef, err := parseObjectID(req.GetPaymentMethodRef(), "payment_method_ref") + if err != nil { + return autoError[methodsv1.SetPaymentMethodArchivedResponse](ctx, s.logger, err) + } + + if err := s.pmstore.SetArchived(ctx, accountRef, organizationRef, methodRef, req.GetArchived(), req.GetCascade()); err != nil { + return autoError[methodsv1.SetPaymentMethodArchivedResponse](ctx, s.logger, err) + } + + return &methodsv1.SetPaymentMethodArchivedResponse{}, nil +} diff --git a/api/payments/methods/internal/service/methods/create.go b/api/payments/methods/internal/service/methods/create.go new file mode 100644 index 00000000..4f5eb58c --- /dev/null +++ b/api/payments/methods/internal/service/methods/create.go @@ -0,0 +1,41 @@ +package methods + +import ( + "context" + + "github.com/tech/sendico/pkg/merrors" + methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1" +) + +func (s *Service) CreatePaymentMethod(ctx context.Context, req *methodsv1.CreatePaymentMethodRequest) (*methodsv1.CreatePaymentMethodResponse, error) { + if req == nil { + return autoError[methodsv1.CreatePaymentMethodResponse](ctx, s.logger, merrors.InvalidArgument("request is required")) + } + if s.pmstore == nil { + return autoError[methodsv1.CreatePaymentMethodResponse](ctx, s.logger, errStoreUnavailable) + } + + accountRef, err := parseObjectID(req.GetAccountRef(), "account_ref") + if err != nil { + return autoError[methodsv1.CreatePaymentMethodResponse](ctx, s.logger, err) + } + organizationRef, err := parseObjectID(req.GetOrganizationRef(), "organization_ref") + if err != nil { + return autoError[methodsv1.CreatePaymentMethodResponse](ctx, s.logger, err) + } + + pm, err := decodePaymentMethod(req.GetPaymentMethodJson()) + if err != nil { + return autoError[methodsv1.CreatePaymentMethodResponse](ctx, s.logger, err) + } + if err := s.pmstore.Create(ctx, accountRef, organizationRef, pm); err != nil { + return autoError[methodsv1.CreatePaymentMethodResponse](ctx, s.logger, err) + } + + payload, err := encodePaymentMethod(pm) + if err != nil { + return autoError[methodsv1.CreatePaymentMethodResponse](ctx, s.logger, err) + } + + return &methodsv1.CreatePaymentMethodResponse{PaymentMethodJson: payload}, nil +} diff --git a/api/payments/methods/internal/service/methods/delete.go b/api/payments/methods/internal/service/methods/delete.go new file mode 100644 index 00000000..4e3aba35 --- /dev/null +++ b/api/payments/methods/internal/service/methods/delete.go @@ -0,0 +1,37 @@ +package methods + +import ( + "context" + + "github.com/tech/sendico/pkg/merrors" + methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1" +) + +func (s *Service) DeletePaymentMethod(ctx context.Context, req *methodsv1.DeletePaymentMethodRequest) (*methodsv1.DeletePaymentMethodResponse, error) { + if req == nil { + return autoError[methodsv1.DeletePaymentMethodResponse](ctx, s.logger, merrors.InvalidArgument("request is required")) + } + if s.pmstore == nil { + return autoError[methodsv1.DeletePaymentMethodResponse](ctx, s.logger, errStoreUnavailable) + } + + accountRef, err := parseObjectID(req.GetAccountRef(), "account_ref") + if err != nil { + return autoError[methodsv1.DeletePaymentMethodResponse](ctx, s.logger, err) + } + methodRef, err := parseObjectID(req.GetPaymentMethodRef(), "payment_method_ref") + if err != nil { + return autoError[methodsv1.DeletePaymentMethodResponse](ctx, s.logger, err) + } + + if req.GetCascade() { + err = s.pmstore.DeleteCascade(ctx, accountRef, methodRef) + } else { + err = s.pmstore.Delete(ctx, accountRef, methodRef) + } + if err != nil { + return autoError[methodsv1.DeletePaymentMethodResponse](ctx, s.logger, err) + } + + return &methodsv1.DeletePaymentMethodResponse{}, nil +} diff --git a/api/payments/methods/internal/service/methods/get.go b/api/payments/methods/internal/service/methods/get.go new file mode 100644 index 00000000..d014767a --- /dev/null +++ b/api/payments/methods/internal/service/methods/get.go @@ -0,0 +1,38 @@ +package methods + +import ( + "context" + + "github.com/tech/sendico/pkg/merrors" + methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1" +) + +func (s *Service) GetPaymentMethod(ctx context.Context, req *methodsv1.GetPaymentMethodRequest) (*methodsv1.GetPaymentMethodResponse, error) { + if req == nil { + return autoError[methodsv1.GetPaymentMethodResponse](ctx, s.logger, merrors.InvalidArgument("request is required")) + } + if s.pmstore == nil { + return autoError[methodsv1.GetPaymentMethodResponse](ctx, s.logger, errStoreUnavailable) + } + + accountRef, err := parseObjectID(req.GetAccountRef(), "account_ref") + if err != nil { + return autoError[methodsv1.GetPaymentMethodResponse](ctx, s.logger, err) + } + methodRef, err := parseObjectID(req.GetPaymentMethodRef(), "payment_method_ref") + if err != nil { + return autoError[methodsv1.GetPaymentMethodResponse](ctx, s.logger, err) + } + + pm, err := s.pmstore.Get(ctx, accountRef, methodRef) + if err != nil { + return autoError[methodsv1.GetPaymentMethodResponse](ctx, s.logger, err) + } + + payload, err := encodePaymentMethod(pm) + if err != nil { + return autoError[methodsv1.GetPaymentMethodResponse](ctx, s.logger, err) + } + + return &methodsv1.GetPaymentMethodResponse{PaymentMethodJson: payload}, nil +} diff --git a/api/payments/methods/internal/service/methods/list.go b/api/payments/methods/internal/service/methods/list.go new file mode 100644 index 00000000..698406ce --- /dev/null +++ b/api/payments/methods/internal/service/methods/list.go @@ -0,0 +1,48 @@ +package methods + +import ( + "context" + + "github.com/tech/sendico/pkg/merrors" + methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1" +) + +func (s *Service) ListPaymentMethods(ctx context.Context, req *methodsv1.ListPaymentMethodsRequest) (*methodsv1.ListPaymentMethodsResponse, error) { + if req == nil { + return autoError[methodsv1.ListPaymentMethodsResponse](ctx, s.logger, merrors.InvalidArgument("request is required")) + } + if s.pmstore == nil { + return autoError[methodsv1.ListPaymentMethodsResponse](ctx, s.logger, errStoreUnavailable) + } + + accountRef, err := parseObjectID(req.GetAccountRef(), "account_ref") + if err != nil { + return autoError[methodsv1.ListPaymentMethodsResponse](ctx, s.logger, err) + } + organizationRef, err := parseObjectID(req.GetOrganizationRef(), "organization_ref") + if err != nil { + return autoError[methodsv1.ListPaymentMethodsResponse](ctx, s.logger, err) + } + recipientRef, err := parseObjectID(req.GetRecipientRef(), "recipient_ref") + if err != nil { + return autoError[methodsv1.ListPaymentMethodsResponse](ctx, s.logger, err) + } + + items, err := s.pmstore.List(ctx, accountRef, organizationRef, recipientRef, toModelCursor(req.GetCursor())) + if err != nil { + return autoError[methodsv1.ListPaymentMethodsResponse](ctx, s.logger, err) + } + + result := make([][]byte, 0, len(items)) + for i := range items { + payload, err := encodePaymentMethod(&items[i]) + if err != nil { + return autoError[methodsv1.ListPaymentMethodsResponse](ctx, s.logger, err) + } + result = append(result, payload) + } + + return &methodsv1.ListPaymentMethodsResponse{ + PaymentMethodsJson: result, + }, nil +} diff --git a/api/payments/methods/internal/service/methods/recipient_consumer.go b/api/payments/methods/internal/service/methods/recipient_consumer.go new file mode 100644 index 00000000..19800e94 --- /dev/null +++ b/api/payments/methods/internal/service/methods/recipient_consumer.go @@ -0,0 +1,87 @@ +package methods + +import ( + "context" + + cons "github.com/tech/sendico/pkg/messaging/consumer" + objectnotifications "github.com/tech/sendico/pkg/messaging/notifications/object" + np "github.com/tech/sendico/pkg/messaging/notifications/processor" + nm "github.com/tech/sendico/pkg/model/notification" + "github.com/tech/sendico/pkg/mservice" + "go.mongodb.org/mongo-driver/v2/bson" + "go.uber.org/zap" +) + +func (s *Service) startRecipientConsumers() { + if s == nil || s.recipientBroker == nil { + s.logger.Warn("Missing broker. Recipient cascade consumers have NOT started") + return + } + + s.consumeRecipientProcessor( + objectnotifications.NewObjectChangedMessageProcessor(s.logger, mservice.Recipients, nm.NAArchived, s.onRecipientNotification), + ) + s.consumeRecipientProcessor( + objectnotifications.NewObjectChangedMessageProcessor(s.logger, mservice.Recipients, nm.NADeleted, s.onRecipientNotification), + ) + + s.logger.Info("Recipient cascade consumers started") +} + +func (s *Service) consumeRecipientProcessor(processor np.EnvelopeProcessor) { + consumer, err := cons.NewConsumer(s.logger, s.recipientBroker, processor.GetSubject()) + if err != nil { + s.logger.Warn("Failed to create recipient consumer", zap.Error(err), zap.String("event", processor.GetSubject().ToString())) + return + } + s.recipientConsumers = append(s.recipientConsumers, consumer) + + go func() { + if err := consumer.ConsumeMessages(processor.Process); err != nil { + s.logger.Warn("Recipient consumer stopped", zap.Error(err), zap.String("event", processor.GetSubject().ToString())) + } + }() +} + +func (s *Service) onRecipientNotification( + ctx context.Context, + objectType mservice.Type, + recipientRef, actorAccountRef bson.ObjectID, + action nm.NotificationAction, +) error { + if s.pmstore == nil { + return errStoreUnavailable + } + if objectType != mservice.Recipients || recipientRef == bson.NilObjectID { + return nil + } + + switch action { + case nm.NAArchived: + updated, err := s.pmstore.SetArchivedByRecipient(ctx, recipientRef, true) + if err != nil { + s.logger.Warn("Failed to cascade archive payment methods by recipient", + zap.Error(err), + zap.String("recipient_ref", recipientRef.Hex()), + zap.String("actor_account_ref", actorAccountRef.Hex())) + return err + } + s.logger.Info("Recipient archive cascade applied to payment methods", + zap.String("recipient_ref", recipientRef.Hex()), + zap.String("actor_account_ref", actorAccountRef.Hex()), + zap.Int("updated_count", updated)) + case nm.NADeleted: + if err := s.pmstore.DeleteByRecipient(ctx, recipientRef); err != nil { + s.logger.Warn("Failed to cascade delete payment methods by recipient", + zap.Error(err), + zap.String("recipient_ref", recipientRef.Hex()), + zap.String("actor_account_ref", actorAccountRef.Hex())) + return err + } + s.logger.Info("Recipient delete cascade applied to payment methods", + zap.String("recipient_ref", recipientRef.Hex()), + zap.String("actor_account_ref", actorAccountRef.Hex())) + } + + return nil +} diff --git a/api/payments/methods/internal/service/methods/service.go b/api/payments/methods/internal/service/methods/service.go new file mode 100644 index 00000000..0df8d498 --- /dev/null +++ b/api/payments/methods/internal/service/methods/service.go @@ -0,0 +1,90 @@ +package methods + +import ( + "github.com/tech/sendico/payments/storage" + "github.com/tech/sendico/pkg/api/routers" + "github.com/tech/sendico/pkg/merrors" + msg "github.com/tech/sendico/pkg/messaging" + mb "github.com/tech/sendico/pkg/messaging/broker" + "github.com/tech/sendico/pkg/mlogger" + methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1" + "google.golang.org/grpc" +) + +var errStoreUnavailable = merrors.Internal("payment-methods: storage is not initialised") + +// Option configures service dependencies. +type Option func(*Service) + +// WithRecipientEventsBroker wires the broker used to consume recipient events. +func WithRecipientEventsBroker(broker mb.Broker) Option { + return func(s *Service) { + if broker != nil { + s.recipientBroker = broker + } + } +} + +// Service implements payments.methods.v1.PaymentMethodsService. +type Service struct { + logger mlogger.Logger + storage storage.Repository + pmstore storage.PaymentMethodsStore + + recipientBroker mb.Broker + recipientConsumers []msg.Consumer + + methodsv1.UnimplementedPaymentMethodsServiceServer +} + +// NewService creates a payment methods gRPC service. +func NewService(logger mlogger.Logger, repo storage.Repository, opts ...Option) (*Service, error) { + if logger == nil { + return nil, merrors.InvalidArgument("payment-methods: logger is required") + } + if repo == nil { + return nil, merrors.InvalidArgument("payment-methods: storage repository is required") + } + + pmstore := repo.PaymentMethods() + if pmstore == nil { + return nil, errStoreUnavailable + } + + svc := &Service{ + logger: logger.Named("payment_methods"), + storage: repo, + pmstore: pmstore, + } + + for _, opt := range opts { + if opt != nil { + opt(svc) + } + } + + svc.startRecipientConsumers() + return svc, nil +} + +// Register attaches the service to the supplied gRPC router. +func (s *Service) Register(router routers.GRPC) error { + return router.Register(func(reg grpc.ServiceRegistrar) { + methodsv1.RegisterPaymentMethodsServiceServer(reg, s) + }) +} + +// Shutdown releases underlying resources. +func (s *Service) Shutdown() { + if s == nil { + return + } + for _, consumer := range s.recipientConsumers { + if consumer != nil { + consumer.Close() + } + } + s.recipientConsumers = nil + s.pmstore = nil + s.storage = nil +} diff --git a/api/payments/methods/internal/service/methods/update.go b/api/payments/methods/internal/service/methods/update.go new file mode 100644 index 00000000..2f880c51 --- /dev/null +++ b/api/payments/methods/internal/service/methods/update.go @@ -0,0 +1,37 @@ +package methods + +import ( + "context" + + "github.com/tech/sendico/pkg/merrors" + methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1" +) + +func (s *Service) UpdatePaymentMethod(ctx context.Context, req *methodsv1.UpdatePaymentMethodRequest) (*methodsv1.UpdatePaymentMethodResponse, error) { + if req == nil { + return autoError[methodsv1.UpdatePaymentMethodResponse](ctx, s.logger, merrors.InvalidArgument("request is required")) + } + if s.pmstore == nil { + return autoError[methodsv1.UpdatePaymentMethodResponse](ctx, s.logger, errStoreUnavailable) + } + + accountRef, err := parseObjectID(req.GetAccountRef(), "account_ref") + if err != nil { + return autoError[methodsv1.UpdatePaymentMethodResponse](ctx, s.logger, err) + } + + pm, err := decodePaymentMethod(req.GetPaymentMethodJson()) + if err != nil { + return autoError[methodsv1.UpdatePaymentMethodResponse](ctx, s.logger, err) + } + if err := s.pmstore.Update(ctx, accountRef, pm); err != nil { + return autoError[methodsv1.UpdatePaymentMethodResponse](ctx, s.logger, err) + } + + payload, err := encodePaymentMethod(pm) + if err != nil { + return autoError[methodsv1.UpdatePaymentMethodResponse](ctx, s.logger, err) + } + + return &methodsv1.UpdatePaymentMethodResponse{PaymentMethodJson: payload}, nil +} diff --git a/api/payments/methods/internal/service/methods/util.go b/api/payments/methods/internal/service/methods/util.go new file mode 100644 index 00000000..a2eaa539 --- /dev/null +++ b/api/payments/methods/internal/service/methods/util.go @@ -0,0 +1,83 @@ +package methods + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/tech/sendico/pkg/api/routers/gsresponse" + "github.com/tech/sendico/pkg/merrors" + "github.com/tech/sendico/pkg/mlogger" + "github.com/tech/sendico/pkg/model" + "github.com/tech/sendico/pkg/mservice" + methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1" + "go.mongodb.org/mongo-driver/v2/bson" +) + +func autoError[T any](ctx context.Context, logger mlogger.Logger, err error) (*T, error) { + return gsresponse.Execute(ctx, gsresponse.Auto[T](logger, mservice.PaymentMethods, err)) +} + +func parseObjectID(value, field string) (bson.ObjectID, error) { + trimmed := strings.TrimSpace(value) + if trimmed == "" { + return bson.NilObjectID, merrors.InvalidArgument(fmt.Sprintf("%s is required", field), field) + } + ref, err := bson.ObjectIDFromHex(trimmed) + if err != nil { + return bson.NilObjectID, merrors.InvalidArgument(fmt.Sprintf("%s must be a valid object id", field), field) + } + return ref, nil +} + +func decodePaymentMethod(data []byte) (*model.PaymentMethod, error) { + if len(data) == 0 { + return nil, merrors.InvalidArgument("payment_method_json is required", "payment_method_json") + } + res := &model.PaymentMethod{} + if err := json.Unmarshal(data, res); err != nil { + return nil, merrors.InvalidArgumentWrap(err, "failed to decode payment method", "payment_method_json") + } + return res, nil +} + +func encodePaymentMethod(pm *model.PaymentMethod) ([]byte, error) { + if pm == nil { + return nil, merrors.InvalidArgument("payment method is required") + } + payload, err := json.Marshal(pm) + if err != nil { + return nil, merrors.InternalWrap(err, "failed to encode payment method") + } + return payload, nil +} + +func toModelCursor(cursor *methodsv1.ViewCursor) *model.ViewCursor { + if cursor == nil { + return nil + } + + res := &model.ViewCursor{} + hasAny := false + + if limit := cursor.GetLimit(); limit != nil { + v := limit.GetValue() + res.Limit = &v + hasAny = true + } + if offset := cursor.GetOffset(); offset != nil { + v := offset.GetValue() + res.Offset = &v + hasAny = true + } + if archived := cursor.GetIsArchived(); archived != nil { + v := archived.GetValue() + res.IsArchived = &v + hasAny = true + } + if !hasAny { + return nil + } + return res +} diff --git a/api/payments/methods/main.go b/api/payments/methods/main.go new file mode 100644 index 00000000..8db5a301 --- /dev/null +++ b/api/payments/methods/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "github.com/tech/sendico/payments/methods/internal/appversion" + si "github.com/tech/sendico/payments/methods/internal/server" + "github.com/tech/sendico/pkg/mlogger" + "github.com/tech/sendico/pkg/server" + smain "github.com/tech/sendico/pkg/server/main" +) + +func factory(logger mlogger.Logger, file string, debug bool) (server.Application, error) { + return si.Create(logger, file, debug) +} + +func main() { + smain.RunServer("main", appversion.Create(), factory) +} diff --git a/api/payments/orchestrator/go.mod b/api/payments/orchestrator/go.mod index 56fa2b5d..f30ae969 100644 --- a/api/payments/orchestrator/go.mod +++ b/api/payments/orchestrator/go.mod @@ -28,7 +28,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) @@ -57,8 +57,6 @@ require ( github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/crypto v0.48.0 // indirect diff --git a/api/payments/orchestrator/go.sum b/api/payments/orchestrator/go.sum index e6f2e0f0..07e82f8b 100644 --- a/api/payments/orchestrator/go.sum +++ b/api/payments/orchestrator/go.sum @@ -213,8 +213,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/payments/orchestrator/internal/service/orchestrator/service_helpers.go b/api/payments/orchestrator/internal/service/orchestrator/service_helpers.go index 38052c9f..f53833b9 100644 --- a/api/payments/orchestrator/internal/service/orchestrator/service_helpers.go +++ b/api/payments/orchestrator/internal/service/orchestrator/service_helpers.go @@ -7,6 +7,7 @@ import ( "github.com/tech/sendico/payments/storage" "github.com/tech/sendico/payments/storage/model" + quotestorage "github.com/tech/sendico/payments/storage/quote" "github.com/tech/sendico/pkg/api/routers/gsresponse" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" @@ -72,7 +73,7 @@ func ensurePaymentsStore(repo storage.Repository) (storage.PaymentsStore, error) return store, nil } -func ensureQuotesStore(repo storage.Repository) (storage.QuotesStore, error) { +func ensureQuotesStore(repo storage.Repository) (quotestorage.QuotesStore, error) { if repo == nil { return nil, errStorageUnavailable } diff --git a/api/payments/orchestrator/internal/service/orchestrator/service_helpers_test.go b/api/payments/orchestrator/internal/service/orchestrator/service_helpers_test.go index 770152bc..971c01f6 100644 --- a/api/payments/orchestrator/internal/service/orchestrator/service_helpers_test.go +++ b/api/payments/orchestrator/internal/service/orchestrator/service_helpers_test.go @@ -8,6 +8,7 @@ import ( ledgerclient "github.com/tech/sendico/ledger/client" "github.com/tech/sendico/payments/storage" "github.com/tech/sendico/payments/storage/model" + quotestorage "github.com/tech/sendico/payments/storage/quote" clockpkg "github.com/tech/sendico/pkg/clock" mloggerfactory "github.com/tech/sendico/pkg/mlogger/factory" "github.com/tech/sendico/pkg/model/account_role" @@ -380,7 +381,7 @@ func TestInitiatePaymentByQuoteRef(t *testing.T) { type stubRepo struct { payments storage.PaymentsStore - quotes storage.QuotesStore + quotes quotestorage.QuotesStore routes storage.RoutesStore plans storage.PlanTemplatesStore pingErr error @@ -388,8 +389,11 @@ type stubRepo struct { func (s stubRepo) Ping(context.Context) error { return s.pingErr } func (s stubRepo) Payments() storage.PaymentsStore { return s.payments } -func (s stubRepo) Quotes() storage.QuotesStore { return s.quotes } -func (s stubRepo) Routes() storage.RoutesStore { return s.routes } +func (s stubRepo) PaymentMethods() storage.PaymentMethodsStore { + return nil +} +func (s stubRepo) Quotes() quotestorage.QuotesStore { return s.quotes } +func (s stubRepo) Routes() storage.RoutesStore { return s.routes } func (s stubRepo) PlanTemplates() storage.PlanTemplatesStore { if s.plans != nil { return s.plans diff --git a/api/payments/orchestrator/internal/service/orchestrator/service_test.go b/api/payments/orchestrator/internal/service/orchestrator/service_test.go index a80baf65..c2f9caa8 100644 --- a/api/payments/orchestrator/internal/service/orchestrator/service_test.go +++ b/api/payments/orchestrator/internal/service/orchestrator/service_test.go @@ -10,6 +10,7 @@ import ( ledgerclient "github.com/tech/sendico/ledger/client" "github.com/tech/sendico/payments/storage" "github.com/tech/sendico/payments/storage/model" + quotestorage "github.com/tech/sendico/payments/storage/quote" "github.com/tech/sendico/pkg/api/routers/gsresponse" "github.com/tech/sendico/pkg/merrors" mo "github.com/tech/sendico/pkg/model" @@ -399,14 +400,17 @@ func TestProcessDepositObservedHandler_MatchesPayment(t *testing.T) { type stubRepository struct { store *stubPaymentsStore - quotes storage.QuotesStore + quotes quotestorage.QuotesStore routes storage.RoutesStore plans storage.PlanTemplatesStore } func (r *stubRepository) Ping(context.Context) error { return nil } func (r *stubRepository) Payments() storage.PaymentsStore { return r.store } -func (r *stubRepository) Quotes() storage.QuotesStore { +func (r *stubRepository) PaymentMethods() storage.PaymentMethodsStore { + return nil +} +func (r *stubRepository) Quotes() quotestorage.QuotesStore { if r.quotes != nil { return r.quotes } diff --git a/api/payments/quotation/go.mod b/api/payments/quotation/go.mod index 80044e13..eb998142 100644 --- a/api/payments/quotation/go.mod +++ b/api/payments/quotation/go.mod @@ -26,7 +26,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/payments/quotation/go.sum b/api/payments/quotation/go.sum index b5ad332c..c4d799eb 100644 --- a/api/payments/quotation/go.sum +++ b/api/payments/quotation/go.sum @@ -155,16 +155,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -213,8 +213,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/payments/quotation/internal/service/quotation/handlers_commands.go b/api/payments/quotation/internal/service/quotation/handlers_commands.go index 86d3c5ce..2b86f4c5 100644 --- a/api/payments/quotation/internal/service/quotation/handlers_commands.go +++ b/api/payments/quotation/internal/service/quotation/handlers_commands.go @@ -11,6 +11,7 @@ import ( "github.com/tech/sendico/payments/storage" "github.com/tech/sendico/payments/storage/model" + quotestorage "github.com/tech/sendico/payments/storage/quote" "github.com/tech/sendico/pkg/api/routers/gsresponse" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" @@ -114,7 +115,7 @@ func (h *quotePaymentCommand) prepareQuoteCtx(req *quotationv1.QuotePaymentReque func (h *quotePaymentCommand) quotePayment( ctx context.Context, - quotesStore storage.QuotesStore, + quotesStore quotestorage.QuotesStore, qc *quoteCtx, req *quotationv1.QuotePaymentRequest, ) (*quotePaymentResult, error) { @@ -415,7 +416,7 @@ func (h *quotePaymentsCommand) prepare(req *quotationv1.QuotePaymentsRequest) (* func (h *quotePaymentsCommand) tryReuse( ctx context.Context, - quotesStore storage.QuotesStore, + quotesStore quotestorage.QuotesStore, qc *quotePaymentsCtx, ) (*model.PaymentQuoteRecord, bool, error) { @@ -515,7 +516,7 @@ func (h *quotePaymentsCommand) aggregate( func (h *quotePaymentsCommand) storeBatch( ctx context.Context, - quotesStore storage.QuotesStore, + quotesStore quotestorage.QuotesStore, qc *quotePaymentsCtx, quoteRef string, intents []*sharedv1.PaymentIntent, diff --git a/api/payments/quotation/internal/service/quotation/handlers_commands_test.go b/api/payments/quotation/internal/service/quotation/handlers_commands_test.go index 59bfb1fc..9ff93376 100644 --- a/api/payments/quotation/internal/service/quotation/handlers_commands_test.go +++ b/api/payments/quotation/internal/service/quotation/handlers_commands_test.go @@ -8,6 +8,7 @@ import ( "github.com/tech/sendico/payments/storage" "github.com/tech/sendico/payments/storage/model" + quotestorage "github.com/tech/sendico/payments/storage/quote" "github.com/tech/sendico/pkg/merrors" mloggerfactory "github.com/tech/sendico/pkg/mlogger/factory" moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1" @@ -155,14 +156,15 @@ func (e *quoteCommandTestEngine) ResolvePaymentQuote(context.Context, quoteResol func (e *quoteCommandTestEngine) Repository() storage.Repository { return e.repo } type quoteCommandTestRepo struct { - quotes storage.QuotesStore + quotes quotestorage.QuotesStore } -func (r quoteCommandTestRepo) Ping(context.Context) error { return nil } -func (r quoteCommandTestRepo) Payments() storage.PaymentsStore { return nil } -func (r quoteCommandTestRepo) Quotes() storage.QuotesStore { return r.quotes } -func (r quoteCommandTestRepo) Routes() storage.RoutesStore { return nil } -func (r quoteCommandTestRepo) PlanTemplates() storage.PlanTemplatesStore { return nil } +func (r quoteCommandTestRepo) Ping(context.Context) error { return nil } +func (r quoteCommandTestRepo) Payments() storage.PaymentsStore { return nil } +func (r quoteCommandTestRepo) PaymentMethods() storage.PaymentMethodsStore { return nil } +func (r quoteCommandTestRepo) Quotes() quotestorage.QuotesStore { return r.quotes } +func (r quoteCommandTestRepo) Routes() storage.RoutesStore { return nil } +func (r quoteCommandTestRepo) PlanTemplates() storage.PlanTemplatesStore { return nil } type quoteCommandTestQuotesStore struct { byID map[string]*model.PaymentQuoteRecord diff --git a/api/payments/quotation/internal/service/quotation/service_helpers.go b/api/payments/quotation/internal/service/quotation/service_helpers.go index b738a813..fd6061ae 100644 --- a/api/payments/quotation/internal/service/quotation/service_helpers.go +++ b/api/payments/quotation/internal/service/quotation/service_helpers.go @@ -7,6 +7,7 @@ import ( "github.com/tech/sendico/payments/storage" "github.com/tech/sendico/payments/storage/model" + quotestorage "github.com/tech/sendico/payments/storage/quote" "github.com/tech/sendico/pkg/merrors" quotationv1 "github.com/tech/sendico/pkg/proto/payments/quotation/v1" sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1" @@ -42,7 +43,7 @@ func requireNonNilIntent(intent *sharedv1.PaymentIntent) error { return nil } -func ensureQuotesStore(repo storage.Repository) (storage.QuotesStore, error) { +func ensureQuotesStore(repo storage.Repository) (quotestorage.QuotesStore, error) { if repo == nil { return nil, errStorageUnavailable } diff --git a/api/payments/storage/mongo/repository.go b/api/payments/storage/mongo/repository.go index d716d268..ebc68cbd 100644 --- a/api/payments/storage/mongo/repository.go +++ b/api/payments/storage/mongo/repository.go @@ -7,11 +7,15 @@ import ( "github.com/tech/sendico/payments/storage" "github.com/tech/sendico/payments/storage/model" "github.com/tech/sendico/payments/storage/mongo/store" + quotestorage "github.com/tech/sendico/payments/storage/quote" quotemongo "github.com/tech/sendico/payments/storage/quote/mongo" + "github.com/tech/sendico/pkg/auth" "github.com/tech/sendico/pkg/db" "github.com/tech/sendico/pkg/db/repository" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" + "github.com/tech/sendico/pkg/mservice" + "go.mongodb.org/mongo-driver/v2/bson" ) // Store implements storage.Repository backed by MongoDB. @@ -20,13 +24,20 @@ type Store struct { ping func(context.Context) error payments storage.PaymentsStore - quotes storage.QuotesStore + methods storage.PaymentMethodsStore + quotes quotestorage.QuotesStore routes storage.RoutesStore plans storage.PlanTemplatesStore } +type paymentMethodsConfig struct { + enforcer auth.Enforcer + permissionRef bson.ObjectID +} + type options struct { - quoteRetention time.Duration + quoteRetention time.Duration + paymentMethodsAuth *paymentMethodsConfig } // Option configures the Mongo-backed payments repository. @@ -39,6 +50,16 @@ func WithQuoteRetention(retention time.Duration) Option { } } +// WithPaymentMethodsAuth enables the payment-methods store and permission checks. +func WithPaymentMethodsAuth(enforcer auth.Enforcer, permissionRef bson.ObjectID) Option { + return func(opts *options) { + opts.paymentMethodsAuth = &paymentMethodsConfig{ + enforcer: enforcer, + permissionRef: permissionRef, + } + } +} + // New constructs a Mongo-backed payments repository from a Mongo connection. func New(logger mlogger.Logger, conn *db.MongoConnection, opts ...Option) (*Store, error) { if conn == nil { @@ -48,11 +69,22 @@ func New(logger mlogger.Logger, conn *db.MongoConnection, opts ...Option) (*Stor quotesRepo := repository.CreateMongoRepository(conn.Database(), (&model.PaymentQuoteRecord{}).Collection()) routesRepo := repository.CreateMongoRepository(conn.Database(), (&model.PaymentRoute{}).Collection()) plansRepo := repository.CreateMongoRepository(conn.Database(), (&model.PaymentPlanTemplate{}).Collection()) - return NewWithRepository(logger, conn.Ping, paymentsRepo, quotesRepo, routesRepo, plansRepo, opts...) + methodsRepo := repository.CreateMongoRepository(conn.Database(), mservice.PaymentMethods) + + return newWithRepository(logger, conn.Ping, paymentsRepo, methodsRepo, quotesRepo, routesRepo, plansRepo, opts...) } // NewWithRepository constructs a payments repository using the provided primitives. func NewWithRepository(logger mlogger.Logger, ping func(context.Context) error, paymentsRepo repository.Repository, quotesRepo repository.Repository, routesRepo repository.Repository, plansRepo repository.Repository, opts ...Option) (*Store, error) { + return newWithRepository(logger, ping, paymentsRepo, nil, quotesRepo, routesRepo, plansRepo, opts...) +} + +func newWithRepository( + logger mlogger.Logger, + ping func(context.Context) error, + paymentsRepo, methodsRepo, quotesRepo, routesRepo, plansRepo repository.Repository, + opts ...Option, +) (*Store, error) { if ping == nil { return nil, merrors.InvalidArgument("payments.storage.mongo: ping func is nil") } @@ -93,10 +125,30 @@ func NewWithRepository(logger mlogger.Logger, ping func(context.Context) error, if err != nil { return nil, err } + + var methodsStore storage.PaymentMethodsStore + if cfg.paymentMethodsAuth != nil { + if methodsRepo == nil { + return nil, merrors.InvalidArgument("payments.storage.mongo: payment methods repository is nil") + } + if cfg.paymentMethodsAuth.enforcer == nil { + return nil, merrors.InvalidArgument("payments.storage.mongo: payment methods enforcer is nil") + } + if cfg.paymentMethodsAuth.permissionRef == bson.NilObjectID { + return nil, merrors.InvalidArgument("payments.storage.mongo: payment methods permission reference is required") + } + + methodsStore, err = store.NewPaymentMethods(childLogger, methodsRepo, cfg.paymentMethodsAuth.enforcer, cfg.paymentMethodsAuth.permissionRef) + if err != nil { + return nil, err + } + } + result := &Store{ logger: childLogger, ping: ping, payments: paymentsStore, + methods: methodsStore, quotes: quotesRepoStore.Quotes(), routes: routesStore, plans: plansStore, @@ -118,8 +170,13 @@ func (s *Store) Payments() storage.PaymentsStore { return s.payments } +// PaymentMethods returns the payment-methods store. +func (s *Store) PaymentMethods() storage.PaymentMethodsStore { + return s.methods +} + // Quotes returns the quotes store. -func (s *Store) Quotes() storage.QuotesStore { +func (s *Store) Quotes() quotestorage.QuotesStore { return s.quotes } diff --git a/api/payments/storage/mongo/store/payment_methods.go b/api/payments/storage/mongo/store/payment_methods.go new file mode 100644 index 00000000..24ea1983 --- /dev/null +++ b/api/payments/storage/mongo/store/payment_methods.go @@ -0,0 +1,235 @@ +package store + +import ( + "context" + "errors" + + "github.com/tech/sendico/payments/storage" + "github.com/tech/sendico/pkg/auth" + "github.com/tech/sendico/pkg/db/repository" + ri "github.com/tech/sendico/pkg/db/repository/index" + "github.com/tech/sendico/pkg/merrors" + "github.com/tech/sendico/pkg/mlogger" + pkgmodel "github.com/tech/sendico/pkg/model" + "github.com/tech/sendico/pkg/mservice" + mauth "github.com/tech/sendico/pkg/mutil/db/auth" + "go.mongodb.org/mongo-driver/v2/bson" + "go.uber.org/zap" +) + +type PaymentMethods struct { + logger mlogger.Logger + repo repository.Repository + enforcer auth.Enforcer + permissionRef bson.ObjectID +} + +// NewPaymentMethods constructs a Mongo-backed payment-methods store. +func NewPaymentMethods(logger mlogger.Logger, repo repository.Repository, enforcer auth.Enforcer, permissionRef bson.ObjectID) (*PaymentMethods, error) { + if repo == nil { + return nil, merrors.InvalidArgument("paymentMethodsStore: repository is nil") + } + if enforcer == nil { + return nil, merrors.InvalidArgument("paymentMethodsStore: enforcer is nil") + } + if permissionRef == bson.NilObjectID { + return nil, merrors.InvalidArgument("paymentMethodsStore: permission reference is required") + } + + indexes := []*ri.Definition{ + { + Keys: []ri.Key{ + {Field: "organizationRef", Sort: ri.Asc}, + {Field: "recipientRef", Sort: ri.Asc}, + }, + }, + { + Keys: []ri.Key{{Field: "recipientRef", Sort: ri.Asc}}, + }, + } + + for _, def := range indexes { + if err := repo.CreateIndex(def); err != nil { + logger.Error("failed to ensure payment methods index", zap.Error(err), zap.String("collection", repo.Collection())) + return nil, err + } + } + + return &PaymentMethods{ + logger: logger.Named("payment_methods"), + repo: repo, + enforcer: enforcer, + permissionRef: permissionRef, + }, nil +} + +func (p *PaymentMethods) Create(ctx context.Context, accountRef, organizationRef bson.ObjectID, method *pkgmodel.PaymentMethod) error { + if method == nil { + return merrors.InvalidArgument("paymentMethodsStore: nil payment method") + } + if accountRef == bson.NilObjectID { + return merrors.InvalidArgument("paymentMethodsStore: account_ref is required") + } + if organizationRef == bson.NilObjectID { + return merrors.InvalidArgument("paymentMethodsStore: organization_ref is required") + } + if method.RecipientRef == bson.NilObjectID { + return merrors.InvalidArgument("paymentMethodsStore: recipient_ref is required") + } + + if method.GetPermissionRef() == bson.NilObjectID { + method.SetPermissionRef(p.permissionRef) + } + method.SetOrganizationRef(organizationRef) + + allowed, err := p.enforcer.Enforce(ctx, method.GetPermissionRef(), accountRef, organizationRef, bson.NilObjectID, pkgmodel.ActionCreate) + if err != nil { + return err + } + if !allowed { + return merrors.AccessDenied(mservice.PaymentMethods, string(pkgmodel.ActionCreate), bson.NilObjectID) + } + + return p.repo.Insert(ctx, method, nil) +} + +func (p *PaymentMethods) Get(ctx context.Context, accountRef, methodRef bson.ObjectID) (*pkgmodel.PaymentMethod, error) { + if accountRef == bson.NilObjectID { + return nil, merrors.InvalidArgument("paymentMethodsStore: account_ref is required") + } + if methodRef == bson.NilObjectID { + return nil, merrors.InvalidArgument("paymentMethodsStore: method_ref is required") + } + + if err := p.enforceObject(ctx, accountRef, methodRef, pkgmodel.ActionRead); err != nil { + return nil, err + } + + method := &pkgmodel.PaymentMethod{} + if err := p.repo.Get(ctx, methodRef, method); err != nil { + return nil, err + } + return method, nil +} + +func (p *PaymentMethods) Update(ctx context.Context, accountRef bson.ObjectID, method *pkgmodel.PaymentMethod) error { + if method == nil { + return merrors.InvalidArgument("paymentMethodsStore: nil payment method") + } + if accountRef == bson.NilObjectID { + return merrors.InvalidArgument("paymentMethodsStore: account_ref is required") + } + if method.GetID() == nil || method.GetID().IsZero() { + return merrors.InvalidArgument("paymentMethodsStore: method id is required") + } + + if err := p.enforceObject(ctx, accountRef, *method.GetID(), pkgmodel.ActionUpdate); err != nil { + return err + } + + return p.repo.Update(ctx, method) +} + +func (p *PaymentMethods) Delete(ctx context.Context, accountRef, methodRef bson.ObjectID) error { + if accountRef == bson.NilObjectID { + return merrors.InvalidArgument("paymentMethodsStore: account_ref is required") + } + if methodRef == bson.NilObjectID { + return merrors.InvalidArgument("paymentMethodsStore: method_ref is required") + } + + if err := p.enforceObject(ctx, accountRef, methodRef, pkgmodel.ActionDelete); err != nil { + return err + } + + return p.repo.Delete(ctx, methodRef) +} + +func (p *PaymentMethods) DeleteCascade(ctx context.Context, accountRef, methodRef bson.ObjectID) error { + return p.Delete(ctx, accountRef, methodRef) +} + +func (p *PaymentMethods) SetArchived(ctx context.Context, accountRef, _ bson.ObjectID, methodRef bson.ObjectID, archived, _ bool) error { + if accountRef == bson.NilObjectID { + return merrors.InvalidArgument("paymentMethodsStore: account_ref is required") + } + if methodRef == bson.NilObjectID { + return merrors.InvalidArgument("paymentMethodsStore: method_ref is required") + } + + if err := p.enforceObject(ctx, accountRef, methodRef, pkgmodel.ActionUpdate); err != nil { + return err + } + + patch := repository.Patch().Set(repository.Field("isArchived"), archived) + return p.repo.Patch(ctx, methodRef, patch) +} + +func (p *PaymentMethods) List(ctx context.Context, accountRef, organizationRef, recipientRef bson.ObjectID, cursor *pkgmodel.ViewCursor) ([]pkgmodel.PaymentMethod, error) { + if accountRef == bson.NilObjectID { + return nil, merrors.InvalidArgument("paymentMethodsStore: account_ref is required") + } + if organizationRef == bson.NilObjectID { + return nil, merrors.InvalidArgument("paymentMethodsStore: organization_ref is required") + } + if recipientRef == bson.NilObjectID { + return nil, merrors.InvalidArgument("paymentMethodsStore: recipient_ref is required") + } + + items, err := mauth.GetProtectedObjects[pkgmodel.PaymentMethod]( + ctx, + p.logger, + accountRef, + organizationRef, + pkgmodel.ActionRead, + repository.OrgFilter(organizationRef).And(repository.Filter("recipientRef", recipientRef)), + cursor, + p.enforcer, + p.repo, + ) + if errors.Is(err, merrors.ErrNoData) { + return []pkgmodel.PaymentMethod{}, nil + } + return items, err +} + +func (p *PaymentMethods) SetArchivedByRecipient(ctx context.Context, recipientRef bson.ObjectID, archived bool) (int, error) { + if recipientRef == bson.NilObjectID { + return 0, merrors.InvalidArgument("paymentMethodsStore: recipient_ref is required") + } + + filter := repository.Filter("recipientRef", recipientRef) + patch := repository.Patch().Set(repository.Field("isArchived"), archived) + return p.repo.PatchMany(ctx, filter, patch) +} + +func (p *PaymentMethods) DeleteByRecipient(ctx context.Context, recipientRef bson.ObjectID) error { + if recipientRef == bson.NilObjectID { + return merrors.InvalidArgument("paymentMethodsStore: recipient_ref is required") + } + return p.repo.DeleteMany(ctx, repository.Filter("recipientRef", recipientRef)) +} + +func (p *PaymentMethods) enforceObject(ctx context.Context, accountRef, methodRef bson.ObjectID, action pkgmodel.Action) error { + refs, err := p.repo.ListPermissionBound(ctx, repository.IDFilter(methodRef)) + if err != nil { + if errors.Is(err, merrors.ErrNoData) { + return merrors.AccessDenied(mservice.PaymentMethods, string(action), methodRef) + } + return err + } + if len(refs) == 0 { + return merrors.AccessDenied(mservice.PaymentMethods, string(action), methodRef) + } + + allowed, err := p.enforcer.Enforce(ctx, refs[0].GetPermissionRef(), accountRef, refs[0].GetOrganizationRef(), methodRef, action) + if err != nil { + return err + } + if !allowed { + return merrors.AccessDenied(mservice.PaymentMethods, string(action), methodRef) + } + return nil +} + +var _ storage.PaymentMethodsStore = (*PaymentMethods)(nil) diff --git a/api/payments/storage/storage.go b/api/payments/storage/storage.go index 2eba4e2c..4a5a9085 100644 --- a/api/payments/storage/storage.go +++ b/api/payments/storage/storage.go @@ -5,6 +5,7 @@ import ( "github.com/tech/sendico/payments/storage/model" quotestorage "github.com/tech/sendico/payments/storage/quote" + pkgmodel "github.com/tech/sendico/pkg/model" "go.mongodb.org/mongo-driver/v2/bson" ) @@ -40,6 +41,7 @@ var ( type Repository interface { Ping(ctx context.Context) error Payments() PaymentsStore + PaymentMethods() PaymentMethodsStore Quotes() quotestorage.QuotesStore Routes() RoutesStore PlanTemplates() PlanTemplatesStore @@ -55,8 +57,19 @@ type PaymentsStore interface { List(ctx context.Context, filter *model.PaymentFilter) (*model.PaymentList, error) } -// Deprecated: use quote/storage.QuotesStore. -type QuotesStore = quotestorage.QuotesStore +// PaymentMethodsStore manages recipient-linked payment methods. +type PaymentMethodsStore interface { + Create(ctx context.Context, accountRef, organizationRef bson.ObjectID, method *pkgmodel.PaymentMethod) error + Get(ctx context.Context, accountRef, methodRef bson.ObjectID) (*pkgmodel.PaymentMethod, error) + Update(ctx context.Context, accountRef bson.ObjectID, method *pkgmodel.PaymentMethod) error + Delete(ctx context.Context, accountRef, methodRef bson.ObjectID) error + DeleteCascade(ctx context.Context, accountRef, methodRef bson.ObjectID) error + SetArchived(ctx context.Context, accountRef, organizationRef, methodRef bson.ObjectID, archived, cascade bool) error + List(ctx context.Context, accountRef, organizationRef, recipientRef bson.ObjectID, cursor *pkgmodel.ViewCursor) ([]pkgmodel.PaymentMethod, error) + + SetArchivedByRecipient(ctx context.Context, recipientRef bson.ObjectID, archived bool) (int, error) + DeleteByRecipient(ctx context.Context, recipientRef bson.ObjectID) error +} // RoutesStore manages allowed routing transitions. type RoutesStore interface { diff --git a/api/pkg/auth/factory.go b/api/pkg/auth/factory.go index 9efa1c9b..3e7fb12d 100644 --- a/api/pkg/auth/factory.go +++ b/api/pkg/auth/factory.go @@ -20,6 +20,12 @@ func CreateAuth( config *Config, ) (Enforcer, Manager, error) { lg := logger.Named("auth") + if config == nil || config.Driver == "" { + lg.Warn("Permissions enforcer config is missing, defaulting to native enforcer") + config = &Config{ + Driver: Native, + } + } lg.Debug("Creating enforcer...", zap.String("driver", string(config.Driver))) l := lg.Named(string(config.Driver)) if config.Driver == Casbin { diff --git a/api/pkg/db/factory.go b/api/pkg/db/factory.go index 30fbd538..4026bab1 100644 --- a/api/pkg/db/factory.go +++ b/api/pkg/db/factory.go @@ -7,7 +7,6 @@ import ( mongoimpl "github.com/tech/sendico/pkg/db/internal/mongo" "github.com/tech/sendico/pkg/db/invitation" "github.com/tech/sendico/pkg/db/organization" - "github.com/tech/sendico/pkg/db/paymethod" "github.com/tech/sendico/pkg/db/policy" "github.com/tech/sendico/pkg/db/recipient" "github.com/tech/sendico/pkg/db/refreshtokens" @@ -28,7 +27,6 @@ type Factory interface { NewOrganizationDB() (organization.DB, error) NewInvitationsDB() (invitation.DB, error) NewRecipientsDB() (recipient.DB, error) - NewPaymentMethodsDB() (paymethod.DB, error) NewVerificationsDB() (verification.DB, error) NewRolesDB() (role.DB, error) diff --git a/api/pkg/db/internal/mongo/db.go b/api/pkg/db/internal/mongo/db.go index 3a9d1785..a5ca103f 100755 --- a/api/pkg/db/internal/mongo/db.go +++ b/api/pkg/db/internal/mongo/db.go @@ -15,7 +15,6 @@ import ( "github.com/tech/sendico/pkg/db/internal/mongo/chainassetsdb" "github.com/tech/sendico/pkg/db/internal/mongo/invitationdb" "github.com/tech/sendico/pkg/db/internal/mongo/organizationdb" - "github.com/tech/sendico/pkg/db/internal/mongo/paymethoddb" "github.com/tech/sendico/pkg/db/internal/mongo/policiesdb" "github.com/tech/sendico/pkg/db/internal/mongo/recipientdb" "github.com/tech/sendico/pkg/db/internal/mongo/refreshtokensdb" @@ -24,7 +23,6 @@ import ( "github.com/tech/sendico/pkg/db/internal/mongo/verificationimp" "github.com/tech/sendico/pkg/db/invitation" "github.com/tech/sendico/pkg/db/organization" - "github.com/tech/sendico/pkg/db/paymethod" "github.com/tech/sendico/pkg/db/policy" "github.com/tech/sendico/pkg/db/recipient" "github.com/tech/sendico/pkg/db/refreshtokens" @@ -204,28 +202,18 @@ func (db *DB) NewOrganizationDB() (organization.DB, error) { } func (db *DB) NewRecipientsDB() (recipient.DB, error) { - pmdb, err := db.NewPaymentMethodsDB() - if err != nil { - db.logger.Warn("Failed to create payment methods database", zap.Error(err)) - return nil, err - } - create := func(ctx context.Context, logger mlogger.Logger, enforcer auth.Enforcer, pdb policy.DB, db *mongo.Database, ) (recipient.DB, error) { - return recipientdb.Create(ctx, logger, enforcer, pdb, pmdb, db) + return recipientdb.Create(ctx, logger, enforcer, pdb, db) } return newProtectedDB(db, create) } -func (db *DB) NewPaymentMethodsDB() (paymethod.DB, error) { - return newProtectedDB(db, paymethoddb.Create) -} - func (db *DB) NewRefreshTokensDB() (refreshtokens.DB, error) { return refreshtokensdb.Create(db.logger, db.db()) } diff --git a/api/pkg/db/internal/mongo/paymethoddb/archived.go b/api/pkg/db/internal/mongo/paymethoddb/archived.go deleted file mode 100644 index d466dba6..00000000 --- a/api/pkg/db/internal/mongo/paymethoddb/archived.go +++ /dev/null @@ -1,20 +0,0 @@ -package paymethoddb - -import ( - "context" - - "github.com/tech/sendico/pkg/mutil/mzap" - "go.mongodb.org/mongo-driver/v2/bson" - "go.uber.org/zap" -) - -func (db *PaymentMethodsDB) SetArchived(ctx context.Context, accountRef, organizationRef, objectRef bson.ObjectID, isArchived, cascade bool) error { - // Use the ArchivableDB for the main archiving logic - if err := db.ArchivableDB.SetArchived(ctx, accountRef, objectRef, isArchived); err != nil { - db.DBImp.Logger.Warn("Failed to chnage object archive status", zap.Error(err), - mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef), - mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", isArchived), zap.Bool("cascade", cascade)) - return err - } - return nil -} diff --git a/api/pkg/db/internal/mongo/paymethoddb/db.go b/api/pkg/db/internal/mongo/paymethoddb/db.go deleted file mode 100644 index 03fd6656..00000000 --- a/api/pkg/db/internal/mongo/paymethoddb/db.go +++ /dev/null @@ -1,59 +0,0 @@ -package paymethoddb - -import ( - "context" - - "github.com/tech/sendico/pkg/auth" - "github.com/tech/sendico/pkg/db/policy" - ri "github.com/tech/sendico/pkg/db/repository/index" - "github.com/tech/sendico/pkg/mlogger" - "github.com/tech/sendico/pkg/model" - "github.com/tech/sendico/pkg/mservice" - "go.mongodb.org/mongo-driver/v2/mongo" - "go.uber.org/zap" -) - -type PaymentMethodsDB struct { - auth.ProtectedDBImp[*model.PaymentMethod] - auth.ArchivableDB[*model.PaymentMethod] -} - -func Create(ctx context.Context, - logger mlogger.Logger, - enforcer auth.Enforcer, - pdb policy.DB, - db *mongo.Database, -) (*PaymentMethodsDB, error) { - p, err := auth.CreateDBImp[*model.PaymentMethod](ctx, logger, pdb, enforcer, mservice.PaymentMethods, db) - if err != nil { - return nil, err - } - - createEmpty := func() *model.PaymentMethod { - return &model.PaymentMethod{} - } - - getArchivable := func(c *model.PaymentMethod) model.Archivable { - return &c.ArchivableBase - } - - res := &PaymentMethodsDB{ - ProtectedDBImp: *p, - ArchivableDB: auth.NewArchivableDB( - p.DBImp, - logger, - p.Enforcer, - createEmpty, - getArchivable, - ), - } - - if err := res.DBImp.Repository.CreateIndex(&ri.Definition{ - Keys: []ri.Key{{Field: "recipientRef", Sort: ri.Asc}}, - }); err != nil { - res.DBImp.Logger.Error("Failed to create recipientRef index for payment methods", zap.Error(err)) - return nil, err - } - - return res, nil -} diff --git a/api/pkg/db/internal/mongo/paymethoddb/list.go b/api/pkg/db/internal/mongo/paymethoddb/list.go deleted file mode 100644 index 63be7a6e..00000000 --- a/api/pkg/db/internal/mongo/paymethoddb/list.go +++ /dev/null @@ -1,28 +0,0 @@ -package paymethoddb - -import ( - "context" - "errors" - - "github.com/tech/sendico/pkg/db/repository" - "github.com/tech/sendico/pkg/merrors" - "github.com/tech/sendico/pkg/model" - mauth "github.com/tech/sendico/pkg/mutil/db/auth" - "go.mongodb.org/mongo-driver/v2/bson" -) - -func (db *PaymentMethodsDB) List(ctx context.Context, accountRef, organizationRef, recipientRef bson.ObjectID, cursor *model.ViewCursor) ([]model.PaymentMethod, error) { - res, err := mauth.GetProtectedObjects[model.PaymentMethod]( - ctx, - db.DBImp.Logger, - accountRef, organizationRef, model.ActionRead, - repository.OrgFilter(organizationRef).And(repository.Filter("recipientRef", recipientRef)), - cursor, - db.Enforcer, - db.DBImp.Repository, - ) - if errors.Is(err, merrors.ErrNoData) { - return []model.PaymentMethod{}, nil - } - return res, err -} diff --git a/api/pkg/db/internal/mongo/recipientdb/archived.go b/api/pkg/db/internal/mongo/recipientdb/archived.go index 1fd0240d..37e54532 100644 --- a/api/pkg/db/internal/mongo/recipientdb/archived.go +++ b/api/pkg/db/internal/mongo/recipientdb/archived.go @@ -2,16 +2,13 @@ package recipientdb import ( "context" - "errors" - "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mutil/mzap" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) func (db *RecipientDB) SetArchived(ctx context.Context, accountRef, organizationRef, objectRef bson.ObjectID, isArchived, cascade bool) error { - // Use the ArchivableDB for the main archiving logic if err := db.ArchivableDB.SetArchived(ctx, accountRef, objectRef, isArchived); err != nil { db.DBImp.Logger.Warn("Failed to change recipient archive status", zap.Error(err), mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef), @@ -19,39 +16,5 @@ func (db *RecipientDB) SetArchived(ctx context.Context, accountRef, organization return err } - if cascade { - if err := db.setArchivedPaymentMethods(ctx, accountRef, organizationRef, objectRef, isArchived); err != nil { - db.DBImp.Logger.Warn("Failed to update payment methods archive status", zap.Error(err), - mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef), - mzap.ObjRef("recipient_ref", objectRef), zap.Bool("archived", isArchived), zap.Bool("cascade", cascade)) - - return err - } - } - - return nil -} - -func (db *RecipientDB) setArchivedPaymentMethods(ctx context.Context, accountRef, organizationRef, recipientRef bson.ObjectID, archived bool) error { - db.DBImp.Logger.Debug("Setting archived status for recipient payment methods", mzap.ObjRef("recipient_ref", recipientRef), zap.Bool("archived", archived)) - - db.DBImp.Logger.Debug("Applying archived status to payment methods for recipient", mzap.ObjRef("recipient_ref", recipientRef)) - - // Get all payMethods for the recipient - payMethods, err := db.pmdb.List(ctx, accountRef, organizationRef, recipientRef, nil) - if err != nil && !errors.Is(err, merrors.ErrNoData) { - db.DBImp.Logger.Warn("Failed to fetch payment methods for recipient", zap.Error(err), mzap.ObjRef("recipient_ref", recipientRef)) - return err - } - - // Archive each payment method - for _, pmethod := range payMethods { - if err := db.pmdb.SetArchived(ctx, accountRef, organizationRef, pmethod.ID, archived, true); err != nil { - db.DBImp.Logger.Warn("Failed to set archived status for payment method", zap.Error(err), mzap.ObjRef("payment_method_ref", pmethod.ID)) - return err - } - } - - db.DBImp.Logger.Debug("Successfully updated payment methods archived status", zap.Int("count", len(payMethods)), mzap.ObjRef("recipient_ref", recipientRef)) return nil } diff --git a/api/pkg/db/internal/mongo/recipientdb/db.go b/api/pkg/db/internal/mongo/recipientdb/db.go index 4135c73f..82b6c511 100644 --- a/api/pkg/db/internal/mongo/recipientdb/db.go +++ b/api/pkg/db/internal/mongo/recipientdb/db.go @@ -4,7 +4,6 @@ import ( "context" "github.com/tech/sendico/pkg/auth" - "github.com/tech/sendico/pkg/db/paymethod" "github.com/tech/sendico/pkg/db/policy" "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/model" @@ -15,14 +14,12 @@ import ( type RecipientDB struct { auth.ProtectedDBImp[*model.Recipient] auth.ArchivableDB[*model.Recipient] - pmdb paymethod.DB } func Create(ctx context.Context, logger mlogger.Logger, enforcer auth.Enforcer, pdb policy.DB, - pmdb paymethod.DB, db *mongo.Database, ) (*RecipientDB, error) { p, err := auth.CreateDBImp[*model.Recipient](ctx, logger, pdb, enforcer, mservice.Recipients, db) @@ -47,7 +44,6 @@ func Create(ctx context.Context, createEmpty, getArchivable, ), - pmdb: pmdb, } return res, nil } diff --git a/api/pkg/db/paymethod/db.go b/api/pkg/db/paymethod/db.go deleted file mode 100644 index 8a915b1c..00000000 --- a/api/pkg/db/paymethod/db.go +++ /dev/null @@ -1,15 +0,0 @@ -package paymethod - -import ( - "context" - - "github.com/tech/sendico/pkg/auth" - "github.com/tech/sendico/pkg/model" - "go.mongodb.org/mongo-driver/v2/bson" -) - -type DB interface { - auth.ProtectedDB[*model.PaymentMethod] - SetArchived(ctx context.Context, accountRef, organizationRef, methodRef bson.ObjectID, archived, cascade bool) error - List(ctx context.Context, accountRef, organizationRef, recipientRef bson.ObjectID, cursor *model.ViewCursor) ([]model.PaymentMethod, error) -} diff --git a/api/pkg/go.mod b/api/pkg/go.mod index 4b252cd4..ce46df58 100644 --- a/api/pkg/go.mod +++ b/api/pkg/go.mod @@ -17,7 +17,7 @@ require ( go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 golang.org/x/crypto v0.48.0 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.0 google.golang.org/protobuf v1.36.11 ) @@ -81,10 +81,10 @@ require ( go.mongodb.org/mongo-driver v1.17.8 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/net v0.50.0 // indirect diff --git a/api/pkg/go.sum b/api/pkg/go.sum index 091b390c..cc8962ea 100644 --- a/api/pkg/go.sum +++ b/api/pkg/go.sum @@ -176,20 +176,20 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -269,12 +269,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE= -google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/pkg/model/internal/notificationevent.go b/api/pkg/model/internal/notificationevent.go index 56230793..3bffd862 100644 --- a/api/pkg/model/internal/notificationevent.go +++ b/api/pkg/model/internal/notificationevent.go @@ -63,7 +63,7 @@ func FromStringImp(s string) (*NotificationEventImp, error) { func StringToNotificationAction(s string) (nm.NotificationAction, error) { switch nm.NotificationAction(s) { - case nm.NACreated, nm.NAPending, nm.NAUpdated, nm.NADeleted, nm.NAAssigned, nm.NAPasswordReset, nm.NAConfirmationRequest, nm.NATelegramReaction, nm.NAPaymentGatewayIntent, nm.NAPaymentGatewayExecution, nm.NADiscoveryServiceAnnounce, nm.NADiscoveryGatewayAnnounce, nm.NADiscoveryHeartbeat, nm.NADiscoveryLookupRequest, nm.NADiscoveryLookupResponse, nm.NADiscoveryRefreshUI: + case nm.NACreated, nm.NAPending, nm.NAUpdated, nm.NAArchived, nm.NADeleted, nm.NAAssigned, nm.NAPasswordReset, nm.NAConfirmationRequest, nm.NATelegramReaction, nm.NAPaymentGatewayIntent, nm.NAPaymentGatewayExecution, nm.NADiscoveryServiceAnnounce, nm.NADiscoveryGatewayAnnounce, nm.NADiscoveryHeartbeat, nm.NADiscoveryLookupRequest, nm.NADiscoveryLookupResponse, nm.NADiscoveryRefreshUI: return nm.NotificationAction(s), nil default: return "", merrors.DataConflict("invalid Notification action: " + s) diff --git a/api/pkg/model/notificationevent.go b/api/pkg/model/notificationevent.go index 2c81f4ea..995e7e39 100644 --- a/api/pkg/model/notificationevent.go +++ b/api/pkg/model/notificationevent.go @@ -78,6 +78,7 @@ func StringToNotificationAction(s string) (nm.NotificationAction, error) { case nm.NACreated, nm.NAPending, nm.NAUpdated, + nm.NAArchived, nm.NADeleted, nm.NAAssigned, nm.NAPasswordReset, diff --git a/api/pkg/mservice/services.go b/api/pkg/mservice/services.go index 0605cf09..eb6a7177 100644 --- a/api/pkg/mservice/services.go +++ b/api/pkg/mservice/services.go @@ -60,8 +60,8 @@ func StringToSType(s string) (Type, error) { case Accounts, Verification, Amplitude, Site, Changes, Clients, ChainGateway, ChainWallets, ChainWalletBalances, ChainTransfers, ChainDeposits, MntxGateway, PaymentGateway, FXOracle, FeePlans, BillingDocuments, FilterProjects, Invitations, Invoices, Logo, Ledger, LedgerAccounts, LedgerBalances, LedgerEntries, LedgerOutbox, LedgerParties, LedgerPlines, Notifications, - Organizations, Payments, PaymentRoutes, PaymentPlanTemplates, PaymentOrchestrator, Permissions, Policies, PolicyAssignements, - RefreshTokens, Roles, Storage, Tenants, Workflows, Discovery: + Organizations, Payments, PaymentRoutes, PaymentPlanTemplates, PaymentOrchestrator, PaymentMethods, Permissions, Policies, PolicyAssignements, + Recipients, RefreshTokens, Roles, Storage, Tenants, Workflows, Discovery: return Type(s), nil default: return "", merrors.InvalidArgument("invalid service type", s) diff --git a/api/proto/payments/methods/v1/methods.proto b/api/proto/payments/methods/v1/methods.proto new file mode 100644 index 00000000..0aa05d68 --- /dev/null +++ b/api/proto/payments/methods/v1/methods.proto @@ -0,0 +1,80 @@ +syntax = "proto3"; + +package payments.methods.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/payments/methods/v1;methodsv1"; + +import "google/protobuf/wrappers.proto"; + +message ViewCursor { + google.protobuf.Int64Value limit = 1; + google.protobuf.Int64Value offset = 2; + google.protobuf.BoolValue is_archived = 3; +} + + +message CreatePaymentMethodRequest { + string account_ref = 1; + string organization_ref = 2; + bytes payment_method_json = 3; +} + +message CreatePaymentMethodResponse { + bytes payment_method_json = 1; +} + +message GetPaymentMethodRequest { + string account_ref = 1; + string payment_method_ref = 2; +} + +message GetPaymentMethodResponse { + bytes payment_method_json = 1; +} + +message UpdatePaymentMethodRequest { + string account_ref = 1; + bytes payment_method_json = 2; +} + +message UpdatePaymentMethodResponse { + bytes payment_method_json = 1; +} + +message DeletePaymentMethodRequest { + string account_ref = 1; + string payment_method_ref = 2; + bool cascade = 3; +} + +message DeletePaymentMethodResponse {} + +message SetPaymentMethodArchivedRequest { + string account_ref = 1; + string organization_ref = 2; + string payment_method_ref = 3; + bool archived = 4; + bool cascade = 5; +} + +message SetPaymentMethodArchivedResponse {} + +message ListPaymentMethodsRequest { + string account_ref = 1; + string organization_ref = 2; + string recipient_ref = 3; + ViewCursor cursor = 4; +} + +message ListPaymentMethodsResponse { + repeated bytes payment_methods_json = 1; +} + +service PaymentMethodsService { + rpc CreatePaymentMethod(CreatePaymentMethodRequest) returns (CreatePaymentMethodResponse); + rpc GetPaymentMethod(GetPaymentMethodRequest) returns (GetPaymentMethodResponse); + rpc UpdatePaymentMethod(UpdatePaymentMethodRequest) returns (UpdatePaymentMethodResponse); + rpc DeletePaymentMethod(DeletePaymentMethodRequest) returns (DeletePaymentMethodResponse); + rpc SetPaymentMethodArchived(SetPaymentMethodArchivedRequest) returns (SetPaymentMethodArchivedResponse); + rpc ListPaymentMethods(ListPaymentMethodsRequest) returns (ListPaymentMethodsResponse); +} diff --git a/api/server/config.dev.yml b/api/server/config.dev.yml index 97a05aeb..bae06ea4 100755 --- a/api/server/config.dev.yml +++ b/api/server/config.dev.yml @@ -103,6 +103,12 @@ api: dial_timeout_seconds: 5 call_timeout_seconds: 5 insecure: true + payment_methods: + address: dev-payments-methods:50066 + address_env: PAYMENTS_METHODS_ADDRESS + dial_timeout_seconds: 5 + call_timeout_seconds: 5 + insecure: true app: diff --git a/api/server/config.yml b/api/server/config.yml index 1a424b75..42440297 100755 --- a/api/server/config.yml +++ b/api/server/config.yml @@ -103,6 +103,12 @@ api: dial_timeout_seconds: 5 call_timeout_seconds: 5 insecure: true + payment_methods: + address: sendico_payments_methods:50066 + address_env: PAYMENTS_METHODS_ADDRESS + dial_timeout_seconds: 5 + call_timeout_seconds: 5 + insecure: true app: diff --git a/api/server/go.mod b/api/server/go.mod index 5e52e34f..4a5ab6bd 100644 --- a/api/server/go.mod +++ b/api/server/go.mod @@ -8,6 +8,8 @@ replace github.com/tech/sendico/ledger => ../ledger replace github.com/tech/sendico/payments/orchestrator => ../payments/orchestrator +replace github.com/tech/sendico/payments/methods => ../payments/methods + replace github.com/tech/sendico/payments/storage => ../payments/storage replace github.com/tech/sendico/gateway/tron => ../gateway/tron @@ -27,6 +29,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tech/sendico/gateway/tron v0.0.0-00010101000000-000000000000 github.com/tech/sendico/ledger v0.0.0-00010101000000-000000000000 + github.com/tech/sendico/payments/methods v0.0.0-00010101000000-000000000000 github.com/tech/sendico/payments/orchestrator v0.0.0-00010101000000-000000000000 github.com/tech/sendico/pkg v0.1.0 github.com/testcontainers/testcontainers-go v0.33.0 @@ -34,7 +37,7 @@ require ( go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 golang.org/x/net v0.50.0 - google.golang.org/grpc v1.78.0 + google.golang.org/grpc v1.79.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 moul.io/chizap v1.0.3 diff --git a/api/server/go.sum b/api/server/go.sum index 79826cbb..eb538f72 100644 --- a/api/server/go.sum +++ b/api/server/go.sum @@ -365,8 +365,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1: google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= +google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/server/interface/api/config.go b/api/server/interface/api/config.go index a7c7f5bc..0f7898b7 100644 --- a/api/server/interface/api/config.go +++ b/api/server/interface/api/config.go @@ -12,6 +12,7 @@ type Config struct { Ledger *LedgerConfig `yaml:"ledger"` PaymentOrchestrator *PaymentOrchestratorConfig `yaml:"payment_orchestrator"` PaymentQuotation *PaymentOrchestratorConfig `yaml:"payment_quotation"` + PaymentMethods *PaymentOrchestratorConfig `yaml:"payment_methods"` } type ChainGatewayConfig struct { diff --git a/api/server/internal/api/discovery_resolver.go b/api/server/internal/api/discovery_resolver.go index f9e4da72..c3876691 100644 --- a/api/server/internal/api/discovery_resolver.go +++ b/api/server/internal/api/discovery_resolver.go @@ -27,6 +27,7 @@ const ( ledgerDebitOperation = "ledger.debit" ledgerCreditOperation = "ledger.credit" gatewayReadBalanceOperation = "balance.read" + paymentMethodsReadOperation = "payment_methods.read" ) var ( @@ -44,6 +45,11 @@ var ( "PAYMENT_QUOTATION", "payment_quotation", } + paymentMethodsDiscoveryServiceNames = []string{ + "PAYMENTS_METHODS", + "PAYMENT_METHODS", + string(mservice.PaymentMethods), + } ) type discoveryEndpoint struct { @@ -105,6 +111,7 @@ func (a *APIImp) resolveServiceAddressesFromDiscovery() { orchestratorFound, orchestratorEndpoint := a.resolvePaymentOrchestratorAddress(lookup.Services) a.resolveLedgerAddress(lookup.Services) a.resolvePaymentQuotationAddress(lookup.Services, orchestratorFound, orchestratorEndpoint) + a.resolvePaymentMethodsAddress(lookup.Services) } func (a *APIImp) resolveChainGatewayAddress(gateways []discovery.GatewaySummary) { @@ -218,6 +225,30 @@ func (a *APIImp) resolvePaymentQuotationAddress(services []discovery.ServiceSumm zap.Bool("insecure", endpoint.insecure)) } +func (a *APIImp) resolvePaymentMethodsAddress(services []discovery.ServiceSummary) { + endpoint, selected, ok := selectServiceEndpoint( + services, + paymentMethodsDiscoveryServiceNames, + []string{paymentMethodsReadOperation}, + ) + if !ok { + return + } + + cfg := ensurePaymentMethodsConfig(a.config) + cfg.Address = endpoint.address + cfg.Insecure = endpoint.insecure + ensureTimeoutsPayment(cfg) + + a.logger.Info("Resolved payment methods address from discovery", + zap.String("service", selected.Service), + zap.String("service_id", selected.ID), + zap.String("instance_id", selected.InstanceID), + zap.String("invoke_uri", endpoint.raw), + zap.String("address", endpoint.address), + zap.Bool("insecure", endpoint.insecure)) +} + func selectServiceEndpoint(services []discovery.ServiceSummary, serviceNames []string, requiredOps []string) (discoveryEndpoint, discovery.ServiceSummary, bool) { selections := make([]serviceSelection, 0) for _, svc := range services { @@ -429,6 +460,16 @@ func ensurePaymentQuotationConfig(cfg *eapi.Config) *eapi.PaymentOrchestratorCon return cfg.PaymentQuotation } +func ensurePaymentMethodsConfig(cfg *eapi.Config) *eapi.PaymentOrchestratorConfig { + if cfg == nil { + return nil + } + if cfg.PaymentMethods == nil { + cfg.PaymentMethods = &eapi.PaymentOrchestratorConfig{} + } + return cfg.PaymentMethods +} + func ensureTimeoutsLedger(cfg *eapi.LedgerConfig) { if cfg == nil { return diff --git a/api/server/internal/server/accountapiimp/service.go b/api/server/internal/server/accountapiimp/service.go index 76bdfba7..53503589 100644 --- a/api/server/internal/server/accountapiimp/service.go +++ b/api/server/internal/server/accountapiimp/service.go @@ -7,6 +7,7 @@ import ( "strings" "time" + ledgerclient "github.com/tech/sendico/ledger/client" trongatewayclient "github.com/tech/sendico/gateway/tron/client" api "github.com/tech/sendico/pkg/api/http" "github.com/tech/sendico/pkg/auth" @@ -22,6 +23,7 @@ import ( "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/mservice" chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" + ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1" "github.com/tech/sendico/server/interface/accountservice" eapi "github.com/tech/sendico/server/interface/api" "github.com/tech/sendico/server/interface/services/fileservice" @@ -49,6 +51,7 @@ type AccountAPI struct { accountsPermissionRef bson.ObjectID accService accountservice.AccountService chainGateway chainWalletClient + ledgerClient ledgerAccountClient chainAsset *chainv1.Asset } @@ -57,6 +60,11 @@ type chainWalletClient interface { Close() error } +type ledgerAccountClient interface { + CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) + Close() error +} + func (a *AccountAPI) Name() mservice.Type { return mservice.Accounts } @@ -70,6 +78,11 @@ func (a *AccountAPI) Finish(ctx context.Context) error { a.logger.Warn("Failed to close chain gateway client", zap.Error(err)) } } + if a.ledgerClient != nil { + if err := a.ledgerClient.Close(); err != nil { + a.logger.Warn("Failed to close ledger client", zap.Error(err)) + } + } return nil } @@ -158,6 +171,10 @@ func CreateAPI(a eapi.API) (*AccountAPI, error) { p.logger.Error("Failed to initialize chain gateway client", zap.Error(err)) return nil, err } + if err := p.initLedgerClient(cfg.Ledger); err != nil { + p.logger.Error("Failed to initialize ledger client", zap.Error(err)) + return nil, err + } return p, nil } @@ -198,6 +215,35 @@ func (a *AccountAPI) initChainGateway(cfg *eapi.ChainGatewayConfig) error { return nil } +func (a *AccountAPI) initLedgerClient(cfg *eapi.LedgerConfig) error { + if cfg == nil { + return merrors.InvalidArgument("ledger configuration is not provided") + } + + address := strings.TrimSpace(cfg.Address) + if address == "" { + address = strings.TrimSpace(os.Getenv(cfg.AddressEnv)) + } + if address == "" { + return merrors.InvalidArgument(fmt.Sprintf("ledger address is not specified and address env %s is empty", cfg.AddressEnv)) + } + + clientCfg := ledgerclient.Config{ + Address: address, + DialTimeout: time.Duration(cfg.DialTimeoutSeconds) * time.Second, + CallTimeout: time.Duration(cfg.CallTimeoutSeconds) * time.Second, + Insecure: cfg.Insecure, + } + + client, err := ledgerclient.New(context.Background(), clientCfg) + if err != nil { + return err + } + + a.ledgerClient = client + return nil +} + func buildGatewayAsset(cfg eapi.ChainGatewayAssetConfig) (*chainv1.Asset, error) { chain, err := parseChainNetwork(cfg.Chain) if err != nil { diff --git a/api/server/internal/server/accountapiimp/signup.go b/api/server/internal/server/accountapiimp/signup.go index 1f850bba..9d1e22c1 100644 --- a/api/server/internal/server/accountapiimp/signup.go +++ b/api/server/internal/server/accountapiimp/signup.go @@ -19,6 +19,7 @@ import ( "github.com/tech/sendico/pkg/mutil/mzap" describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1" chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" + ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1" "github.com/tech/sendico/server/interface/api/srequest" "github.com/tech/sendico/server/interface/api/sresponse" "go.mongodb.org/mongo-driver/v2/bson" @@ -199,6 +200,10 @@ func (a *AccountAPI) signupTransactionBody(ctx context.Context, sr *srequest.Sig return nil, err } a.logger.Info("Organization wallet created", mzap.StorableRef(org), zap.String("account", sr.Account.Login)) + if err := a.openOrgLedgerAccount(ctx, org, sr); err != nil { + return nil, err + } + a.logger.Info("Organization ledger account created", mzap.StorableRef(org), zap.String("account", sr.Account.Login)) roleDescription, err := a.pmanager.Role().Create(ctx, org.ID, &sr.OwnerRole) if err != nil { @@ -323,3 +328,57 @@ func (a *AccountAPI) openOrgWallet(ctx context.Context, org *model.Organization, a.logger.Info("Managed wallet created for organization", mzap.StorableRef(org), zap.String("wallet_ref", resp.Wallet.WalletRef)) return nil } + +func (a *AccountAPI) openOrgLedgerAccount(ctx context.Context, org *model.Organization, sr *srequest.Signup) error { + if a.ledgerClient == nil { + a.logger.Warn("Ledger client not configured, skipping ledger account creation", mzap.StorableRef(org)) + return merrors.Internal("ledger client is not configured") + } + if a.chainAsset == nil { + return merrors.Internal("chain gateway default asset is not configured") + } + + currency := strings.ToUpper(strings.TrimSpace(a.chainAsset.TokenSymbol)) + if currency == "" { + return merrors.Internal("chain gateway default asset token symbol is not configured") + } + + var describable *describablev1.Describable + name := strings.TrimSpace(sr.LedgerWallet.Name) + var description *string + if sr.LedgerWallet.Description != nil { + trimmed := strings.TrimSpace(*sr.LedgerWallet.Description) + if trimmed != "" { + description = &trimmed + } + } + if name != "" || description != nil { + describable = &describablev1.Describable{ + Name: name, + Description: description, + } + } + + resp, err := a.ledgerClient.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{ + OrganizationRef: org.ID.Hex(), + AccountType: ledgerv1.AccountType_ACCOUNT_TYPE_ASSET, + Currency: currency, + Status: ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE, + Role: ledgerv1.AccountRole_ACCOUNT_ROLE_OPERATING, + Metadata: map[string]string{ + "source": "signup", + "login": sr.Account.Login, + }, + Describable: describable, + }) + if err != nil { + a.logger.Warn("Failed to create ledger account for organization", zap.Error(err), mzap.StorableRef(org)) + return err + } + if resp == nil || resp.GetAccount() == nil || strings.TrimSpace(resp.GetAccount().GetLedgerAccountRef()) == "" { + return merrors.Internal("ledger returned empty account reference") + } + + a.logger.Info("Ledger account created for organization", mzap.StorableRef(org), zap.String("ledger_account_ref", resp.GetAccount().GetLedgerAccountRef())) + return nil +} diff --git a/api/server/internal/server/accountapiimp/signup_ledger_test.go b/api/server/internal/server/accountapiimp/signup_ledger_test.go new file mode 100644 index 00000000..d6981a98 --- /dev/null +++ b/api/server/internal/server/accountapiimp/signup_ledger_test.go @@ -0,0 +1,116 @@ +package accountapiimp + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tech/sendico/pkg/merrors" + "github.com/tech/sendico/pkg/model" + chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" + ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1" + "github.com/tech/sendico/server/interface/api/srequest" + "go.mongodb.org/mongo-driver/v2/bson" + "go.uber.org/zap" +) + +type stubLedgerAccountClient struct { + createReq *ledgerv1.CreateAccountRequest + createResp *ledgerv1.CreateAccountResponse + createErr error +} + +func (s *stubLedgerAccountClient) CreateAccount(_ context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) { + s.createReq = req + return s.createResp, s.createErr +} + +func (s *stubLedgerAccountClient) Close() error { + return nil +} + +func TestOpenOrgLedgerAccount(t *testing.T) { + t.Run("creates operating ledger account", func(t *testing.T) { + desc := " Main org ledger account " + sr := &srequest.Signup{ + Account: model.AccountData{ + LoginData: model.LoginData{ + UserDataBase: model.UserDataBase{ + Login: "owner@example.com", + }, + }, + }, + LedgerWallet: model.Describable{ + Name: " Primary Ledger ", + Description: &desc, + }, + } + + org := &model.Organization{} + org.SetID(bson.NewObjectID()) + + ledgerStub := &stubLedgerAccountClient{ + createResp: &ledgerv1.CreateAccountResponse{ + Account: &ledgerv1.LedgerAccount{LedgerAccountRef: bson.NewObjectID().Hex()}, + }, + } + api := &AccountAPI{ + logger: zap.NewNop(), + ledgerClient: ledgerStub, + chainAsset: &chainv1.Asset{ + TokenSymbol: " usdt ", + }, + } + + err := api.openOrgLedgerAccount(context.Background(), org, sr) + assert.NoError(t, err) + if assert.NotNil(t, ledgerStub.createReq) { + assert.Equal(t, org.ID.Hex(), ledgerStub.createReq.GetOrganizationRef()) + assert.Equal(t, "USDT", ledgerStub.createReq.GetCurrency()) + assert.Equal(t, ledgerv1.AccountType_ACCOUNT_TYPE_ASSET, ledgerStub.createReq.GetAccountType()) + assert.Equal(t, ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE, ledgerStub.createReq.GetStatus()) + assert.Equal(t, ledgerv1.AccountRole_ACCOUNT_ROLE_OPERATING, ledgerStub.createReq.GetRole()) + assert.Equal(t, map[string]string{ + "source": "signup", + "login": "owner@example.com", + }, ledgerStub.createReq.GetMetadata()) + if assert.NotNil(t, ledgerStub.createReq.GetDescribable()) { + assert.Equal(t, "Primary Ledger", ledgerStub.createReq.GetDescribable().GetName()) + if assert.NotNil(t, ledgerStub.createReq.GetDescribable().Description) { + assert.Equal(t, "Main org ledger account", ledgerStub.createReq.GetDescribable().GetDescription()) + } + } + } + }) + + t.Run("fails when ledger client is missing", func(t *testing.T) { + api := &AccountAPI{ + logger: zap.NewNop(), + chainAsset: &chainv1.Asset{ + TokenSymbol: "USDT", + }, + } + + err := api.openOrgLedgerAccount(context.Background(), &model.Organization{}, &srequest.Signup{}) + assert.Error(t, err) + assert.True(t, errors.Is(err, merrors.ErrInternal)) + }) + + t.Run("fails when ledger response has empty reference", func(t *testing.T) { + ledgerStub := &stubLedgerAccountClient{ + createResp: &ledgerv1.CreateAccountResponse{}, + } + api := &AccountAPI{ + logger: zap.NewNop(), + ledgerClient: ledgerStub, + chainAsset: &chainv1.Asset{ + TokenSymbol: "USDT", + }, + } + + err := api.openOrgLedgerAccount(context.Background(), &model.Organization{}, &srequest.Signup{}) + assert.Error(t, err) + assert.True(t, errors.Is(err, merrors.ErrInternal)) + }) +} diff --git a/api/server/internal/server/paymethodsimp/service.go b/api/server/internal/server/paymethodsimp/service.go index 7b4147a7..538e3d08 100644 --- a/api/server/internal/server/paymethodsimp/service.go +++ b/api/server/internal/server/paymethodsimp/service.go @@ -2,45 +2,352 @@ package paymethodsimp import ( "context" + "encoding/json" + "io" + "net/http" + "os" + "strings" + "time" - "github.com/tech/sendico/pkg/db/paymethod" + methodsclient "github.com/tech/sendico/payments/methods/client" + api "github.com/tech/sendico/pkg/api/http" + "github.com/tech/sendico/pkg/api/http/response" + "github.com/tech/sendico/pkg/merrors" + "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/mservice" + methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1" eapi "github.com/tech/sendico/server/interface/api" - "github.com/tech/sendico/server/internal/server/papitemplate" - "go.uber.org/zap" + "github.com/tech/sendico/server/interface/api/sresponse" + mutil "github.com/tech/sendico/server/internal/mutil/param" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/wrapperspb" ) -type RecipientAPI struct { - papitemplate.ProtectedAPI[model.PaymentMethod] - db paymethod.DB +type PaymentMethodsAPI struct { + logger mlogger.Logger + client methodsclient.Client + oph mutil.ParamHelper + rph mutil.ParamHelper + mph mutil.ParamHelper } -func (a *RecipientAPI) Name() mservice.Type { +func (a *PaymentMethodsAPI) Name() mservice.Type { return mservice.PaymentMethods } -func (a *RecipientAPI) Finish(_ context.Context) error { +func (a *PaymentMethodsAPI) Finish(_ context.Context) error { + if a.client != nil { + return a.client.Close() + } return nil } -func CreateAPI(a eapi.API) (*RecipientAPI, error) { - dbFactory := func() (papitemplate.ProtectedDB[model.PaymentMethod], error) { - return a.DBFactory().NewPaymentMethodsDB() +func CreateAPI(apiCtx eapi.API) (*PaymentMethodsAPI, error) { + logger := apiCtx.Logger().Named(mservice.PaymentMethods) + + cfg := apiCtx.Config().PaymentMethods + if cfg == nil { + return nil, merrors.InvalidArgument("payment methods configuration is not provided") } - res := &RecipientAPI{} - - p, err := papitemplate.CreateAPI(a, dbFactory, mservice.Recipients, mservice.PaymentMethods) + address, err := resolveClientAddress("payment methods", cfg) if err != nil { return nil, err } - res.ProtectedAPI = *p.Build() - if res.db, err = a.DBFactory().NewPaymentMethodsDB(); err != nil { - res.Logger.Warn("Failed to create payment methods database", zap.Error(err)) + clientCfg := methodsclient.Config{ + Address: address, + DialTimeout: time.Duration(cfg.DialTimeoutSeconds) * time.Second, + CallTimeout: time.Duration(cfg.CallTimeoutSeconds) * time.Second, + Insecure: cfg.Insecure, + } + + client, err := methodsclient.New(context.Background(), clientCfg) + if err != nil { return nil, err } + res := &PaymentMethodsAPI{ + logger: logger, + client: client, + oph: mutil.CreatePH(mservice.Organizations), + rph: mutil.CreatePH(mservice.Recipients), + mph: mutil.CreatePH(mservice.PaymentMethods), + } + + apiCtx.Register().AccountHandler(res.Name(), res.oph.AddRef("/"), api.Post, res.create) + apiCtx.Register().AccountHandler(res.Name(), res.rph.AddRef(res.oph.AddRef("/list")), api.Get, res.list) + apiCtx.Register().AccountHandler(res.Name(), res.mph.AddRef("/"), api.Get, res.get) + apiCtx.Register().AccountHandler(res.Name(), "/", api.Put, res.update) + apiCtx.Register().AccountHandler(res.Name(), res.mph.AddRef("/"), api.Delete, res.delete) + apiCtx.Register().AccountHandler(res.Name(), res.mph.AddRef(res.oph.AddRef("/archive")), api.Get, res.archive) + return res, nil } + +func (a *PaymentMethodsAPI) create(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc { + orgRef, err := a.oph.GetRef(r) + if err != nil { + return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err) + } + + payload, err := io.ReadAll(r.Body) + if err != nil { + return response.BadPayload(a.logger, a.Name(), err) + } + + resp, err := a.client.CreatePaymentMethod(r.Context(), &methodsv1.CreatePaymentMethodRequest{ + AccountRef: account.ID.Hex(), + OrganizationRef: orgRef.Hex(), + PaymentMethodJson: payload, + }) + if err != nil { + return grpcErrorResponse(a.logger, a.Name(), err) + } + + pm, err := decodePaymentMethod(resp.GetPaymentMethodJson()) + if err != nil { + return response.Internal(a.logger, a.Name(), err) + } + return sresponse.ObjectAuthCreated(a.logger, pm, token, a.Name()) +} + +func (a *PaymentMethodsAPI) list(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc { + orgRef, err := a.oph.GetRef(r) + if err != nil { + return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err) + } + recipientRef, err := a.rph.GetRef(r) + if err != nil { + return response.BadReference(a.logger, a.Name(), a.rph.Name(), a.rph.GetID(r), err) + } + + cursor, err := mutil.GetViewCursor(a.logger, r) + if err != nil { + return response.Auto(a.logger, a.Name(), err) + } + + resp, err := a.client.ListPaymentMethods(r.Context(), &methodsv1.ListPaymentMethodsRequest{ + AccountRef: account.ID.Hex(), + OrganizationRef: orgRef.Hex(), + RecipientRef: recipientRef.Hex(), + Cursor: toProtoCursor(cursor), + }) + if err != nil { + return grpcErrorResponse(a.logger, a.Name(), err) + } + + items, err := decodePaymentMethods(resp.GetPaymentMethodsJson()) + if err != nil { + return response.Internal(a.logger, a.Name(), err) + } + return sresponse.ObjectsAuth(a.logger, items, token, a.Name()) +} + +func (a *PaymentMethodsAPI) get(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc { + methodRef, err := a.mph.GetRef(r) + if err != nil { + return response.BadReference(a.logger, a.Name(), a.mph.Name(), a.mph.GetID(r), err) + } + + resp, err := a.client.GetPaymentMethod(r.Context(), &methodsv1.GetPaymentMethodRequest{ + AccountRef: account.ID.Hex(), + PaymentMethodRef: methodRef.Hex(), + }) + if err != nil { + return grpcErrorResponse(a.logger, a.Name(), err) + } + + pm, err := decodePaymentMethod(resp.GetPaymentMethodJson()) + if err != nil { + return response.Internal(a.logger, a.Name(), err) + } + return sresponse.ObjectAuth(a.logger, pm, token, a.Name()) +} + +func (a *PaymentMethodsAPI) update(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc { + payload, err := io.ReadAll(r.Body) + if err != nil { + return response.BadPayload(a.logger, a.Name(), err) + } + + resp, err := a.client.UpdatePaymentMethod(r.Context(), &methodsv1.UpdatePaymentMethodRequest{ + AccountRef: account.ID.Hex(), + PaymentMethodJson: payload, + }) + if err != nil { + return grpcErrorResponse(a.logger, a.Name(), err) + } + + pm, err := decodePaymentMethod(resp.GetPaymentMethodJson()) + if err != nil { + return response.Internal(a.logger, a.Name(), err) + } + return sresponse.ObjectAuth(a.logger, pm, token, a.Name()) +} + +func (a *PaymentMethodsAPI) delete(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc { + methodRef, err := a.mph.GetRef(r) + if err != nil { + return response.BadReference(a.logger, a.Name(), a.mph.Name(), a.mph.GetID(r), err) + } + + cascade, err := mutil.GetCascadeParam(a.logger, r) + if err != nil { + return response.Auto(a.logger, a.Name(), err) + } + + cascadeValue := false + if cascade != nil { + cascadeValue = *cascade + } + + _, err = a.client.DeletePaymentMethod(r.Context(), &methodsv1.DeletePaymentMethodRequest{ + AccountRef: account.ID.Hex(), + PaymentMethodRef: methodRef.Hex(), + Cascade: cascadeValue, + }) + if err != nil { + return grpcErrorResponse(a.logger, a.Name(), err) + } + + return sresponse.ObjectsAuth(a.logger, []model.PaymentMethod{}, token, a.Name()) +} + +func (a *PaymentMethodsAPI) archive(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc { + methodRef, err := a.mph.GetRef(r) + if err != nil { + return response.BadReference(a.logger, a.Name(), a.mph.Name(), a.mph.GetID(r), err) + } + orgRef, err := a.oph.GetRef(r) + if err != nil { + return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err) + } + + archived, err := mutil.GetArchiveParam(a.logger, r) + if err != nil { + return response.Auto(a.logger, a.Name(), err) + } + if archived == nil { + return response.BadRequest(a.logger, a.Name(), "invalid_query_parameter", "'archived' param must be present") + } + + cascade, err := mutil.GetCascadeParam(a.logger, r) + if err != nil { + return response.Auto(a.logger, a.Name(), err) + } + cascadeValue := false + if cascade != nil { + cascadeValue = *cascade + } + + _, err = a.client.SetPaymentMethodArchived(r.Context(), &methodsv1.SetPaymentMethodArchivedRequest{ + AccountRef: account.ID.Hex(), + OrganizationRef: orgRef.Hex(), + PaymentMethodRef: methodRef.Hex(), + Archived: *archived, + Cascade: cascadeValue, + }) + if err != nil { + return grpcErrorResponse(a.logger, a.Name(), err) + } + + return sresponse.ObjectsAuth(a.logger, []model.PaymentMethod{}, token, a.Name()) +} + +func resolveClientAddress(service string, cfg *eapi.PaymentOrchestratorConfig) (string, error) { + if cfg == nil { + return "", merrors.InvalidArgument(strings.TrimSpace(service) + " configuration is not provided") + } + address := strings.TrimSpace(cfg.Address) + if address != "" { + return address, nil + } + if env := strings.TrimSpace(cfg.AddressEnv); env != "" { + if resolved := strings.TrimSpace(os.Getenv(env)); resolved != "" { + return resolved, nil + } + return "", merrors.InvalidArgument(service + " address is not specified and address env " + env + " is empty") + } + return "", merrors.InvalidArgument(strings.TrimSpace(service) + " address is not specified") +} + +func toProtoCursor(cursor *model.ViewCursor) *methodsv1.ViewCursor { + if cursor == nil { + return nil + } + + res := &methodsv1.ViewCursor{} + hasAny := false + if cursor.Limit != nil { + res.Limit = wrapperspb.Int64(*cursor.Limit) + hasAny = true + } + if cursor.Offset != nil { + res.Offset = wrapperspb.Int64(*cursor.Offset) + hasAny = true + } + if cursor.IsArchived != nil { + res.IsArchived = wrapperspb.Bool(*cursor.IsArchived) + hasAny = true + } + if !hasAny { + return nil + } + return res +} + +func decodePaymentMethod(payload []byte) (*model.PaymentMethod, error) { + var pm model.PaymentMethod + if err := json.Unmarshal(payload, &pm); err != nil { + return nil, err + } + return &pm, nil +} + +func decodePaymentMethods(items [][]byte) ([]model.PaymentMethod, error) { + if len(items) == 0 { + return nil, nil + } + res := make([]model.PaymentMethod, 0, len(items)) + for i := range items { + pm, err := decodePaymentMethod(items[i]) + if err != nil { + return nil, err + } + res = append(res, *pm) + } + return res, nil +} + +func grpcErrorResponse(logger mlogger.Logger, source mservice.Type, err error) http.HandlerFunc { + statusErr, ok := status.FromError(err) + if !ok { + return response.Internal(logger, source, err) + } + + switch statusErr.Code() { + case codes.InvalidArgument: + return response.BadRequest(logger, source, "invalid_argument", statusErr.Message()) + case codes.NotFound: + return response.NotFound(logger, source, statusErr.Message()) + case codes.PermissionDenied: + return response.AccessDenied(logger, source, statusErr.Message()) + case codes.Unauthenticated: + return response.Unauthorized(logger, source, statusErr.Message()) + case codes.AlreadyExists, codes.Aborted: + return response.DataConflict(logger, source, statusErr.Message()) + case codes.Unimplemented: + return response.NotImplemented(logger, source, statusErr.Message()) + case codes.FailedPrecondition: + return response.Error(logger, source, http.StatusPreconditionFailed, "failed_precondition", statusErr.Message()) + case codes.DeadlineExceeded: + return response.Error(logger, source, http.StatusGatewayTimeout, "deadline_exceeded", statusErr.Message()) + case codes.Unavailable: + return response.Error(logger, source, http.StatusServiceUnavailable, "service_unavailable", statusErr.Message()) + default: + return response.Internal(logger, source, err) + } +} diff --git a/api/server/internal/server/recipientimp/notifications.go b/api/server/internal/server/recipientimp/notifications.go new file mode 100644 index 00000000..b3709824 --- /dev/null +++ b/api/server/internal/server/recipientimp/notifications.go @@ -0,0 +1,22 @@ +package recipientimp + +import ( + messaging "github.com/tech/sendico/pkg/messaging/envelope" + notifications "github.com/tech/sendico/pkg/messaging/notifications/object" + "github.com/tech/sendico/pkg/model" + nm "github.com/tech/sendico/pkg/model/notification" + "go.mongodb.org/mongo-driver/v2/bson" +) + +func (a *RecipientAPI) notification( + recipient *model.Recipient, + actorAccountRef bson.ObjectID, + t nm.NotificationAction, +) messaging.Envelope { + objectRef := bson.NilObjectID + if recipient != nil { + objectRef = recipient.ID + } + + return notifications.Object(a.Name(), actorAccountRef, a.Name(), objectRef, t) +} diff --git a/api/server/internal/server/recipientimp/service.go b/api/server/internal/server/recipientimp/service.go index d2b678bf..8dadd672 100644 --- a/api/server/internal/server/recipientimp/service.go +++ b/api/server/internal/server/recipientimp/service.go @@ -35,7 +35,11 @@ func CreateAPI(a eapi.API) (*RecipientAPI, error) { if err != nil { return nil, err } - res.ProtectedAPI = *p.Build() + res.ProtectedAPI = *p. + WithNotifications(res.notification). + WithNoCreateNotification(). + WithNoUpdateNotification(). + Build() if res.db, err = a.DBFactory().NewRecipientsDB(); err != nil { res.Logger.Warn("Failed to create recipients database", zap.Error(err)) diff --git a/ci/dev/bff.dockerfile b/ci/dev/bff.dockerfile index 8fe4f6dd..e1cb3298 100644 --- a/ci/dev/bff.dockerfile +++ b/ci/dev/bff.dockerfile @@ -17,6 +17,7 @@ RUN bash ci/scripts/proto/generate.sh # Copy service dependencies (needed for go.mod replace directives) COPY api/ledger ./api/ledger COPY api/payments/orchestrator ./api/payments/orchestrator +COPY api/payments/methods ./api/payments/methods COPY api/payments/storage ./api/payments/storage COPY api/gateway/tron ./api/gateway/tron COPY api/billing/fees ./api/billing/fees @@ -39,6 +40,7 @@ COPY --from=builder /src/api/pkg ./api/pkg # Copy service dependencies COPY --from=builder /src/api/ledger ./api/ledger COPY --from=builder /src/api/payments/orchestrator ./api/payments/orchestrator +COPY --from=builder /src/api/payments/methods ./api/payments/methods COPY --from=builder /src/api/payments/storage ./api/payments/storage COPY --from=builder /src/api/gateway/tron ./api/gateway/tron COPY --from=builder /src/api/billing/fees ./api/billing/fees diff --git a/ci/dev/payments-methods.dockerfile b/ci/dev/payments-methods.dockerfile new file mode 100644 index 00000000..f7649895 --- /dev/null +++ b/ci/dev/payments-methods.dockerfile @@ -0,0 +1,40 @@ +# Development Dockerfile for Payments Methods Service with Air hot reload + +FROM golang:alpine AS builder + +RUN apk add --no-cache bash git build-base protoc protobuf-dev && \ + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \ + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \ + go install github.com/air-verse/air@latest + +WORKDIR /src + +COPY api/proto ./api/proto +COPY api/pkg ./api/pkg +COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/ +RUN bash ci/scripts/proto/generate.sh + +# Copy service dependencies (needed for go.mod replace directives) +COPY api/payments/storage ./api/payments/storage + +# Runtime stage for development with Air +FROM golang:alpine + +RUN apk add --no-cache bash git build-base && \ + go install github.com/air-verse/air@latest + +WORKDIR /src + +# Copy generated proto and pkg from builder +COPY --from=builder /src/api/proto ./api/proto +COPY --from=builder /src/api/pkg ./api/pkg + +# Copy service dependencies +COPY --from=builder /src/api/payments/storage ./api/payments/storage + +# Source code will be mounted at runtime +WORKDIR /src/api/payments/methods + +EXPOSE 50066 9416 + +CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"] diff --git a/ci/prod/.env.runtime b/ci/prod/.env.runtime index 7a314669..268cd7e0 100644 --- a/ci/prod/.env.runtime +++ b/ci/prod/.env.runtime @@ -148,6 +148,13 @@ PAYMENTS_QUOTATION_SERVICE_NAME=sendico_payments_quotation PAYMENTS_QUOTATION_GRPC_PORT=50064 PAYMENTS_QUOTATION_METRICS_PORT=9414 +# Payments methods stack +PAYMENTS_METHODS_DIR=payments_methods +PAYMENTS_METHODS_COMPOSE_PROJECT=sendico-payments-methods +PAYMENTS_METHODS_SERVICE_NAME=sendico_payments_methods +PAYMENTS_METHODS_GRPC_PORT=50066 +PAYMENTS_METHODS_METRICS_PORT=9416 + # Payments orchestrator Mongo settings PAYMENTS_MONGO_HOST=sendico_db1 PAYMENTS_MONGO_PORT=27017 diff --git a/ci/prod/compose/bff.yml b/ci/prod/compose/bff.yml index 39cb5bd4..ff30eefd 100644 --- a/ci/prod/compose/bff.yml +++ b/ci/prod/compose/bff.yml @@ -33,6 +33,7 @@ services: LEDGER_ADDRESS: ${LEDGER_SERVICE_NAME}:${LEDGER_GRPC_PORT} PAYMENTS_ADDRESS: ${PAYMENTS_SERVICE_NAME}:${PAYMENTS_GRPC_PORT} PAYMENTS_QUOTE_ADDRESS: ${PAYMENTS_QUOTATION_SERVICE_NAME}:${PAYMENTS_QUOTATION_GRPC_PORT} + PAYMENTS_METHODS_ADDRESS: ${PAYMENTS_METHODS_SERVICE_NAME}:${PAYMENTS_METHODS_GRPC_PORT} MONGO_HOST: ${MONGO_HOST} MONGO_PORT: ${MONGO_PORT} MONGO_DATABASE: ${MONGO_DATABASE} diff --git a/ci/prod/compose/payments_methods.dockerfile b/ci/prod/compose/payments_methods.dockerfile new file mode 100644 index 00000000..07b3008a --- /dev/null +++ b/ci/prod/compose/payments_methods.dockerfile @@ -0,0 +1,40 @@ +# syntax=docker/dockerfile:1.7 + +ARG TARGETOS=linux +ARG TARGETARCH=amd64 + +FROM golang:alpine AS build +ARG APP_VERSION=dev +ARG GIT_REV=unknown +ARG BUILD_BRANCH=unknown +ARG BUILD_DATE=unknown +ARG BUILD_USER=ci +ENV GO111MODULE=on +ENV PATH="/go/bin:${PATH}" +WORKDIR /src +COPY . . +RUN apk add --no-cache bash git build-base protoc protobuf-dev \ + && go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \ + && go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \ + && bash ci/scripts/proto/generate.sh +WORKDIR /src/api/payments/methods +RUN --mount=type=cache,target=/root/.cache/go-build \ + --mount=type=cache,target=/go/pkg/mod \ + CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ + go build -trimpath -ldflags "\ + -s -w \ + -X github.com/tech/sendico/payments/methods/internal/appversion.Version=${APP_VERSION} \ + -X github.com/tech/sendico/payments/methods/internal/appversion.Revision=${GIT_REV} \ + -X github.com/tech/sendico/payments/methods/internal/appversion.Branch=${BUILD_BRANCH} \ + -X github.com/tech/sendico/payments/methods/internal/appversion.BuildUser=${BUILD_USER} \ + -X github.com/tech/sendico/payments/methods/internal/appversion.BuildDate=${BUILD_DATE}" \ + -o /out/payments-methods . + +FROM alpine:latest AS runtime +RUN apk add --no-cache ca-certificates tzdata wget +WORKDIR /app +COPY api/payments/methods/config.yml /app/config.yml +COPY --from=build /out/payments-methods /app/payments-methods +EXPOSE 50066 9416 +ENTRYPOINT ["/app/payments-methods"] +CMD ["--config.file", "/app/config.yml"] diff --git a/ci/prod/compose/payments_methods.yml b/ci/prod/compose/payments_methods.yml new file mode 100644 index 00000000..4c24fb89 --- /dev/null +++ b/ci/prod/compose/payments_methods.yml @@ -0,0 +1,53 @@ +# Compose v2 - Payments Methods + +x-common-env: &common-env + env_file: + - ../env/.env.runtime + - ../env/.env.version + +networks: + sendico-net: + external: true + name: sendico-net + +services: + sendico_payments_methods: + <<: *common-env + container_name: sendico-payments-methods + restart: unless-stopped + image: ${REGISTRY_URL}/payments/methods:${APP_V} + pull_policy: always + environment: + PAYMENTS_MONGO_HOST: ${PAYMENTS_MONGO_HOST} + PAYMENTS_MONGO_PORT: ${PAYMENTS_MONGO_PORT} + PAYMENTS_MONGO_DATABASE: ${PAYMENTS_MONGO_DATABASE} + PAYMENTS_MONGO_USER: ${PAYMENTS_MONGO_USER} + PAYMENTS_MONGO_PASSWORD: ${PAYMENTS_MONGO_PASSWORD} + PAYMENTS_MONGO_AUTH_SOURCE: ${PAYMENTS_MONGO_AUTH_SOURCE} + PAYMENTS_MONGO_REPLICA_SET: ${PAYMENTS_MONGO_REPLICA_SET} + MONGO_HOST: ${MONGO_HOST} + MONGO_PORT: ${MONGO_PORT} + MONGO_DATABASE: ${MONGO_DATABASE} + MONGO_USER: ${PAYMENTS_MONGO_USER} + MONGO_PASSWORD: ${PAYMENTS_MONGO_PASSWORD} + MONGO_AUTH_SOURCE: ${MONGO_AUTH_SOURCE} + MONGO_REPLICA_SET: ${MONGO_REPLICA_SET} + NATS_URL: ${NATS_URL} + NATS_HOST: ${NATS_HOST} + NATS_PORT: ${NATS_PORT} + NATS_USER: ${NATS_USER} + NATS_PASSWORD: ${NATS_PASSWORD} + PAYMENTS_METHODS_GRPC_PORT: ${PAYMENTS_METHODS_GRPC_PORT} + PAYMENTS_METHODS_METRICS_PORT: ${PAYMENTS_METHODS_METRICS_PORT} + command: ["--config.file", "/app/config.yml"] + ports: + - "0.0.0.0:${PAYMENTS_METHODS_GRPC_PORT}:50066" + - "0.0.0.0:${PAYMENTS_METHODS_METRICS_PORT}:9416" + healthcheck: + test: ["CMD-SHELL","wget -qO- http://localhost:9416/health | grep -q '\"status\":\"ok\"'"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + networks: + - sendico-net diff --git a/ci/prod/scripts/deploy/payments_methods.sh b/ci/prod/scripts/deploy/payments_methods.sh new file mode 100755 index 00000000..01871ca3 --- /dev/null +++ b/ci/prod/scripts/deploy/payments_methods.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +set -euo pipefail +[[ "${DEBUG_DEPLOY:-0}" = "1" ]] && set -x +trap 'echo "[deploy-payments-methods] error at line $LINENO" >&2' ERR + +: "${REMOTE_BASE:?missing REMOTE_BASE}" +: "${SSH_USER:?missing SSH_USER}" +: "${SSH_HOST:?missing SSH_HOST}" +: "${PAYMENTS_METHODS_DIR:?missing PAYMENTS_METHODS_DIR}" +: "${PAYMENTS_METHODS_COMPOSE_PROJECT:?missing PAYMENTS_METHODS_COMPOSE_PROJECT}" +: "${PAYMENTS_METHODS_SERVICE_NAME:?missing PAYMENTS_METHODS_SERVICE_NAME}" + +REMOTE_DIR="${REMOTE_BASE%/}/${PAYMENTS_METHODS_DIR}" +REMOTE_TARGET="${SSH_USER}@${SSH_HOST}" +COMPOSE_FILE="payments_methods.yml" +SERVICE_NAMES="${PAYMENTS_METHODS_SERVICE_NAME}" + +REQUIRED_SECRETS=( + PAYMENTS_MONGO_USER + PAYMENTS_MONGO_PASSWORD + NATS_USER + NATS_PASSWORD + NATS_URL +) + +for var in "${REQUIRED_SECRETS[@]}"; do + if [[ -z "${!var:-}" ]]; then + echo "missing required secret env: ${var}" >&2 + exit 65 + fi +done + +if [[ ! -s .env.version ]]; then + echo ".env.version is missing; run version step first" >&2 + exit 66 +fi + +b64enc() { + printf '%s' "$1" | base64 | tr -d '\n' +} + +PAYMENTS_MONGO_USER_B64="$(b64enc "${PAYMENTS_MONGO_USER}")" +PAYMENTS_MONGO_PASSWORD_B64="$(b64enc "${PAYMENTS_MONGO_PASSWORD}")" +NATS_USER_B64="$(b64enc "${NATS_USER}")" +NATS_PASSWORD_B64="$(b64enc "${NATS_PASSWORD}")" +NATS_URL_B64="$(b64enc "${NATS_URL}")" + +SSH_OPTS=( + -i /root/.ssh/id_rsa + -o StrictHostKeyChecking=no + -o UserKnownHostsFile=/dev/null + -o LogLevel=ERROR + -q +) +if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then + SSH_OPTS=("${SSH_OPTS[@]/-q/}" -vv) +fi + +RSYNC_FLAGS=(-az --delete) +[[ "${DEBUG_DEPLOY:-0}" = "1" ]] && RSYNC_FLAGS=(-avz --delete) + +ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "mkdir -p ${REMOTE_DIR}/{compose,env}" + +rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" ci/prod/compose/ "$REMOTE_TARGET:${REMOTE_DIR}/compose/" +rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" ci/prod/.env.runtime "$REMOTE_TARGET:${REMOTE_DIR}/env/.env.runtime" +rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" .env.version "$REMOTE_TARGET:${REMOTE_DIR}/env/.env.version" + +SERVICES_LINE="${SERVICE_NAMES}" + +ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" \ + REMOTE_DIR="$REMOTE_DIR" \ + COMPOSE_FILE="$COMPOSE_FILE" \ + COMPOSE_PROJECT="$PAYMENTS_METHODS_COMPOSE_PROJECT" \ + SERVICES_LINE="$SERVICES_LINE" \ + PAYMENTS_MONGO_USER_B64="$PAYMENTS_MONGO_USER_B64" \ + PAYMENTS_MONGO_PASSWORD_B64="$PAYMENTS_MONGO_PASSWORD_B64" \ + NATS_USER_B64="$NATS_USER_B64" \ + NATS_PASSWORD_B64="$NATS_PASSWORD_B64" \ + NATS_URL_B64="$NATS_URL_B64" \ + bash -s <<'EOSSH' +set -euo pipefail +cd "${REMOTE_DIR}/compose" +set -a +. ../env/.env.runtime +load_kv_file() { + local file="$1" + while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + ''|\#*) continue ;; + esac + if printf '%s' "$line" | grep -Eq '^[[:alpha:]_][[:alnum:]_]*='; then + local key="${line%%=*}" + local value="${line#*=}" + key="$(printf '%s' "$key" | tr -d '[:space:]')" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + if [[ -n "$key" ]]; then + export "$key=$value" + fi + fi + done <"$file" +} +load_kv_file ../env/.env.version +set +a + +if base64 -d >/dev/null 2>&1 <<<'AA=='; then + BASE64_DECODE_FLAG='-d' +else + BASE64_DECODE_FLAG='--decode' +fi + +decode_b64() { + val="$1" + if [[ -z "$val" ]]; then + printf '' + return + fi + printf '%s' "$val" | base64 "${BASE64_DECODE_FLAG}" +} + +PAYMENTS_MONGO_USER="$(decode_b64 "$PAYMENTS_MONGO_USER_B64")" +PAYMENTS_MONGO_PASSWORD="$(decode_b64 "$PAYMENTS_MONGO_PASSWORD_B64")" +NATS_USER="$(decode_b64 "$NATS_USER_B64")" +NATS_PASSWORD="$(decode_b64 "$NATS_PASSWORD_B64")" +NATS_URL="$(decode_b64 "$NATS_URL_B64")" + +export PAYMENTS_MONGO_USER PAYMENTS_MONGO_PASSWORD NATS_USER NATS_PASSWORD NATS_URL +COMPOSE_PROJECT_NAME="$COMPOSE_PROJECT" +export COMPOSE_PROJECT_NAME +read -r -a SERVICES <<<"${SERVICES_LINE}" + +pull_cmd=(docker compose -f "$COMPOSE_FILE" pull) +up_cmd=(docker compose -f "$COMPOSE_FILE" up -d --remove-orphans) +ps_cmd=(docker compose -f "$COMPOSE_FILE" ps) +if [[ "${#SERVICES[@]}" -gt 0 ]]; then + pull_cmd+=("${SERVICES[@]}") + up_cmd+=("${SERVICES[@]}") + ps_cmd+=("${SERVICES[@]}") +fi + +"${pull_cmd[@]}" +"${up_cmd[@]}" +"${ps_cmd[@]}" + +date -Is > .last_deploy +logger -t "deploy-${COMPOSE_PROJECT_NAME}" "${COMPOSE_PROJECT_NAME} deployed at $(date -Is) in ${REMOTE_DIR}" +EOSSH diff --git a/ci/scripts/payments_methods/build-image.sh b/ci/scripts/payments_methods/build-image.sh new file mode 100755 index 00000000..a2dce25a --- /dev/null +++ b/ci/scripts/payments_methods/build-image.sh @@ -0,0 +1,85 @@ +#!/bin/sh +set -eu + +if ! set -o pipefail 2>/dev/null; then + : +fi + +REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +cd "${REPO_ROOT}" + +sh ci/scripts/common/ensure_env_version.sh + +normalize_env_file() { + file="$1" + tmp="${file}.tmp.$$" + tr -d '\r' <"$file" >"$tmp" + mv "$tmp" "$file" +} + +load_env_file() { + file="$1" + while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + ''|\#*) continue ;; + esac + key="${line%%=*}" + value="${line#*=}" + key="$(printf '%s' "$key" | tr -d '[:space:]')" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + export "$key=$value" + done <"$file" +} + +PAYMENTS_METHODS_ENV_NAME="${PAYMENTS_METHODS_ENV:-prod}" +RUNTIME_ENV_FILE="./ci/${PAYMENTS_METHODS_ENV_NAME}/.env.runtime" + +if [ ! -f "${RUNTIME_ENV_FILE}" ]; then + echo "[payments-methods-build] runtime env file not found: ${RUNTIME_ENV_FILE}" >&2 + exit 1 +fi + +normalize_env_file "${RUNTIME_ENV_FILE}" +normalize_env_file ./.env.version + +load_env_file "${RUNTIME_ENV_FILE}" +load_env_file ./.env.version + +REGISTRY_URL="${REGISTRY_URL:?missing REGISTRY_URL}" +APP_V="${APP_V:?missing APP_V}" +PAYMENTS_METHODS_DOCKERFILE="${PAYMENTS_METHODS_DOCKERFILE:?missing PAYMENTS_METHODS_DOCKERFILE}" +PAYMENTS_METHODS_IMAGE_PATH="${PAYMENTS_METHODS_IMAGE_PATH:?missing PAYMENTS_METHODS_IMAGE_PATH}" + +REGISTRY_HOST="${REGISTRY_URL#http://}" +REGISTRY_HOST="${REGISTRY_HOST#https://}" +REGISTRY_USER="$(cat secrets/REGISTRY_USER)" +REGISTRY_PASSWORD="$(cat secrets/REGISTRY_PASSWORD)" +: "${REGISTRY_USER:?missing registry user}" +: "${REGISTRY_PASSWORD:?missing registry password}" + +mkdir -p /kaniko/.docker +AUTH_B64="$(printf '%s:%s' "$REGISTRY_USER" "$REGISTRY_PASSWORD" | base64 | tr -d '\n')" +cat </kaniko/.docker/config.json +{ + "auths": { + "https://${REGISTRY_HOST}": { "auth": "${AUTH_B64}" } + } +} +EOF + +BUILD_CONTEXT="${PAYMENTS_METHODS_BUILD_CONTEXT:-${WOODPECKER_WORKSPACE:-${CI_WORKSPACE:-${PWD:-/workspace}}}}" +if [ ! -d "${BUILD_CONTEXT}" ]; then + BUILD_CONTEXT="/workspace" +fi + +/kaniko/executor \ + --context "${BUILD_CONTEXT}" \ + --dockerfile "${PAYMENTS_METHODS_DOCKERFILE}" \ + --destination "${REGISTRY_URL}/${PAYMENTS_METHODS_IMAGE_PATH}:${APP_V}" \ + --build-arg APP_VERSION="${APP_V}" \ + --build-arg GIT_REV="${GIT_REV}" \ + --build-arg BUILD_BRANCH="${BUILD_BRANCH}" \ + --build-arg BUILD_DATE="${BUILD_DATE}" \ + --build-arg BUILD_USER="${BUILD_USER}" \ + --single-snapshot diff --git a/ci/scripts/payments_methods/deploy.sh b/ci/scripts/payments_methods/deploy.sh new file mode 100755 index 00000000..e81bc4b3 --- /dev/null +++ b/ci/scripts/payments_methods/deploy.sh @@ -0,0 +1,59 @@ +#!/bin/sh +set -eu + +if ! set -o pipefail 2>/dev/null; then + : +fi + +REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +cd "${REPO_ROOT}" + +sh ci/scripts/common/ensure_env_version.sh + +normalize_env_file() { + file="$1" + tmp="${file}.tmp.$$" + tr -d '\r' <"$file" >"$tmp" + mv "$tmp" "$file" +} + +load_env_file() { + file="$1" + while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + ''|\#*) continue ;; + esac + key="${line%%=*}" + value="${line#*=}" + key="$(printf '%s' "$key" | tr -d '[:space:]')" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + export "$key=$value" + done <"$file" +} + +. ci/scripts/common/nats_env.sh + +PAYMENTS_METHODS_ENV_NAME="${PAYMENTS_METHODS_ENV:-prod}" +RUNTIME_ENV_FILE="./ci/${PAYMENTS_METHODS_ENV_NAME}/.env.runtime" + +if [ ! -f "${RUNTIME_ENV_FILE}" ]; then + echo "[payments-methods-deploy] runtime env file not found: ${RUNTIME_ENV_FILE}" >&2 + exit 1 +fi + +normalize_env_file "${RUNTIME_ENV_FILE}" +normalize_env_file ./.env.version + +load_env_file "${RUNTIME_ENV_FILE}" +load_env_file ./.env.version + +PAYMENTS_METHODS_MONGO_SECRET_PATH="${PAYMENTS_METHODS_MONGO_SECRET_PATH:?missing PAYMENTS_METHODS_MONGO_SECRET_PATH}" + +export PAYMENTS_MONGO_USER="$(./ci/vlt kv_get kv "${PAYMENTS_METHODS_MONGO_SECRET_PATH}" user)" +export PAYMENTS_MONGO_PASSWORD="$(./ci/vlt kv_get kv "${PAYMENTS_METHODS_MONGO_SECRET_PATH}" password)" + +load_nats_env + +bash ci/prod/scripts/bootstrap/network.sh +bash ci/prod/scripts/deploy/payments_methods.sh diff --git a/ci/scripts/proto/generate.sh b/ci/scripts/proto/generate.sh index c6ff673b..3c7aa9b4 100755 --- a/ci/scripts/proto/generate.sh +++ b/ci/scripts/proto/generate.sh @@ -140,6 +140,12 @@ if [ -f "${PROTO_DIR}/payments/quotation/v1/quotation.proto" ]; then generate_go_with_grpc "${PROTO_DIR}/payments/quotation/v1/quotation.proto" fi +if [ -f "${PROTO_DIR}/payments/methods/v1/methods.proto" ]; then + info "Compiling payments methods protos" + clean_pb_files "./pkg/proto/payments/methods" + generate_go_with_grpc "${PROTO_DIR}/payments/methods/v1/methods.proto" +fi + if [ -f "${PROTO_DIR}/billing/fees/v1/fees.proto" ]; then info "Compiling billing fees protos" clean_pb_files "./pkg/proto/billing/fees" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index ee7e82f1..95943698 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -518,6 +518,53 @@ services: ORACLE_ADDRESS: dev-fx-oracle:50051 CHAIN_GATEWAY_ADDRESS: dev-chain-gateway:50053 + # -------------------------------------------------------------------------- + # Payments Methods Service + # -------------------------------------------------------------------------- + dev-payments-methods: + <<: *common-env + build: + context: . + dockerfile: ci/dev/payments-methods.dockerfile + image: sendico-dev/payments-methods:latest + container_name: dev-payments-methods + restart: unless-stopped + depends_on: + dev-mongo-init: { condition: service_completed_successfully } + dev-nats: { condition: service_started } + dev-discovery: { condition: service_started } + volumes: + - ./api/payments/methods:/src/api/payments/methods + - ./api/payments/storage:/src/api/payments/storage + - ./api/payments/methods/config.dev.yml:/app/config.yml:ro + ports: + - "50066:50066" + - "9416:9416" + networks: + - sendico-dev + environment: + PAYMENTS_MONGO_HOST: dev-mongo-1 + PAYMENTS_MONGO_PORT: 27017 + PAYMENTS_MONGO_DATABASE: payments_orchestrator + PAYMENTS_MONGO_USER: ${MONGO_USER} + PAYMENTS_MONGO_PASSWORD: ${MONGO_PASSWORD} + PAYMENTS_MONGO_AUTH_SOURCE: admin + PAYMENTS_MONGO_REPLICA_SET: dev-rs + MONGO_HOST: dev-mongo-1 + MONGO_PORT: 27017 + MONGO_DATABASE: sendico + MONGO_USER: ${MONGO_USER} + MONGO_PASSWORD: ${MONGO_PASSWORD} + MONGO_AUTH_SOURCE: admin + MONGO_REPLICA_SET: dev-rs + NATS_HOST: dev-nats + NATS_PORT: 4222 + NATS_USER: ${NATS_USER} + NATS_PASSWORD: ${NATS_PASSWORD} + NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222 + PAYMENTS_METHODS_GRPC_PORT: 50066 + PAYMENTS_METHODS_METRICS_PORT: 9416 + # -------------------------------------------------------------------------- # Chain Gateway Vault Agent (sidecar for AppRole authentication) # -------------------------------------------------------------------------- @@ -809,6 +856,7 @@ services: dev-ledger: { condition: service_started } dev-payments-orchestrator: { condition: service_started } dev-payments-quotation: { condition: service_started } + dev-payments-methods: { condition: service_started } dev-chain-gateway: { condition: service_started } volumes: - ./api/server:/src/api/server @@ -839,6 +887,7 @@ services: LEDGER_ADDRESS: dev-ledger:50052 PAYMENTS_ADDRESS: dev-payments-orchestrator:50062 PAYMENTS_QUOTE_ADDRESS: dev-payments-quotation:50064 + PAYMENTS_METHODS_ADDRESS: dev-payments-methods:50066 TRON_GATEWAY_ADDRESS: dev-tron-gateway:50070 BFF_HTTP_PORT: 8080 API_PROTOCOL: http