Skip to content

Commit dbf9dca

Browse files
committed
feat(flipcash): add confirmation for user to handle their own cash collection in app
Signed-off-by: Brandon McAnsh <[email protected]>
1 parent 5e7b17c commit dbf9dca

File tree

13 files changed

+127
-22
lines changed

13 files changed

+127
-22
lines changed

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/internal/bill/BillController.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ class BillController @Inject constructor(
6060
fun receiveGiftCard(
6161
entropy: String,
6262
owner: AccountCluster,
63+
claimIfOwned: Boolean,
6364
onReceived: (LocalFiat) -> Unit,
6465
onError: (Throwable) -> Unit,
65-
) = transactionManager.receiveGiftCard(owner, entropy, onReceived, onError)
66+
) = transactionManager.receiveGiftCard(owner, entropy, claimIfOwned, onReceived, onError)
6667
}

apps/flipcash/core/src/main/res/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,9 @@
159159

160160
<string name="error_title_failedToOpenExternalLink">Something Went Wrong</string>
161161
<string name="error_description_failedToOpenExternalLink">We were unable to open the link on this device. Please try again later</string>
162+
163+
<string name="prompt_title_collectOwnCash">Collect Your Own Cash?</string>
164+
<string name="prompt_description_collectOwnCash">You tapped to collect the cash you sent. Are you sure you want to collect it yourself?</string>
165+
<string name="action_dontCollect">Don\'t Collect</string>
166+
<string name="action_collect">Collect</string>
162167
</resources>

apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/RealSessionController.kt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.flipcash.core.R
2828
import com.flipcash.services.billing.BillingClient
2929
import com.flipcash.services.controllers.AccountController
3030
import com.flipcash.services.user.UserManager
31+
import com.getcode.manager.BottomBarAction
3132
import com.getcode.manager.BottomBarManager
3233
import com.getcode.opencode.controllers.BalanceController
3334
import com.getcode.opencode.controllers.TransactionController
@@ -522,10 +523,18 @@ class RealSessionController @Inject constructor(
522523
}
523524

524525
// TODO: analytics
526+
claimGiftCard(owner = owner, entropy = entropy, claimIfOwned = false)
527+
}
525528

529+
private fun claimGiftCard(
530+
owner: AccountCluster,
531+
entropy: String,
532+
claimIfOwned: Boolean
533+
) {
526534
billController.receiveGiftCard(
527535
entropy = entropy,
528536
owner = owner,
537+
claimIfOwned = claimIfOwned,
529538
onReceived = {
530539
toastController.enqueue(it, isDeposit = true)
531540
showBill(
@@ -537,6 +546,36 @@ class RealSessionController @Inject constructor(
537546
},
538547
onError = { cause ->
539548
when (cause) {
549+
is ReceiveGiftTransactorError.UsersGiftCard -> {
550+
// present confirmation to claim (cancel) own gift card
551+
BottomBarManager.showMessage(
552+
title = resources.getString(R.string.prompt_title_collectOwnCash),
553+
subtitle = resources.getString(R.string.prompt_description_collectOwnCash),
554+
isDismissible = false,
555+
actions = buildList {
556+
add(
557+
BottomBarAction(
558+
text = resources.getString(R.string.action_dontCollect),
559+
)
560+
)
561+
562+
add(
563+
BottomBarAction(
564+
text = resources.getString(R.string.action_collect),
565+
style = BottomBarManager.BottomBarButtonStyle.Text,
566+
onClick = {
567+
claimGiftCard(
568+
owner = owner,
569+
entropy = entropy,
570+
// confirmed claim of own cash link
571+
claimIfOwned = true
572+
)
573+
}
574+
)
575+
)
576+
}
577+
)
578+
}
540579
is ReceiveGiftTransactorError.AlreadyClaimed -> {
541580
BottomBarManager.showError(
542581
resources.getString(R.string.error_title_alreadyCollected),

libs/messaging/src/main/kotlin/com/getcode/manager/BottomBarManager.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,32 @@ object BottomBarManager {
8888
}
8989
}
9090

91+
fun showMessage(
92+
title: String,
93+
subtitle: String,
94+
actions: List<BottomBarAction>,
95+
showCancel: Boolean = false,
96+
isDismissible: Boolean = true,
97+
showScrim: Boolean = false,
98+
type: BottomBarMessageType = BottomBarMessageType.DESTRUCTIVE,
99+
timeoutSeconds: Int? = null,
100+
onClose: (SelectedBottomBarAction) -> Unit = { }
101+
) {
102+
showMessage(
103+
BottomBarMessage(
104+
title = title,
105+
subtitle = subtitle,
106+
showCancel = showCancel,
107+
type = type,
108+
actions = actions,
109+
isDismissible = isDismissible,
110+
showScrim = showScrim,
111+
onClose = onClose,
112+
timeoutSeconds = timeoutSeconds,
113+
)
114+
)
115+
}
116+
91117
/**
92118
* This replaces the error messaging from [TopBarManager] into a simpler, easy-to-reach
93119
* bottom anchored error message.

services/opencode/src/main/kotlin/com/getcode/opencode/controllers/AccountController.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.getcode.opencode.controllers
33
import com.getcode.opencode.internal.network.api.intents.IntentCreateAccount
44
import com.getcode.opencode.model.accounts.AccountCluster
55
import com.getcode.opencode.model.accounts.AccountInfo
6+
import com.getcode.opencode.model.accounts.GiftCardAccount
67
import com.getcode.opencode.model.core.ID
78
import com.getcode.opencode.repositories.AccountRepository
89
import com.getcode.solana.keys.PublicKey
@@ -26,7 +27,13 @@ class AccountController @Inject constructor(
2627
.map { it.id.bytes }
2728
}
2829

29-
suspend fun getAccounts(owner: AccountCluster): Result<Map<PublicKey, AccountInfo>> {
30-
return accountRepository.getAccounts(owner.authority.keyPair)
30+
suspend fun getAccounts(
31+
accountOwner: AccountCluster,
32+
requestingOwner: AccountCluster
33+
): Result<Map<PublicKey, AccountInfo>> {
34+
return accountRepository.getAccounts(
35+
accountOwner = accountOwner.authority.keyPair,
36+
requestingOwner = requestingOwner.authority.keyPair
37+
)
3138
}
3239
}

services/opencode/src/main/kotlin/com/getcode/opencode/controllers/BalanceController.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,13 @@ class BalanceController @Inject constructor(
137137

138138
retryable(
139139
maxRetries = 3,
140-
call = suspend { accountController.getAccounts(owner) }
140+
call = suspend { accountController.getAccounts(owner, owner) }
141141
)?.recoverCatching { error ->
142142
if (error is GetAccountsError.NotFound) {
143143
// No account yet, let's create it
144144
val createResult = accountController.createUserAccount(owner)
145145
if (createResult.isSuccess) {
146-
accountController.getAccounts(owner)
146+
accountController.getAccounts(owner, owner)
147147
.getOrElse { throw it }
148148
} else {
149149
throw createResult.exceptionOrNull() ?: Exception("Account creation failed")

services/opencode/src/main/kotlin/com/getcode/opencode/internal/domain/repositories/InternalAccountRepository.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ internal class InternalAccountRepository @Inject constructor(
1515
): Result<Boolean> = service.isCodeAccount(owner)
1616

1717
override suspend fun getAccounts(
18-
owner: Ed25519.KeyPair
19-
): Result<Map<PublicKey, AccountInfo>> = service.getAccounts(owner)
18+
accountOwner: Ed25519.KeyPair,
19+
requestingOwner: Ed25519.KeyPair,
20+
): Result<Map<PublicKey, AccountInfo>> = service.getAccounts(accountOwner, requestingOwner)
2021
}

services/opencode/src/main/kotlin/com/getcode/opencode/internal/network/api/AccountApi.kt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,28 @@ internal class AccountApi @Inject constructor(
4444
* Returns token account metadata relevant to the Code owner
4545
* account.
4646
*
47-
* @param owner The owner account, which can also be thought of as a parent account for this
48-
* RPC that links to one or more token accounts.
47+
* @param accountOwner The owner account to fetch balances for, which can also be thought of as a
48+
* parent account for this RPC that links to one or more token accounts.
49+
* @param requestingOwner A requesting owner account that is requesting the balance for owner. Additional
50+
* metadata that is considered private will be provided, if applicable. An example
51+
* use case includes a user owner account requesting account info for a gift card
52+
* owner account.
4953
*
50-
* @return The [AccountService.GetTokenAccountInfosResponse]
54+
* @return The [AccountService.GetTokenAccountInfosResponse] for the owner account.
5155
*/
5256
suspend fun getTokenAccounts(
53-
owner: KeyPair
57+
accountOwner: KeyPair,
58+
requestingOwner: KeyPair,
5459
): AccountService.GetTokenAccountInfosResponse {
5560
val request = AccountService.GetTokenAccountInfosRequest.newBuilder()
56-
.setOwner(owner.asSolanaAccountId())
57-
.apply { setSignature(sign(owner)) }
61+
.setOwner(accountOwner.asSolanaAccountId())
62+
.setRequestingOwner(requestingOwner.asSolanaAccountId())
63+
.apply {
64+
val ownerSig = sign(accountOwner)
65+
val requestingOwnerSig = sign(requestingOwner)
66+
setSignature(ownerSig)
67+
setRequestingOwnerSignature(requestingOwnerSig)
68+
}
5869
.build()
5970

6071
return withContext(Dispatchers.IO) {

services/opencode/src/main/kotlin/com/getcode/opencode/internal/network/services/AccountService.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ internal class AccountService @Inject constructor(
3636
}
3737

3838
suspend fun getAccounts(
39-
owner: KeyPair
39+
accountOwner: KeyPair,
40+
requestingOwner: KeyPair,
4041
): Result<Map<PublicKey, AccountInfo>> {
4142
return runCatching {
42-
api.getTokenAccounts(owner)
43+
api.getTokenAccounts(accountOwner, requestingOwner)
4344
}.fold(
4445
onSuccess = { response ->
4546
when (response.result) {

services/opencode/src/main/kotlin/com/getcode/opencode/internal/transactors/ReceiveGiftCardTransactor.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ internal class ReceiveGiftCardTransactor(
2929
giftCardAccount = giftCardManager.createGiftCard(mnemonic)
3030
}
3131

32-
suspend fun start(): Result<LocalFiat> {
32+
suspend fun start(claimIfOwned: Boolean): Result<LocalFiat> {
3333
val ownerKey = owner ?: return logAndFail(ReceiveGiftTransactorError.Other(message = "No owner key. Did you call with() first?"))
3434
val giftCard = giftCardAccount ?: return logAndFail(
3535
ReceiveGiftTransactorError.Other(
@@ -39,8 +39,10 @@ internal class ReceiveGiftCardTransactor(
3939

4040
// before we can receive the gift card
4141
// we need to determine the balance of it
42-
val accounts = accountController.getAccounts(giftCard.cluster)
43-
.getOrElse { return logAndFail(ReceiveGiftTransactorError.FailedToQuery()) }
42+
val accounts = accountController.getAccounts(
43+
accountOwner = giftCard.cluster,
44+
requestingOwner = ownerKey
45+
).getOrElse { return logAndFail(ReceiveGiftTransactorError.FailedToQuery()) }
4446
.takeIf { it.isNotEmpty() }
4547
?: return logAndFail(ReceiveGiftTransactorError.FailedToQuery())
4648

@@ -54,6 +56,10 @@ internal class ReceiveGiftCardTransactor(
5456
return logAndFail(ReceiveGiftTransactorError.Expired())
5557
}
5658

59+
if (info.isGiftCardIssuer && !claimIfOwned) {
60+
return Result.failure(ReceiveGiftTransactorError.UsersGiftCard())
61+
}
62+
5763
val exchangeData = info.originalExchangeData
5864
val amount = LocalFiat(exchangeData)
5965

@@ -84,6 +90,7 @@ sealed class ReceiveGiftTransactorError(
8490
) : CodeServerError(message, cause) {
8591
class FailedToQuery: GrabTransactorError(message = "Failed to query account")
8692
class AlreadyClaimed: GrabTransactorError(message = "Already claimed")
93+
class UsersGiftCard: GrabTransactorError(message = "User is gift card issuer")
8794
class Expired: GrabTransactorError(message = "Expired")
8895

8996
data class Other(

0 commit comments

Comments
 (0)