From 3d01f12fabe2d26536300be4fa275a358ecdc078 Mon Sep 17 00:00:00 2001 From: benk10 Date: Mon, 12 Oct 2026 14:38:16 -0500 Subject: [PATCH] chore: upgrade paykit to rc74 --- .../repositories/PaykitPaymentRequestRepo.kt | 11 ++++ .../bitkit/repositories/PaykitSubscription.kt | 4 ++ .../to/bitkit/services/PaykitSdkService.kt | 5 +- ...aykitPaymentRequestRepoSubscriptionTest.kt | 43 ++++++++++++-- .../PaykitPaymentRequestRepoTest.kt | 56 +++++++++++++++---- .../bitkit/services/PaykitSdkServiceTest.kt | 16 +++--- changelog.d/next/1470.security.md | 1 + gradle/libs.versions.toml | 2 +- 8 files changed, 112 insertions(+), 26 deletions(-) create mode 100644 changelog.d/next/1470.security.md diff --git a/app/src/main/java/to/bitkit/repositories/PaykitPaymentRequestRepo.kt b/app/src/main/java/to/bitkit/repositories/PaykitPaymentRequestRepo.kt index 53d4e3acd6..632b81a791 100644 --- a/app/src/main/java/to/bitkit/repositories/PaykitPaymentRequestRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/PaykitPaymentRequestRepo.kt @@ -95,6 +95,7 @@ data class PaykitPaymentRequest( val paymentProofKind: PaykitPaymentProofKind? = null, val paymentDeadlineAt: Instant? = null, val isPaymentInFlight: Boolean = false, + val proposalStreamItemId: ULong? = null, ) { enum class ParseFailure( val logValue: String, @@ -987,7 +988,12 @@ class PaykitPaymentRequestRepo @Inject constructor( } } } else { + val proposalStreamItemId = request.proposalStreamItemId + ?: throw PaykitPaymentRequestError.RequestUnavailable updateRequest(request, PaymentRequestLifecycleState.ACCEPTED) { + if (it.proposalStreamItemId != proposalStreamItemId) { + throw PaykitPaymentRequestError.RequestUnavailable + } val identity = activeIdentity ?: throw PaykitPaymentRequestError.RequestUnavailable val generation = stateGeneration.get() ensurePaymentAllowed(it, forExecution = false).getOrThrow() @@ -999,6 +1005,7 @@ class PaykitPaymentRequestRepo @Inject constructor( paykitSdkService.acceptPaymentRequest( counterparty = it.counterparty, paymentRequestId = it.paymentRequestId, + expectedProposalStreamItemId = proposalStreamItemId, ) }.onFailure { error -> // Acceptance may already be committed. Reconcile before discarding its owner. @@ -1063,10 +1070,13 @@ class PaykitPaymentRequestRepo @Inject constructor( val current = _subscriptions.value.firstOrNull { it.id == subscription.id } ?.takeIf { it == subscription && it.isPayer && it.isProposalActionable(validationDate) } ?: throw PaykitPaymentRequestError.RequestUnavailable + val proposalStreamItemId = subscription.proposalStreamItemId + ?: throw PaykitPaymentRequestError.RequestUnavailable actionVersion++ val record = paykitSdkService.acceptPaymentRequest( current.counterparty, current.paymentRequestId, + proposalStreamItemId, ) // Stored on real time, so a period the offset made due is not dropped when it turns Off. val acceptanceDate = clock.now() @@ -1856,6 +1866,7 @@ private fun PaymentRequestRecord.toPaykitPaymentRequest( now: Instant, ) = PaykitPaymentRequest( paymentRequestId = paymentRequestId, + proposalStreamItemId = proposalStreamItemId, counterparty = counterparty, amountValue = parsedTerms.terms.amount.value, amountSats = parsedTerms.amountSats, diff --git a/app/src/main/java/to/bitkit/repositories/PaykitSubscription.kt b/app/src/main/java/to/bitkit/repositories/PaykitSubscription.kt index 1f4a1ca2e6..83a5d33b2b 100644 --- a/app/src/main/java/to/bitkit/repositories/PaykitSubscription.kt +++ b/app/src/main/java/to/bitkit/repositories/PaykitSubscription.kt @@ -187,6 +187,7 @@ data class PaykitSubscription( val paidPeriods: List, val paymentProofKinds: Map = emptyMap(), val hasPaymentDeadline: Boolean = false, + val proposalStreamItemId: ULong? = null, ) { val id: PaykitSubscriptionId get() = PaykitSubscriptionId(paymentRequestId, counterparty) @@ -241,6 +242,7 @@ data class PaykitSubscription( return recurrence.periodsThrough(date, acceptedAt).map { period -> PaykitPaymentRequest( paymentRequestId = paymentRequestId, + proposalStreamItemId = proposalStreamItemId, counterparty = counterparty, amountValue = amountValue, amountSats = amountSats, @@ -268,6 +270,7 @@ data class PaykitSubscription( return paidPeriods.map { period -> PaykitPaymentRequest( paymentRequestId = paymentRequestId, + proposalStreamItemId = proposalStreamItemId, counterparty = counterparty, amountValue = amountValue, amountSats = amountSats, @@ -332,6 +335,7 @@ internal fun PaymentRequestRecord.toPaykitSubscription( val payments = paymentProofs.mapNotNull { it.toSubscriptionPayment(recurrence) } return PaykitSubscription( paymentRequestId = paymentRequestId, + proposalStreamItemId = proposalStreamItemId, counterparty = counterparty, amountValue = requestTerms.amount.value, amountSats = amountSats, diff --git a/app/src/main/java/to/bitkit/services/PaykitSdkService.kt b/app/src/main/java/to/bitkit/services/PaykitSdkService.kt index e5cc363f39..fcd4038ad6 100644 --- a/app/src/main/java/to/bitkit/services/PaykitSdkService.kt +++ b/app/src/main/java/to/bitkit/services/PaykitSdkService.kt @@ -1078,11 +1078,14 @@ class PaykitSdkService @Inject constructor( suspend fun acceptPaymentRequest( counterparty: String, paymentRequestId: String, + expectedProposalStreamItemId: ULong, ): PaymentRequestRecord { isSetup.await() return operationLock.withLock(Priority.Interactive) { withStateRevisionTracking { handle -> - completeSdkCall { handle.claimAndAcceptPaymentRequest(counterparty, paymentRequestId) } + completeSdkCall { + handle.claimAndAcceptPaymentRequest(counterparty, paymentRequestId, expectedProposalStreamItemId) + } } } } diff --git a/app/src/test/java/to/bitkit/repositories/PaykitPaymentRequestRepoSubscriptionTest.kt b/app/src/test/java/to/bitkit/repositories/PaykitPaymentRequestRepoSubscriptionTest.kt index 2ae695da73..119d9acf6f 100644 --- a/app/src/test/java/to/bitkit/repositories/PaykitPaymentRequestRepoSubscriptionTest.kt +++ b/app/src/test/java/to/bitkit/repositories/PaykitPaymentRequestRepoSubscriptionTest.kt @@ -51,6 +51,7 @@ import to.bitkit.services.PaykitPaymentRequestProposalTerms import to.bitkit.services.PaykitSdkService import to.bitkit.test.BaseUnitTest import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.time.Clock @@ -604,13 +605,14 @@ class PaykitPaymentRequestRepoSubscriptionTest : BaseUnitTest(StandardTestDispat @Test fun `accepting subscription returns current period and preserves payment targets`() = test { - val proposal = paymentRequestRecord() - val active = paymentRequestRecord(state = PaymentRequestLifecycleState.ACTIVE_RECURRING) + val proposal = paymentRequestRecord().copy(proposalStreamItemId = 41uL) + val active = proposal.copy(state = PaymentRequestLifecycleState.ACTIVE_RECURRING) whenever(paykitSdkService.allPaymentRequests(anyOrNull())).thenReturn(listOf(proposal), listOf(active)) whenever( paykitSdkService.acceptPaymentRequest( COUNTERPARTY, PAYMENT_REQUEST_ID, + 41uL, ) ).thenReturn(active) whenever(paykitSdkService.linkedPeers()).thenReturn( @@ -637,6 +639,35 @@ class PaykitPaymentRequestRepoSubscriptionTest : BaseUnitTest(StandardTestDispat } } + @Test + fun `subscription acceptance rejects a replaced proposal with matching terms`() = test { + val original = paymentRequestRecord().copy(proposalStreamItemId = 41uL) + whenever(paykitSdkService.allPaymentRequests(anyOrNull())).thenReturn(listOf(original)) + sut.refresh().getOrThrow() + val reviewed = sut.subscriptions.value.single() + whenever(paykitSdkService.allPaymentRequests(anyOrNull())) + .thenReturn(listOf(original.copy(proposalStreamItemId = 42uL))) + sut.refresh().getOrThrow() + + assertFailsWith { sut.accept(reviewed).getOrThrow() } + + verify(paykitSdkService, never()).acceptPaymentRequest(any(), any(), any()) + assertEquals(42uL, sut.subscriptions.value.single().proposalStreamItemId) + } + + @Test + fun `subscription acceptance refuses a missing proposal stream item`() = test { + whenever(paykitSdkService.allPaymentRequests(anyOrNull())) + .thenReturn(listOf(paymentRequestRecord().copy(proposalStreamItemId = null))) + sut.refresh().getOrThrow() + + assertFailsWith { + sut.accept(sut.subscriptions.value.single()).getOrThrow() + } + + verify(paykitSdkService, never()).acceptPaymentRequest(any(), any(), any()) + } + @Test fun `subscription clock change refreshes periods after an overlapping refresh`() = test { val proposal = paymentRequestRecord() @@ -646,6 +677,7 @@ class PaykitPaymentRequestRepoSubscriptionTest : BaseUnitTest(StandardTestDispat paykitSdkService.acceptPaymentRequest( COUNTERPARTY, PAYMENT_REQUEST_ID, + 1uL, ) ).thenReturn(active) sut.refresh().getOrThrow() @@ -686,6 +718,7 @@ class PaykitPaymentRequestRepoSubscriptionTest : BaseUnitTest(StandardTestDispat paykitSdkService.acceptPaymentRequest( COUNTERPARTY, PAYMENT_REQUEST_ID, + 1uL, ) ).thenReturn(active) sut.refresh().getOrThrow() @@ -718,6 +751,7 @@ class PaykitPaymentRequestRepoSubscriptionTest : BaseUnitTest(StandardTestDispat paykitSdkService.acceptPaymentRequest( COUNTERPARTY, PAYMENT_REQUEST_ID, + 1uL, ) ).thenReturn(active) sut.refresh().getOrThrow() @@ -756,7 +790,7 @@ class PaykitPaymentRequestRepoSubscriptionTest : BaseUnitTest(StandardTestDispat val result = sut.accept(reviewedSubscription) assertTrue(result.exceptionOrNull() is PaykitPaymentRequestError.RequestUnavailable) - verifyBlocking(paykitSdkService, never()) { acceptPaymentRequest(any(), any()) } + verifyBlocking(paykitSdkService, never()) { acceptPaymentRequest(any(), any(), any()) } } @Test @@ -770,6 +804,7 @@ class PaykitPaymentRequestRepoSubscriptionTest : BaseUnitTest(StandardTestDispat paykitSdkService.acceptPaymentRequest( COUNTERPARTY, PAYMENT_REQUEST_ID, + 1uL, ) ).thenReturn(active) sut.refresh().getOrThrow() @@ -1212,7 +1247,7 @@ class PaykitPaymentRequestRepoSubscriptionTest : BaseUnitTest(StandardTestDispat val deadlineSubscription = subscriptions.first { it.paymentRequestId == "deadline" } assertEquals(null, deadlineSubscription.paymentDueOnAcceptance(clock.now())) assertTrue(sut.accept(deadlineSubscription).exceptionOrNull() is PaykitPaymentRequestError.RequestUnavailable) - verifyBlocking(paykitSdkService, never()) { acceptPaymentRequest(any(), any()) } + verifyBlocking(paykitSdkService, never()) { acceptPaymentRequest(any(), any(), any()) } } @Test diff --git a/app/src/test/java/to/bitkit/repositories/PaykitPaymentRequestRepoTest.kt b/app/src/test/java/to/bitkit/repositories/PaykitPaymentRequestRepoTest.kt index 2777e9cb97..b0b703fa6a 100644 --- a/app/src/test/java/to/bitkit/repositories/PaykitPaymentRequestRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/PaykitPaymentRequestRepoTest.kt @@ -1172,12 +1172,13 @@ class PaykitPaymentRequestRepoTest : BaseUnitTest(StandardTestDispatcher()) { @Test fun `accept commits locally without waiting for peer delivery during payment`() = test { - val record = paymentRequestRecord() + val record = paymentRequestRecord().copy(proposalStreamItemId = 41uL) whenever(paykitSdkService.allPaymentRequests(anyOrNull())).thenReturn(listOf(record)) whenever( paykitSdkService.acceptPaymentRequest( COUNTERPARTY, PAYMENT_REQUEST_ID, + 41uL, ), ).thenReturn(record) sut.refresh().getOrThrow() @@ -1204,14 +1205,45 @@ class PaykitPaymentRequestRepoTest : BaseUnitTest(StandardTestDispatcher()) { .thenReturn(listOf(record.copy(state = PaymentRequestLifecycleState.ACCEPTED))) sut.refresh(PaykitPaymentRequestRefreshMode.FULL, Priority.Background).getOrThrow() verify(paykitSdkService).processPendingPrivateMessages(Priority.Background) - verify(paykitSdkService, times(1)).acceptPaymentRequest(any(), any()) + verify(paykitSdkService, times(1)).acceptPaymentRequest(COUNTERPARTY, PAYMENT_REQUEST_ID, 41uL) + } + + @Test + fun `accept rejects a replacement proposal even when its terms match`() = test { + val original = paymentRequestRecord().copy(proposalStreamItemId = 41uL) + whenever(paykitSdkService.allPaymentRequests(anyOrNull())).thenReturn(listOf(original)) + sut.refresh().getOrThrow() + val reviewed = sut.pendingRequests.value.single() + whenever(paykitSdkService.allPaymentRequests(anyOrNull())) + .thenReturn(listOf(original.copy(proposalStreamItemId = 42uL))) + sut.refresh().getOrThrow() + + assertFailsWith { sut.accept(reviewed).getOrThrow() } + + verify(paykitSdkService, never()).acceptPaymentRequest(any(), any(), any()) + verify(presentationStore, never()).addAcceptedOneTimeId(any(), any()) + assertEquals(42uL, sut.pendingRequests.value.single().proposalStreamItemId) + } + + @Test + fun `accept refuses a request without its proposal stream item`() = test { + whenever(paykitSdkService.allPaymentRequests(anyOrNull())) + .thenReturn(listOf(paymentRequestRecord().copy(proposalStreamItemId = null))) + sut.refresh().getOrThrow() + + assertFailsWith { + sut.accept(sut.pendingRequests.value.single()).getOrThrow() + } + + verify(paykitSdkService, never()).acceptPaymentRequest(any(), any(), any()) + verify(presentationStore, never()).addAcceptedOneTimeId(any(), any()) } @Test fun `deferred acceptance delivery is discarded when identity changes`() = test { val record = paymentRequestRecord() whenever(paykitSdkService.allPaymentRequests(anyOrNull())).thenReturn(listOf(record)) - whenever(paykitSdkService.acceptPaymentRequest(any(), any())).thenReturn(record) + whenever(paykitSdkService.acceptPaymentRequest(any(), any(), any())).thenReturn(record) sut.refresh().getOrThrow() sut.setPaymentSubmissionActive(true) sut.accept(sut.pendingRequests.value.single()).getOrThrow() @@ -1229,7 +1261,7 @@ class PaykitPaymentRequestRepoTest : BaseUnitTest(StandardTestDispatcher()) { val proposed = paymentRequestRecord() val accepted = proposed.copy(state = PaymentRequestLifecycleState.ACCEPTED) whenever(paykitSdkService.allPaymentRequests(anyOrNull())).thenReturn(listOf(proposed)) - whenever(paykitSdkService.acceptPaymentRequest(any(), any())).thenReturn(accepted) + whenever(paykitSdkService.acceptPaymentRequest(any(), any(), any())).thenReturn(accepted) sut.refresh().getOrThrow() val request = sut.pendingRequests.value.single() sut.accept(request).getOrThrow() @@ -1242,7 +1274,7 @@ class PaykitPaymentRequestRepoTest : BaseUnitTest(StandardTestDispatcher()) { sut.activate(LOCAL_IDENTITY) sut.refresh().getOrThrow() sut.accept(sut.pendingRequests.value.single()).getOrThrow() - verify(paykitSdkService, times(1)).acceptPaymentRequest(any(), any()) + verify(paykitSdkService, times(1)).acceptPaymentRequest(any(), any(), any()) sut.ensurePaymentAllowed(request).getOrThrow() sut.activate(SECOND_IDENTITY) @@ -1492,7 +1524,7 @@ class PaykitPaymentRequestRepoTest : BaseUnitTest(StandardTestDispatcher()) { whenever(paykitSdkService.allPaymentRequests(anyOrNull())).thenReturn(listOf(proposed)) sut.refresh().getOrThrow() assertTrue(sut.accept(sut.pendingRequests.value.single()).isFailure) - verify(paykitSdkService, never()).acceptPaymentRequest(any(), any()) + verify(paykitSdkService, never()).acceptPaymentRequest(any(), any(), any()) whenever(paykitSdkService.allPaymentRequests(anyOrNull())).thenReturn(listOf(accepted)) sut.refresh().getOrThrow() @@ -1513,7 +1545,7 @@ class PaykitPaymentRequestRepoTest : BaseUnitTest(StandardTestDispatcher()) { sut.activate(LOCAL_IDENTITY) val proposed = paymentRequestRecord() whenever(paykitSdkService.allPaymentRequests(anyOrNull())).thenReturn(listOf(proposed)) - doSuspendableAnswer { throw error }.whenever(paykitSdkService).acceptPaymentRequest(any(), any()) + doSuspendableAnswer { throw error }.whenever(paykitSdkService).acceptPaymentRequest(any(), any(), any()) sut.refresh().getOrThrow() val request = sut.pendingRequests.value.single() assertTrue(sut.accept(request).isFailure) @@ -1536,7 +1568,7 @@ class PaykitPaymentRequestRepoTest : BaseUnitTest(StandardTestDispatcher()) { fun `final authorization requires durable ownership regardless of snapshot lifecycle`() = test { val proposed = paymentRequestRecord() whenever(paykitSdkService.allPaymentRequests(anyOrNull())).thenReturn(listOf(proposed)) - whenever(paykitSdkService.acceptPaymentRequest(any(), any())) + whenever(paykitSdkService.acceptPaymentRequest(any(), any(), any())) .thenReturn(proposed.copy(state = PaymentRequestLifecycleState.ACCEPTED)) val saving = CompletableDeferred() val saved = CompletableDeferred() @@ -1580,7 +1612,7 @@ class PaykitPaymentRequestRepoTest : BaseUnitTest(StandardTestDispatcher()) { other.activate(LOCAL_IDENTITY) var record = paymentRequestRecord() whenever(paykitSdkService.allPaymentRequests(anyOrNull())).thenAnswer { listOf(record) } - whenever(paykitSdkService.acceptPaymentRequest(any(), any())).thenAnswer { + whenever(paykitSdkService.acceptPaymentRequest(any(), any(), any())).thenAnswer { check(record.state == PaymentRequestLifecycleState.PROPOSED) record = record.copy(state = PaymentRequestLifecycleState.ACCEPTED) record @@ -1612,7 +1644,7 @@ class PaykitPaymentRequestRepoTest : BaseUnitTest(StandardTestDispatcher()) { val accepting = CompletableDeferred() val accepted = CompletableDeferred() whenever(paykitSdkService.allPaymentRequests(anyOrNull())).thenReturn(listOf(proposed)) - whenever(paykitSdkService.acceptPaymentRequest(any(), any())).doSuspendableAnswer { + whenever(paykitSdkService.acceptPaymentRequest(any(), any(), any())).doSuspendableAnswer { accepting.complete(Unit) accepted.await() proposed.copy(state = PaymentRequestLifecycleState.ACCEPTED) @@ -2548,7 +2580,7 @@ class PaykitPaymentRequestRepoTest : BaseUnitTest(StandardTestDispatcher()) { sut.accept(request).getOrThrow() } verifyBlocking(paykitSdkService, never()) { - acceptPaymentRequest(COUNTERPARTY, PAYMENT_REQUEST_ID) + acceptPaymentRequest(COUNTERPARTY, PAYMENT_REQUEST_ID, 1uL) } } @@ -2631,7 +2663,7 @@ class PaykitPaymentRequestRepoTest : BaseUnitTest(StandardTestDispatcher()) { ) verify(paykitSdkService, never()).cancelPaymentRequest(any(), any(), anyOrNull()) verify(paykitSdkService, never()).rejectPaymentRequest(any(), any(), anyOrNull()) - verify(paykitSdkService, never()).acceptPaymentRequest(any(), any()) + verify(paykitSdkService, never()).acceptPaymentRequest(any(), any(), any()) val guarded = listOf( proof.copy(hardwareRefusedForNavigation = false), proof.copy(hardwareDispatchDenied = true), diff --git a/app/src/test/java/to/bitkit/services/PaykitSdkServiceTest.kt b/app/src/test/java/to/bitkit/services/PaykitSdkServiceTest.kt index 75cdb94d48..13371711ef 100644 --- a/app/src/test/java/to/bitkit/services/PaykitSdkServiceTest.kt +++ b/app/src/test/java/to/bitkit/services/PaykitSdkServiceTest.kt @@ -585,7 +585,7 @@ class PaykitSdkServiceTest { fun `payment mutations overtake background work without crossing signout barriers`() = runTest { val payments = listOf Any?>( { claimPaymentRequestForExecution(RING_PUBKY, "request") }, - { acceptPaymentRequest(RING_PUBKY, "request") }, + { acceptPaymentRequest(RING_PUBKY, "request", 1uL) }, ) for ((index, payment) in payments.withIndex()) { for (withBarrier in listOf(false, true)) { @@ -608,7 +608,7 @@ class PaykitSdkServiceTest { events += "payment" mock() } - whenever { sdk.claimAndAcceptPaymentRequest(RING_PUBKY, "request") }.thenAnswer { + whenever { sdk.claimAndAcceptPaymentRequest(RING_PUBKY, "request", 1uL) }.thenAnswer { events += "payment" mock() } @@ -632,8 +632,8 @@ class PaykitSdkServiceTest { events, ) verify(sdk, times(if (index == 0) 1 else 0)).claimPaymentRequestForExecution(RING_PUBKY, "request") - verify(sdk, times(if (index == 1) 1 else 0)).claimAndAcceptPaymentRequest(RING_PUBKY, "request") - verify(sdk, never()).acceptPaymentRequest(any(), any()) + verify(sdk, times(if (index == 1) 1 else 0)).claimAndAcceptPaymentRequest(RING_PUBKY, "request", 1uL) + verify(sdk, never()).acceptPaymentRequest(any(), any(), any()) } } } @@ -837,13 +837,13 @@ class PaykitSdkServiceTest { val sdk = mock() val releaseAcceptance = CompletableDeferred() var acceptanceFinished = false - whenever { sdk.claimAndAcceptPaymentRequest(RING_PUBKY, "request") }.doSuspendableAnswer { + whenever { sdk.claimAndAcceptPaymentRequest(RING_PUBKY, "request", 1uL) }.doSuspendableAnswer { releaseAcceptance.await() acceptanceFinished = true mock() } val service = PaykitSdkService(mock(), mock(), mock(), settingsStore = mock()) { sdk } - val acceptance = async { service.acceptPaymentRequest(RING_PUBKY, "request") } + val acceptance = async { service.acceptPaymentRequest(RING_PUBKY, "request", 1uL) } runCurrent() acceptance.cancel() runCurrent() @@ -852,9 +852,9 @@ class PaykitSdkServiceTest { assertFailsWith { acceptance.await() } assertTrue(acceptanceFinished) - verify(sdk).claimAndAcceptPaymentRequest(RING_PUBKY, "request") + verify(sdk).claimAndAcceptPaymentRequest(RING_PUBKY, "request", 1uL) verify(sdk, never()).claimPaymentRequestForExecution(any(), any()) - verify(sdk, never()).acceptPaymentRequest(any(), any()) + verify(sdk, never()).acceptPaymentRequest(any(), any(), any()) assertEquals(1L, service.backupStateVersion.value) } diff --git a/changelog.d/next/1470.security.md b/changelog.d/next/1470.security.md new file mode 100644 index 0000000000..3540edac0c --- /dev/null +++ b/changelog.d/next/1470.security.md @@ -0,0 +1 @@ +Paykit payment approvals remain tied to the reviewed proposal when private state changes. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9562316c50..d45449a5e7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.1" } barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version = "17.3.0" } biometric = { module = "androidx.biometric:biometric", version = "1.4.0-alpha05" } bitkit-core = { module = "com.synonym:bitkit-core-android", version = "0.5.18" } -paykit = { module = "com.synonym:paykit-android", version = "0.1.0-rc73" } +paykit = { module = "com.synonym:paykit-android", version = "0.1.0-rc74" } bouncycastle-provider-jdk = { module = "org.bouncycastle:bcprov-jdk18on", version = "1.83" } camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" } camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera" }