interface refactoring

This commit is contained in:
Stephan D
2026-01-05 01:22:47 +01:00
parent fcd831902a
commit 7424ef751c
16 changed files with 3623 additions and 133 deletions

View File

@@ -5,12 +5,15 @@ import (
"strings"
"time"
"github.com/shopspring/decimal"
"github.com/tech/sendico/pkg/merrors"
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
mntxv1 "github.com/tech/sendico/pkg/proto/gateway/mntx/v1"
unifiedv1 "github.com/tech/sendico/pkg/proto/gateway/unified/v1"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/protobuf/types/known/structpb"
)
// Client wraps the Monetix gateway gRPC API.
@@ -22,9 +25,14 @@ type Client interface {
Close() error
}
type grpcConnectorClient interface {
SubmitOperation(ctx context.Context, in *connectorv1.SubmitOperationRequest, opts ...grpc.CallOption) (*connectorv1.SubmitOperationResponse, error)
GetOperation(ctx context.Context, in *connectorv1.GetOperationRequest, opts ...grpc.CallOption) (*connectorv1.GetOperationResponse, error)
}
type gatewayClient struct {
conn *grpc.ClientConn
client unifiedv1.UnifiedGatewayServiceClient
client grpcConnectorClient
cfg Config
logger *zap.Logger
}
@@ -44,12 +52,12 @@ func New(ctx context.Context, cfg Config, opts ...grpc.DialOption) (Client, erro
conn, err := grpc.DialContext(dialCtx, cfg.Address, dialOpts...)
if err != nil {
return nil, merrors.Internal("mntx: dial failed: " + err.Error())
return nil, merrors.Internal("mntx: dial failed: "+err.Error())
}
return &gatewayClient{
conn: conn,
client: unifiedv1.NewUnifiedGatewayServiceClient(conn),
client: connectorv1.NewConnectorServiceClient(conn),
cfg: cfg,
logger: cfg.Logger,
}, nil
@@ -70,37 +78,253 @@ func (g *gatewayClient) callContext(ctx context.Context, method string) (context
if timeout <= 0 {
timeout = 5 * time.Second
}
fields := []zap.Field{
zap.String("method", method),
zap.Duration("timeout", timeout),
if g.logger != nil {
fields := []zap.Field{
zap.String("method", method),
zap.Duration("timeout", timeout),
}
if deadline, ok := ctx.Deadline(); ok {
fields = append(fields, zap.Time("parent_deadline", deadline), zap.Duration("parent_deadline_in", time.Until(deadline)))
}
g.logger.Info("Mntx gateway client call timeout applied", fields...)
}
if deadline, ok := ctx.Deadline(); ok {
fields = append(fields, zap.Time("parent_deadline", deadline), zap.Duration("parent_deadline_in", time.Until(deadline)))
}
g.logger.Info("Mntx gateway client call timeout applied", fields...)
return context.WithTimeout(ctx, timeout)
}
func (g *gatewayClient) CreateCardPayout(ctx context.Context, req *mntxv1.CardPayoutRequest) (*mntxv1.CardPayoutResponse, error) {
ctx, cancel := g.callContext(ctx, "CreateCardPayout")
defer cancel()
return g.client.CreateCardPayout(ctx, req)
operation, err := operationFromCardPayout(req)
if err != nil {
return nil, err
}
resp, err := g.client.SubmitOperation(ctx, &connectorv1.SubmitOperationRequest{Operation: operation})
if err != nil {
return nil, err
}
if resp.GetReceipt() != nil && resp.GetReceipt().GetError() != nil {
return nil, connectorError(resp.GetReceipt().GetError())
}
return &mntxv1.CardPayoutResponse{Payout: payoutFromReceipt(req.GetPayoutId(), resp.GetReceipt())}, nil
}
func (g *gatewayClient) CreateCardTokenPayout(ctx context.Context, req *mntxv1.CardTokenPayoutRequest) (*mntxv1.CardTokenPayoutResponse, error) {
ctx, cancel := g.callContext(ctx, "CreateCardTokenPayout")
defer cancel()
return g.client.CreateCardTokenPayout(ctx, req)
operation, err := operationFromTokenPayout(req)
if err != nil {
return nil, err
}
resp, err := g.client.SubmitOperation(ctx, &connectorv1.SubmitOperationRequest{Operation: operation})
if err != nil {
return nil, err
}
if resp.GetReceipt() != nil && resp.GetReceipt().GetError() != nil {
return nil, connectorError(resp.GetReceipt().GetError())
}
return &mntxv1.CardTokenPayoutResponse{Payout: payoutFromReceipt(req.GetPayoutId(), resp.GetReceipt())}, nil
}
func (g *gatewayClient) GetCardPayoutStatus(ctx context.Context, req *mntxv1.GetCardPayoutStatusRequest) (*mntxv1.GetCardPayoutStatusResponse, error) {
ctx, cancel := g.callContext(ctx, "GetCardPayoutStatus")
defer cancel()
return g.client.GetCardPayoutStatus(ctx, req)
if req == nil || strings.TrimSpace(req.GetPayoutId()) == "" {
return nil, merrors.InvalidArgument("mntx: payout_id is required")
}
resp, err := g.client.GetOperation(ctx, &connectorv1.GetOperationRequest{OperationId: strings.TrimSpace(req.GetPayoutId())})
if err != nil {
return nil, err
}
return &mntxv1.GetCardPayoutStatusResponse{Payout: payoutFromOperation(resp.GetOperation())}, nil
}
func (g *gatewayClient) ListGatewayInstances(ctx context.Context, req *mntxv1.ListGatewayInstancesRequest) (*mntxv1.ListGatewayInstancesResponse, error) {
ctx, cancel := g.callContext(ctx, "ListGatewayInstances")
defer cancel()
return g.client.ListGatewayInstances(ctx, req)
return nil, merrors.NotImplemented("mntx: ListGatewayInstances not supported via connector")
}
func operationFromCardPayout(req *mntxv1.CardPayoutRequest) (*connectorv1.Operation, error) {
if req == nil {
return nil, merrors.InvalidArgument("mntx: request is required")
}
params := payoutParamsFromCard(req)
money := moneyFromMinor(req.GetAmountMinor(), req.GetCurrency())
return &connectorv1.Operation{
Type: connectorv1.OperationType_PAYOUT,
IdempotencyKey: strings.TrimSpace(req.GetPayoutId()),
Money: money,
Params: structFromMap(params),
}, nil
}
func operationFromTokenPayout(req *mntxv1.CardTokenPayoutRequest) (*connectorv1.Operation, error) {
if req == nil {
return nil, merrors.InvalidArgument("mntx: request is required")
}
params := payoutParamsFromToken(req)
money := moneyFromMinor(req.GetAmountMinor(), req.GetCurrency())
return &connectorv1.Operation{
Type: connectorv1.OperationType_PAYOUT,
IdempotencyKey: strings.TrimSpace(req.GetPayoutId()),
Money: money,
Params: structFromMap(params),
}, nil
}
func payoutParamsFromCard(req *mntxv1.CardPayoutRequest) map[string]interface{} {
params := map[string]interface{}{
"payout_id": strings.TrimSpace(req.GetPayoutId()),
"project_id": req.GetProjectId(),
"customer_id": strings.TrimSpace(req.GetCustomerId()),
"customer_first_name": strings.TrimSpace(req.GetCustomerFirstName()),
"customer_middle_name": strings.TrimSpace(req.GetCustomerMiddleName()),
"customer_last_name": strings.TrimSpace(req.GetCustomerLastName()),
"customer_ip": strings.TrimSpace(req.GetCustomerIp()),
"customer_zip": strings.TrimSpace(req.GetCustomerZip()),
"customer_country": strings.TrimSpace(req.GetCustomerCountry()),
"customer_state": strings.TrimSpace(req.GetCustomerState()),
"customer_city": strings.TrimSpace(req.GetCustomerCity()),
"customer_address": strings.TrimSpace(req.GetCustomerAddress()),
"amount_minor": req.GetAmountMinor(),
"currency": strings.TrimSpace(req.GetCurrency()),
"card_pan": strings.TrimSpace(req.GetCardPan()),
"card_exp_year": req.GetCardExpYear(),
"card_exp_month": req.GetCardExpMonth(),
"card_holder": strings.TrimSpace(req.GetCardHolder()),
}
if len(req.GetMetadata()) > 0 {
params["metadata"] = mapStringToInterface(req.GetMetadata())
}
return params
}
func payoutParamsFromToken(req *mntxv1.CardTokenPayoutRequest) map[string]interface{} {
params := map[string]interface{}{
"payout_id": strings.TrimSpace(req.GetPayoutId()),
"project_id": req.GetProjectId(),
"customer_id": strings.TrimSpace(req.GetCustomerId()),
"customer_first_name": strings.TrimSpace(req.GetCustomerFirstName()),
"customer_middle_name": strings.TrimSpace(req.GetCustomerMiddleName()),
"customer_last_name": strings.TrimSpace(req.GetCustomerLastName()),
"customer_ip": strings.TrimSpace(req.GetCustomerIp()),
"customer_zip": strings.TrimSpace(req.GetCustomerZip()),
"customer_country": strings.TrimSpace(req.GetCustomerCountry()),
"customer_state": strings.TrimSpace(req.GetCustomerState()),
"customer_city": strings.TrimSpace(req.GetCustomerCity()),
"customer_address": strings.TrimSpace(req.GetCustomerAddress()),
"amount_minor": req.GetAmountMinor(),
"currency": strings.TrimSpace(req.GetCurrency()),
"card_token": strings.TrimSpace(req.GetCardToken()),
"card_holder": strings.TrimSpace(req.GetCardHolder()),
"masked_pan": strings.TrimSpace(req.GetMaskedPan()),
}
if len(req.GetMetadata()) > 0 {
params["metadata"] = mapStringToInterface(req.GetMetadata())
}
return params
}
func moneyFromMinor(amount int64, currency string) *moneyv1.Money {
if amount <= 0 {
return nil
}
dec := decimal.NewFromInt(amount).Div(decimal.NewFromInt(100))
return &moneyv1.Money{
Amount: dec.StringFixed(2),
Currency: strings.ToUpper(strings.TrimSpace(currency)),
}
}
func payoutFromReceipt(payoutID string, receipt *connectorv1.OperationReceipt) *mntxv1.CardPayoutState {
state := &mntxv1.CardPayoutState{PayoutId: strings.TrimSpace(payoutID)}
if receipt == nil {
return state
}
state.Status = payoutStatusFromOperation(receipt.GetStatus())
state.ProviderPaymentId = strings.TrimSpace(receipt.GetProviderRef())
return state
}
func payoutFromOperation(op *connectorv1.Operation) *mntxv1.CardPayoutState {
if op == nil {
return nil
}
state := &mntxv1.CardPayoutState{
PayoutId: strings.TrimSpace(op.GetOperationId()),
Status: payoutStatusFromOperation(op.GetStatus()),
ProviderPaymentId: strings.TrimSpace(op.GetProviderRef()),
}
if money := op.GetMoney(); money != nil {
state.Currency = strings.TrimSpace(money.GetCurrency())
state.AmountMinor = minorFromMoney(money)
}
return state
}
func minorFromMoney(m *moneyv1.Money) int64 {
if m == nil {
return 0
}
amount := strings.TrimSpace(m.GetAmount())
if amount == "" {
return 0
}
dec, err := decimal.NewFromString(amount)
if err != nil {
return 0
}
return dec.Mul(decimal.NewFromInt(100)).IntPart()
}
func payoutStatusFromOperation(status connectorv1.OperationStatus) mntxv1.PayoutStatus {
switch status {
case connectorv1.OperationStatus_CONFIRMED:
return mntxv1.PayoutStatus_PAYOUT_STATUS_PROCESSED
case connectorv1.OperationStatus_FAILED:
return mntxv1.PayoutStatus_PAYOUT_STATUS_FAILED
case connectorv1.OperationStatus_PENDING, connectorv1.OperationStatus_SUBMITTED:
return mntxv1.PayoutStatus_PAYOUT_STATUS_PENDING
default:
return mntxv1.PayoutStatus_PAYOUT_STATUS_UNSPECIFIED
}
}
func connectorError(err *connectorv1.ConnectorError) error {
if err == nil {
return nil
}
msg := strings.TrimSpace(err.GetMessage())
switch err.GetCode() {
case connectorv1.ErrorCode_INVALID_PARAMS:
return merrors.InvalidArgument(msg)
case connectorv1.ErrorCode_NOT_FOUND:
return merrors.NoData(msg)
case connectorv1.ErrorCode_UNSUPPORTED_OPERATION, connectorv1.ErrorCode_UNSUPPORTED_ACCOUNT_KIND:
return merrors.NotImplemented(msg)
case connectorv1.ErrorCode_RATE_LIMITED, connectorv1.ErrorCode_TEMPORARY_UNAVAILABLE:
return merrors.Internal(msg)
default:
return merrors.Internal(msg)
}
}
func structFromMap(data map[string]interface{}) *structpb.Struct {
if len(data) == 0 {
return nil
}
result, err := structpb.NewStruct(data)
if err != nil {
return nil
}
return result
}
func mapStringToInterface(input map[string]string) map[string]interface{} {
if len(input) == 0 {
return nil
}
out := make(map[string]interface{}, len(input))
for k, v := range input {
out[k] = v
}
return out
}

View File

@@ -7,6 +7,7 @@ replace github.com/tech/sendico/pkg => ../../pkg
require (
github.com/go-chi/chi/v5 v5.2.3
github.com/prometheus/client_golang v1.23.2
github.com/shopspring/decimal v1.4.0
github.com/tech/sendico/pkg v0.1.0
go.uber.org/zap v1.27.1
google.golang.org/grpc v1.78.0

View File

@@ -0,0 +1,294 @@
package gateway
import (
"context"
"errors"
"fmt"
"strings"
"github.com/shopspring/decimal"
"github.com/tech/sendico/gateway/mntx/internal/appversion"
"github.com/tech/sendico/pkg/connector/params"
"github.com/tech/sendico/pkg/merrors"
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
mntxv1 "github.com/tech/sendico/pkg/proto/gateway/mntx/v1"
)
const mntxConnectorID = "mntx"
func (s *Service) GetCapabilities(_ context.Context, _ *connectorv1.GetCapabilitiesRequest) (*connectorv1.GetCapabilitiesResponse, error) {
return &connectorv1.GetCapabilitiesResponse{
Capabilities: &connectorv1.ConnectorCapabilities{
ConnectorType: mntxConnectorID,
Version: appversion.Create().Short(),
SupportedAccountKinds: nil,
SupportedOperationTypes: []connectorv1.OperationType{connectorv1.OperationType_PAYOUT},
OperationParams: mntxOperationParams(),
},
}, nil
}
func (s *Service) OpenAccount(_ context.Context, _ *connectorv1.OpenAccountRequest) (*connectorv1.OpenAccountResponse, error) {
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_ACCOUNT_KIND, "open_account: unsupported", nil, "")}, nil
}
func (s *Service) GetAccount(_ context.Context, _ *connectorv1.GetAccountRequest) (*connectorv1.GetAccountResponse, error) {
return nil, merrors.NotImplemented("get_account: unsupported")
}
func (s *Service) ListAccounts(_ context.Context, _ *connectorv1.ListAccountsRequest) (*connectorv1.ListAccountsResponse, error) {
return nil, merrors.NotImplemented("list_accounts: unsupported")
}
func (s *Service) GetBalance(_ context.Context, _ *connectorv1.GetBalanceRequest) (*connectorv1.GetBalanceResponse, error) {
return nil, merrors.NotImplemented("get_balance: unsupported")
}
func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOperationRequest) (*connectorv1.SubmitOperationResponse, error) {
if req == nil || req.GetOperation() == nil {
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: operation is required", nil, "")}}, nil
}
op := req.GetOperation()
if strings.TrimSpace(op.GetIdempotencyKey()) == "" {
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: idempotency_key is required", op, "")}}, nil
}
if op.GetType() != connectorv1.OperationType_PAYOUT {
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_OPERATION, "submit_operation: unsupported operation type", op, "")}}, nil
}
reader := params.New(op.GetParams())
amountMinor, currency, err := payoutAmount(op, reader)
if err != nil {
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), op, "")}}, nil
}
payoutID := strings.TrimSpace(reader.String("payout_id"))
if payoutID == "" {
payoutID = strings.TrimSpace(op.GetIdempotencyKey())
}
if strings.TrimSpace(reader.String("card_token")) != "" {
resp, err := s.CreateCardTokenPayout(ctx, buildCardTokenPayoutRequest(reader, payoutID, amountMinor, currency))
if err != nil {
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
}
return &connectorv1.SubmitOperationResponse{Receipt: payoutReceipt(resp.GetPayout())}, nil
}
resp, err := s.CreateCardPayout(ctx, buildCardPayoutRequest(reader, payoutID, amountMinor, currency))
if err != nil {
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
}
return &connectorv1.SubmitOperationResponse{Receipt: payoutReceipt(resp.GetPayout())}, nil
}
func (s *Service) GetOperation(ctx context.Context, req *connectorv1.GetOperationRequest) (*connectorv1.GetOperationResponse, error) {
if req == nil || strings.TrimSpace(req.GetOperationId()) == "" {
return nil, merrors.InvalidArgument("get_operation: operation_id is required")
}
resp, err := s.GetCardPayoutStatus(ctx, &mntxv1.GetCardPayoutStatusRequest{PayoutId: strings.TrimSpace(req.GetOperationId())})
if err != nil {
return nil, err
}
return &connectorv1.GetOperationResponse{Operation: payoutToOperation(resp.GetPayout())}, nil
}
func (s *Service) ListOperations(_ context.Context, _ *connectorv1.ListOperationsRequest) (*connectorv1.ListOperationsResponse, error) {
return nil, merrors.NotImplemented("list_operations: unsupported")
}
func mntxOperationParams() []*connectorv1.OperationParamSpec {
return []*connectorv1.OperationParamSpec{
{OperationType: connectorv1.OperationType_PAYOUT, Params: []*connectorv1.ParamSpec{
{Key: "customer_id", Type: connectorv1.ParamType_STRING, Required: true},
{Key: "customer_first_name", Type: connectorv1.ParamType_STRING, Required: true},
{Key: "customer_last_name", Type: connectorv1.ParamType_STRING, Required: true},
{Key: "customer_ip", Type: connectorv1.ParamType_STRING, Required: true},
{Key: "card_token", Type: connectorv1.ParamType_STRING, Required: false},
{Key: "card_pan", Type: connectorv1.ParamType_STRING, Required: false},
{Key: "card_exp_year", Type: connectorv1.ParamType_INT, Required: false},
{Key: "card_exp_month", Type: connectorv1.ParamType_INT, Required: false},
{Key: "card_holder", Type: connectorv1.ParamType_STRING, Required: false},
{Key: "amount_minor", Type: connectorv1.ParamType_INT, Required: false},
{Key: "project_id", Type: connectorv1.ParamType_INT, Required: false},
{Key: "customer_middle_name", Type: connectorv1.ParamType_STRING, Required: false},
{Key: "customer_country", Type: connectorv1.ParamType_STRING, Required: false},
{Key: "customer_state", Type: connectorv1.ParamType_STRING, Required: false},
{Key: "customer_city", Type: connectorv1.ParamType_STRING, Required: false},
{Key: "customer_address", Type: connectorv1.ParamType_STRING, Required: false},
{Key: "customer_zip", Type: connectorv1.ParamType_STRING, Required: false},
{Key: "masked_pan", Type: connectorv1.ParamType_STRING, Required: false},
{Key: "metadata", Type: connectorv1.ParamType_JSON, Required: false},
}},
}
}
func payoutAmount(op *connectorv1.Operation, reader params.Reader) (int64, string, error) {
if op == nil {
return 0, "", fmt.Errorf("payout: operation is required")
}
currency := currencyFromOperation(op)
if currency == "" {
return 0, "", fmt.Errorf("payout: currency is required")
}
if minor, ok := reader.Int64("amount_minor"); ok && minor > 0 {
return minor, currency, nil
}
money := op.GetMoney()
if money == nil {
return 0, "", fmt.Errorf("payout: money is required")
}
amount := strings.TrimSpace(money.GetAmount())
if amount == "" {
return 0, "", fmt.Errorf("payout: amount is required")
}
dec, err := decimal.NewFromString(amount)
if err != nil {
return 0, "", fmt.Errorf("payout: invalid amount")
}
minor := dec.Mul(decimal.NewFromInt(100)).IntPart()
return minor, currency, nil
}
func currencyFromOperation(op *connectorv1.Operation) string {
if op == nil || op.GetMoney() == nil {
return ""
}
currency := strings.TrimSpace(op.GetMoney().GetCurrency())
if idx := strings.Index(currency, "-"); idx > 0 {
currency = currency[:idx]
}
return strings.ToUpper(currency)
}
func buildCardTokenPayoutRequest(reader params.Reader, payoutID string, amountMinor int64, currency string) *mntxv1.CardTokenPayoutRequest {
req := &mntxv1.CardTokenPayoutRequest{
PayoutId: payoutID,
ProjectId: readerInt64(reader, "project_id"),
CustomerId: strings.TrimSpace(reader.String("customer_id")),
CustomerFirstName: strings.TrimSpace(reader.String("customer_first_name")),
CustomerMiddleName: strings.TrimSpace(reader.String("customer_middle_name")),
CustomerLastName: strings.TrimSpace(reader.String("customer_last_name")),
CustomerIp: strings.TrimSpace(reader.String("customer_ip")),
CustomerZip: strings.TrimSpace(reader.String("customer_zip")),
CustomerCountry: strings.TrimSpace(reader.String("customer_country")),
CustomerState: strings.TrimSpace(reader.String("customer_state")),
CustomerCity: strings.TrimSpace(reader.String("customer_city")),
CustomerAddress: strings.TrimSpace(reader.String("customer_address")),
AmountMinor: amountMinor,
Currency: currency,
CardToken: strings.TrimSpace(reader.String("card_token")),
CardHolder: strings.TrimSpace(reader.String("card_holder")),
MaskedPan: strings.TrimSpace(reader.String("masked_pan")),
Metadata: reader.StringMap("metadata"),
}
return req
}
func buildCardPayoutRequest(reader params.Reader, payoutID string, amountMinor int64, currency string) *mntxv1.CardPayoutRequest {
return &mntxv1.CardPayoutRequest{
PayoutId: payoutID,
ProjectId: readerInt64(reader, "project_id"),
CustomerId: strings.TrimSpace(reader.String("customer_id")),
CustomerFirstName: strings.TrimSpace(reader.String("customer_first_name")),
CustomerMiddleName: strings.TrimSpace(reader.String("customer_middle_name")),
CustomerLastName: strings.TrimSpace(reader.String("customer_last_name")),
CustomerIp: strings.TrimSpace(reader.String("customer_ip")),
CustomerZip: strings.TrimSpace(reader.String("customer_zip")),
CustomerCountry: strings.TrimSpace(reader.String("customer_country")),
CustomerState: strings.TrimSpace(reader.String("customer_state")),
CustomerCity: strings.TrimSpace(reader.String("customer_city")),
CustomerAddress: strings.TrimSpace(reader.String("customer_address")),
AmountMinor: amountMinor,
Currency: currency,
CardPan: strings.TrimSpace(reader.String("card_pan")),
CardExpYear: uint32(readerInt64(reader, "card_exp_year")),
CardExpMonth: uint32(readerInt64(reader, "card_exp_month")),
CardHolder: strings.TrimSpace(reader.String("card_holder")),
Metadata: reader.StringMap("metadata"),
}
}
func readerInt64(reader params.Reader, key string) int64 {
if v, ok := reader.Int64(key); ok {
return v
}
return 0
}
func payoutReceipt(state *mntxv1.CardPayoutState) *connectorv1.OperationReceipt {
if state == nil {
return &connectorv1.OperationReceipt{
Status: connectorv1.OperationStatus_PENDING,
}
}
return &connectorv1.OperationReceipt{
OperationId: strings.TrimSpace(state.GetPayoutId()),
Status: payoutStatusToOperation(state.GetStatus()),
ProviderRef: strings.TrimSpace(state.GetProviderPaymentId()),
}
}
func payoutToOperation(state *mntxv1.CardPayoutState) *connectorv1.Operation {
if state == nil {
return nil
}
return &connectorv1.Operation{
OperationId: strings.TrimSpace(state.GetPayoutId()),
Type: connectorv1.OperationType_PAYOUT,
Status: payoutStatusToOperation(state.GetStatus()),
Money: &moneyv1.Money{
Amount: minorToDecimal(state.GetAmountMinor()),
Currency: strings.ToUpper(strings.TrimSpace(state.GetCurrency())),
},
ProviderRef: strings.TrimSpace(state.GetProviderPaymentId()),
CreatedAt: state.GetCreatedAt(),
UpdatedAt: state.GetUpdatedAt(),
}
}
func minorToDecimal(amount int64) string {
dec := decimal.NewFromInt(amount).Div(decimal.NewFromInt(100))
return dec.StringFixed(2)
}
func payoutStatusToOperation(status mntxv1.PayoutStatus) connectorv1.OperationStatus {
switch status {
case mntxv1.PayoutStatus_PAYOUT_STATUS_PROCESSED:
return connectorv1.OperationStatus_CONFIRMED
case mntxv1.PayoutStatus_PAYOUT_STATUS_FAILED:
return connectorv1.OperationStatus_FAILED
case mntxv1.PayoutStatus_PAYOUT_STATUS_PENDING:
return connectorv1.OperationStatus_PENDING
default:
return connectorv1.OperationStatus_PENDING
}
}
func connectorError(code connectorv1.ErrorCode, message string, op *connectorv1.Operation, accountID string) *connectorv1.ConnectorError {
err := &connectorv1.ConnectorError{
Code: code,
Message: strings.TrimSpace(message),
AccountId: strings.TrimSpace(accountID),
}
if op != nil {
err.CorrelationId = strings.TrimSpace(op.GetCorrelationId())
err.ParentIntentId = strings.TrimSpace(op.GetParentIntentId())
err.OperationId = strings.TrimSpace(op.GetOperationId())
}
return err
}
func mapErrorCode(err error) connectorv1.ErrorCode {
switch {
case errors.Is(err, merrors.ErrInvalidArg):
return connectorv1.ErrorCode_INVALID_PARAMS
case errors.Is(err, merrors.ErrNoData):
return connectorv1.ErrorCode_NOT_FOUND
case errors.Is(err, merrors.ErrNotImplemented):
return connectorv1.ErrorCode_UNSUPPORTED_OPERATION
case errors.Is(err, merrors.ErrInternal):
return connectorv1.ErrorCode_TEMPORARY_UNAVAILABLE
default:
return connectorv1.ErrorCode_PROVIDER_ERROR
}
}

View File

@@ -15,7 +15,7 @@ import (
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mservice"
gatewayv1 "github.com/tech/sendico/pkg/proto/common/gateway/v1"
unifiedv1 "github.com/tech/sendico/pkg/proto/gateway/unified/v1"
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
"go.uber.org/zap"
"google.golang.org/grpc"
)
@@ -31,7 +31,7 @@ type Service struct {
gatewayDescriptor *gatewayv1.GatewayInstanceDescriptor
announcer *discovery.Announcer
unifiedv1.UnimplementedUnifiedGatewayServiceServer
connectorv1.UnimplementedConnectorServiceServer
}
type payoutFailure interface {
@@ -96,7 +96,7 @@ func NewService(logger mlogger.Logger, opts ...Option) *Service {
// Register wires the service onto the provided gRPC router.
func (s *Service) Register(router routers.GRPC) error {
return router.Register(func(reg grpc.ServiceRegistrar) {
unifiedv1.RegisterUnifiedGatewayServiceServer(reg, s)
connectorv1.RegisterConnectorServiceServer(reg, s)
})
}