mirror of
https://github.com/hanzo-fi/payments.git
synced 2026-10-30 20:31:20 +00:00
* Update PR with latest updates from `main`, fix conflicts * Update PR with changes from main --------- Co-authored-by: gitstart_bot <[email protected]>
260 lines
8.4 KiB
Go
260 lines
8.4 KiB
Go
package column
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/formancehq/payments/internal/connectors/plugins/public/column/client"
|
|
"github.com/formancehq/payments/internal/models"
|
|
)
|
|
|
|
type ColumnAddress struct {
|
|
Line1 string
|
|
Line2 string
|
|
City string
|
|
State string
|
|
PostalCode string
|
|
Country string
|
|
}
|
|
|
|
func (p *Plugin) validateTransferRequest(pi models.PSPPaymentInitiation) error {
|
|
|
|
if pi.Amount == nil {
|
|
return models.NewConnectorValidationError("amount", ErrMissingAmount)
|
|
}
|
|
|
|
if pi.Asset == "" {
|
|
return models.NewConnectorValidationError("asset", ErrMissingAsset)
|
|
}
|
|
|
|
if pi.Metadata == nil {
|
|
return models.NewConnectorValidationError("metadata", ErrMissingMetadata)
|
|
}
|
|
|
|
if pi.SourceAccount == nil {
|
|
return models.NewConnectorValidationError("sourceAccount", ErrMissingSourceAccount)
|
|
}
|
|
|
|
if pi.SourceAccount.Name == nil {
|
|
return models.NewConnectorValidationError("sourceAccount.name", ErrMissingSourceAccountName)
|
|
}
|
|
|
|
if pi.SourceAccount.Reference == "" {
|
|
return models.NewConnectorValidationError("sourceAccount.reference", ErrSourceAccountReferenceRequired)
|
|
}
|
|
|
|
if pi.DestinationAccount == nil {
|
|
return models.NewConnectorValidationError("destinationAccount", ErrMissingDestinationAccount)
|
|
}
|
|
|
|
if pi.DestinationAccount.Reference == "" {
|
|
return models.NewConnectorValidationError("destinationAccount.reference", ErrMissingDestinationAccountReference)
|
|
}
|
|
|
|
allowOverdraft := models.ExtractNamespacedMetadata(pi.Metadata, client.ColumnAllowOverdraftMetadataKey)
|
|
if allowOverdraft != "" {
|
|
if allowOverdraft != "true" && allowOverdraft != "false" {
|
|
return models.NewConnectorValidationError(client.ColumnAllowOverdraftMetadataKey, ErrMissingMetadataAllowOverDrafts)
|
|
}
|
|
}
|
|
|
|
hold := models.ExtractNamespacedMetadata(pi.Metadata, client.ColumnHoldMetadataKey)
|
|
if hold != "" {
|
|
if hold != "true" && hold != "false" {
|
|
return models.NewConnectorValidationError(client.ColumnHoldMetadataKey, ErrMissingMetadataHold)
|
|
|
|
}
|
|
}
|
|
|
|
countryCode := models.ExtractNamespacedMetadata(pi.Metadata, client.ColumnAddressCountryCodeMetadataKey)
|
|
address := extractAddressFromMetadata(pi.Metadata, countryCode)
|
|
|
|
return validateAddressForTransfer(address)
|
|
}
|
|
|
|
func (p *Plugin) validatePayoutRequests(pi models.PSPPaymentInitiation) error {
|
|
|
|
if pi.Amount == nil {
|
|
return models.NewConnectorValidationError("Amount", ErrMissingAmount)
|
|
}
|
|
|
|
if pi.SourceAccount == nil {
|
|
return models.NewConnectorValidationError("SourceAccount", ErrMissingSourceAccount)
|
|
}
|
|
|
|
if pi.SourceAccount.Reference == "" {
|
|
return models.NewConnectorValidationError("SourceAccount.Reference", ErrSourceAccountReferenceRequired)
|
|
}
|
|
|
|
if pi.DestinationAccount == nil {
|
|
return models.NewConnectorValidationError("DestinationAccount", ErrMissingDestinationAccount)
|
|
}
|
|
|
|
if pi.DestinationAccount.Reference == "" {
|
|
return models.NewConnectorValidationError("DestinationAccount.Reference", ErrMissingDestinationAccountReference)
|
|
}
|
|
|
|
if pi.Metadata == nil {
|
|
return models.NewConnectorValidationError("metadata", ErrMissingMetadata)
|
|
}
|
|
|
|
payoutType := models.ExtractNamespacedMetadata(pi.Metadata, client.ColumnPayoutTypeMetadataKey)
|
|
|
|
if payoutType == "" {
|
|
return models.NewConnectorValidationError("metadata", ErrMissingMetadata)
|
|
}
|
|
|
|
if payoutType != "ach" && payoutType != "wire" && payoutType != "realtime" && payoutType != "international-wire" {
|
|
return models.NewConnectorValidationError(client.ColumnPayoutTypeMetadataKey, ErrInvalidMetadataPayoutType)
|
|
}
|
|
|
|
if pi.Asset == "" {
|
|
return models.NewConnectorValidationError("asset", ErrMissingAsset)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Plugin) validateReversePayout(pr models.PSPPaymentInitiationReversal) error {
|
|
if pr.Metadata == nil {
|
|
return models.NewConnectorValidationError("metadata", ErrMissingMetadata)
|
|
}
|
|
|
|
reason := models.ExtractNamespacedMetadata(pr.Metadata, client.ColumnReasonMetadataKey)
|
|
if reason == "" {
|
|
return models.NewConnectorValidationError(client.ColumnReasonMetadataKey, ErrMissingMetadataReason)
|
|
}
|
|
|
|
if !IsValidReversePayoutReason(reason) {
|
|
return models.NewConnectorValidationError(client.ColumnReasonMetadataKey, ErrInvalidMetadataReason)
|
|
}
|
|
|
|
if pr.RelatedPaymentInitiation.Reference == "" {
|
|
return models.NewConnectorValidationError("relatedPaymentInitiation.reference", ErrMissingRelatedPaymentInitiationReference)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Plugin) validateExternalBankAccount(newExternalBankAccount models.BankAccount) error {
|
|
if newExternalBankAccount.AccountNumber == nil {
|
|
return models.NewConnectorValidationError("AccountNumber", ErrAccountNumberRequired)
|
|
}
|
|
|
|
routingNumber := models.ExtractNamespacedMetadata(newExternalBankAccount.Metadata, client.ColumnRoutingNumberMetadataKey)
|
|
|
|
if routingNumber == "" {
|
|
return models.NewConnectorValidationError(client.ColumnRoutingNumberMetadataKey, ErrMissingRoutingNumber)
|
|
}
|
|
|
|
country := ""
|
|
if newExternalBankAccount.Country != nil {
|
|
country = *newExternalBankAccount.Country
|
|
}
|
|
|
|
address := extractAddressFromMetadata(newExternalBankAccount.Metadata, country)
|
|
return validateAddressForBankAccount(address)
|
|
}
|
|
|
|
func validateAddressForTransfer(address ColumnAddress) error {
|
|
// If Line1 is provided, we need a complete address
|
|
if address.Line1 != "" {
|
|
if address.City == "" {
|
|
return models.NewConnectorValidationError(client.ColumnAddressCityMetadataKey, ErrMissingMetadataAddressCity)
|
|
}
|
|
|
|
if address.Country == "" {
|
|
return models.NewConnectorValidationError(client.ColumnAddressCountryCodeMetadataKey, ErrMissingMetadataCountry)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// No address line provided, ensure no other address fields are provided
|
|
if address.Line2 != "" {
|
|
return models.NewConnectorValidationError(client.ColumnAddressLine2MetadataKey, ErrMetadataAddressLine2NotRequired)
|
|
}
|
|
|
|
if address.City != "" {
|
|
return models.NewConnectorValidationError(client.ColumnAddressCityMetadataKey, ErrMetadataAddressCityNotRequired)
|
|
}
|
|
|
|
if address.State != "" {
|
|
return models.NewConnectorValidationError(client.ColumnAddressStateMetadataKey, ErrMetadataAddressStateNotRequired)
|
|
}
|
|
|
|
if address.PostalCode != "" {
|
|
return models.NewConnectorValidationError(client.ColumnAddressPostalCodeMetadataKey, ErrMetadataPostalCodeNotRequired)
|
|
}
|
|
|
|
if address.Country != "" {
|
|
return models.NewConnectorValidationError(client.ColumnAddressCountryCodeMetadataKey, ErrMetadataAddressCountryNotRequired)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateAddressForBankAccount(address ColumnAddress) error {
|
|
// If Line1 is provided, we need a complete address
|
|
if address.Line1 != "" {
|
|
if address.City == "" {
|
|
return models.NewConnectorValidationError(client.ColumnAddressCityMetadataKey, ErrMissingMetadataAddressCity)
|
|
}
|
|
|
|
if address.Country == "" {
|
|
return models.NewConnectorValidationError("country", ErrMissingCountry)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// No address line provided, ensure no other address fields are provided
|
|
if address.Line2 != "" {
|
|
return models.NewConnectorValidationError(client.ColumnAddressLine2MetadataKey, ErrMetadataAddressLine2NotRequired)
|
|
}
|
|
|
|
if address.City != "" {
|
|
return models.NewConnectorValidationError(client.ColumnAddressCityMetadataKey, ErrMetadataAddressCityNotRequired)
|
|
}
|
|
|
|
if address.State != "" {
|
|
return models.NewConnectorValidationError(client.ColumnAddressStateMetadataKey, ErrMetadataAddressStateNotRequired)
|
|
}
|
|
|
|
if address.PostalCode != "" {
|
|
return models.NewConnectorValidationError(client.ColumnAddressPostalCodeMetadataKey, ErrMetadataPostalCodeNotRequired)
|
|
}
|
|
|
|
if address.Country != "" {
|
|
return models.NewConnectorValidationError("country", ErrCountryNotRequired)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func extractAddressFromMetadata(metadata map[string]string, country string) ColumnAddress {
|
|
return ColumnAddress{
|
|
Line1: models.ExtractNamespacedMetadata(metadata, client.ColumnAddressLine1MetadataKey),
|
|
Line2: models.ExtractNamespacedMetadata(metadata, client.ColumnAddressLine2MetadataKey),
|
|
City: models.ExtractNamespacedMetadata(metadata, client.ColumnAddressCityMetadataKey),
|
|
State: models.ExtractNamespacedMetadata(metadata, client.ColumnAddressStateMetadataKey),
|
|
PostalCode: models.ExtractNamespacedMetadata(metadata, client.ColumnAddressPostalCodeMetadataKey),
|
|
Country: country,
|
|
}
|
|
}
|
|
|
|
func ParseColumnTimestamp(value string) (time.Time, error) {
|
|
return time.Parse(time.RFC3339, value)
|
|
}
|
|
|
|
func IsValidReversePayoutReason(reason string) bool {
|
|
switch client.ReversePayoutReason(reason) {
|
|
case client.ReasonDuplicatedEntry,
|
|
client.ReasonIncorrectAmount,
|
|
client.ReasonIncorrectReceiverAccount,
|
|
client.ReasonDebitEarlierThanIntended,
|
|
client.ReasonCreditLaterThanIntended:
|
|
return true
|
|
}
|
|
return false
|
|
}
|