Skip to content

Commit

Permalink
Payments with relationship accounts (#8)
Browse files Browse the repository at this point in the history
* Refactor GetLatestByOwnerAddress

* GetLatestByOwnerAddress refactor updates for dependent code

* GetTokenAccountInfos returns relationship metadata

* Hook up relationship accounts to Geyser external deposit handler

* Private receive flows for relationship accounts

* Public send flows for relationship accounts as a source

* Public send flows for relationship accounts as a destination

* Private send flows for relationship accounts

* Payment request messaging flows with relationship accounts

* Update Payment Received push, and a small caching optimization

* Update payment history tests

* Timelock account management state tests for SubmitIntent calls involving relationship accounts

* Handle chat mute status for payment received notification when destination is a relationship account

* Chat updates for merchant to user payments
  • Loading branch information
jeffyanta committed Dec 8, 2023
1 parent ace4388 commit 4df5073
Show file tree
Hide file tree
Showing 28 changed files with 1,354 additions and 406 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
firebase.google.com/go/v4 v4.8.0
github.com/aws/aws-sdk-go-v2 v0.17.0
github.com/bits-and-blooms/bloom/v3 v3.1.0
github.com/code-payments/code-protobuf-api v1.3.0
github.com/code-payments/code-protobuf-api v1.3.1-0.20231206151310-7246ff3de9a5
github.com/emirpasic/gods v1.12.0
github.com/envoyproxy/protoc-gen-validate v0.1.0
github.com/golang-jwt/jwt/v5 v5.0.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/code-payments/code-protobuf-api v1.3.0 h1:0hhUq7BoTjBo1GwKpgC76VHMrThhvXkjh4B+bT5O1Ec=
github.com/code-payments/code-protobuf-api v1.3.0/go.mod h1:pHQm75vydD6Cm2qHAzlimW6drysm489Z4tVxC2zHSsU=
github.com/code-payments/code-protobuf-api v1.3.1-0.20231206151310-7246ff3de9a5 h1:A/h5RjpuoU0a8ypTVYGE/EsODATcPlUdnqWxIeWEoZA=
github.com/code-payments/code-protobuf-api v1.3.1-0.20231206151310-7246ff3de9a5/go.mod h1:pHQm75vydD6Cm2qHAzlimW6drysm489Z4tVxC2zHSsU=
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw=
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
Expand Down
10 changes: 5 additions & 5 deletions pkg/code/async/geyser/external_deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,13 @@ func processPotentialExternalDeposit(ctx context.Context, data code_data.Provide
return errors.Wrap(err, "error getting account info record")
}

// Mark anything other than primary account as synced and move on without
// Mark anything other than deposit accounts as synced and move on without
// saving anything. There's a potential someone could overutilize our treasury
// by depositing large sums into temporary or bucket accounts, which have
// more lenient checks ATM. We'll deal with these adhoc as they arise. It
// should be a rare case given anything other than primary isn't exposed to
// users.
if accountInfoRecord.AccountType != commonpb.AccountType_PRIMARY {
// more lenient checks ATM. We'll deal with these adhoc as they arise.
switch accountInfoRecord.AccountType {
case commonpb.AccountType_PRIMARY, commonpb.AccountType_RELATIONSHIP:
default:
syncedDepositCache.Insert(cacheKey, true, 1)
return nil
}
Expand Down
24 changes: 14 additions & 10 deletions pkg/code/balance/calculator.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (

commonpb "github.com/code-payments/code-protobuf-api/generated/go/common/v1"

"github.com/code-payments/code-server/pkg/metrics"
timelock_token "github.com/code-payments/code-server/pkg/solana/timelock/v1"
"github.com/code-payments/code-server/pkg/code/common"
code_data "github.com/code-payments/code-server/pkg/code/data"
"github.com/code-payments/code-server/pkg/code/data/timelock"
"github.com/code-payments/code-server/pkg/metrics"
timelock_token "github.com/code-payments/code-server/pkg/solana/timelock/v1"
)

const (
Expand Down Expand Up @@ -393,7 +393,7 @@ func GetTotalBalance(ctx context.Context, data code_data.Provider, owner *common

var accountRecordsBatch []*common.AccountRecords
for _, accountRecords := range accountRecordsByType {
accountRecordsBatch = append(accountRecordsBatch, accountRecords)
accountRecordsBatch = append(accountRecordsBatch, accountRecords...)
}

balanceByAccount, err := DefaultBatchCalculationWithAccountRecords(ctx, data, accountRecordsBatch...)
Expand All @@ -404,8 +404,10 @@ func GetTotalBalance(ctx context.Context, data code_data.Provider, owner *common
}

var total uint64
for _, records := range accountRecordsByType {
total += balanceByAccount[records.General.TokenAccount]
for _, batchRecords := range accountRecordsByType {
for _, records := range batchRecords {
total += balanceByAccount[records.General.TokenAccount]
}
}
return total, nil
}
Expand Down Expand Up @@ -438,12 +440,12 @@ func GetPrivateBalance(ctx context.Context, data code_data.Provider, owner *comm

var accountRecordsBatch []*common.AccountRecords
for _, accountRecords := range accountRecordsByType {
switch accountRecords.General.AccountType {
case commonpb.AccountType_PRIMARY, commonpb.AccountType_LEGACY_PRIMARY_2022, commonpb.AccountType_REMOTE_SEND_GIFT_CARD:
switch accountRecords[0].General.AccountType {
case commonpb.AccountType_PRIMARY, commonpb.AccountType_LEGACY_PRIMARY_2022, commonpb.AccountType_REMOTE_SEND_GIFT_CARD, commonpb.AccountType_RELATIONSHIP:
continue
}

accountRecordsBatch = append(accountRecordsBatch, accountRecords)
accountRecordsBatch = append(accountRecordsBatch, accountRecords...)
}

balanceByAccount, err := DefaultBatchCalculationWithAccountRecords(ctx, data, accountRecordsBatch...)
Expand All @@ -454,8 +456,10 @@ func GetPrivateBalance(ctx context.Context, data code_data.Provider, owner *comm
}

var total uint64
for _, records := range accountRecordsByType {
total += balanceByAccount[records.General.TokenAccount]
for _, batchRecords := range accountRecordsByType {
for _, records := range batchRecords {
total += balanceByAccount[records.General.TokenAccount]
}
}
return total, nil
}
24 changes: 14 additions & 10 deletions pkg/code/balance/calculator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ import (

commonpb "github.com/code-payments/code-protobuf-api/generated/go/common/v1"

"github.com/code-payments/code-server/pkg/currency"
timelock_token_v1 "github.com/code-payments/code-server/pkg/solana/timelock/v1"
"github.com/code-payments/code-server/pkg/testutil"
"github.com/code-payments/code-server/pkg/code/common"
code_data "github.com/code-payments/code-server/pkg/code/data"
"github.com/code-payments/code-server/pkg/code/data/account"
Expand All @@ -23,6 +20,10 @@ import (
"github.com/code-payments/code-server/pkg/code/data/intent"
"github.com/code-payments/code-server/pkg/code/data/payment"
"github.com/code-payments/code-server/pkg/code/data/transaction"
"github.com/code-payments/code-server/pkg/currency"
"github.com/code-payments/code-server/pkg/pointer"
timelock_token_v1 "github.com/code-payments/code-server/pkg/solana/timelock/v1"
"github.com/code-payments/code-server/pkg/testutil"
)

func TestDefaultCalculationMethods_NewCodeAccount(t *testing.T) {
Expand All @@ -45,7 +46,7 @@ func TestDefaultCalculationMethods_NewCodeAccount(t *testing.T) {
require.NoError(t, err)
assert.EqualValues(t, 0, balance)

balanceByAccount, err := DefaultBatchCalculationWithAccountRecords(env.ctx, env.data, accountRecords[commonpb.AccountType_PRIMARY])
balanceByAccount, err := DefaultBatchCalculationWithAccountRecords(env.ctx, env.data, accountRecords[commonpb.AccountType_PRIMARY][0])
require.NoError(t, err)
require.Len(t, balanceByAccount, 1)
assert.EqualValues(t, 0, balanceByAccount[newTokenAccount.PublicKey().ToBase58()])
Expand Down Expand Up @@ -91,7 +92,7 @@ func TestDefaultCalculationMethods_DepositFromExternalWallet(t *testing.T) {
accountRecords, err := common.GetLatestTokenAccountRecordsForOwner(env.ctx, env.data, owner)
require.NoError(t, err)

balanceByAccount, err := DefaultBatchCalculationWithAccountRecords(env.ctx, env.data, accountRecords[commonpb.AccountType_PRIMARY])
balanceByAccount, err := DefaultBatchCalculationWithAccountRecords(env.ctx, env.data, accountRecords[commonpb.AccountType_PRIMARY][0])
require.NoError(t, err)
require.Len(t, balanceByAccount, 1)
assert.EqualValues(t, 11, balanceByAccount[depositAccount.PublicKey().ToBase58()])
Expand Down Expand Up @@ -197,7 +198,7 @@ func TestDefaultCalculationMethods_MultipleIntents(t *testing.T) {
accountRecords4, err := common.GetLatestTokenAccountRecordsForOwner(env.ctx, env.data, owner4)
require.NoError(t, err)

balanceByAccount, err := DefaultBatchCalculationWithAccountRecords(env.ctx, env.data, accountRecords1[commonpb.AccountType_PRIMARY], accountRecords2[commonpb.AccountType_PRIMARY], accountRecords3[commonpb.AccountType_PRIMARY], accountRecords4[commonpb.AccountType_PRIMARY])
balanceByAccount, err := DefaultBatchCalculationWithAccountRecords(env.ctx, env.data, accountRecords1[commonpb.AccountType_PRIMARY][0], accountRecords2[commonpb.AccountType_PRIMARY][0], accountRecords3[commonpb.AccountType_PRIMARY][0], accountRecords4[commonpb.AccountType_PRIMARY][0])
require.NoError(t, err)
require.Len(t, balanceByAccount, 4)
assert.EqualValues(t, 11, balanceByAccount[a1.PublicKey().ToBase58()])
Expand Down Expand Up @@ -264,7 +265,7 @@ func TestDefaultCalculationMethods_BackAndForth(t *testing.T) {
accountRecords2, err := common.GetLatestTokenAccountRecordsForOwner(env.ctx, env.data, owner2)
require.NoError(t, err)

balanceByAccount, err := DefaultBatchCalculationWithAccountRecords(env.ctx, env.data, accountRecords1[commonpb.AccountType_PRIMARY], accountRecords2[commonpb.AccountType_PRIMARY])
balanceByAccount, err := DefaultBatchCalculationWithAccountRecords(env.ctx, env.data, accountRecords1[commonpb.AccountType_PRIMARY][0], accountRecords2[commonpb.AccountType_PRIMARY][0])
require.NoError(t, err)
require.Len(t, balanceByAccount, 2)
assert.EqualValues(t, 0, balanceByAccount[a1.PublicKey().ToBase58()])
Expand Down Expand Up @@ -316,7 +317,7 @@ func TestDefaultCalculationMethods_SelfPayments(t *testing.T) {
accountRecords, err := common.GetLatestTokenAccountRecordsForOwner(env.ctx, env.data, ownerAccount)
require.NoError(t, err)

balanceByAccount, err := DefaultBatchCalculationWithAccountRecords(env.ctx, env.data, accountRecords[commonpb.AccountType_PRIMARY])
balanceByAccount, err := DefaultBatchCalculationWithAccountRecords(env.ctx, env.data, accountRecords[commonpb.AccountType_PRIMARY][0])
require.NoError(t, err)
require.Len(t, balanceByAccount, 1)
assert.EqualValues(t, 1, balanceByAccount[tokenAccount.PublicKey().ToBase58()])
Expand Down Expand Up @@ -354,7 +355,7 @@ func TestDefaultCalculationMethods_NotManagedByCode(t *testing.T) {
_, err = DefaultCalculation(env.ctx, env.data, tokenAccount)
assert.Equal(t, ErrNotManagedByCode, err)

_, err = DefaultBatchCalculationWithAccountRecords(env.ctx, env.data, accountRecords[commonpb.AccountType_PRIMARY])
_, err = DefaultBatchCalculationWithAccountRecords(env.ctx, env.data, accountRecords[commonpb.AccountType_PRIMARY][0])
assert.Equal(t, ErrNotManagedByCode, err)

_, err = DefaultBatchCalculationWithTokenAccounts(env.ctx, env.data, tokenAccount)
Expand Down Expand Up @@ -419,7 +420,7 @@ func TestGetAggregatedBalances(t *testing.T) {

balance := uint64(math.Pow10(i))
expectedTotalBalance += balance
if accountType != commonpb.AccountType_PRIMARY {
if accountType != commonpb.AccountType_PRIMARY && accountType != commonpb.AccountType_RELATIONSHIP {
expectedPrivateBalance += balance
}

Expand All @@ -435,6 +436,9 @@ func TestGetAggregatedBalances(t *testing.T) {
TokenAccount: timelockRecord.VaultAddress,
AccountType: accountType,
}
if accountType == commonpb.AccountType_RELATIONSHIP {
accountInfoRecord.RelationshipTo = pointer.String("example.com")
}
require.NoError(t, env.data.CreateAccountInfo(env.ctx, &accountInfoRecord))

actionRecord := action.Record{
Expand Down
24 changes: 21 additions & 3 deletions pkg/code/chat/message_cash_transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/pkg/errors"

chatpb "github.com/code-payments/code-protobuf-api/generated/go/chat/v1"
commonpb "github.com/code-payments/code-protobuf-api/generated/go/common/v1"

"github.com/code-payments/code-server/pkg/code/common"
code_data "github.com/code-payments/code-server/pkg/code/data"
Expand All @@ -18,13 +19,16 @@ import (
// chat with exchange data content related to the submitted intent.
//
// Note: Tests covered in SubmitIntent history tests
//
// todo: How are we handling relationship account flows?
func SendCashTransactionsExchangeMessage(ctx context.Context, data code_data.Provider, intentRecord *intent.Record) error {
messageId := intentRecord.IntentId

exchangeData, ok := getExchangeDataFromIntent(intentRecord)
if !ok {
return nil
}

messageId := intentRecord.IntentId
verbByMessageReceiver := make(map[string]chatpb.ExchangeDataContent_Verb)
switch intentRecord.IntentType {
case intent.SendPrivatePayment:
Expand All @@ -39,7 +43,14 @@ func SendCashTransactionsExchangeMessage(ctx context.Context, data code_data.Pro

verbByMessageReceiver[intentRecord.InitiatorOwnerAccount] = chatpb.ExchangeDataContent_WITHDREW
if len(intentRecord.SendPrivatePaymentMetadata.DestinationOwnerAccount) > 0 {
verbByMessageReceiver[intentRecord.SendPrivatePaymentMetadata.DestinationOwnerAccount] = chatpb.ExchangeDataContent_DEPOSITED
destinationAccountInfoRecord, err := data.GetAccountInfoByTokenAddress(ctx, intentRecord.SendPrivatePaymentMetadata.DestinationTokenAccount)
if err != nil {
return err
} else if destinationAccountInfoRecord.AccountType != commonpb.AccountType_RELATIONSHIP {
// Relationship accounts payments will show up in the verified
// merchant chat
verbByMessageReceiver[intentRecord.SendPrivatePaymentMetadata.DestinationOwnerAccount] = chatpb.ExchangeDataContent_DEPOSITED
}
}
} else if intentRecord.SendPrivatePaymentMetadata.IsRemoteSend {
verbByMessageReceiver[intentRecord.InitiatorOwnerAccount] = chatpb.ExchangeDataContent_SENT
Expand All @@ -54,7 +65,14 @@ func SendCashTransactionsExchangeMessage(ctx context.Context, data code_data.Pro
if intentRecord.SendPublicPaymentMetadata.IsWithdrawal {
verbByMessageReceiver[intentRecord.InitiatorOwnerAccount] = chatpb.ExchangeDataContent_WITHDREW
if len(intentRecord.SendPublicPaymentMetadata.DestinationOwnerAccount) > 0 {
verbByMessageReceiver[intentRecord.SendPublicPaymentMetadata.DestinationOwnerAccount] = chatpb.ExchangeDataContent_DEPOSITED
destinationAccountInfoRecord, err := data.GetAccountInfoByTokenAddress(ctx, intentRecord.SendPublicPaymentMetadata.DestinationTokenAccount)
if err != nil {
return err
} else if destinationAccountInfoRecord.AccountType != commonpb.AccountType_RELATIONSHIP {
// Relationship accounts payments will show up in the verified
// merchant chat
verbByMessageReceiver[intentRecord.SendPublicPaymentMetadata.DestinationOwnerAccount] = chatpb.ExchangeDataContent_DEPOSITED
}
}
}

Expand Down
73 changes: 50 additions & 23 deletions pkg/code/chat/message_merchant.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/pkg/errors"

chatpb "github.com/code-payments/code-protobuf-api/generated/go/chat/v1"
commonpb "github.com/code-payments/code-protobuf-api/generated/go/common/v1"

"github.com/code-payments/code-server/pkg/code/common"
code_data "github.com/code-payments/code-server/pkg/code/data"
Expand All @@ -18,19 +19,7 @@ import (
//
// Note: Tests covered in SubmitIntent history tests
func SendMerchantExchangeMessage(ctx context.Context, data code_data.Provider, intentRecord *intent.Record) error {
switch intentRecord.IntentType {
case intent.SendPrivatePayment:
if !intentRecord.SendPrivatePaymentMetadata.IsMicroPayment {
return nil
}
default:
return nil
}

paymentRequestRecord, err := data.GetPaymentRequest(ctx, intentRecord.IntentId)
if err != nil {
return errors.Wrap(err, "error getting payment request record")
}
messageId := intentRecord.IntentId

// There are three possible chats for a merchant:
// 1. Verified chat with a verified identifier that server has validated
Expand All @@ -44,13 +33,6 @@ func SendMerchantExchangeMessage(ctx context.Context, data code_data.Provider, i
chatTitle := PaymentsName
chatType := chat.ChatTypeInternal
isVerified := false
if paymentRequestRecord.Domain != nil {
chatTitle = *paymentRequestRecord.Domain
chatType = chat.ChatTypeExternalApp
isVerified = paymentRequestRecord.IsVerified
}

messageId := intentRecord.IntentId

exchangeData, ok := getExchangeDataFromIntent(intentRecord)
if !ok {
Expand All @@ -60,10 +42,55 @@ func SendMerchantExchangeMessage(ctx context.Context, data code_data.Provider, i
verbByMessageReceiver := make(map[string]chatpb.ExchangeDataContent_Verb)
switch intentRecord.IntentType {
case intent.SendPrivatePayment:
verbByMessageReceiver[intentRecord.InitiatorOwnerAccount] = chatpb.ExchangeDataContent_SPENT
if len(intentRecord.SendPrivatePaymentMetadata.DestinationOwnerAccount) > 0 {
verbByMessageReceiver[intentRecord.SendPrivatePaymentMetadata.DestinationOwnerAccount] = chatpb.ExchangeDataContent_PAID
if intentRecord.SendPrivatePaymentMetadata.IsMicroPayment {
paymentRequestRecord, err := data.GetPaymentRequest(ctx, intentRecord.IntentId)
if err != nil {
return errors.Wrap(err, "error getting payment request record")
}

if paymentRequestRecord.Domain != nil {
chatTitle = *paymentRequestRecord.Domain
chatType = chat.ChatTypeExternalApp
isVerified = paymentRequestRecord.IsVerified
}

verbByMessageReceiver[intentRecord.InitiatorOwnerAccount] = chatpb.ExchangeDataContent_SPENT
if len(intentRecord.SendPrivatePaymentMetadata.DestinationOwnerAccount) > 0 {
verbByMessageReceiver[intentRecord.SendPrivatePaymentMetadata.DestinationOwnerAccount] = chatpb.ExchangeDataContent_PAID
}
} else if intentRecord.SendPrivatePaymentMetadata.IsWithdrawal {
if len(intentRecord.SendPrivatePaymentMetadata.DestinationOwnerAccount) > 0 {
destinationAccountInfoRecord, err := data.GetAccountInfoByTokenAddress(ctx, intentRecord.SendPrivatePaymentMetadata.DestinationTokenAccount)
if err != nil {
return err
} else if destinationAccountInfoRecord.AccountType == commonpb.AccountType_RELATIONSHIP {
// Relationship accounts only exist against verified merchants,
// and will have merchant payments appear in the verified merchant
// chat.
chatTitle = *destinationAccountInfoRecord.RelationshipTo
isVerified = true
verbByMessageReceiver[intentRecord.SendPrivatePaymentMetadata.DestinationOwnerAccount] = chatpb.ExchangeDataContent_DEPOSITED
}
}
}
case intent.SendPublicPayment:
if intentRecord.SendPublicPaymentMetadata.IsWithdrawal {
if len(intentRecord.SendPublicPaymentMetadata.DestinationOwnerAccount) > 0 {
destinationAccountInfoRecord, err := data.GetAccountInfoByTokenAddress(ctx, intentRecord.SendPublicPaymentMetadata.DestinationTokenAccount)
if err != nil {
return err
} else if destinationAccountInfoRecord.AccountType == commonpb.AccountType_RELATIONSHIP {
// Relationship accounts only exist against verified merchants,
// and will have merchant payments appear in the verified merchant
// chat.
chatTitle = *destinationAccountInfoRecord.RelationshipTo
isVerified = true
verbByMessageReceiver[intentRecord.SendPublicPaymentMetadata.DestinationOwnerAccount] = chatpb.ExchangeDataContent_DEPOSITED
}
}
}
default:
return nil
}

for account, verb := range verbByMessageReceiver {
Expand Down
Loading

0 comments on commit 4df5073

Please sign in to comment.