package client import ( "context" "crypto/tls" "fmt" "strings" "time" "github.com/tech/sendico/pkg/merrors" orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" ) // Client exposes typed helpers around the payment orchestrator gRPC API. type Client interface { QuotePayment(ctx context.Context, req *orchestratorv1.QuotePaymentRequest) (*orchestratorv1.QuotePaymentResponse, error) InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error) CancelPayment(ctx context.Context, req *orchestratorv1.CancelPaymentRequest) (*orchestratorv1.CancelPaymentResponse, error) GetPayment(ctx context.Context, req *orchestratorv1.GetPaymentRequest) (*orchestratorv1.GetPaymentResponse, error) ListPayments(ctx context.Context, req *orchestratorv1.ListPaymentsRequest) (*orchestratorv1.ListPaymentsResponse, error) InitiateConversion(ctx context.Context, req *orchestratorv1.InitiateConversionRequest) (*orchestratorv1.InitiateConversionResponse, error) ProcessTransferUpdate(ctx context.Context, req *orchestratorv1.ProcessTransferUpdateRequest) (*orchestratorv1.ProcessTransferUpdateResponse, error) ProcessDepositObserved(ctx context.Context, req *orchestratorv1.ProcessDepositObservedRequest) (*orchestratorv1.ProcessDepositObservedResponse, error) Close() error } type grpcOrchestratorClient interface { QuotePayment(ctx context.Context, in *orchestratorv1.QuotePaymentRequest, opts ...grpc.CallOption) (*orchestratorv1.QuotePaymentResponse, error) InitiatePayment(ctx context.Context, in *orchestratorv1.InitiatePaymentRequest, opts ...grpc.CallOption) (*orchestratorv1.InitiatePaymentResponse, error) CancelPayment(ctx context.Context, in *orchestratorv1.CancelPaymentRequest, opts ...grpc.CallOption) (*orchestratorv1.CancelPaymentResponse, error) GetPayment(ctx context.Context, in *orchestratorv1.GetPaymentRequest, opts ...grpc.CallOption) (*orchestratorv1.GetPaymentResponse, error) ListPayments(ctx context.Context, in *orchestratorv1.ListPaymentsRequest, opts ...grpc.CallOption) (*orchestratorv1.ListPaymentsResponse, error) InitiateConversion(ctx context.Context, in *orchestratorv1.InitiateConversionRequest, opts ...grpc.CallOption) (*orchestratorv1.InitiateConversionResponse, error) ProcessTransferUpdate(ctx context.Context, in *orchestratorv1.ProcessTransferUpdateRequest, opts ...grpc.CallOption) (*orchestratorv1.ProcessTransferUpdateResponse, error) ProcessDepositObserved(ctx context.Context, in *orchestratorv1.ProcessDepositObservedRequest, opts ...grpc.CallOption) (*orchestratorv1.ProcessDepositObservedResponse, error) } type orchestratorClient struct { cfg Config conn *grpc.ClientConn client grpcOrchestratorClient } // New dials the payment orchestrator 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-orchestrator: 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-orchestrator: dial %s", cfg.Address)) } return &orchestratorClient{ cfg: cfg, conn: conn, client: orchestratorv1.NewPaymentOrchestratorClient(conn), }, nil } // NewWithClient injects a pre-built orchestrator client (useful for tests). func NewWithClient(cfg Config, oc grpcOrchestratorClient) Client { cfg.setDefaults() return &orchestratorClient{ cfg: cfg, client: oc, } } func (c *orchestratorClient) Close() error { if c.conn != nil { return c.conn.Close() } return nil } func (c *orchestratorClient) QuotePayment(ctx context.Context, req *orchestratorv1.QuotePaymentRequest) (*orchestratorv1.QuotePaymentResponse, error) { ctx, cancel := c.callContext(ctx) defer cancel() return c.client.QuotePayment(ctx, req) } func (c *orchestratorClient) InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error) { ctx, cancel := c.callContext(ctx) defer cancel() return c.client.InitiatePayment(ctx, req) } func (c *orchestratorClient) CancelPayment(ctx context.Context, req *orchestratorv1.CancelPaymentRequest) (*orchestratorv1.CancelPaymentResponse, error) { ctx, cancel := c.callContext(ctx) defer cancel() return c.client.CancelPayment(ctx, req) } func (c *orchestratorClient) GetPayment(ctx context.Context, req *orchestratorv1.GetPaymentRequest) (*orchestratorv1.GetPaymentResponse, error) { ctx, cancel := c.callContext(ctx) defer cancel() return c.client.GetPayment(ctx, req) } func (c *orchestratorClient) ListPayments(ctx context.Context, req *orchestratorv1.ListPaymentsRequest) (*orchestratorv1.ListPaymentsResponse, error) { ctx, cancel := c.callContext(ctx) defer cancel() return c.client.ListPayments(ctx, req) } func (c *orchestratorClient) InitiateConversion(ctx context.Context, req *orchestratorv1.InitiateConversionRequest) (*orchestratorv1.InitiateConversionResponse, error) { ctx, cancel := c.callContext(ctx) defer cancel() return c.client.InitiateConversion(ctx, req) } func (c *orchestratorClient) ProcessTransferUpdate(ctx context.Context, req *orchestratorv1.ProcessTransferUpdateRequest) (*orchestratorv1.ProcessTransferUpdateResponse, error) { ctx, cancel := c.callContext(ctx) defer cancel() return c.client.ProcessTransferUpdate(ctx, req) } func (c *orchestratorClient) ProcessDepositObserved(ctx context.Context, req *orchestratorv1.ProcessDepositObservedRequest) (*orchestratorv1.ProcessDepositObservedResponse, error) { ctx, cancel := c.callContext(ctx) defer cancel() return c.client.ProcessDepositObserved(ctx, req) } func (c *orchestratorClient) callContext(ctx context.Context) (context.Context, context.CancelFunc) { timeout := c.cfg.CallTimeout if timeout <= 0 { timeout = 3 * time.Second } return context.WithTimeout(ctx, timeout) }