Skip to content

Commit b87e817

Browse files
authored
fix(payments): fix payment storage constraint (#1763)
1 parent ea5f0ba commit b87e817

File tree

5 files changed

+121
-1
lines changed

5 files changed

+121
-1
lines changed

components/payments/cmd/api/internal/storage/payments.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func (s *Storage) UpsertPayments(ctx context.Context, payments []*models.Payment
146146

147147
_, err := s.db.NewInsert().
148148
Model(&payments).
149-
On("CONFLICT (reference) DO UPDATE").
149+
On("CONFLICT (reference, connector_id) DO UPDATE").
150150
Set("amount = EXCLUDED.amount").
151151
Set("type = EXCLUDED.type").
152152
Set("status = EXCLUDED.status").

components/payments/cmd/api/internal/storage/payments_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,45 @@ func insertPayments(t *testing.T, store *Storage, connectorID models.ConnectorID
7777
return []models.Payment{p1, p2}
7878
}
7979

80+
func TestUpsertPayments(t *testing.T) {
81+
t.Parallel()
82+
83+
store := newStore(t)
84+
85+
connectorID := installConnector(t, store)
86+
insertAccounts(t, store, connectorID)
87+
insertPayments(t, store, connectorID)
88+
89+
p1 := models.Payment{
90+
ID: models.PaymentID{
91+
PaymentReference: models.PaymentReference{
92+
Reference: "test_1",
93+
Type: models.PaymentTypePayIn,
94+
},
95+
ConnectorID: connectorID,
96+
},
97+
ConnectorID: connectorID,
98+
CreatedAt: time.Date(2023, 11, 14, 8, 0, 0, 0, time.UTC),
99+
Reference: "test_1",
100+
Amount: big.NewInt(100),
101+
InitialAmount: big.NewInt(100),
102+
Type: models.PaymentTypePayIn,
103+
Status: models.PaymentStatusPending,
104+
Scheme: models.PaymentSchemeA2A,
105+
Asset: models.Asset("USD/2"),
106+
SourceAccountID: &models.AccountID{
107+
Reference: "test_account",
108+
ConnectorID: connectorID,
109+
},
110+
DestinationAccountID: &models.AccountID{
111+
Reference: "test_account2",
112+
ConnectorID: connectorID,
113+
},
114+
}
115+
err := store.UpsertPayments(context.Background(), []*models.Payment{&p1})
116+
require.NoError(t, err)
117+
}
118+
80119
func TestListPayments(t *testing.T) {
81120
t.Parallel()
82121

components/payments/cmd/connectors/internal/storage/connectors_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ var (
1616
Reference: uuid.New(),
1717
Provider: models.ConnectorProviderDummyPay,
1818
}
19+
20+
connectorID2 = models.ConnectorID{
21+
Reference: uuid.New(),
22+
Provider: models.ConnectorProviderDummyPay,
23+
}
1924
)
2025

2126
func TestConnectors(t *testing.T) {
@@ -41,6 +46,18 @@ func testInstallConnectors(t *testing.T, store *storage.Storage) {
4146
)
4247
require.NoError(t, err)
4348

49+
connector2 := models.Connector{
50+
ID: connectorID2,
51+
Name: "test2",
52+
Provider: models.ConnectorProviderDummyPay,
53+
}
54+
err = store.Install(
55+
context.Background(),
56+
&connector2,
57+
json.RawMessage([]byte(`{"foo": "bar"}`)),
58+
)
59+
require.NoError(t, err)
60+
4461
err = store.Install(
4562
context.Background(),
4663
&connector1,

components/payments/cmd/connectors/internal/storage/payments_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,60 @@ func TestPayments(t *testing.T) {
2626

2727
testInstallConnectors(t, store)
2828
testCreatePayments(t, store)
29+
testCreatePaymentsSameReferenceButDifferentConnectorID(t, store)
2930
testUpdatePayment(t, store)
3031
testUninstallConnectors(t, store)
3132
testPaymentsDeletedAfterConnectorUninstall(t, store)
3233
}
3334

35+
func testCreatePaymentsSameReferenceButDifferentConnectorID(t *testing.T, store *storage.Storage) {
36+
id1 := &models.PaymentID{
37+
PaymentReference: models.PaymentReference{
38+
Reference: "test-same-reference",
39+
Type: models.PaymentTypePayOut,
40+
},
41+
ConnectorID: connectorID,
42+
}
43+
p1 := &models.Payment{
44+
ID: *id1,
45+
CreatedAt: p1T,
46+
Reference: "test-same-reference",
47+
Amount: big.NewInt(100),
48+
ConnectorID: connectorID,
49+
Type: models.PaymentTypePayOut,
50+
Status: models.PaymentStatusSucceeded,
51+
Scheme: models.PaymentSchemeCardVisa,
52+
Asset: models.Asset("USD/2"),
53+
}
54+
55+
ids, err := store.UpsertPayments(context.Background(), []*models.Payment{p1})
56+
require.NoError(t, err)
57+
require.Len(t, ids, 1)
58+
59+
id2 := &models.PaymentID{
60+
PaymentReference: models.PaymentReference{
61+
Reference: "test-same-reference2",
62+
Type: models.PaymentTypePayOut,
63+
},
64+
ConnectorID: connectorID2,
65+
}
66+
p2 := &models.Payment{
67+
ID: *id2,
68+
CreatedAt: p1T,
69+
Reference: "test-same-reference2",
70+
Amount: big.NewInt(100),
71+
ConnectorID: connectorID2,
72+
Type: models.PaymentTypePayOut,
73+
Status: models.PaymentStatusSucceeded,
74+
Scheme: models.PaymentSchemeCardVisa,
75+
Asset: models.Asset("USD/2"),
76+
}
77+
78+
ids, err = store.UpsertPayments(context.Background(), []*models.Payment{p2})
79+
require.NoError(t, err)
80+
require.Len(t, ids, 1)
81+
}
82+
3483
func testCreatePayments(t *testing.T, store *storage.Storage) {
3584
p1ID = &models.PaymentID{
3685
PaymentReference: models.PaymentReference{

components/payments/internal/storage/migrations_v1.x.go

+15
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,21 @@ func registerMigrationsV1(ctx context.Context, migrator *migrations.Migrator) {
396396
return fixMissingReferenceTransferInitiation(ctx, tx)
397397
},
398398
},
399+
migrations.Migration{
400+
Up: func(tx bun.Tx) error {
401+
// Rererence should be unique within a connector, but not accross
402+
// connectors. This migration fixes it.
403+
_, err := tx.Exec(`
404+
ALTER TABLE payments.payment ADD CONSTRAINT payment_reference_connector_id_unique_key UNIQUE (reference, connector_id);
405+
ALTER TABLE payments.payment DROP CONSTRAINT IF EXISTS payment_reference_key;
406+
`)
407+
if err != nil {
408+
return err
409+
}
410+
411+
return nil
412+
},
413+
},
399414
)
400415
}
401416

0 commit comments

Comments
 (0)