diff --git a/api/edge/bff/interface/api/sresponse/payment_test.go b/api/edge/bff/interface/api/sresponse/payment_test.go index 2a3c77d6..a452bc55 100644 --- a/api/edge/bff/interface/api/sresponse/payment_test.go +++ b/api/edge/bff/interface/api/sresponse/payment_test.go @@ -293,7 +293,7 @@ func TestToPaymentOperation_MapsOperationRefAndGateway(t *testing.T) { if got, want := op.OperationRef, "op-123"; got != want { t.Fatalf("operation_ref mismatch: got=%q want=%q", got, want) } - if got, want := op.Gateway, "mntx_gateway"; got != want { + if got, want := op.Gateway, "mcards_gateway"; got != want { t.Fatalf("gateway mismatch: got=%q want=%q", got, want) } } @@ -331,7 +331,7 @@ func TestToPaymentOperation_DoesNotFallbackToCardPayoutRef(t *testing.T) { if got := op.OperationRef; got != "" { t.Fatalf("expected empty operation_ref, got=%q", got) } - if got, want := op.Gateway, "mntx_gateway"; got != want { + if got, want := op.Gateway, "mcards_gateway"; got != want { t.Fatalf("gateway mismatch: got=%q want=%q", got, want) } } diff --git a/api/edge/bff/internal/server/paymentapiimp/get.go b/api/edge/bff/internal/server/paymentapiimp/get.go new file mode 100644 index 00000000..adb3bc78 --- /dev/null +++ b/api/edge/bff/internal/server/paymentapiimp/get.go @@ -0,0 +1,52 @@ +package paymentapiimp + +import ( + "net/http" + "strings" + + "github.com/tech/sendico/pkg/api/http/response" + "github.com/tech/sendico/pkg/merrors" + "github.com/tech/sendico/pkg/model" + "github.com/tech/sendico/pkg/mutil/mzap" + orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2" + "github.com/tech/sendico/server/interface/api/sresponse" + "go.mongodb.org/mongo-driver/v2/bson" + "go.uber.org/zap" +) + +func (a *PaymentAPI) getPayment(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc { + paymentRef := strings.TrimSpace(a.pph.GetID(r)) + if paymentRef == "" { + return response.BadReference(a.logger, a.Name(), a.pph.Name(), a.pph.GetID(r), merrors.InvalidArgument("payment reference is required")) + } + + resp, err := a.execution.GetPayment(r.Context(), &orchestrationv2.GetPaymentRequest{ + PaymentRef: paymentRef, + }) + if err != nil { + a.logger.Warn("Failed to fetch payment", zap.Error(err), zap.String("payment_ref", paymentRef)) + return grpcErrorResponse(a.logger, a.Name(), err) + } + if resp == nil || resp.GetPayment() == nil { + return response.Auto(a.logger, a.Name(), merrors.NoData("payment not found")) + } + + orgRefRaw := strings.TrimSpace(resp.GetOrganizationRef()) + orgRef, err := bson.ObjectIDFromHex(orgRefRaw) + if err != nil { + a.logger.Warn("Payment lookup returned invalid organization reference", zap.Error(err), zap.String("organization_ref", orgRefRaw), zap.String("payment_ref", paymentRef)) + return response.Internal(a.logger, a.Name(), merrors.DataConflict("payment lookup returned invalid organization reference")) + } + + allowed, err := a.enf.Enforce(r.Context(), a.permissionRef, account.ID, orgRef, bson.NilObjectID, model.ActionRead) + if err != nil { + a.logger.Warn("Failed to check payment access permissions", zap.Error(err), mzap.ObjRef("organization_ref", orgRef), zap.String("payment_ref", paymentRef)) + return response.Auto(a.logger, a.Name(), err) + } + if !allowed { + a.logger.Debug("Payment access denied, hiding existence", mzap.ObjRef("organization_ref", orgRef), zap.String("payment_ref", paymentRef)) + return response.NotFound(a.logger, a.Name(), "payment not found") + } + + return sresponse.PaymentResponse(a.logger, resp.GetPayment(), token) +} diff --git a/api/edge/bff/internal/server/paymentapiimp/get_test.go b/api/edge/bff/internal/server/paymentapiimp/get_test.go new file mode 100644 index 00000000..b9b6479b --- /dev/null +++ b/api/edge/bff/internal/server/paymentapiimp/get_test.go @@ -0,0 +1,119 @@ +package paymentapiimp + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/go-chi/chi/v5" + "github.com/tech/sendico/pkg/auth" + "github.com/tech/sendico/pkg/model" + "github.com/tech/sendico/pkg/mservice" + orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2" + "github.com/tech/sendico/server/interface/api/sresponse" + mutil "github.com/tech/sendico/server/internal/mutil/param" + "go.mongodb.org/mongo-driver/v2/bson" + "go.uber.org/zap" +) + +func TestGetPayment_FetchesByPaymentRefWithoutOrganizationPath(t *testing.T) { + orgRef := bson.NewObjectID() + paymentRef := "payment-123" + exec := &fakeExecutionClientForGet{ + getPaymentResp: &orchestrationv2.GetPaymentResponse{ + OrganizationRef: orgRef.Hex(), + Payment: &orchestrationv2.Payment{ + PaymentRef: paymentRef, + }, + }, + } + enf := &capturingPaymentEnforcer{allowed: true} + api := &PaymentAPI{ + logger: zap.NewNop(), + execution: exec, + enf: enf, + oph: mutil.CreatePH(mservice.Organizations), + pph: mutil.CreatePH(mservice.Payments), + permissionRef: bson.NewObjectID(), + } + + req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/object/"+paymentRef, nil) + routeCtx := chi.NewRouteContext() + routeCtx.URLParams.Add("payments_ref", paymentRef) + req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, routeCtx)) + account := &model.Account{} + account.ID = bson.NewObjectID() + + rr := httptest.NewRecorder() + handler := api.getPayment(req, account, &sresponse.TokenData{ + Token: "token", + Expiration: time.Now().UTC().Add(time.Hour), + }) + handler.ServeHTTP(rr, req) + + if got, want := rr.Code, http.StatusOK; got != want { + t.Fatalf("status mismatch: got=%d want=%d body=%s", got, want, rr.Body.String()) + } + if exec.getPaymentReq == nil { + t.Fatal("expected GetPayment request") + } + if got, want := exec.getPaymentReq.GetPaymentRef(), paymentRef; got != want { + t.Fatalf("payment_ref mismatch: got=%q want=%q", got, want) + } + if exec.getPaymentReq.GetMeta() != nil { + t.Fatalf("expected no organization metadata in request, got=%+v", exec.getPaymentReq.GetMeta()) + } + if got, want := enf.organizationRef, orgRef; got != want { + t.Fatalf("permission organization_ref mismatch: got=%s want=%s", got.Hex(), want.Hex()) + } +} + +type fakeExecutionClientForGet struct { + getPaymentReq *orchestrationv2.GetPaymentRequest + getPaymentResp *orchestrationv2.GetPaymentResponse +} + +func (*fakeExecutionClientForGet) ExecutePayment(context.Context, *orchestrationv2.ExecutePaymentRequest) (*orchestrationv2.ExecutePaymentResponse, error) { + return &orchestrationv2.ExecutePaymentResponse{}, nil +} + +func (*fakeExecutionClientForGet) ExecuteBatchPayment(context.Context, *orchestrationv2.ExecuteBatchPaymentRequest) (*orchestrationv2.ExecuteBatchPaymentResponse, error) { + return &orchestrationv2.ExecuteBatchPaymentResponse{}, nil +} + +func (f *fakeExecutionClientForGet) GetPayment(_ context.Context, req *orchestrationv2.GetPaymentRequest) (*orchestrationv2.GetPaymentResponse, error) { + f.getPaymentReq = req + return f.getPaymentResp, nil +} + +func (*fakeExecutionClientForGet) ListPayments(context.Context, *orchestrationv2.ListPaymentsRequest) (*orchestrationv2.ListPaymentsResponse, error) { + return &orchestrationv2.ListPaymentsResponse{}, nil +} + +func (*fakeExecutionClientForGet) Close() error { return nil } + +type capturingPaymentEnforcer struct { + allowed bool + organizationRef bson.ObjectID +} + +func (f *capturingPaymentEnforcer) Enforce(_ context.Context, _ bson.ObjectID, _ bson.ObjectID, organizationRef bson.ObjectID, _ bson.ObjectID, _ model.Action) (bool, error) { + f.organizationRef = organizationRef + return f.allowed, nil +} + +func (*capturingPaymentEnforcer) EnforceBatch(context.Context, []model.PermissionBoundStorable, bson.ObjectID, model.Action) (map[bson.ObjectID]bool, error) { + return nil, nil +} + +func (*capturingPaymentEnforcer) GetRoles(context.Context, bson.ObjectID, bson.ObjectID) ([]model.Role, error) { + return nil, nil +} + +func (*capturingPaymentEnforcer) GetPermissions(context.Context, bson.ObjectID, bson.ObjectID) ([]model.Role, []model.Permission, error) { + return nil, nil, nil +} + +var _ auth.Enforcer = (*capturingPaymentEnforcer)(nil) diff --git a/api/edge/bff/internal/server/paymentapiimp/service.go b/api/edge/bff/internal/server/paymentapiimp/service.go index 1b374ab1..909f6e35 100644 --- a/api/edge/bff/internal/server/paymentapiimp/service.go +++ b/api/edge/bff/internal/server/paymentapiimp/service.go @@ -49,6 +49,7 @@ type PaymentAPI struct { quotation quotationClient enf auth.Enforcer oph mutil.ParamHelper + pph mutil.ParamHelper discovery *discovery.Client refreshConsumer msg.Consumer refreshMu sync.RWMutex @@ -84,6 +85,7 @@ func CreateAPI(apiCtx eapi.API) (*PaymentAPI, error) { logger: apiCtx.Logger().Named(mservice.Payments), enf: apiCtx.Permissions().Enforcer(), oph: mutil.CreatePH(mservice.Organizations), + pph: mutil.CreatePH(mservice.Payments), } desc, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.Payments) @@ -106,6 +108,7 @@ func CreateAPI(apiCtx eapi.API) (*PaymentAPI, error) { apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/immediate"), api.Post, p.initiateImmediate) apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/by-quote"), api.Post, p.initiateByQuote) apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/by-multiquote"), api.Post, p.initiatePaymentsByQuote) + apiCtx.Register().AccountHandler(p.Name(), p.pph.AddRef("/by-ref"), api.Get, p.getPayment) apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Get, p.listPayments) apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/documents/operation"), api.Get, p.getOperationDocument) apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/registry"), api.Get, p.listDiscoveryRegistry) diff --git a/api/gateway/mntx/client/client.go b/api/gateway/mntx/client/client.go index 4d792078..0ceee49e 100644 --- a/api/gateway/mntx/client/client.go +++ b/api/gateway/mntx/client/client.go @@ -43,7 +43,7 @@ type gatewayClient struct { func New(ctx context.Context, cfg Config, opts ...grpc.DialOption) (Client, error) { cfg.setDefaults() if strings.TrimSpace(cfg.Address) == "" { - return nil, merrors.InvalidArgument("mntx: address is required") + return nil, merrors.InvalidArgument("mcards: address is required") } dialOpts := make([]grpc.DialOption, 0, len(opts)+1) dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials())) @@ -51,7 +51,7 @@ func New(ctx context.Context, cfg Config, opts ...grpc.DialOption) (Client, erro conn, err := grpc.NewClient(cfg.Address, dialOpts...) if err != nil { - return nil, merrors.Internal("mntx: dial failed: " + err.Error()) + return nil, merrors.Internal("mcards: dial failed: " + err.Error()) } return &gatewayClient{ @@ -128,7 +128,7 @@ func (g *gatewayClient) GetCardPayoutStatus(ctx context.Context, req *mntxv1.Get ctx, cancel := g.callContext(ctx, "GetCardPayoutStatus") defer cancel() if req == nil || strings.TrimSpace(req.GetPayoutId()) == "" { - return nil, merrors.InvalidArgument("mntx: payout_id is required") + return nil, merrors.InvalidArgument("mcards: payout_id is required") } resp, err := g.client.GetOperation(ctx, &connectorv1.GetOperationRequest{OperationId: strings.TrimSpace(req.GetPayoutId())}) if err != nil { @@ -138,12 +138,12 @@ func (g *gatewayClient) GetCardPayoutStatus(ctx context.Context, req *mntxv1.Get } func (g *gatewayClient) ListGatewayInstances(ctx context.Context, req *mntxv1.ListGatewayInstancesRequest) (*mntxv1.ListGatewayInstancesResponse, error) { - return nil, merrors.NotImplemented("mntx: ListGatewayInstances not supported via connector") + return nil, merrors.NotImplemented("mcards: ListGatewayInstances not supported via connector") } func operationFromCardPayout(req *mntxv1.CardPayoutRequest) (*connectorv1.Operation, error) { if req == nil { - return nil, merrors.InvalidArgument("mntx: request is required") + return nil, merrors.InvalidArgument("mcards: request is required") } params := payoutParamsFromCard(req) money := moneyFromMinor(req.GetAmountMinor(), req.GetCurrency()) @@ -163,7 +163,7 @@ func operationFromCardPayout(req *mntxv1.CardPayoutRequest) (*connectorv1.Operat func operationFromTokenPayout(req *mntxv1.CardTokenPayoutRequest) (*connectorv1.Operation, error) { if req == nil { - return nil, merrors.InvalidArgument("mntx: request is required") + return nil, merrors.InvalidArgument("mcards: request is required") } params := payoutParamsFromToken(req) money := moneyFromMinor(req.GetAmountMinor(), req.GetCurrency()) diff --git a/api/gateway/mntx/internal/service/gateway/metrics.go b/api/gateway/mntx/internal/service/gateway/metrics.go index dde3be3a..a7025824 100644 --- a/api/gateway/mntx/internal/service/gateway/metrics.go +++ b/api/gateway/mntx/internal/service/gateway/metrics.go @@ -21,7 +21,7 @@ func initMetrics() { metricsOnce.Do(func() { rpcLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{ Namespace: "sendico", - Subsystem: "mntx_gateway", + Subsystem: "mcards_gateway", Name: "rpc_latency_seconds", Help: "Latency distribution for Monetix gateway RPC handlers.", Buckets: prometheus.DefBuckets, @@ -29,7 +29,7 @@ func initMetrics() { rpcStatus = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: "sendico", - Subsystem: "mntx_gateway", + Subsystem: "mcards_gateway", Name: "rpc_requests_total", Help: "Total number of RPC invocations grouped by method and status.", }, []string{"method", "status"}) diff --git a/api/gateway/mntx/internal/service/monetix/metrics.go b/api/gateway/mntx/internal/service/monetix/metrics.go index 9d0b3fb7..28b4e616 100644 --- a/api/gateway/mntx/internal/service/monetix/metrics.go +++ b/api/gateway/mntx/internal/service/monetix/metrics.go @@ -21,21 +21,21 @@ func initMetrics() { metricsOnce.Do(func() { cardPayoutRequests = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: "sendico", - Subsystem: "mntx_gateway", + Subsystem: "mcards_gateway", Name: "card_payout_requests_total", Help: "Monetix card payout submissions grouped by outcome.", }, []string{"outcome"}) cardPayoutCallbacks = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: "sendico", - Subsystem: "mntx_gateway", + Subsystem: "mcards_gateway", Name: "card_payout_callbacks_total", Help: "Monetix card payout callbacks grouped by provider status.", }, []string{"status"}) cardPayoutLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{ Namespace: "sendico", - Subsystem: "mntx_gateway", + Subsystem: "mcards_gateway", Name: "card_payout_request_latency_seconds", Help: "Latency distribution for outbound Monetix card payout requests.", Buckets: prometheus.DefBuckets, diff --git a/api/payments/orchestrator/internal/service/orchestrationv2/pquery/service.go b/api/payments/orchestrator/internal/service/orchestrationv2/pquery/service.go index ca6e72ff..4d3acf8b 100644 --- a/api/payments/orchestrator/internal/service/orchestrationv2/pquery/service.go +++ b/api/payments/orchestrator/internal/service/orchestrationv2/pquery/service.go @@ -80,13 +80,14 @@ func (s *svc) GetPayment(ctx context.Context, in GetPaymentInput) (payment *agg. logger.Debug("Completed Get payment", fields...) }(time.Now()) - if in.OrganizationRef.IsZero() { - return nil, merrors.InvalidArgument("organization_ref is required") - } paymentRef := strings.TrimSpace(in.PaymentRef) if paymentRef == "" { return nil, merrors.InvalidArgument("payment_ref is required") } + if in.OrganizationRef.IsZero() { + payment, err = s.repo.GetByPaymentRefGlobal(ctx, paymentRef) + return payment, err + } payment, err = s.repo.GetByPaymentRef(ctx, in.OrganizationRef, paymentRef) return payment, err } diff --git a/api/payments/orchestrator/internal/service/orchestrationv2/psvc/query.go b/api/payments/orchestrator/internal/service/orchestrationv2/psvc/query.go index b4f137ea..ebd50048 100644 --- a/api/payments/orchestrator/internal/service/orchestrationv2/psvc/query.go +++ b/api/payments/orchestrator/internal/service/orchestrationv2/psvc/query.go @@ -9,6 +9,7 @@ import ( "github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/pquery" "github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/prmap" + "github.com/tech/sendico/pkg/merrors" paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1" orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2" "go.uber.org/zap" @@ -39,7 +40,10 @@ func (s *svc) GetPayment(ctx context.Context, req *orchestrationv2.GetPaymentReq logger.Debug("Completed Get payment", fields...) }(time.Now()) - _, orgID, err := parseOrganization(req.GetMeta()) + if req == nil { + return nil, merrors.InvalidArgument("request is required") + } + _, orgID, hasOrg, err := parseOptionalOrganization(req.GetMeta()) if err != nil { return nil, err } @@ -48,10 +52,11 @@ func (s *svc) GetPayment(ctx context.Context, req *orchestrationv2.GetPaymentReq return nil, err } - payment, err := s.query.GetPayment(ctx, pquery.GetPaymentInput{ - OrganizationRef: orgID, - PaymentRef: paymentRef, - }) + queryInput := pquery.GetPaymentInput{PaymentRef: paymentRef} + if hasOrg { + queryInput.OrganizationRef = orgID + } + payment, err := s.query.GetPayment(ctx, queryInput) if err != nil { return nil, err } @@ -59,7 +64,10 @@ func (s *svc) GetPayment(ctx context.Context, req *orchestrationv2.GetPaymentReq if err != nil { return nil, err } - resp = &orchestrationv2.GetPaymentResponse{Payment: protoPayment} + resp = &orchestrationv2.GetPaymentResponse{ + Payment: protoPayment, + OrganizationRef: payment.OrganizationRef.Hex(), + } return resp, nil } diff --git a/api/payments/orchestrator/internal/service/orchestrationv2/psvc/request_helpers.go b/api/payments/orchestrator/internal/service/orchestrationv2/psvc/request_helpers.go index 789c7ed0..fe5327f0 100644 --- a/api/payments/orchestrator/internal/service/orchestrationv2/psvc/request_helpers.go +++ b/api/payments/orchestrator/internal/service/orchestrationv2/psvc/request_helpers.go @@ -35,6 +35,21 @@ func parseOrganization(meta *sharedv1.RequestMeta) (string, bson.ObjectID, error return orgRef, orgID, nil } +func parseOptionalOrganization(meta *sharedv1.RequestMeta) (string, bson.ObjectID, bool, error) { + if meta == nil { + return "", bson.NilObjectID, false, nil + } + orgRef := strings.TrimSpace(meta.GetOrganizationRef()) + if orgRef == "" { + return "", bson.NilObjectID, false, nil + } + orgID, err := bson.ObjectIDFromHex(orgRef) + if err != nil { + return "", bson.NilObjectID, false, merrors.InvalidArgument("meta.organization_ref must be a valid objectID") + } + return orgRef, orgID, true, nil +} + func parsePaymentRef(value string) (string, error) { ref := strings.TrimSpace(value) if ref == "" { diff --git a/api/payments/orchestrator/internal/service/orchestrationv2/psvc/service_e2e_test.go b/api/payments/orchestrator/internal/service/orchestrationv2/psvc/service_e2e_test.go index a7abc0d1..d3b8a959 100644 --- a/api/payments/orchestrator/internal/service/orchestrationv2/psvc/service_e2e_test.go +++ b/api/payments/orchestrator/internal/service/orchestrationv2/psvc/service_e2e_test.go @@ -67,6 +67,25 @@ func TestExecutePayment_EndToEndSyncSettled(t *testing.T) { if got, want := getResp.GetPayment().GetPaymentRef(), resp.GetPayment().GetPaymentRef(); got != want { t.Fatalf("payment_ref mismatch: got=%q want=%q", got, want) } + if got, want := getResp.GetOrganizationRef(), env.orgID.Hex(); got != want { + t.Fatalf("organization_ref mismatch: got=%q want=%q", got, want) + } + + getRespGlobal, err := env.svc.GetPayment(context.Background(), &orchestrationv2.GetPaymentRequest{ + PaymentRef: resp.GetPayment().GetPaymentRef(), + }) + if err != nil { + t.Fatalf("GetPayment(global) returned error: %v", err) + } + if getRespGlobal.GetPayment() == nil { + t.Fatal("expected payment from global GetPayment") + } + if got, want := getRespGlobal.GetPayment().GetPaymentRef(), resp.GetPayment().GetPaymentRef(); got != want { + t.Fatalf("global payment_ref mismatch: got=%q want=%q", got, want) + } + if got, want := getRespGlobal.GetOrganizationRef(), env.orgID.Hex(); got != want { + t.Fatalf("global organization_ref mismatch: got=%q want=%q", got, want) + } timeline, err := env.observer.PaymentTimeline(context.Background(), oobs.PaymentTimelineInput{ PaymentRef: resp.GetPayment().GetPaymentRef(), diff --git a/api/pkg/mservice/services.go b/api/pkg/mservice/services.go index 02f0c075..bf1e1afe 100644 --- a/api/pkg/mservice/services.go +++ b/api/pkg/mservice/services.go @@ -13,7 +13,7 @@ const ( Changes Type = "changes" // Tracks changes made to resources Clients Type = "clients" // Represents client information ChainGateway Type = "chain_gateway" // Represents chain gateway microservice - MntxGateway Type = "mntx_gateway" // Represents Monetix gateway microservice + MntxGateway Type = "mcards_gateway" // Represents Monetix gateway microservice PaymentGateway Type = "payment_gateway" // Represents payment gateway microservice FXOracle Type = "fx_oracle" // Represents FX oracle microservice FXIngestor Type = "fx_ingestor" // Represents FX ingestor microservice diff --git a/api/proto/payments/orchestration/v2/orchestration.proto b/api/proto/payments/orchestration/v2/orchestration.proto index 0b806c64..d76076da 100644 --- a/api/proto/payments/orchestration/v2/orchestration.proto +++ b/api/proto/payments/orchestration/v2/orchestration.proto @@ -72,6 +72,10 @@ message ExecuteBatchPaymentResponse { } // GetPaymentRequest fetches one payment by payment_ref. +// +// If meta.organization_ref is provided, the lookup is scoped to that +// organization. If it is omitted, the service resolves the payment globally by +// payment_ref and returns the resolved organization_ref in the response. message GetPaymentRequest { payments.shared.v1.RequestMeta meta = 1; string payment_ref = 2; @@ -80,6 +84,7 @@ message GetPaymentRequest { // GetPaymentResponse returns one orchestration payment aggregate. message GetPaymentResponse { Payment payment = 1; + string organization_ref = 2; } // ListPaymentsRequest lists payments within the caller organization scope. diff --git a/ci/devserver/.env.runtime b/ci/devserver/.env.runtime index 8a3a716c..ee869578 100644 --- a/ci/devserver/.env.runtime +++ b/ci/devserver/.env.runtime @@ -1,9 +1,9 @@ # Overrides for the shared prod-style runtime when deploying main to the dev server. AMPLI_ENVIRONMENT=development -API_PROTOCOL=http -SERVICE_HOST=178.57.67.136 -WS_PROTOCOL=ws +API_PROTOCOL=https +SERVICE_HOST=dev.sendico.io +WS_PROTOCOL=wss SSH_HOST=178.57.67.136 SSH_USER=cloud diff --git a/ci/prod/scripts/bootstrap/network.sh b/ci/prod/scripts/bootstrap/network.sh index 57572b2a..00a42cc8 100755 --- a/ci/prod/scripts/bootstrap/network.sh +++ b/ci/prod/scripts/bootstrap/network.sh @@ -14,6 +14,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then SSH_OPTS+=(-vv) diff --git a/ci/prod/scripts/deploy/bff.sh b/ci/prod/scripts/deploy/bff.sh index 4f85643d..d6338c85 100755 --- a/ci/prod/scripts/deploy/bff.sh +++ b/ci/prod/scripts/deploy/bff.sh @@ -57,6 +57,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/billing_documents.sh b/ci/prod/scripts/deploy/billing_documents.sh index 37f593b7..ab5ccaa6 100755 --- a/ci/prod/scripts/deploy/billing_documents.sh +++ b/ci/prod/scripts/deploy/billing_documents.sh @@ -61,6 +61,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/billing_fees.sh b/ci/prod/scripts/deploy/billing_fees.sh index 2d7112cc..3d83fd9b 100644 --- a/ci/prod/scripts/deploy/billing_fees.sh +++ b/ci/prod/scripts/deploy/billing_fees.sh @@ -51,6 +51,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/callbacks.sh b/ci/prod/scripts/deploy/callbacks.sh index 049456f1..20dece45 100755 --- a/ci/prod/scripts/deploy/callbacks.sh +++ b/ci/prod/scripts/deploy/callbacks.sh @@ -55,6 +55,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/chain_gateway.sh b/ci/prod/scripts/deploy/chain_gateway.sh index 25f9476c..9ba00185 100755 --- a/ci/prod/scripts/deploy/chain_gateway.sh +++ b/ci/prod/scripts/deploy/chain_gateway.sh @@ -61,6 +61,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/db.sh b/ci/prod/scripts/deploy/db.sh index 2519ab7a..a7435ab5 100755 --- a/ci/prod/scripts/deploy/db.sh +++ b/ci/prod/scripts/deploy/db.sh @@ -23,6 +23,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/discovery.sh b/ci/prod/scripts/deploy/discovery.sh index 5d9662a0..88176b5d 100644 --- a/ci/prod/scripts/deploy/discovery.sh +++ b/ci/prod/scripts/deploy/discovery.sh @@ -47,6 +47,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/frontend.sh b/ci/prod/scripts/deploy/frontend.sh index 934c17d6..b3976e45 100755 --- a/ci/prod/scripts/deploy/frontend.sh +++ b/ci/prod/scripts/deploy/frontend.sh @@ -26,6 +26,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/fx.sh b/ci/prod/scripts/deploy/fx.sh index d2d1ea5d..86f18d0d 100644 --- a/ci/prod/scripts/deploy/fx.sh +++ b/ci/prod/scripts/deploy/fx.sh @@ -77,6 +77,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/ledger.sh b/ci/prod/scripts/deploy/ledger.sh index a126a24f..5e6b54dc 100755 --- a/ci/prod/scripts/deploy/ledger.sh +++ b/ci/prod/scripts/deploy/ledger.sh @@ -51,6 +51,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/mntx_gateway.sh b/ci/prod/scripts/deploy/mntx_gateway.sh index 986be6b6..96f538f3 100644 --- a/ci/prod/scripts/deploy/mntx_gateway.sh +++ b/ci/prod/scripts/deploy/mntx_gateway.sh @@ -55,6 +55,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/nats.sh b/ci/prod/scripts/deploy/nats.sh index b1185c38..1d281556 100755 --- a/ci/prod/scripts/deploy/nats.sh +++ b/ci/prod/scripts/deploy/nats.sh @@ -19,6 +19,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/notification.sh b/ci/prod/scripts/deploy/notification.sh index 58c71bb4..64c54916 100755 --- a/ci/prod/scripts/deploy/notification.sh +++ b/ci/prod/scripts/deploy/notification.sh @@ -62,6 +62,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/payments_methods.sh b/ci/prod/scripts/deploy/payments_methods.sh index d629889f..fb0baf48 100755 --- a/ci/prod/scripts/deploy/payments_methods.sh +++ b/ci/prod/scripts/deploy/payments_methods.sh @@ -51,6 +51,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/payments_orchestrator.sh b/ci/prod/scripts/deploy/payments_orchestrator.sh index 8f9c37db..6a59dfe3 100755 --- a/ci/prod/scripts/deploy/payments_orchestrator.sh +++ b/ci/prod/scripts/deploy/payments_orchestrator.sh @@ -51,6 +51,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/payments_quotation.sh b/ci/prod/scripts/deploy/payments_quotation.sh index 3c25444e..20b218d3 100755 --- a/ci/prod/scripts/deploy/payments_quotation.sh +++ b/ci/prod/scripts/deploy/payments_quotation.sh @@ -51,6 +51,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/tgsettle_gateway.sh b/ci/prod/scripts/deploy/tgsettle_gateway.sh index 3c58427f..aafc8260 100755 --- a/ci/prod/scripts/deploy/tgsettle_gateway.sh +++ b/ci/prod/scripts/deploy/tgsettle_gateway.sh @@ -51,6 +51,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/ci/prod/scripts/deploy/tron_gateway.sh b/ci/prod/scripts/deploy/tron_gateway.sh index 18d75464..817db54d 100755 --- a/ci/prod/scripts/deploy/tron_gateway.sh +++ b/ci/prod/scripts/deploy/tron_gateway.sh @@ -63,6 +63,9 @@ SSH_OPTS=( -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR + -o BatchMode=yes + -o PreferredAuthentications=publickey + -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then diff --git a/interface/api.yaml b/interface/api.yaml index 5bb3e4ca..fff747ad 100644 --- a/interface/api.yaml +++ b/interface/api.yaml @@ -93,6 +93,8 @@ paths: $ref: ./api/payments/by_quote.yaml /payments/by-multiquote/{organizations_ref}: $ref: ./api/payments/by_multiquote.yaml + /payments/object/{payments_ref}: + $ref: ./api/payments/object.yaml /payments/{organizations_ref}: $ref: ./api/payments/list.yaml /payments/documents/operation/{organizations_ref}: diff --git a/interface/api/parameters/payments_ref.yaml b/interface/api/parameters/payments_ref.yaml new file mode 100644 index 00000000..28a015bf --- /dev/null +++ b/interface/api/parameters/payments_ref.yaml @@ -0,0 +1,10 @@ +components: + parameters: + PaymentsRef: + name: payments_ref + in: path + required: true + description: Stable payment reference. + schema: + type: string + minLength: 1 diff --git a/interface/api/payments/object.yaml b/interface/api/payments/object.yaml new file mode 100644 index 00000000..0b717523 --- /dev/null +++ b/interface/api/payments/object.yaml @@ -0,0 +1,31 @@ +get: + tags: [Payments] + summary: Get payment by reference + description: Returns a single payment by `payments_ref`. + operationId: paymentsGet + security: + - bearerAuth: [] + parameters: + - $ref: ../parameters/payments_ref.yaml#/components/parameters/PaymentsRef + responses: + '200': + description: Payment data + content: + application/json: + schema: + allOf: + - $ref: ../response/response.yaml#/components/schemas/BaseResponse + - type: object + properties: + data: + $ref: ./response/payment.yaml#/components/schemas/PaymentData + '400': + $ref: ../response/operation.yaml#/components/responses/BadRequest + '401': + $ref: ../response/operation.yaml#/components/responses/Unauthorized + '403': + $ref: ../response/operation.yaml#/components/responses/Forbidden + '404': + $ref: ../response/operation.yaml#/components/responses/NotFound + '500': + $ref: ../response/operation.yaml#/components/responses/InternalServerError