Skip to content

Commit 040c475

Browse files
finish unfinished expired transactions
1 parent 96ef620 commit 040c475

9 files changed

Lines changed: 37 additions & 4 deletions

File tree

LocalPackages/PIALibrary/Sources/PIALibrary/Account/DefaultAccountProvider.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,10 @@ public final class DefaultAccountProvider: AccountProvider, ConfigurationAccess,
481481

482482
Task { @MainActor in
483483
await performSignup(with: request, callback: callback)
484+
// In theory we should only finish transactions on success, but then users get into
485+
// some weird state (unfinished, expired transactions), so we finish them all.
486+
// Finishing all transactions is fine because users can use the restore flow.
487+
await request.transaction?.finish()
484488
}
485489
}
486490

LocalPackages/PIALibrary/Sources/PIALibrary/InApp/AppStoreProvider.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,10 @@ final class AppStoreProvider: NSObject, InAppProvider {
8383
switch result {
8484
case .unverified(let transaction, let error):
8585
log.warning("Unverified transaction: \(transaction.id) \(error)")
86+
log.debug("Finishing transaction: \(transaction.id)")
8687
await transaction.finish()
8788
case .verified(let transaction):
89+
log.debug("Finishing transaction: \(transaction.id)")
8890
await transaction.finish()
8991
}
9092
}

LocalPackages/PIALibrary/Sources/PIALibrary/InApp/AppStoreTransaction.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ struct AppStoreTransaction: InAppTransaction<Transaction> {
3333

3434
let jwsRepresentation: JWS
3535

36+
var isExpired: Bool {
37+
if let expirationDate = native.expirationDate, expirationDate < Date.now {
38+
return true
39+
}
40+
return false
41+
}
42+
3643
init(native: Transaction, jwsRepresentation: JWS) {
3744
self.native = native
3845
self.jwsRepresentation = jwsRepresentation

LocalPackages/PIALibrary/Sources/PIALibrary/InApp/InAppTransaction.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public protocol InAppTransaction<Native>: CustomStringConvertible {
3636
/// The underlying native transaction implementation.
3737
var native: Native { get }
3838

39+
/// Transaction has an expirationDate in the past.
40+
///
41+
/// (Less than Date.now)
42+
var isExpired: Bool { get }
43+
3944
func finish() async
4045
}
4146

LocalPackages/PIALibrary/Sources/PIALibrary/Mock/MockInAppProvider.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ import StoreKit
5353

5454
let native: Native = .none
5555

56+
let isExpired: Bool = false
57+
5658
func finish() async {}
5759
}
5860

PIA VPN-tvOSTests/Signup/Mocks/InAppTransactionMock.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,20 @@ final class InAppTransactionMock: InAppTransaction {
1414
var identifier: String
1515
var jwsRepresentation: JWS
1616
var native: Any?
17+
var isExpired: Bool
1718
var description: String
1819

1920
init(
2021
identifier: String,
2122
jwsRepresentation: JWS = JWS("mock-jws-transaction")!,
2223
native: Any? = nil,
24+
isExpired: Bool = false,
2325
description: String
2426
) {
2527
self.identifier = identifier
2628
self.jwsRepresentation = jwsRepresentation
2729
self.native = native
30+
self.isExpired = isExpired
2831
self.description = description
2932
}
3033

PIA VPN/Bootstrapper.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ final class Bootstrapper {
178178
})
179179
}
180180
#endif
181+
182+
Client.observeTransactions()
181183
Client.providers.accountProvider.subscriptionInformation { [weak self] (info, error) in
182184

183185
if let _ = error {
@@ -197,7 +199,6 @@ final class Bootstrapper {
197199
}
198200

199201
Client.refreshProducts()
200-
Client.observeTransactions()
201202
}
202203

203204
if (self.isSimulator || Flags.shared.usesMockVPN) {

PIA VPN/UI/GetStartedViewController.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,18 @@ final class GetStartedViewController: PIAWelcomeViewController {
223223
)
224224
case .success(let transaction):
225225
log.debug("Purchase successful with transaction id \(transaction.identifier)")
226+
if transaction.isExpired {
227+
// Users in this state (unfished, expired transaction) is a weird edge case,
228+
// since we always finish all transactions. However, users still get into this state.
229+
// Finishing all transactions is fine because users can use the restore flow.
230+
log.warning("Transaction \(transaction.identifier) is expired. Finishing it and cancel purchase.")
231+
await transaction.finish()
232+
Macros.displayImageNote(
233+
withImage: Asset.iconWarning.image,
234+
message: ClientError.badReceipt.localizedDescription
235+
)
236+
return
237+
}
226238
self.signupEmail = email
227239
self.signupTransaction = transaction
228240
self.signupAttemptCount += 1

PIA VPN/UI/SignupInProgressViewController.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,6 @@ final class SignupInProgressViewController: AutolayoutViewController, BrandableN
9191
topViewController: self
9292
)
9393
} else {
94-
Task {
95-
await request.transaction?.finish()
96-
}
9794
self.perform(segue: StoryboardSegue.Signup.successSegueIdentifier)
9895
}
9996
}

0 commit comments

Comments
 (0)