mirror of
https://github.com/hanzo-fi/payments.git
synced 2026-10-30 20:31:20 +00:00
324 lines
10 KiB
Go
324 lines
10 KiB
Go
package increase
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"math/big"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/types/currency"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/types/pointer"
|
|
"github.com/hanzo-fi/payments/ce/plugins/increase/client"
|
|
"github.com/hanzo-fi/payments/pkg/domain/models"
|
|
)
|
|
|
|
type paymentsState struct {
|
|
StopSucceeded bool `json:"stop_succeeded"`
|
|
StopPending bool `json:"stop_pending"`
|
|
StopDeclined bool `json:"stop_declined"`
|
|
SucceededTimeline client.Timeline `json:"succeeded_timeline"`
|
|
DeclinedTimeline client.Timeline `json:"declined_timeline"`
|
|
PendingTimeline client.Timeline `json:"pending_timeline"`
|
|
}
|
|
|
|
func (p *Plugin) fetchNextPayments(ctx context.Context, req models.FetchNextPaymentsRequest) (models.FetchNextPaymentsResponse, error) {
|
|
var oldState paymentsState
|
|
if req.State != nil {
|
|
if err := json.Unmarshal(req.State, &oldState); err != nil {
|
|
return models.FetchNextPaymentsResponse{}, err
|
|
}
|
|
}
|
|
|
|
newState := paymentsState{
|
|
StopSucceeded: oldState.StopSucceeded,
|
|
StopPending: oldState.StopPending,
|
|
StopDeclined: oldState.StopDeclined,
|
|
SucceededTimeline: oldState.SucceededTimeline,
|
|
PendingTimeline: oldState.PendingTimeline,
|
|
DeclinedTimeline: oldState.DeclinedTimeline,
|
|
}
|
|
|
|
payments := make([]models.PSPPayment, 0, req.PageSize)
|
|
payments, hasMore, err := p.processPaymentTypes(ctx, &newState, payments, req.PageSize)
|
|
if err != nil {
|
|
return models.FetchNextPaymentsResponse{}, err
|
|
}
|
|
|
|
payload, err := json.Marshal(newState)
|
|
if err != nil {
|
|
return models.FetchNextPaymentsResponse{}, err
|
|
}
|
|
|
|
return models.FetchNextPaymentsResponse{
|
|
Payments: payments,
|
|
NewState: payload,
|
|
HasMore: hasMore,
|
|
}, nil
|
|
}
|
|
|
|
func (p *Plugin) processPaymentTypes(ctx context.Context, state *paymentsState, payments []models.PSPPayment, pageSize int) ([]models.PSPPayment, bool, error) {
|
|
var err error
|
|
payments, err = p.processPendingPayments(ctx, state, payments, pageSize)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
payments, err = p.processSucceededPayments(ctx, state, payments, pageSize)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
payments, err = p.processDeclinedPayments(ctx, state, payments, pageSize)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
sort.SliceStable(payments, func(i, j int) bool {
|
|
return payments[i].CreatedAt.Before(payments[j].CreatedAt)
|
|
})
|
|
|
|
hasMore := !state.StopPending || !state.StopSucceeded || !state.StopDeclined
|
|
|
|
return payments, hasMore, nil
|
|
}
|
|
|
|
func (p *Plugin) fillPayments(
|
|
pagedTransactions []*client.Transaction,
|
|
payments []models.PSPPayment,
|
|
pageSize int,
|
|
status models.PaymentStatus,
|
|
) ([]models.PSPPayment, error) {
|
|
for i, transaction := range pagedTransactions {
|
|
if i > pageSize*3 {
|
|
break
|
|
}
|
|
|
|
pspPayment, err := p.mapPayment(transaction, status)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
payments = append(payments, pspPayment)
|
|
}
|
|
|
|
return payments, nil
|
|
}
|
|
|
|
func getTransferID(transaction *client.Transaction) string {
|
|
if transaction.Source.TransferID != "" {
|
|
return transaction.Source.TransferID
|
|
}
|
|
if transaction.Source.WireTransferID != "" {
|
|
return transaction.Source.WireTransferID
|
|
}
|
|
if transaction.Source.CheckDepositID != "" {
|
|
return transaction.Source.CheckDepositID
|
|
}
|
|
if transaction.Source.InboundCheckDepositID != "" {
|
|
return transaction.Source.InboundCheckDepositID
|
|
}
|
|
if transaction.Source.InboundAchTransferID != "" {
|
|
return transaction.Source.InboundAchTransferID
|
|
}
|
|
if transaction.Source.InboundWireTransferID != "" {
|
|
return transaction.Source.InboundWireTransferID
|
|
}
|
|
if transaction.Source.ID != "" {
|
|
return transaction.Source.ID
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (p *Plugin) processPendingPayments(ctx context.Context, state *paymentsState, payments []models.PSPPayment, pageSize int) ([]models.PSPPayment, error) {
|
|
if state.StopPending {
|
|
return payments, nil
|
|
}
|
|
|
|
pagedPendingTransactions, timeline, hasMore, err := p.client.GetPendingTransactions(ctx, pageSize, state.PendingTimeline)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
payments, err = p.fillPayments(pagedPendingTransactions, payments, pageSize, models.PAYMENT_STATUS_PENDING)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
state.PendingTimeline = timeline
|
|
state.StopPending = !hasMore
|
|
|
|
return payments, nil
|
|
}
|
|
|
|
func (p *Plugin) processSucceededPayments(ctx context.Context, state *paymentsState, payments []models.PSPPayment, pageSize int) ([]models.PSPPayment, error) {
|
|
if state.StopSucceeded {
|
|
return payments, nil
|
|
}
|
|
|
|
pagedTransactions, timeline, hasMore, err := p.client.GetTransactions(ctx, pageSize, state.SucceededTimeline)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
payments, err = p.fillPayments(pagedTransactions, payments, pageSize, models.PAYMENT_STATUS_SUCCEEDED)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
state.SucceededTimeline = timeline
|
|
state.StopSucceeded = !hasMore
|
|
|
|
return payments, nil
|
|
}
|
|
|
|
func (p *Plugin) processDeclinedPayments(ctx context.Context, state *paymentsState, payments []models.PSPPayment, pageSize int) ([]models.PSPPayment, error) {
|
|
if state.StopDeclined {
|
|
return payments, nil
|
|
}
|
|
|
|
pagedDeclinedTransactions, timeline, hasMore, err := p.client.GetDeclinedTransactions(ctx, pageSize, state.DeclinedTimeline)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
payments, err = p.fillPayments(pagedDeclinedTransactions, payments, pageSize, models.PAYMENT_STATUS_FAILED)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
state.DeclinedTimeline = timeline
|
|
state.StopDeclined = !hasMore
|
|
|
|
return payments, nil
|
|
}
|
|
|
|
func (p *Plugin) mapPayment(transaction *client.Transaction, status models.PaymentStatus) (models.PSPPayment, error) {
|
|
createdTime, err := time.Parse(time.RFC3339, transaction.CreatedAt)
|
|
if err != nil {
|
|
return models.PSPPayment{}, err
|
|
}
|
|
|
|
raw, err := json.Marshal(transaction)
|
|
if err != nil {
|
|
return models.PSPPayment{}, err
|
|
}
|
|
|
|
paymentStatus := mapTransactionStatus(transaction.Source.Category, status)
|
|
|
|
pspPayment := models.PSPPayment{
|
|
Reference: transaction.ID,
|
|
CreatedAt: createdTime,
|
|
Asset: *pointer.For(currency.FormatAsset(supportedCurrenciesWithDecimal, transaction.Currency)),
|
|
Status: paymentStatus,
|
|
Amount: new(big.Int).Abs(big.NewInt(transaction.Amount)),
|
|
Type: mapTransactionType(transaction),
|
|
Raw: raw,
|
|
Metadata: map[string]string{
|
|
client.IncreaseRouteIDMetadataKey: transaction.RouteID,
|
|
client.IncreaseRouteTypeMetadataKey: transaction.RouteType,
|
|
client.IncreaseSourceCategoryMetadataKey: transaction.Source.Category,
|
|
},
|
|
}
|
|
pspPayment = fillParentReference(transaction, pspPayment)
|
|
pspPayment = fillAccountID(transaction, pspPayment)
|
|
|
|
return pspPayment, nil
|
|
}
|
|
|
|
func fillParentReference(transaction *client.Transaction, pspPayment models.PSPPayment) models.PSPPayment {
|
|
transferID := getTransferID(transaction)
|
|
if transferID != "" {
|
|
pspPayment.ParentReference = transferID
|
|
}
|
|
|
|
return pspPayment
|
|
}
|
|
|
|
func fillAccountID(transaction *client.Transaction, pspPayment models.PSPPayment) models.PSPPayment {
|
|
category := transaction.Source.Category
|
|
transactionAmount := transaction.Amount
|
|
if category == "account_transfer_intention" && transactionAmount < 0 {
|
|
pspPayment.SourceAccountReference = &transaction.Source.SourceAccountID
|
|
pspPayment.DestinationAccountReference = &transaction.Source.DestinationAccountID
|
|
} else if category == "account_transfer_intention" && transactionAmount > 0 {
|
|
pspPayment.SourceAccountReference = &transaction.Source.DestinationAccountID
|
|
pspPayment.DestinationAccountReference = &transaction.Source.SourceAccountID
|
|
} else if isPayin(category) {
|
|
pspPayment.DestinationAccountReference = &transaction.AccountID
|
|
} else {
|
|
pspPayment.SourceAccountReference = &transaction.AccountID
|
|
}
|
|
|
|
return pspPayment
|
|
}
|
|
|
|
func isPayin(transactionType string) bool {
|
|
if transactionType == "inbound_ach_transfer" ||
|
|
transactionType == "inbound_ach_transfer_return_intention" ||
|
|
transactionType == "inbound_funds_hold" ||
|
|
transactionType == "inbound_check_adjustment" ||
|
|
transactionType == "inbound_check_deposit_return_intention" ||
|
|
transactionType == "inbound_real_time_payments_transfer_confirmation" ||
|
|
transactionType == "inbound_real_time_payments_transfer_decline" ||
|
|
transactionType == "inbound_wire_transfer" ||
|
|
transactionType == "inbound_wire_transfer_reversal" ||
|
|
transactionType == "card_refund" ||
|
|
transactionType == "interest_payment" ||
|
|
transactionType == "check_transfer_deposit" ||
|
|
transactionType == "cashback_payment" ||
|
|
transactionType == "check_deposit_instruction" ||
|
|
transactionType == "check_deposit_rejection" ||
|
|
transactionType == "check_decline" ||
|
|
transactionType == "wire_decline" ||
|
|
transactionType == "ach_decline" ||
|
|
transactionType == "check_deposit_return" ||
|
|
transactionType == "check_deposit_acceptance" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isPayout(transactionType string) bool {
|
|
if transactionType == "wire_transfer_intention" ||
|
|
transactionType == "real_time_payments_transfer_acknowledgement" ||
|
|
transactionType == "ach_transfer_intention" ||
|
|
transactionType == "ach_transfer_return" ||
|
|
transactionType == "fee_payment" ||
|
|
transactionType == "inbound_wire_reversal" ||
|
|
transactionType == "ach_transfer_instruction" ||
|
|
transactionType == "check_transfer_instruction" ||
|
|
transactionType == "wire_transfer_instruction" ||
|
|
transactionType == "real_time_payments_transfer_instruction" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func mapTransactionType(transaction *client.Transaction) models.PaymentType {
|
|
transactionType := transaction.Source.Category
|
|
transactionAmount := transaction.Amount
|
|
if isPayin(transactionType) ||
|
|
(transactionType == "account_transfer_intention" && transactionAmount > 0) {
|
|
return models.PAYMENT_TYPE_PAYIN
|
|
} else if isPayout(transactionType) {
|
|
return models.PAYMENT_TYPE_PAYOUT
|
|
} else if transactionType == "account_transfer_intention" ||
|
|
transactionType == "account_transfer_instruction" {
|
|
return models.PAYMENT_TYPE_TRANSFER
|
|
} else {
|
|
return models.PAYMENT_TYPE_OTHER
|
|
}
|
|
}
|
|
|
|
func mapTransactionStatus(transactionType string, status models.PaymentStatus) models.PaymentStatus {
|
|
switch transactionType {
|
|
case "ach_transfer_return", "inbound_ach_transfer_return_intention",
|
|
"check_deposit_return", "inbound_check_deposit_return_intention",
|
|
"inbound_wire_reversal", "inbound_wire_transfer_reversal":
|
|
return models.PAYMENT_STATUS_REFUNDED
|
|
default:
|
|
return status
|
|
}
|
|
}
|