I am building an app with In-App Purchases. The app has 6 products for sale (all consumable).
The app is able to recognize the products (from itunes connect) available for purchase using the following code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Apple Store
appleStoreProducts = [productID, product5ID, product25ID, product100ID, product500ID, product1000ID]
SKPaymentQueue.default().add(self)
self.getProductInfo()
}
func getProductInfo() {
if SKPaymentQueue.canMakePayments() {
for product in appleStoreProducts {
let request = SKProductsRequest(productIdentifiers: NSSet(objects: product) as! Set<String>)
request.delegate = self
request.start()
}
} else {
// Tell user that In-App Purchase is disabled in settings
print("In-App Purchase is disabled in settings")
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
var products = response.products
if (products.count != 0) {
let newProduct = products[0]
switch newProduct.localizedTitle {
case "DOLLAR":
product = newProduct
case "five dollars":
product2 = newProduct
case "twenty five":
product3 = newProduct
case "one hundred dollars":
product4 = newProduct
case "thousand dollars":
product5 = newProduct
default:
print("none of the above")
}
print("titile : \(newProduct.localizedTitle) \n description: \(newProduct.localizedDescription)")
}
else {
print("Product not found")
}
let invalids = response.invalidProductIdentifiers
for product in invalids {
print("Product not found: \(product)")
}
}
Then you can click a button to make the purchase of one of the items, for example:
#IBAction func productOne(_ sender: Any) {
let payment = SKPayment(product: product!)
SKPaymentQueue.default().add(payment)
}
My PROBLEM appears next, when the paymentQueue method is called:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case SKPaymentTransactionState.purchased:
// self.pointsBought()
SKPaymentQueue.default().finishTransaction(transaction)
case SKPaymentTransactionState.failed:
SKPaymentQueue.default().finishTransaction(transaction)
print("transaction failed")
default:
print("ESTDO: \(transaction.transactionState)")
break
}
}
}
This method is called twice when I click once the button. The first time the transactionState is neither "purchased" or "failed", so it calls the default option. The second time the result is "failed". However, I don't know what am I doing wrong.
Relevant information:
I am using an iOS device (iphone 6) to run the app
I am using the developer account (of the app) in my iphone
You are failing to cover every case. You need to cover all of them, and you need to call finishTransaction for certain cases. This is the format of a correct updatedTransactions method:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for t in transactions {
switch t.transactionState {
case .purchasing, .deferred: break // do nothing
case .purchased, .restored:
let p = t.payment
if p.productIdentifier == whatever {
// ... do stuff ...
queue.finishTransaction(t)
}
case .failed:
queue.finishTransaction(t)
}
}
}
Related
I'm trying to setup IAP for the first time and I am having problems with the Restore functionality. It works fine in iOS 15 using...
let refresh = SKReceiptRefreshRequest()
refresh.delegate = self
refresh.start()
...
func requestDidFinish(_ request: SKRequest) {
if request is SKReceiptRefreshRequest {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
request.cancel()
}
...but when I test on an iPhone 11 simulator running iOS 14.5 the restoreCompletedTransactions method is reached but no updates are triggered in paymentQueue's updatedTransactions delegate method.
I've also noticed that if the iCloud account is not logged in, it doesn't trigger an authentication (which the documentation says should happen).
Why does the restore code work for iOS 15, but not iOS 14.5?
and
[Optional, but possibly related:] How do I trigger the authentication check for iCloud while restoring?
It's not shown below, but the view has a spinner which starts at the beginning of the restore and is ended by the completionBlock passed along when the process starts in the restore:purchase:completion method. There's also a modal alert that reports the results when completed. Neither of these are triggering in iOS 14.5.
This is the full class I'm doing the restore in...
import StoreKit
final class PurchaseManager: NSObject, SKPaymentTransactionObserver, SKProductsRequestDelegate, SKRequestDelegate, CanCreatePopUpMessage {
// MARK: - Properties
var products = [SKProduct]()
var isTesting = false
var completion: OptionalBlock = nil
var productToRestore: Product?
var productsRestored = [Product]()
var failedRestores = [Product]()
// MARK: - Properties: Static
static var shared = PurchaseManager()
// MARK: - Functions
func restore(purchase: Product, complete: OptionalBlock = nil) { // <-- Starts here.
self.completion = complete
self.productToRestore = purchase
let refresh = SKReceiptRefreshRequest()
refresh.delegate = self
refresh.start() // <-- This concludes in requestDidFinish below...
// SKPaymentQueue.default().add(self)
// SKPaymentQueue.default().restoreCompletedTransactions()
// if #available(iOS 15.0, *) {
// let _ = Task {
// await refreshPurchasedProducts()
// }
// }
}
...
private func restoreFollowUp() {
for product in productsRestored {
handleRestore(product)
}
completion?()
guard let p = productToRestore else { return }
restoreUpdateAlert(for: p, didFail: !productsRestored.contains(p))
}
private func handleRestore(_ product: Product) {
switch product {
case .unlock(let gameMode):
switch gameMode {
case .defense:
TrenchesScene.current.infiniteBullets = true
TrenchesScene.current.pushAmmo()
case .offense:
TrenchesScene.current.unlimitedInfantry = true
TrenchesScene.current.pushUnitCounts()
}
default: break
}
}
private func getProduct(from transaction: SKPaymentTransaction) -> Product? {
getProduct(from: transaction.payment.productIdentifier)
}
private func getProduct(from transactionId: String) -> Product? {
switch transactionId {
case PurchaseId.coin4000 : return .coins(4000)
case PurchaseId.infiniteAmmo : return .unlock(.defense)
case PurchaseId.unlimitedInfantry: return .unlock(.offense)
default : return nil
}
}
...
#available(iOS 15.0, *)
func refreshPurchasedProducts() async {
self.productsRestored = []
self.failedRestores = []
for await verificationResult in Transaction.currentEntitlements {
switch verificationResult {
case .verified(let transaction):
NSLog(" #$ refreshPurchasedProducts verified: \(transaction.productID)")
if let p = getProduct(from: transaction.productID) {
productsRestored.append(p)
}
case .unverified(let unverifiedTransaction, let verificationError):
NSLog(" #$ refreshPurchasedProducts unverified: \(unverifiedTransaction.productID),\n #$ error: \(verificationError)")
if let p = getProduct(from: unverifiedTransaction.productID) {
failedRestores.append(p)
}
}
}
restoreFollowUp()
}
// MARK: - Functions: SKRequestDelegate
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print(" #$ Restore completed transaction count:\(queue.transactions.count)")
for transaction in queue.transactions {
print(" #$ completed transaction: \(transaction.payment.productIdentifier)")
}
}
// MARK: - Functions: SKPaymentTransactionObserver
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
transactions.forEach { transaction in
switch transaction.transactionState {
case .purchased: ...
case .purchasing: ...
case .restored:
print(" #$ update restoring: \(transaction.payment.productIdentifier)")
if let p = getProduct(from: transaction) {
productsRestored.append(p)
}
if transaction.transactionIdentifier == transactions.last?.transactionIdentifier {
restoreFollowUp()
}
queue.finishTransaction(transaction)
case .failed: ...
case .deferred: ...
#unknown default: ...
}
}
}
func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
NSLog(" #$ Product requests removed: \(transactions.map({ $0.payment.productIdentifier }))")
}
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
for transaction in queue.transactions {
print(" #$ failed transaction: \(transaction.original?.transactionIdentifier ?? "nil")")
}
}
func requestDidFinish(_ request: SKRequest) {
if request is SKReceiptRefreshRequest {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
request.cancel()
}
// MARK: - Functions: SKProductsRequestDelegate
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
products = response.products
}
func request(_ request: SKRequest, didFailWithError error: Error) {
guard request is SKProductsRequest else { return }
// TODO: Handle errors
print(" #$ Product request failed? \(error.localizedDescription)")
}
}
The issue wasn't OS version at all, but it had to do with testing on simulators (as #paulw11 mentioned in comments).
Under normal conditions, a user with the same apple id on multiple devices should be able to do a purchase on one and then restore on the other. THIS IS NOT HOW IT WORKS ON SIMULATORS.
I assumed that I could just hit restore on a new simulator and that it should retrieve transactions, but for testing purposes simulators need to have the purchase happen on each device, regardless of account.
I have followed a walkthrough to include a non-consumable in-app purchase. Unfortunately, nothing is happening at all after the user clicks the button for the purchase. I am not getting any prompt to log in to iTunes or to accept the payment. Is there something I am missing here?
I have tried multiple walkthroughs and they all seem to have the similar code, I have followed the steps through the apple website, however I am unsure as to whether I have to do a full app submission before I can test the in-app purchases through a sandbox.
IAPService.swift
import Foundation
import StoreKit
import UIKit
class IAPService: NSObject {
private override init() {}
static let shared = IAPService()
var products = [SKProduct]()
let paymentQueue = SKPaymentQueue.default()
func getProducts() {
let products: Set = [IAPProduct.nonConsumable.rawValue]
let request = SKProductsRequest(productIdentifiers: products)
request.delegate = self
request.start()
paymentQueue.add(self)
}
func purchase(product: IAPProduct) {
guard let productToPurchase = products.filter({
$0.productIdentifier == product.rawValue }).first else { return }
let payment = SKPayment(product: productToPurchase)
paymentQueue.add(payment)
}
func restorePurchases() {
print("restore purchases")
paymentQueue.restoreCompletedTransactions()
}
}
extension IAPService: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive
response: SKProductsResponse) {
products = response.products
for product in response.products {
print(product.localizedTitle)
}
}
}
extension IAPService: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions
transactions: [SKPaymentTransaction]) {
for transaction in transactions {
print(transaction.transactionState.status(),
transaction.payment.productIdentifier)
switch transaction.transactionState {
case .purchasing: break
default: queue.finishTransaction(transaction)
}
}
}
}
extension SKPaymentTransactionState {
func status() -> String {
switch self {
case .deferred: return "deferred"
case .failed: return "failed"
case .purchased: return "purchased"
case .purchasing: return "purchasing"
case .restored: return "restored"
}
}
}
In my products file
IAP.Products.swift
import Foundation
enum IAPProduct: String {
case nonConsumable = "Quizly"
}
In my mainVC
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
IAPService.shared.getProducts()
print("IAP == \(IAPService.shared.products)") // Why is this an empty array?
}
#objc func pressToGetPremium(_ sender : UIButton) {
IAPService.shared.purchase(product: .nonConsumable)
print("IAP ===== \(IAPService.shared.products)")
}
When the user clicks the button I was hoping that a pop up would come up first making the user have to sign in to their apple account and then another pop up would ask if they wanted to accept the non-consumable product ($1.99) etc. But I am not getting a pop up at all.
I am receiving this back from didReceive products......
response SKProductsResponse 0x00000002811cba10
baseNSObject#0 NSObject
isa Class 0x2811cba10 0x00000002811cba10
_internal SKProductsResponseInternal * 0x28139c0e0 0x000000028139c0e0
NSObject NSObject
_invalidIdentifiers __NSSingleObjectArrayI * 1 element 0x00000002811cb930
[0] __NSCFString * "Quizly" 0x000000028139c620
NSMutableString NSMutableString
_products __NSArray0 * 0 elements 0x00000002811c0050
NSArray NSArray
NSObject NSObject
isa Class __NSArray0 0x000001a25a605811
As per our comment discussion - The error produced from the didReceive products method indicates your bundleIdentifiers are not matching the ones on the appStore.
I am implementing the non renewable purchase in my app. I am still using in sandbox mode. After I subscribe for the product, when I again try to subscribe the same product, it gives me an alert saying ‘This In-App purchase has already been bought. It will be restored for free.’. I don’t know how I should enable user to subscribe again.
How can I handle multiple user on same device? If one user has paid for the subscription and another user log in into same device to my application he/she should not get the alert as above.
Code :
import StoreKit
class className: SKProductsRequestDelegate
{
var productIDs: Array<String?> = []
var productsArray: Array<SKProduct?> = []
override func viewDidLoad(){
// product IDs for in-app purchase products
productIDs.append(“monthly_subscription_id”) // Monthly
productIDs.append(“yearly_subscription_id”) // Year
requestProductInfo()
SKPaymentQueue.default().add(self)
}
func requestProductInfo() {
if SKPaymentQueue.canMakePayments() {
let productIdentifiers = NSSet(array: productIDs)
let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers as Set<NSObject> as! Set<String>)
productRequest.delegate = self
productRequest.start()
}
else {
print("Cannot perform In App Purchases.")
}
}
// MARK: SKProductsRequestDelegate method implementation
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if response.products.count != 0 {
for product in response.products {
productsArray.append(product )
}
}
else {
print("There are no products.")
}
if response.invalidProductIdentifiers.count != 0 {
print(response.invalidProductIdentifiers.description)
}
}
// MARK: Buy Subscription button action
#IBAction func btn_purchase_Action(_ sender: Any){
let payment = SKPayment(product: self.productsArray[productIndex]!)
SKPaymentQueue.default().add(payment)
self.transactionInProgress = true
}
}
// MARK: - SKPaymentTransactionObserver
extension className: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
complete(transaction: transaction)
break
case .failed:
fail(transaction: transaction)
break
case .restored:
restore(transaction: transaction)
break
case .deferred:
break
case .purchasing:
break
}
}
}
private func complete(transaction: SKPaymentTransaction){
print("complete...")
SKPaymentQueue.default().finishTransaction(transaction)
}
private func restore(transaction: SKPaymentTransaction){
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
print("restore... \(productIdentifier)")
SKPaymentQueue.default().finishTransaction(transaction)
}
private func fail(transaction: SKPaymentTransaction){
print("fail...")
if let transactionError = transaction.error as? NSError {
if transactionError.code != SKError.paymentCancelled.rawValue {
print("Transaction Error: \(transaction.error?.localizedDescription)")
}
}
SKPaymentQueue.default().finishTransaction(transaction)
}
}
I could see popup saying in-app purchase is successful, but "updatedTransaction" function is not called when i successfully finish in-app purchase process.
First time in-app purchase is completed but when i try to purchase the same product again it shows the alert that product is already purchased and could restore for free.
From your code it looks like your transaction observer is a view controller.
If the view controller is dismissed before the payment transaction has been processed then you won't get a chance to complete the transaction.
Your payment queue observer should be an object that is instantiated as soon as your app launches and remains in memory for the lifetime of your app.
Creating the payment queue observer in didFinishLaunching and holding a reference to it in your app delegate is one approach that you can use.
Im using in-app purchase in SpriteKit. The first transaction does fine, but when i do the second one my
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction])
called 2 times, than 3,4,5
so i do one request but there it adds 100 coins instead of 50...
i gues the problem in that function:
func buyProduct() {
print("buy +" + p.productIdentifier)
var payment = SKMutablePayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
i also have :
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
var myProduct = response.products
for product in myProduct {
list.append(product)
print("product added")
print(product.productIdentifier)
print(product.localizedTitle)
}
print("list is \(list)")
}
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print(transactions.count)
for transaction in transactions {
print(transaction.error)
switch transaction.transactionState {
case .Purchased:
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
print("buyed")
print(p.productIdentifier)
let prodID = p.productIdentifier
switch prodID {
case "com.addCoins" :
print("increaing coinsCount")
coinsCount = coinsCount + 50
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setInteger(coinsCount, forKey: "coins")
userDefaults.synchronize() // don't forget this!!!!
coinsLabel.text = String(coinsCount)
default: print("IAD not setuped")
}
print("premium added")
queue.finishTransaction(transaction)
break
case .Failed:
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
print("buy error")
queue.finishTransaction(transaction)
break
default: break
}
}
}
im calling purchase with:
for product in list {
let loadingNotification = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
loadingNotification.mode = MBProgressHUDMode.Indeterminate
loadingNotification.labelText = "Loading"
let prodID = product.productIdentifier
if prodID == "com.addCoins" {
p = product
buyProduct()
break
}
}
At some point you have to tell Apple that a purchase has been carried out and you delivered the goods. Until then, the purchases stay in the transaction queue forever.
Its bad practice to add
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
everytime you buy something. You should add the transaction observer at app launch and remove it when your app is closed, as per apples recommended guidelines.
Also you dont need to call synchronise for your userDefaults anymore since iOS 8, yet I still see a lot of people doing it.
In regards to you problem the code seems to be ok. The only thing I noticed so far is why in the buy function are you adding a SKMutablePayment?
Try changing SKMutablePayment to SKPayment and see if it makes a difference.
func buyProduct() {
print("buy +" + p.productIdentifier)
var payment = SKPayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self) // should not be here
SKPaymentQueue.defaultQueue().addPayment(payment)
}
Ok, so I'm working in Swift and I just need help. I have followed 4 different tutorials on how to implement in app purchases in sprite kit with Swift, copied code verbatim, and nothing is working for me.
Here are the steps I have taken:
Gone in Itunes Connect and created an in app purchase under my app's record. My in app purchase's product Id is "GameOverSaveSavior"
In Xcode, I've turned my app's In app purchase capability to ON, made sure my team is set to my account, and made sure my Bundle Identifier under info is set to my app's bundle identifier in ITunes Connect
Before writing any code, I have import StoreKit in my GameScene.swift file
As for code, this is what I have done:
(1) In Gamescene.swift, at the end of my didMoveToView func, I have:
// Set IAPS
if(SKPaymentQueue.canMakePayments()) {
println("IAP is enabled, loading")
var productID:NSSet = NSSet(objects: "GameOverSaveSavior")
var request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as Set<NSObject>)
request.delegate = self
request.start()
} else {
println("please enable IAPS")
}
This outputs "IAP is enabled, loading" when the app is run.
(2) In GameScene.swift, within the class but outside of didMoveToView, I have all the functions and variables others have used for in app purchases:
var list = [SKProduct]()
var p = SKProduct()
func purchaseMade() {
println("they bought it!")
}
func buyProduct() {
println("buy" + p.productIdentifier)
var pay = SKPayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
}
func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {
println("product request")
var myProduct = response.products
for product in myProduct {
println("product added")
println(product.productIdentifier)
println(product.localizedTitle)
println(product.localizedDescription)
println(product.price)
list.append(product as! SKProduct)
}
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue!) {
println("transactions restored")
var purchasedItemIDS = []
for transaction in queue.transactions {
var t: SKPaymentTransaction = transaction as! SKPaymentTransaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "GameOverSaveSavior":
purchaseMade()
//Right here is where you should put the function that you want to execute when your in app purchase is complete
default:
println("IAP not setup")
}
}
var alert = UIAlertView(title: "Thank You", message: "Your purchase(s) were restored. You may have to restart the app before banner ads are removed.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
println("add paymnet")
for transaction:AnyObject in transactions {
var trans = transaction as! SKPaymentTransaction
println(trans.error)
switch trans.transactionState {
case .Purchased, .Restored:
println("buy, ok unlock iap here")
println(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID {
case "GameOverSaveSavior":
//Here you should put the function you want to execute when the purchase is complete
var alert = UIAlertView(title: "Thank You", message: "You may have to restart the app before the banner ads are removed.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
default:
println("IAP not setup")
}
queue.finishTransaction(trans)
break;
case .Failed:
println("buy error")
queue.finishTransaction(trans)
break;
default:
println("default")
break;
}
}
}
func finishTransaction(trans:SKPaymentTransaction)
{
println("finish trans")
}
func paymentQueue(queue: SKPaymentQueue!, removedTransactions transactions: [AnyObject]!)
{
println("remove trans");
}
This outputs "product request" to the console when the app is run.
(3) In GameScene.swift, in my touchesBegan func, I have the following for when the right button is touched:
//In app purchase
if touchedNode == saveMeBtn {
println("button touched!")
for product in list {
var prodID = product.productIdentifier
if(prodID == "GameOverSaveSavior") {
p = product
buyProduct() //This is one of the functions we added earlier
break;
}
}
This outputs "button touched!" when the button is touched.
I do not know what I am doing wrong. The code compiles with no errors, yet nothing occurs when my button is touched. There is no alert asking the user to sign in or to purchase anything- nothing.
I have mainly followed this question:
in app purchase in SKScene
Is there anything else I should have done prior to writing the code? Am I missing something in my code?
I think I can help you:
I had your issue in the past. And another similar one which I fixed here: My IAP isn't working. Bugs at func Paymentqueue
Here is the solution I had found:
Delete
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
everywhere you have it and put it once (ONLY ONCE) in a place where it will be executed each time your app boots up ( I put it in viewDidLoad()).
This will check for all unfinished transactions and terminate them once the app has loaded, thus removing any possible errors before your users triggers an IAP.
(If this answer helped you, don't forget to upvote ;))
P.S.: Also, this wasn't my issue, but make sure to finishTransaction() for each PurchaseState, like here:
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("Add Payment")
for transaction:AnyObject in transactions{
let trans = transaction as! SKPaymentTransaction
print(trans.error)
switch trans.transactionState{
case .Purchased:
print("IAP unlocked")
print(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID{
case "IAP id":
print("Keep on")
keepOn()
default:
print("IAP not setup")
}
queue.finishTransaction(trans)
break
case .Failed:
print ("Buy error")
queue.finishTransaction(trans)
break
default:
print("default: Error")
break
}
}
}
Never forget this:
queue.finishTransaction(trans)
And make separate case for .Restored.