Skip to content

Commit 0b598ee

Browse files
committed
Track details for fiat onramp purchases
1 parent 0e94332 commit 0b598ee

File tree

14 files changed

+709
-3
lines changed

14 files changed

+709
-3
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
firebase.google.com/go/v4 v4.8.0
77
github.com/aws/aws-sdk-go-v2 v0.17.0
88
github.com/bits-and-blooms/bloom/v3 v3.1.0
9-
github.com/code-payments/code-protobuf-api v1.11.0
9+
github.com/code-payments/code-protobuf-api v1.12.2
1010
github.com/emirpasic/gods v1.12.0
1111
github.com/envoyproxy/protoc-gen-validate v0.1.0
1212
github.com/golang-jwt/jwt/v5 v5.0.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
108108
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
109109
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
110110
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
111-
github.com/code-payments/code-protobuf-api v1.11.0 h1:xlE4cwDAkyzsa8Jxmvy5D0trtTGLCw+wqs142tYhTXo=
112-
github.com/code-payments/code-protobuf-api v1.11.0/go.mod h1:pHQm75vydD6Cm2qHAzlimW6drysm489Z4tVxC2zHSsU=
111+
github.com/code-payments/code-protobuf-api v1.12.2 h1:jU3CBQubbgjycyYkfwcm4v+Y0Q2mYZJJPDAQ4AXgZjI=
112+
github.com/code-payments/code-protobuf-api v1.12.2/go.mod h1:pHQm75vydD6Cm2qHAzlimW6drysm489Z4tVxC2zHSsU=
113113
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw=
114114
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
115115
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=

pkg/code/data/internal.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/code-payments/code-server/pkg/code/data/login"
3434
"github.com/code-payments/code-server/pkg/code/data/merkletree"
3535
"github.com/code-payments/code-server/pkg/code/data/nonce"
36+
"github.com/code-payments/code-server/pkg/code/data/onramp"
3637
"github.com/code-payments/code-server/pkg/code/data/payment"
3738
"github.com/code-payments/code-server/pkg/code/data/paymentrequest"
3839
"github.com/code-payments/code-server/pkg/code/data/paywall"
@@ -65,6 +66,7 @@ import (
6566
messaging "github.com/code-payments/code-server/pkg/code/data/messaging"
6667
messaging_memory_client "github.com/code-payments/code-server/pkg/code/data/messaging/memory"
6768
nonce_memory_client "github.com/code-payments/code-server/pkg/code/data/nonce/memory"
69+
onramp_memory_client "github.com/code-payments/code-server/pkg/code/data/onramp/memory"
6870
payment_memory_client "github.com/code-payments/code-server/pkg/code/data/payment/memory"
6971
paymentrequest_memory_client "github.com/code-payments/code-server/pkg/code/data/paymentrequest/memory"
7072
paywall_memory_client "github.com/code-payments/code-server/pkg/code/data/paywall/memory"
@@ -95,6 +97,7 @@ import (
9597
merkletree_postgres_client "github.com/code-payments/code-server/pkg/code/data/merkletree/postgres"
9698
messaging_postgres_client "github.com/code-payments/code-server/pkg/code/data/messaging/postgres"
9799
nonce_postgres_client "github.com/code-payments/code-server/pkg/code/data/nonce/postgres"
100+
onramp_postgres_client "github.com/code-payments/code-server/pkg/code/data/onramp/postgres"
98101
payment_postgres_client "github.com/code-payments/code-server/pkg/code/data/payment/postgres"
99102
paymentrequest_postgres_client "github.com/code-payments/code-server/pkg/code/data/paymentrequest/postgres"
100103
paywall_postgres_client "github.com/code-payments/code-server/pkg/code/data/paywall/postgres"
@@ -393,6 +396,11 @@ type DatabaseData interface {
393396
SaveBalanceCheckpoint(ctx context.Context, record *balance.Record) error
394397
GetBalanceCheckpoint(ctx context.Context, account string) (*balance.Record, error)
395398

399+
// Onramp
400+
// --------------------------------------------------------------------------------
401+
PutFiatOnrampPurchase(ctx context.Context, record *onramp.Record) error
402+
GetFiatOnrampPurchase(ctx context.Context, nonce uuid.UUID) (*onramp.Record, error)
403+
396404
// ExecuteInTx executes fn with a single DB transaction that is scoped to the call.
397405
// This enables more complex transactions that can span many calls across the provider.
398406
//
@@ -432,6 +440,7 @@ type DatabaseProvider struct {
432440
badgecount badgecount.Store
433441
login login.Store
434442
balance balance.Store
443+
onramp onramp.Store
435444

436445
exchangeCache cache.Cache
437446
timelockCache cache.Cache
@@ -490,6 +499,7 @@ func NewDatabaseProvider(dbConfig *pg.Config) (DatabaseData, error) {
490499
badgecount: badgecount_postgres_client.New(db),
491500
login: login_postgres_client.New(db),
492501
balance: balance_postgres_client.New(db),
502+
onramp: onramp_postgres_client.New(db),
493503

494504
exchangeCache: cache.NewCache(maxExchangeRateCacheBudget),
495505
timelockCache: cache.NewCache(maxTimelockCacheBudget),
@@ -529,6 +539,7 @@ func NewTestDatabaseProvider() DatabaseData {
529539
badgecount: badgecount_memory_client.New(),
530540
login: login_memory_client.New(),
531541
balance: balance_memory_client.New(),
542+
onramp: onramp_memory_client.New(),
532543

533544
exchangeCache: cache.NewCache(maxExchangeRateCacheBudget),
534545
timelockCache: nil, // Shouldn't be used for tests
@@ -1415,3 +1426,12 @@ func (dp *DatabaseProvider) SaveBalanceCheckpoint(ctx context.Context, record *b
14151426
func (dp *DatabaseProvider) GetBalanceCheckpoint(ctx context.Context, account string) (*balance.Record, error) {
14161427
return dp.balance.GetCheckpoint(ctx, account)
14171428
}
1429+
1430+
// Onramp
1431+
// --------------------------------------------------------------------------------
1432+
func (dp *DatabaseProvider) PutFiatOnrampPurchase(ctx context.Context, record *onramp.Record) error {
1433+
return dp.onramp.Put(ctx, record)
1434+
}
1435+
func (dp *DatabaseProvider) GetFiatOnrampPurchase(ctx context.Context, nonce uuid.UUID) (*onramp.Record, error) {
1436+
return dp.onramp.Get(ctx, nonce)
1437+
}

pkg/code/data/onramp/memory/store.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package memory
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"sync"
7+
"time"
8+
9+
"github.com/google/uuid"
10+
11+
"github.com/code-payments/code-server/pkg/code/data/onramp"
12+
)
13+
14+
type store struct {
15+
mu sync.Mutex
16+
records []*onramp.Record
17+
last uint64
18+
}
19+
20+
// New returns a new in memory onramp.Store
21+
func New() onramp.Store {
22+
return &store{}
23+
}
24+
25+
// Put implements onramp.Store.Put
26+
func (s *store) Put(_ context.Context, data *onramp.Record) error {
27+
if err := data.Validate(); err != nil {
28+
return err
29+
}
30+
31+
s.mu.Lock()
32+
defer s.mu.Unlock()
33+
34+
s.last++
35+
if item := s.find(data); item != nil {
36+
return onramp.ErrPurchaseAlreadyExists
37+
} else {
38+
if data.Id == 0 {
39+
data.Id = s.last
40+
}
41+
if data.CreatedAt.IsZero() {
42+
data.CreatedAt = time.Now()
43+
}
44+
c := data.Clone()
45+
s.records = append(s.records, &c)
46+
}
47+
48+
return nil
49+
}
50+
51+
// Get implements onramp.Store.Get
52+
func (s *store) Get(_ context.Context, nonce uuid.UUID) (*onramp.Record, error) {
53+
s.mu.Lock()
54+
defer s.mu.Unlock()
55+
56+
if item := s.findByNonce(nonce); item != nil {
57+
cloned := item.Clone()
58+
return &cloned, nil
59+
}
60+
return nil, onramp.ErrPurchaseNotFound
61+
}
62+
63+
func (s *store) find(data *onramp.Record) *onramp.Record {
64+
for _, item := range s.records {
65+
if item.Id == data.Id {
66+
return item
67+
}
68+
if bytes.Equal(data.Nonce[:], item.Nonce[:]) {
69+
return item
70+
}
71+
}
72+
return nil
73+
}
74+
75+
func (s *store) findByNonce(nonce uuid.UUID) *onramp.Record {
76+
for _, item := range s.records {
77+
if bytes.Equal(nonce[:], item.Nonce[:]) {
78+
return item
79+
}
80+
}
81+
return nil
82+
}
83+
84+
func (s *store) reset() {
85+
s.mu.Lock()
86+
defer s.mu.Unlock()
87+
88+
s.records = nil
89+
s.last = 0
90+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package memory
2+
3+
import (
4+
"testing"
5+
6+
"github.com/code-payments/code-server/pkg/code/data/onramp/tests"
7+
)
8+
9+
func TestOnrampMemoryStore(t *testing.T) {
10+
testStore := New()
11+
teardown := func() {
12+
testStore.(*store).reset()
13+
}
14+
tests.RunTests(t, testStore, teardown)
15+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package postgres
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"time"
7+
8+
"github.com/google/uuid"
9+
"github.com/jmoiron/sqlx"
10+
11+
"github.com/code-payments/code-server/pkg/code/data/onramp"
12+
pgutil "github.com/code-payments/code-server/pkg/database/postgres"
13+
)
14+
15+
const (
16+
tableName = "codewallet__core_onramppurchase"
17+
)
18+
19+
type model struct {
20+
Id sql.NullInt64 `db:"id"`
21+
22+
Owner string `db:"owner"`
23+
Currency string `db:"currency"`
24+
Amount float64 `db:"amount"`
25+
Nonce uuid.UUID `db:"nonce"`
26+
27+
CreatedAt time.Time `db:"created_at"`
28+
}
29+
30+
func toModel(obj *onramp.Record) (*model, error) {
31+
if err := obj.Validate(); err != nil {
32+
return nil, err
33+
}
34+
35+
return &model{
36+
Owner: obj.Owner,
37+
Currency: obj.Currency,
38+
Amount: obj.Amount,
39+
Nonce: obj.Nonce,
40+
CreatedAt: obj.CreatedAt,
41+
}, nil
42+
}
43+
44+
func fromModel(obj *model) *onramp.Record {
45+
return &onramp.Record{
46+
Id: uint64(obj.Id.Int64),
47+
Owner: obj.Owner,
48+
Currency: obj.Currency,
49+
Amount: obj.Amount,
50+
Nonce: obj.Nonce,
51+
CreatedAt: obj.CreatedAt,
52+
}
53+
}
54+
55+
func (m *model) dbPut(ctx context.Context, db *sqlx.DB) error {
56+
return pgutil.ExecuteInTx(ctx, db, sql.LevelDefault, func(tx *sqlx.Tx) error {
57+
query := `INSERT INTO ` + tableName + `
58+
(owner, currency, amount, nonce, created_at)
59+
VALUES ($1, $2, $3, $4, $5)
60+
RETURNING id, owner, currency, amount, nonce, created_at`
61+
62+
if m.CreatedAt.IsZero() {
63+
m.CreatedAt = time.Now()
64+
}
65+
66+
err := tx.QueryRowxContext(
67+
ctx,
68+
query,
69+
m.Owner,
70+
m.Currency,
71+
m.Amount,
72+
m.Nonce,
73+
m.CreatedAt.UTC(),
74+
).StructScan(m)
75+
76+
return pgutil.CheckUniqueViolation(err, onramp.ErrPurchaseAlreadyExists)
77+
})
78+
}
79+
80+
func dbGet(ctx context.Context, db *sqlx.DB, nonce uuid.UUID) (*model, error) {
81+
res := &model{}
82+
83+
query := `SELECT
84+
id, owner, currency, amount, nonce, created_at
85+
FROM ` + tableName + `
86+
WHERE nonce = $1
87+
LIMIT 1`
88+
89+
err := db.GetContext(ctx, res, query, nonce)
90+
if err != nil {
91+
return nil, pgutil.CheckNoRows(err, onramp.ErrPurchaseNotFound)
92+
}
93+
return res, nil
94+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package postgres
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
7+
"github.com/google/uuid"
8+
"github.com/jmoiron/sqlx"
9+
10+
"github.com/code-payments/code-server/pkg/code/data/onramp"
11+
)
12+
13+
type store struct {
14+
db *sqlx.DB
15+
}
16+
17+
// New returns a new postgres-backed onramp.Store
18+
func New(db *sql.DB) onramp.Store {
19+
return &store{
20+
db: sqlx.NewDb(db, "pgx"),
21+
}
22+
}
23+
24+
// Put implements onramp.Store.Put
25+
func (s *store) Put(ctx context.Context, record *onramp.Record) error {
26+
model, err := toModel(record)
27+
if err != nil {
28+
return err
29+
}
30+
31+
if err := model.dbPut(ctx, s.db); err != nil {
32+
return err
33+
}
34+
35+
res := fromModel(model)
36+
res.CopyTo(record)
37+
38+
return nil
39+
}
40+
41+
// Get implements onramp.Store.Get
42+
func (s *store) Get(ctx context.Context, nonce uuid.UUID) (*onramp.Record, error) {
43+
model, err := dbGet(ctx, s.db, nonce)
44+
if err != nil {
45+
return nil, err
46+
}
47+
return fromModel(model), nil
48+
}

0 commit comments

Comments
 (0)