monetix gateway
This commit is contained in:
134
api/gateway/mntx/internal/service/gateway/callback.go
Normal file
134
api/gateway/mntx/internal/service/gateway/callback.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/gateway/mntx/internal/service/monetix"
|
||||
clockpkg "github.com/tech/sendico/pkg/clock"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
mntxv1 "github.com/tech/sendico/pkg/proto/gateway/mntx/v1"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type callbackPayment struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Date string `json:"date"`
|
||||
Method string `json:"method"`
|
||||
Description string `json:"description"`
|
||||
Sum struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
} `json:"sum"`
|
||||
}
|
||||
|
||||
type callbackOperation struct {
|
||||
ID int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Date string `json:"date"`
|
||||
CreatedDate string `json:"created_date"`
|
||||
RequestID string `json:"request_id"`
|
||||
SumInitial struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
} `json:"sum_initial"`
|
||||
SumConverted struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
} `json:"sum_converted"`
|
||||
Provider struct {
|
||||
ID int64 `json:"id"`
|
||||
PaymentID string `json:"payment_id"`
|
||||
Date string `json:"date"`
|
||||
AuthCode string `json:"auth_code"`
|
||||
} `json:"provider"`
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type monetixCallback struct {
|
||||
ProjectID int64 `json:"project_id"`
|
||||
Payment callbackPayment `json:"payment"`
|
||||
Account struct {
|
||||
Number string `json:"number"`
|
||||
} `json:"account"`
|
||||
Customer struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"customer"`
|
||||
Operation callbackOperation `json:"operation"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
// ProcessMonetixCallback ingests Monetix provider callbacks and updates payout state.
|
||||
func (s *Service) ProcessMonetixCallback(ctx context.Context, payload []byte) (int, error) {
|
||||
if s.card == nil {
|
||||
return http.StatusInternalServerError, merrors.Internal("card payout processor not initialised")
|
||||
}
|
||||
return s.card.ProcessCallback(ctx, payload)
|
||||
}
|
||||
|
||||
func mapCallbackToState(clock clockpkg.Clock, cfg monetix.Config, cb monetixCallback) (*mntxv1.CardPayoutState, string) {
|
||||
status := strings.ToLower(strings.TrimSpace(cb.Payment.Status))
|
||||
opStatus := strings.ToLower(strings.TrimSpace(cb.Operation.Status))
|
||||
code := strings.TrimSpace(cb.Operation.Code)
|
||||
|
||||
outcome := monetix.OutcomeDecline
|
||||
internalStatus := mntxv1.PayoutStatus_PAYOUT_STATUS_FAILED
|
||||
|
||||
if status == cfg.SuccessStatus() && opStatus == cfg.SuccessStatus() && (code == "" || code == "0") {
|
||||
internalStatus = mntxv1.PayoutStatus_PAYOUT_STATUS_PROCESSED
|
||||
outcome = monetix.OutcomeSuccess
|
||||
} else if status == cfg.ProcessingStatus() || opStatus == cfg.ProcessingStatus() {
|
||||
internalStatus = mntxv1.PayoutStatus_PAYOUT_STATUS_PENDING
|
||||
outcome = monetix.OutcomeProcessing
|
||||
}
|
||||
|
||||
now := timestamppb.New(clock.Now())
|
||||
state := &mntxv1.CardPayoutState{
|
||||
PayoutId: cb.Payment.ID,
|
||||
ProjectId: cb.ProjectID,
|
||||
CustomerId: cb.Customer.ID,
|
||||
AmountMinor: cb.Payment.Sum.Amount,
|
||||
Currency: strings.ToUpper(strings.TrimSpace(cb.Payment.Sum.Currency)),
|
||||
Status: internalStatus,
|
||||
ProviderCode: cb.Operation.Code,
|
||||
ProviderMessage: cb.Operation.Message,
|
||||
ProviderPaymentId: fallbackProviderPaymentID(cb),
|
||||
UpdatedAt: now,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
return state, outcome
|
||||
}
|
||||
|
||||
func fallbackProviderPaymentID(cb monetixCallback) string {
|
||||
if cb.Operation.Provider.PaymentID != "" {
|
||||
return cb.Operation.Provider.PaymentID
|
||||
}
|
||||
if cb.Operation.RequestID != "" {
|
||||
return cb.Operation.RequestID
|
||||
}
|
||||
return cb.Payment.ID
|
||||
}
|
||||
|
||||
func verifyCallbackSignature(cb monetixCallback, secret string) error {
|
||||
expected := cb.Signature
|
||||
cb.Signature = ""
|
||||
calculated, err := monetix.SignPayload(cb, secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if subtleConstantTimeCompare(expected, calculated) {
|
||||
return nil
|
||||
}
|
||||
return merrors.DataConflict("signature mismatch")
|
||||
}
|
||||
|
||||
func subtleConstantTimeCompare(a, b string) bool {
|
||||
return hmac.Equal([]byte(strings.TrimSpace(a)), []byte(strings.TrimSpace(b)))
|
||||
}
|
||||
Reference in New Issue
Block a user