mirror of
https://github.com/hanzo-fi/payments.git
synced 2026-10-30 20:31:20 +00:00
460 lines
14 KiB
Go
460 lines
14 KiB
Go
package moneycorp
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
"time"
|
|
|
|
logging "github.com/hanzo-fi/go-libs/v5/pkg/observe/log"
|
|
"github.com/hanzo-fi/payments/ce/plugins/moneycorp/client"
|
|
"github.com/hanzo-fi/payments/pkg/domain/models"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
gomock "go.uber.org/mock/gomock"
|
|
)
|
|
|
|
var _ = Describe("Moneycorp *Plugin Payments - check types and minor conversion", func() {
|
|
var (
|
|
plg models.Plugin
|
|
)
|
|
|
|
Context("fetch next Payments", func() {
|
|
var (
|
|
ctrl *gomock.Controller
|
|
m *client.MockClient
|
|
|
|
samplePayments []*client.Transaction
|
|
sampleTransfer *client.TransferResponse
|
|
accRef int32
|
|
pageSize int
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
ctrl = gomock.NewController(GinkgoT())
|
|
m = client.NewMockClient(ctrl)
|
|
plg = &Plugin{client: m, logger: logging.NewDefaultLogger(GinkgoWriter, true, false, false)}
|
|
|
|
pageSize = 5
|
|
accRef = 3796
|
|
sampleTransfer = &client.TransferResponse{
|
|
ID: "transfer-1",
|
|
Attributes: client.TransferAttributes{
|
|
SendingAccountID: 1234,
|
|
ReceivingAccountID: 4321,
|
|
CreatedAt: strings.TrimSuffix(time.Now().UTC().Format(time.RFC3339Nano), "Z"),
|
|
UpdatedAt: strings.TrimSuffix(time.Now().UTC().Format(time.RFC3339Nano), "Z"),
|
|
TransferReference: "test1",
|
|
ClientReference: "test1",
|
|
TransferAmount: json.Number("65"),
|
|
TransferCurrency: "EUR",
|
|
TransferStatus: "Cleared",
|
|
},
|
|
}
|
|
samplePayments = []*client.Transaction{
|
|
{
|
|
ID: "transfer-1",
|
|
Attributes: client.TransactionAttributes{
|
|
AccountID: accRef,
|
|
Type: "Transfer",
|
|
Direction: "Debit",
|
|
Currency: "EUR",
|
|
Amount: json.Number("65"),
|
|
CreatedAt: strings.TrimSuffix(time.Now().UTC().Format(time.RFC3339Nano), "Z"),
|
|
},
|
|
Relationships: client.RelationShips{
|
|
Data: client.Data{
|
|
ID: sampleTransfer.ID,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ID: "payment-1",
|
|
Attributes: client.TransactionAttributes{
|
|
AccountID: accRef,
|
|
Type: "Payment",
|
|
Direction: "Debit",
|
|
Currency: "DKK",
|
|
Amount: json.Number("42"),
|
|
CreatedAt: strings.TrimSuffix(time.Now().UTC().Format(time.RFC3339Nano), "Z"),
|
|
},
|
|
Relationships: client.RelationShips{
|
|
Data: client.Data{
|
|
ID: "Payout-1",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ID: "exchange-1",
|
|
Attributes: client.TransactionAttributes{
|
|
AccountID: accRef,
|
|
Type: "Exchange",
|
|
Direction: "Debit",
|
|
Currency: "GBP",
|
|
Amount: json.Number("28"),
|
|
CreatedAt: strings.TrimSuffix(time.Now().UTC().Format(time.RFC3339Nano), "Z"),
|
|
},
|
|
},
|
|
{
|
|
ID: "charge-1",
|
|
Attributes: client.TransactionAttributes{
|
|
AccountID: accRef,
|
|
Type: "Charge",
|
|
Direction: "Credit",
|
|
Currency: "JPY",
|
|
Amount: json.Number("6400"),
|
|
CreatedAt: strings.TrimSuffix(time.Now().UTC().Format(time.RFC3339Nano), "Z"),
|
|
},
|
|
},
|
|
{
|
|
ID: "refund-1",
|
|
Attributes: client.TransactionAttributes{
|
|
AccountID: accRef,
|
|
Type: "Refund",
|
|
Direction: "Credit",
|
|
Currency: "MAD",
|
|
Amount: json.Number("64"),
|
|
CreatedAt: strings.TrimSuffix(time.Now().UTC().Format(time.RFC3339Nano), "Z"),
|
|
},
|
|
},
|
|
{
|
|
ID: "unsupported-1",
|
|
Attributes: client.TransactionAttributes{
|
|
AccountID: accRef,
|
|
Type: "Unsupported",
|
|
Currency: "USD",
|
|
Amount: json.Number("29"),
|
|
CreatedAt: strings.TrimSuffix(time.Now().UTC().Format(time.RFC3339Nano), "Z"),
|
|
},
|
|
},
|
|
}
|
|
})
|
|
|
|
AfterEach(func() {
|
|
ctrl.Finish()
|
|
})
|
|
|
|
It("skips payments with unsupported currencies", func(ctx SpecContext) {
|
|
req := models.FetchNextPaymentsRequest{
|
|
FromPayload: json.RawMessage(fmt.Sprintf(`{"reference": "%d"}`, accRef)),
|
|
State: json.RawMessage(`{}`),
|
|
PageSize: pageSize,
|
|
}
|
|
p := []*client.Transaction{
|
|
{
|
|
ID: "someid",
|
|
Attributes: client.TransactionAttributes{
|
|
Direction: "Debit",
|
|
AccountID: accRef,
|
|
Type: "Payment",
|
|
CreatedAt: strings.TrimSuffix(time.Now().UTC().Format(time.RFC3339Nano), "Z"),
|
|
Currency: "EEK",
|
|
},
|
|
},
|
|
}
|
|
m.EXPECT().GetTransactions(gomock.Any(), "3796", gomock.Any(), pageSize, gomock.Any()).Return(
|
|
p,
|
|
nil,
|
|
)
|
|
|
|
res, err := plg.FetchNextPayments(ctx, req)
|
|
Expect(err).To(BeNil())
|
|
Expect(res.Payments).To(BeEmpty())
|
|
Expect(res.HasMore).To(BeFalse())
|
|
})
|
|
|
|
It("fetches payments", func(ctx SpecContext) {
|
|
req := models.FetchNextPaymentsRequest{
|
|
FromPayload: json.RawMessage(fmt.Sprintf(`{"reference": "%d"}`, accRef)),
|
|
State: json.RawMessage(`{}`),
|
|
PageSize: pageSize,
|
|
}
|
|
|
|
m.EXPECT().GetTransactions(gomock.Any(), "3796", gomock.Any(), pageSize, gomock.Any()).Return(
|
|
samplePayments,
|
|
nil,
|
|
)
|
|
|
|
m.EXPECT().GetTransfer(gomock.Any(), "3796", sampleTransfer.ID).Return(
|
|
sampleTransfer,
|
|
nil,
|
|
)
|
|
|
|
res, err := plg.FetchNextPayments(ctx, req)
|
|
Expect(err).To(BeNil())
|
|
Expect(res.Payments).To(HaveLen(len(samplePayments) - 1))
|
|
Expect(res.HasMore).To(BeTrue())
|
|
|
|
// Transfer
|
|
Expect(res.Payments[0].Reference).To(Equal(samplePayments[0].ID))
|
|
Expect(res.Payments[0].Type).To(Equal(models.PAYMENT_TYPE_TRANSFER))
|
|
expectedAmount, err := samplePayments[0].Attributes.Amount.Int64()
|
|
Expect(err).To(BeNil())
|
|
Expect(res.Payments[0].Amount).To(Equal(big.NewInt(expectedAmount * 100))) // after conversion to minors
|
|
// Payment
|
|
Expect(res.Payments[1].Reference).To(Equal(samplePayments[1].Relationships.Data.ID))
|
|
Expect(res.Payments[1].Type).To(Equal(models.PAYMENT_TYPE_PAYOUT))
|
|
expectedAmount, err = samplePayments[1].Attributes.Amount.Int64()
|
|
Expect(err).To(BeNil())
|
|
Expect(res.Payments[1].Amount).To(Equal(big.NewInt(expectedAmount * 100))) // after conversion to minors
|
|
// Exchange
|
|
Expect(res.Payments[2].Reference).To(Equal(samplePayments[2].ID))
|
|
Expect(res.Payments[2].Type).To(Equal(models.PAYMENT_TYPE_PAYOUT))
|
|
expectedAmount, err = samplePayments[2].Attributes.Amount.Int64()
|
|
Expect(err).To(BeNil())
|
|
Expect(res.Payments[2].Amount).To(Equal(big.NewInt(expectedAmount * 100))) // after conversion to minors
|
|
// Charge
|
|
Expect(res.Payments[3].Reference).To(Equal(samplePayments[3].ID))
|
|
Expect(res.Payments[3].Type).To(Equal(models.PAYMENT_TYPE_PAYIN))
|
|
expectedAmount, err = samplePayments[3].Attributes.Amount.Int64()
|
|
Expect(err).To(BeNil())
|
|
Expect(res.Payments[3].Amount).To(Equal(big.NewInt(expectedAmount))) // currency already in minors
|
|
// Refund
|
|
Expect(res.Payments[4].Reference).To(Equal(samplePayments[4].ID))
|
|
Expect(res.Payments[4].Type).To(Equal(models.PAYMENT_TYPE_PAYIN))
|
|
expectedAmount, err = samplePayments[4].Attributes.Amount.Int64()
|
|
Expect(err).To(BeNil())
|
|
Expect(res.Payments[4].Amount).To(Equal(big.NewInt(expectedAmount * 100))) // after conversion to minors
|
|
|
|
})
|
|
|
|
It("skips transfer payments with unsupported currency", func(ctx SpecContext) {
|
|
req := models.FetchNextPaymentsRequest{
|
|
FromPayload: json.RawMessage(fmt.Sprintf(`{"reference": "%d"}`, accRef)),
|
|
State: json.RawMessage(`{}`),
|
|
PageSize: pageSize,
|
|
}
|
|
|
|
unsupportedTransfer := &client.TransferResponse{
|
|
ID: "transfer-zzz",
|
|
Attributes: client.TransferAttributes{
|
|
SendingAccountID: 1234,
|
|
ReceivingAccountID: 4321,
|
|
CreatedAt: strings.TrimSuffix(time.Now().UTC().Format(time.RFC3339Nano), "Z"),
|
|
TransferAmount: json.Number("65"),
|
|
TransferCurrency: "ZZZ", // unsupported
|
|
TransferStatus: "Cleared",
|
|
},
|
|
}
|
|
txns := []*client.Transaction{
|
|
{
|
|
ID: "transfer-unsupported",
|
|
Attributes: client.TransactionAttributes{
|
|
AccountID: accRef,
|
|
Type: "Transfer",
|
|
Direction: "Debit",
|
|
Currency: "EUR",
|
|
Amount: json.Number("65"),
|
|
CreatedAt: strings.TrimSuffix(time.Now().UTC().Format(time.RFC3339Nano), "Z"),
|
|
},
|
|
Relationships: client.RelationShips{
|
|
Data: client.Data{ID: unsupportedTransfer.ID},
|
|
},
|
|
},
|
|
}
|
|
|
|
m.EXPECT().GetTransactions(gomock.Any(), "3796", gomock.Any(), pageSize, gomock.Any()).Return(
|
|
txns,
|
|
nil,
|
|
)
|
|
m.EXPECT().GetTransfer(gomock.Any(), "3796", unsupportedTransfer.ID).Return(
|
|
unsupportedTransfer,
|
|
nil,
|
|
)
|
|
|
|
res, err := plg.FetchNextPayments(ctx, req)
|
|
Expect(err).To(BeNil())
|
|
Expect(res.Payments).To(BeEmpty())
|
|
})
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Moneycorp *Plugin Payments - check pagination", func() {
|
|
var (
|
|
plg models.Plugin
|
|
)
|
|
|
|
Context("fetch next Payments", func() {
|
|
var (
|
|
ctrl *gomock.Controller
|
|
m *client.MockClient
|
|
|
|
samplePayments []*client.Transaction
|
|
accRef int32
|
|
now time.Time
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
ctrl = gomock.NewController(GinkgoT())
|
|
m = client.NewMockClient(ctrl)
|
|
plg = &Plugin{client: m, logger: logging.NewDefaultLogger(GinkgoWriter, true, false, false)}
|
|
accRef = 3796
|
|
now = time.Now().UTC()
|
|
|
|
samplePayments = make([]*client.Transaction, 0)
|
|
for i := 0; i < 50; i++ {
|
|
samplePayments = append(samplePayments, &client.Transaction{
|
|
ID: fmt.Sprintf("transaction-%d", i),
|
|
Attributes: client.TransactionAttributes{
|
|
AccountID: accRef,
|
|
Type: "Payment",
|
|
Direction: "Debit",
|
|
Currency: "EUR",
|
|
Amount: json.Number("42"),
|
|
CreatedAt: strings.TrimSuffix(now.Add(-time.Duration(60-i)*time.Minute).UTC().Format(time.RFC3339Nano), "Z"),
|
|
},
|
|
Relationships: client.RelationShips{
|
|
Data: client.Data{
|
|
ID: fmt.Sprintf("%d", i),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
})
|
|
|
|
AfterEach(func() {
|
|
ctrl.Finish()
|
|
})
|
|
|
|
It("should return an error - missing from payload", func(ctx SpecContext) {
|
|
req := models.FetchNextPaymentsRequest{
|
|
State: []byte(`{}`),
|
|
PageSize: 60,
|
|
}
|
|
|
|
resp, err := plg.FetchNextPayments(ctx, req)
|
|
Expect(err).ToNot(BeNil())
|
|
Expect(err).To(MatchError("missing from payload in request"))
|
|
Expect(resp).To(Equal(models.FetchNextPaymentsResponse{}))
|
|
})
|
|
|
|
It("should return an error - get transactions error", func(ctx SpecContext) {
|
|
req := models.FetchNextPaymentsRequest{
|
|
State: []byte(`{}`),
|
|
PageSize: 60,
|
|
FromPayload: json.RawMessage(fmt.Sprintf(`{"reference": "%d"}`, accRef)),
|
|
}
|
|
|
|
m.EXPECT().GetTransactions(gomock.Any(), fmt.Sprintf("%d", accRef), 0, 60, time.Time{}).Return(
|
|
[]*client.Transaction{},
|
|
errors.New("test error"),
|
|
)
|
|
|
|
resp, err := plg.FetchNextPayments(ctx, req)
|
|
Expect(err).ToNot(BeNil())
|
|
Expect(err).To(MatchError("test error"))
|
|
Expect(resp).To(Equal(models.FetchNextPaymentsResponse{}))
|
|
})
|
|
|
|
It("should fetch next payments - no state no results", func(ctx SpecContext) {
|
|
req := models.FetchNextPaymentsRequest{
|
|
State: []byte(`{}`),
|
|
PageSize: 60,
|
|
FromPayload: json.RawMessage(fmt.Sprintf(`{"reference": "%d"}`, accRef)),
|
|
}
|
|
|
|
m.EXPECT().GetTransactions(gomock.Any(), fmt.Sprintf("%d", accRef), 0, 60, time.Time{}).Return(
|
|
[]*client.Transaction{},
|
|
nil,
|
|
)
|
|
|
|
resp, err := plg.FetchNextPayments(ctx, req)
|
|
Expect(err).To(BeNil())
|
|
Expect(resp.Payments).To(HaveLen(0))
|
|
Expect(resp.HasMore).To(BeFalse())
|
|
Expect(resp.NewState).ToNot(BeNil())
|
|
|
|
var state paymentsState
|
|
err = json.Unmarshal(resp.NewState, &state)
|
|
Expect(err).To(BeNil())
|
|
// We fetched everything, state should be resetted
|
|
Expect(state.LastCreatedAt.IsZero()).To(BeTrue())
|
|
})
|
|
|
|
It("should fetch next payments - no state pageSize > total payments", func(ctx SpecContext) {
|
|
req := models.FetchNextPaymentsRequest{
|
|
State: []byte(`{}`),
|
|
PageSize: 60,
|
|
FromPayload: json.RawMessage(fmt.Sprintf(`{"reference": "%d"}`, accRef)),
|
|
}
|
|
|
|
m.EXPECT().GetTransactions(gomock.Any(), fmt.Sprintf("%d", accRef), 0, 60, time.Time{}).Return(
|
|
samplePayments,
|
|
nil,
|
|
)
|
|
|
|
resp, err := plg.FetchNextPayments(ctx, req)
|
|
Expect(err).To(BeNil())
|
|
Expect(resp.Payments).To(HaveLen(50))
|
|
Expect(resp.HasMore).To(BeFalse())
|
|
Expect(resp.NewState).ToNot(BeNil())
|
|
|
|
var state paymentsState
|
|
err = json.Unmarshal(resp.NewState, &state)
|
|
Expect(err).To(BeNil())
|
|
// We fetched everything, state should be resetted
|
|
createdAtTime, _ := time.Parse(time.RFC3339Nano, samplePayments[49].Attributes.CreatedAt+"Z")
|
|
Expect(state.LastCreatedAt.UTC()).To(Equal(createdAtTime.UTC()))
|
|
})
|
|
|
|
It("should fetch next payments - no state pageSize < total payments", func(ctx SpecContext) {
|
|
req := models.FetchNextPaymentsRequest{
|
|
State: []byte(`{}`),
|
|
PageSize: 40,
|
|
FromPayload: json.RawMessage(fmt.Sprintf(`{"reference": "%d"}`, accRef)),
|
|
}
|
|
|
|
m.EXPECT().GetTransactions(gomock.Any(), fmt.Sprintf("%d", accRef), 0, 40, time.Time{}).Return(
|
|
samplePayments[:40],
|
|
nil,
|
|
)
|
|
|
|
resp, err := plg.FetchNextPayments(ctx, req)
|
|
Expect(err).To(BeNil())
|
|
Expect(resp.Payments).To(HaveLen(40))
|
|
Expect(resp.HasMore).To(BeTrue())
|
|
Expect(resp.NewState).ToNot(BeNil())
|
|
|
|
var state paymentsState
|
|
err = json.Unmarshal(resp.NewState, &state)
|
|
Expect(err).To(BeNil())
|
|
createdAtTime, _ := time.Parse(time.RFC3339Nano, samplePayments[39].Attributes.CreatedAt+"Z")
|
|
Expect(state.LastCreatedAt.UTC()).To(Equal(createdAtTime.UTC()))
|
|
})
|
|
|
|
It("should fetch next payments - with state pageSize < total payments", func(ctx SpecContext) {
|
|
lastCreatedAt, _ := time.Parse(time.RFC3339Nano, samplePayments[38].Attributes.CreatedAt+"Z")
|
|
req := models.FetchNextPaymentsRequest{
|
|
State: []byte(fmt.Sprintf(`{"lastCreatedAt": "%s"}`, lastCreatedAt.Format(time.RFC3339Nano))),
|
|
PageSize: 40,
|
|
FromPayload: json.RawMessage(fmt.Sprintf(`{"reference": "%d"}`, accRef)),
|
|
}
|
|
|
|
m.EXPECT().GetTransactions(gomock.Any(), fmt.Sprintf("%d", accRef), 0, 40, lastCreatedAt.UTC()).Return(
|
|
samplePayments[:40],
|
|
nil,
|
|
)
|
|
|
|
m.EXPECT().GetTransactions(gomock.Any(), fmt.Sprintf("%d", accRef), 1, 40, lastCreatedAt.UTC()).Return(
|
|
samplePayments[41:],
|
|
nil,
|
|
)
|
|
|
|
resp, err := plg.FetchNextPayments(ctx, req)
|
|
Expect(err).To(BeNil())
|
|
Expect(resp.Payments).To(HaveLen(10))
|
|
Expect(resp.HasMore).To(BeFalse())
|
|
Expect(resp.NewState).ToNot(BeNil())
|
|
|
|
var state paymentsState
|
|
err = json.Unmarshal(resp.NewState, &state)
|
|
Expect(err).To(BeNil())
|
|
// We fetched everything, state should be resetted
|
|
createdAtTime, _ := time.Parse(time.RFC3339Nano, samplePayments[49].Attributes.CreatedAt+"Z")
|
|
Expect(state.LastCreatedAt.UTC()).To(Equal(createdAtTime.UTC()))
|
|
})
|
|
})
|
|
})
|