Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Bitkit/Services/PaykitPaymentRequestService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1982,7 +1982,8 @@ final class PaykitPaymentRequestManager {
request,
resultingState: .accepted,
markApprovedForPayment: true,
preservePending: !request.requiresAcceptance
preservePending: !request.requiresAcceptance,
failureRefreshMode: .stored
) {
guard $0.billingPeriod != nil || $0.requiresAcceptance || acceptedRequestIds.contains($0.id) else {
throw PaykitPaymentRequestError.requestUnavailable
Expand Down Expand Up @@ -2766,6 +2767,7 @@ final class PaykitPaymentRequestManager {
resultingState: Paykit.PaymentRequestLifecycleState,
markApprovedForPayment: Bool = false,
preservePending: Bool = false,
failureRefreshMode: PaykitPaymentRequestRefreshMode = .full,
operation: (PaykitPaymentRequest) async throws -> Void
) async throws {
guard !request.isExpired(at: now()) else {
Expand Down Expand Up @@ -2816,7 +2818,7 @@ final class PaykitPaymentRequestManager {
} catch {
guard actionGeneration == stateGeneration else { throw error }
invalidateRefresh()
await refresh(excludingProtectedRequestId: request.id, forceFresh: true)
await refresh(excludingProtectedRequestId: request.id, mode: failureRefreshMode, forceFresh: true)
throw error
}
}
Expand Down
81 changes: 76 additions & 5 deletions BitkitTests/PaykitPaymentRequestServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6741,6 +6741,8 @@ final class PaykitPaymentRequestServiceTests: XCTestCase {
await manager.refresh()
let request = try XCTUnwrap(manager.pendingRequests.first)
await sdk.failNextAcceptAfterRemoval()
await sdk.setReceiveError(.process)
let beforeFailure = await sdk.snapshot()

do {
try await manager.prepareForPayment(request)
Expand All @@ -6751,6 +6753,57 @@ final class PaykitPaymentRequestServiceTests: XCTestCase {

XCTAssertTrue(manager.pendingRequests.isEmpty)
XCTAssertTrue(manager.requestsForPresentation().isEmpty)
XCTAssertFalse(manager.isApprovedForPayment(request))
let afterFailure = await sdk.snapshot()
XCTAssertEqual(afterFailure.processCallCount, beforeFailure.processCallCount)
XCTAssertEqual(afterFailure.receiveCallCount, beforeFailure.receiveCallCount)
XCTAssertEqual(afterFailure.paymentRequestListCallCount, beforeFailure.paymentRequestListCallCount + 1)

let firstFullRefresh = await manager.refresh()
XCTAssertFalse(firstFullRefresh, "The inbox transport failure belongs to the next full refresh")
let afterFullRefresh = await sdk.snapshot()
XCTAssertEqual(afterFullRefresh.processCallCount, beforeFailure.processCallCount + 1)
await sdk.setReceiveError(nil)
let next = try paymentRequestRecord(id: "next")
await sdk.setIncomingRecords([next])
let recovered = await manager.refresh()
XCTAssertTrue(recovered)
XCTAssertEqual(manager.pendingRequests.map(\.paymentRequestId), [next.paymentRequestId])
}

func testPreparationFailureRefreshKeepsProcessingGuardAndRejectsReplacementIdentity() async throws {
let sdk = try PaymentRequestSdkMock(records: [paymentRequestRecord()])
let manager = paymentRequestManager(sdk: sdk)
await manager.refresh()
let request = try XCTUnwrap(manager.pendingRequests.first)
await sdk.pauseNextPaymentRequestList()
let preparation = Task {
try await manager.prepareForPayment(request) {
throw PaymentRequestSdkMockError.preparation
}
}
try await waitUntil { await sdk.paymentRequestListIsPaused() }
XCTAssertFalse(manager.requestPresentation(request))
do {
try await manager.prepareForPayment(request)
XCTFail("A pending failure refresh must keep the request in flight")
} catch {
XCTAssertEqual(error as? PaykitPaymentRequestError, .operationInProgress)
}
let replacementIdentity = "pubky\(String(repeating: "y", count: 52))"
manager.clear()
manager.activate(identity: replacementIdentity)
await sdk.setActiveIdentity(replacementIdentity)
await sdk.resumePaymentRequestList()
do {
try await preparation.value
XCTFail("Expected the original preparation failure")
} catch {
XCTAssertEqual(error as? PaymentRequestSdkMockError, .preparation)
}
XCTAssertTrue(manager.pendingRequests.isEmpty)
XCTAssertTrue(manager.historyRequests.isEmpty)
XCTAssertFalse(manager.isApprovedForPayment(request))
}

func testFailedRejectionDropsRequestRemovedFromAuthoritativeQueue() async throws {
Expand All @@ -6759,6 +6812,7 @@ final class PaykitPaymentRequestServiceTests: XCTestCase {
await manager.refresh()
let request = try XCTUnwrap(manager.pendingRequests.first)
await sdk.failNextRejectAfterRemoval()
let beforeFailure = await sdk.snapshot()

do {
try await manager.reject(request)
Expand All @@ -6769,6 +6823,9 @@ final class PaykitPaymentRequestServiceTests: XCTestCase {

XCTAssertTrue(manager.pendingRequests.isEmpty)
XCTAssertTrue(manager.requestsForPresentation().isEmpty)
let afterFailure = await sdk.snapshot()
XCTAssertEqual(afterFailure.processCallCount, beforeFailure.processCallCount + 1)
XCTAssertEqual(afterFailure.receiveCallCount, beforeFailure.receiveCallCount + 1)
}

func testInterruptedPreparationRemainsImmediatelyPresentableAfterForegroundOrUnlock() async throws {
Expand Down Expand Up @@ -7995,16 +8052,25 @@ final class PaykitPaymentRequestServiceTests: XCTestCase {
let manager = paymentRequestManager(sdk: sdk, acceptanceStore: store)
await manager.refresh()
let request = try XCTUnwrap(manager.pendingRequests.first)
await sdk.setAcceptanceResponseError(error)
var accepted = record
accepted.state = .accepted
accepted.acceptedEventId = "accepted"
accepted.executionClaimAppId = "bitkit"
await sdk.setAcceptanceResponseError(error, committedRecord: accepted)
let beforeFailure = await sdk.snapshot()

do {
try await manager.prepareForPayment(request)
XCTFail("The interrupted call must not authorize execution")
} catch {}
XCTAssertFalse(manager.isApprovedForPayment(request))
var accepted = record
accepted.state = .accepted
await sdk.setRecords([accepted])
XCTAssertEqual(try store.load(identity: "pubky\(String(repeating: "z", count: 52))"), [request.id])
XCTAssertEqual(manager.pendingRequests.map(\.id), [request.id])
XCTAssertEqual(manager.historyRequests.first?.lifecycleState, .accepted)
let afterFailure = await sdk.snapshot()
XCTAssertEqual(afterFailure.processCallCount, beforeFailure.processCallCount)
XCTAssertEqual(afterFailure.receiveCallCount, beforeFailure.receiveCallCount)
XCTAssertEqual(afterFailure.paymentRequestListCallCount, beforeFailure.paymentRequestListCallCount + 1)
let restarted = paymentRequestManager(sdk: sdk, acceptanceStore: store)
await restarted.refresh()
let retry = try XCTUnwrap(restarted.pendingRequests.first)
Expand Down Expand Up @@ -10284,6 +10350,7 @@ private actor PaymentRequestSdkMock: PaykitPaymentRequestSdkHandling, PaykitPaym
private var rejectedRequests: [PaymentRequestInvocation] = []
private var acceptFailuresAfterRemoval = 0
private var acceptanceResponseError: Error?
private var acceptanceCommittedRecord: PaymentRequestRecord?

private var rejectFailuresAfterRemoval = 0
private var proposedRequests: [ProposedPaymentRequestInvocation] = []
Expand Down Expand Up @@ -10606,6 +10673,9 @@ private actor PaymentRequestSdkMock: PaykitPaymentRequestSdkHandling, PaykitPaym
)
}
if let acceptanceResponseError {
if let acceptanceCommittedRecord {
records.append(acceptanceCommittedRecord)
}
throw acceptanceResponseError
}
if acceptFailuresAfterRemoval > 0 {
Expand Down Expand Up @@ -10662,8 +10732,9 @@ private actor PaymentRequestSdkMock: PaykitPaymentRequestSdkHandling, PaykitPaym
processFailuresRemaining += 1
}

func setAcceptanceResponseError(_ error: Error) {
func setAcceptanceResponseError(_ error: Error, committedRecord: PaymentRequestRecord? = nil) {
acceptanceResponseError = error
acceptanceCommittedRecord = committedRecord
}

func pauseNextProcess(for counterparty: String? = nil) {
Expand Down
1 change: 1 addition & 0 deletions changelog.d/next/1081.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Keep payment request state fresh after preparation failures without waiting for message delivery.
Loading