Skip to content

Commit

Permalink
[SDP-1253] Add Circle transfer ID to GET /payments endpoints (#346)
Browse files Browse the repository at this point in the history
* SDP-1253 [Refactor] extract basePaymentQuery to unify fetching payment records

* SDP-1253 [Refactor] cleanups

* SDP-1253 [main] decorate payments with circle transaction information

* SDP-1253 address PR comments
  • Loading branch information
marwen-abid authored Jul 10, 2024
1 parent 83db2b1 commit ec9837b
Show file tree
Hide file tree
Showing 7 changed files with 485 additions and 117 deletions.
40 changes: 37 additions & 3 deletions internal/data/circle_transfer_requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,15 @@ func (m CircleTransferRequestModel) GetPendingReconciliation(ctx context.Context
return m.GetAll(ctx, sqlExec, queryParams)
}

const baseQuery = `
const baseCircleQuery = `
SELECT
*
FROM
circle_transfer_requests c
`

func (m CircleTransferRequestModel) GetAll(ctx context.Context, sqlExec db.SQLExecuter, queryParams QueryParams) ([]*CircleTransferRequest, error) {
query, params := buildCircleTransferRequestQuery(baseQuery, queryParams, sqlExec)
query, params := buildCircleTransferRequestQuery(baseCircleQuery, queryParams, sqlExec)

var circleTransferRequests []*CircleTransferRequest
err := sqlExec.SelectContext(ctx, &circleTransferRequests, query, params...)
Expand All @@ -161,7 +161,7 @@ func (m CircleTransferRequestModel) GetAll(ctx context.Context, sqlExec db.SQLEx
}

func (m CircleTransferRequestModel) Get(ctx context.Context, sqlExec db.SQLExecuter, queryParams QueryParams) (*CircleTransferRequest, error) {
query, params := buildCircleTransferRequestQuery(baseQuery, queryParams, sqlExec)
query, params := buildCircleTransferRequestQuery(baseCircleQuery, queryParams, sqlExec)

var circleTransferRequests CircleTransferRequest
err := sqlExec.GetContext(ctx, &circleTransferRequests, query, params...)
Expand All @@ -175,6 +175,40 @@ func (m CircleTransferRequestModel) Get(ctx context.Context, sqlExec db.SQLExecu
return &circleTransferRequests, nil
}

func (m CircleTransferRequestModel) GetCurrentTransfersForPaymentIDs(ctx context.Context, sqlExec db.SQLExecuter, paymentIDs []string) (map[string]*CircleTransferRequest, error) {
if len(paymentIDs) == 0 {
return nil, fmt.Errorf("paymentIDs is required")
}

query := `
SELECT DISTINCT ON (payment_id)
*
FROM
circle_transfer_requests
WHERE
payment_id = ANY($1)
ORDER BY
payment_id, created_at DESC;
`

var circleTransferRequests []*CircleTransferRequest
err := sqlExec.SelectContext(ctx, &circleTransferRequests, query, pq.Array(paymentIDs))
if err != nil {
return nil, fmt.Errorf("getting circle transfer requests: %w", err)
}

circleTransferRequestsByPaymentID := make(map[string]*CircleTransferRequest)
if len(circleTransferRequests) == 0 {
return circleTransferRequestsByPaymentID, nil
}

for _, circleTransferRequest := range circleTransferRequests {
circleTransferRequestsByPaymentID[circleTransferRequest.PaymentID] = circleTransferRequest
}

return circleTransferRequestsByPaymentID, nil
}

func (m CircleTransferRequestModel) Update(ctx context.Context, sqlExec db.SQLExecuter, idempotencyKey string, update CircleTransferRequestUpdate) (*CircleTransferRequest, error) {
if idempotencyKey == "" {
return nil, fmt.Errorf("idempotencyKey is required")
Expand Down
154 changes: 153 additions & 1 deletion internal/data/circle_transfer_requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/stellar/stellar-disbursement-platform-backend/db"
"github.com/stellar/stellar-disbursement-platform-backend/db/dbtest"
"github.com/stellar/stellar-disbursement-platform-backend/internal/testutils"
"github.com/stellar/stellar-disbursement-platform-backend/internal/utils"
)

Expand Down Expand Up @@ -78,7 +79,6 @@ func Test_CircleTransferRequestModel_Update(t *testing.T) {
ctx := context.Background()
m := CircleTransferRequestModel{dbConnectionPool: dbConnectionPool}

// idempotencyKey := uuid.NewString()
updateRequest := CircleTransferRequestUpdate{
CircleTransferID: "circle-transfer-id",
Status: CircleTransferStatusPending,
Expand Down Expand Up @@ -532,3 +532,155 @@ func Test_buildCircleTransferRequestQuery(t *testing.T) {
})
}
}

func Test_CircleTransferRequestModel_GetCurrentTransfersForPaymentIDs(t *testing.T) {
dbt := dbtest.Open(t)
defer dbt.Close()
dbConnectionPool, outerErr := db.OpenDBConnectionPool(dbt.DSN)
require.NoError(t, outerErr)
defer dbConnectionPool.Close()

ctx := context.Background()
m := CircleTransferRequestModel{dbConnectionPool: dbConnectionPool}

// Create fixtures
models, outerErr := NewModels(dbConnectionPool)
require.NoError(t, outerErr)
asset := CreateAssetFixture(t, ctx, dbConnectionPool, "USDC", "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVV")
country := CreateCountryFixture(t, ctx, dbConnectionPool, "FRA", "France")
wallet := CreateWalletFixture(t, ctx, dbConnectionPool, "wallet1", "https://www.wallet.com", "www.wallet.com", "wallet1://")
disbursement := CreateDisbursementFixture(t, ctx, dbConnectionPool, models.Disbursements, &Disbursement{
Country: country,
Wallet: wallet,
Status: ReadyDisbursementStatus,
Asset: asset,
})
receiverReady := CreateReceiverFixture(t, ctx, dbConnectionPool, &Receiver{})
rwReady := CreateReceiverWalletFixture(t, ctx, dbConnectionPool, receiverReady.ID, wallet.ID, ReadyReceiversWalletStatus)
payment1 := CreatePaymentFixture(t, ctx, dbConnectionPool, models.Payment, &Payment{
ReceiverWallet: rwReady,
Disbursement: disbursement,
Asset: *asset,
Amount: "100",
Status: DraftPaymentStatus,
})
payment2 := CreatePaymentFixture(t, ctx, dbConnectionPool, models.Payment, &Payment{
ReceiverWallet: rwReady,
Disbursement: disbursement,
Asset: *asset,
Amount: "200",
Status: DraftPaymentStatus,
})
payment3 := CreatePaymentFixture(t, ctx, dbConnectionPool, models.Payment, &Payment{
ReceiverWallet: rwReady,
Disbursement: disbursement,
Asset: *asset,
Amount: "300",
Status: DraftPaymentStatus,
})

testCases := []struct {
name string
paymentIDs []string
initFn func(t *testing.T, ctx context.Context, sqlExec db.SQLExecuter)
expectedResult map[string]*CircleTransferRequest
expectedErr string
}{
{
name: "return an error if paymentIDs is empty",
paymentIDs: []string{},
expectedResult: nil,
expectedErr: "paymentIDs is required",
},
{
name: "🎉 successfully finds circle current transfer request",
paymentIDs: []string{payment3.ID},
initFn: func(t *testing.T, ctx context.Context, sqlExec db.SQLExecuter) {
// insert a transfer for payment 3
tr, err := m.Insert(ctx, payment3.ID)
require.NoError(t, err)

_, err = m.Update(ctx, dbConnectionPool, tr.IdempotencyKey, CircleTransferRequestUpdate{
CircleTransferID: "circle-transfer-id-3",
Status: CircleTransferStatusFailed,
})
require.NoError(t, err)

// insert another transfer for payment 3
tr2, err := m.Insert(ctx, payment3.ID)
require.NoError(t, err)

_, err = m.Update(ctx, sqlExec, tr2.IdempotencyKey, CircleTransferRequestUpdate{
CircleTransferID: "circle-transfer-id-3-NEW",
Status: CircleTransferStatusSuccess,
})
require.NoError(t, err)
},
expectedResult: map[string]*CircleTransferRequest{
payment3.ID: {
PaymentID: payment3.ID,
CircleTransferID: utils.StringPtr("circle-transfer-id-3-NEW"),
},
},
},

{
name: "🎉 successfully finds circle transfer requests for multiple payments",
paymentIDs: []string{payment1.ID, payment2.ID},
initFn: func(t *testing.T, ctx context.Context, sqlExec db.SQLExecuter) {
transfer1, err := m.Insert(ctx, payment1.ID)
require.NoError(t, err)

_, err = m.Update(ctx, dbConnectionPool, transfer1.IdempotencyKey, CircleTransferRequestUpdate{
CircleTransferID: "circle-transfer-id-1",
Status: CircleTransferStatusFailed,
})
require.NoError(t, err)

transfer2, err := m.Insert(ctx, payment2.ID)
require.NoError(t, err)

_, err = m.Update(ctx, dbConnectionPool, transfer2.IdempotencyKey, CircleTransferRequestUpdate{
CircleTransferID: "circle-transfer-id-2",
Status: CircleTransferStatusPending,
})
require.NoError(t, err)
},
expectedResult: map[string]*CircleTransferRequest{
payment1.ID: {
PaymentID: payment1.ID,
CircleTransferID: utils.StringPtr("circle-transfer-id-1"),
},
payment2.ID: {
PaymentID: payment2.ID,
CircleTransferID: utils.StringPtr("circle-transfer-id-2"),
},
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tx := testutils.BeginTxWithRollback(t, ctx, dbConnectionPool)

if tc.initFn != nil {
tc.initFn(t, ctx, tx)
}

result, err := m.GetCurrentTransfersForPaymentIDs(ctx, tx, tc.paymentIDs)
if tc.expectedErr != "" {
require.ErrorContains(t, err, tc.expectedErr)
require.Nil(t, result)
} else {
require.NoError(t, err)
require.NotNil(t, result)
assert.Equal(t, len(tc.expectedResult), len(result))
for expectedPaymentID, expectedResult := range tc.expectedResult {
assert.NotNil(t, result[expectedPaymentID])
assert.Equal(t, expectedResult.CircleTransferID, result[expectedPaymentID].CircleTransferID)
assert.Equal(t, expectedResult.PaymentID, result[expectedPaymentID].PaymentID)
}
}
})
}
}
Loading

0 comments on commit ec9837b

Please sign in to comment.