Meet StoreKit 2

RSS for tag

Discuss the WWDC21 session Meet StoreKit 2.

View Session

Posts under wwdc21-10114 tag

48 Posts
Sort by:
Post marked as solved
2 Replies
1k Views
In the session on StoreKit 2 (which looks amazing!), the presenter says: In fact, if your app is running when a purchase is made on another device, you'll be notified about the new transaction. This seems to mean that when an app uses the listener API to be notified of transactions, it will get transactions that happened on other devices. My app offers purchases across other platforms in addition to iOS, and when a purchase happens we register it with our own account system. If a user has the app running on both their iPad and iPhone and makes a purchase on the phone, if the iPad gets notified of it the same way it would of a purchase made on the iPad, both devices will try to report it to our system. This seems undesirable. What's the recommended approach here? Should we just make sure our system will disregard duplicate transaction reports? Or is there a way to know whether a transaction originated on this device? I don't see a property on the transaction type that looks like it could accomplish this. Maybe the deviceVerification properties? But that's seems more like the new edition of transaction receipt verification - failing that check would presumably mean that the purchase is invalid, not that it didn't happen on this device...?
Posted
by
Post marked as Apple Recommended
1.3k Views
Following the sample code from the session, I received errors when verifying the renewal info: [Default] [StoreKit] Failed to verify certificate chain due to client recoverable failure: Error Domain=NSOSStatusErrorDomain Code=-67843 "“Test Apple Root CA - G3” certificate is not trusted" UserInfo={NSLocalizedDescription=“Test Apple Root CA - G3” certificate is not trusted, NSUnderlyingError=0x2800835d0 {Error Domain=NSOSStatusErrorDomain Code=-67843 "Certificate 2 “Test Apple Root CA - G3” has errors: Root is not trusted;" UserInfo={NSLocalizedDescription=Certificate 2 “Test Apple Root CA - G3” has errors: Root is not trusted;}}} [Default] [StoreKit] Failed to verify signature for subscription status, will assume invalid: failedToVerifyCertificateChain Here's the code I'm using for getting subscription status @MainActor func subscriptionStatus() async -> (Product, Product.SubscriptionInfo.Status)? {   do {     // This app has only one subscription group so products in the subscriptions     // array all belong to the same group. The statuses returned by     // `product.subscription.status` apply to the entire subscription group.     guard let product = subscriptions.first,        let statuses = try await product.subscription?.status else {          return nil        }     var highestSubscriptionStatus: (product: Product, status: Product.SubscriptionInfo.Status)?     for status in statuses {       switch status.state {       case .expired, .revoked:         continue       default:         let renewalInfo = try checkVerified(status.renewalInfo)         guard let newSubscription = subscriptions.first(where: { $0.id == renewalInfo.currentProductID }) else {           continue         }         guard let currentProduct = highestSubscriptionStatus?.product else {           highestSubscriptionStatus = (newSubscription, status)           continue         }         let highestTier = tier(for: currentProduct.id)         let newTier = tier(for: renewalInfo.currentProductID)         if newTier > highestTier {           highestSubscriptionStatus = (newSubscription, status)         }       }     }     return highestSubscriptionStatus   } catch {     print("Could not update subscription status \(error)")     return nil   } } I'm running this on a real device without the storekit configuration file. Is this a known issue or am I missing something to get this working? Thanks
Posted
by
Post not yet marked as solved
7 Replies
2.2k Views
Hi, I have been testing StoreKit 2. I'm trying to query for available subscription products by using the following query code as per the document. I have configured the in-app purchases with subscriptions in appstoreconnect. The request query keeps throwing an error. Would appreciate some help. Query Code let productIdentifiers: Set = ["monthly_subscription", "yearly_subscription", "family_monthly_subscription", "family_yearly_subscription"]         async {             do {                 let _subscribableProducts = try await Product.request(with: productIdentifiers)                 self.subscribableProducts = _subscribableProducts             }             catch {                 print("Something went wronge \(error)")             }         } Error thrown systemError(Error Domain=ASDErrorDomain Code=507 "Error decoding response" UserInfo={NSLocalizedDescription=Error decoding response, NSLocalizedFailureReason=Could not decode media products response})
Posted
by
Post not yet marked as solved
1 Replies
603 Views
I'm trying to test a declined "Ask to buy" transaction and it runs but I'm getting a result I didn't expect: after the decline, the transaction's status shows as .pending, not .failed, which is what my mental model of the transaction cycle expects. Here's the test code: func testAskToBuyThenDecline() throws { let session = try SKTestSession(configurationFileNamed: "Configuration") // We clear out all the old transactions before doing this transaction session.clearTransactions() // Make the test run without human intervention session.disableDialogs = true // Set AskToBuy session.askToBuyEnabled = true // Make the purchase XCTAssertNoThrow(try session.buyProduct(productIdentifier: "com.borderstamp.IAP0002"), "Couldn't buy product IAP0002") // A new transaction should be created with the purchase status == .deferred. The transaction will remain in the deferred state until it gets approved or rejected. XCTAssertTrue(session.allTransactions().count == 1, "Expected transaction count of 1, got \(session.allTransactions().count)") XCTAssertTrue(session.allTransactions()[0].state == .deferred, "Deferred purchase should be .deferred. Instead, we got a status of \(session.allTransactions()[0].state)") // Now we decline that transaction XCTAssertNoThrow(try session.declineAskToBuyTransaction(identifier: session.allTransactions()[0].identifier), "Failed to approve AskToBuy transaction") XCTAssertTrue(session.allTransactions()[0].state == .failed, "Declined purchase should fail. Instead, we got a status of \(session.allTransactions()[0].state.rawValue)") // Why is transaction.state set to .pending instead of .failed? My mental model looks like PurchaseFlow1, where there are two separate transactions that occur. The result of my unit test, though, seems to imply PurchaseFlow2. Are either of these models correct? PurchaseFlow1 PurchaseFlow2 In the SKDemo app (sample code project associated with WWDC21 session 10114: Meet StoreKit 2, the code to handle deferred transactions is func listenForTransactions() -> Task.Handle<Void, Error> { return detach { //Iterate through any transactions which didn't come from a direct call to `purchase()`. for await result in Transaction.updates { do { let transaction = try self.checkVerified(result) //Deliver content to the user. await self.updatePurchasedIdentifiers(transaction) //Always finish a transaction. await transaction.finish() } catch { //StoreKit has a receipt it can read but it failed verification. Don't deliver content to the user. print("Transaction failed verification") } } } } It seems to me the catch section should try to handle declined transactions but we don't get a transaction to handle unless we pass checkVerified. But we failed to pass checkVerified, which is how we ended up in the catch section. I'm obviously thinking about this wrongly. Can anyone help me understand how it all actually does work? Thanks
Posted
by
Post marked as solved
3 Replies
949 Views
We try to get transaction history per this doc https://developer.apple.com/documentation/appstoreserverapi/get_transaction_history However, we got 404, and response details are < HTTP/2 404 < server: daiquiri/3.0.0 < date: Mon, 19 Jul 2021 09:04:48 GMT < content-length: 0 < x-apple-jingle-correlation-key: TROLAF3A72KS6FCSQRCVCFFJKY < x-apple-request-uuid: 9c5cb017-60fe-952f-1452-84455114a956 < b3: 9c5cb01760fe952f145284455114a956-cdbaee3838492961 < x-b3-traceid: 9c5cb01760fe952f145284455114a956 < x-b3-spanid: cdbaee3838492961 < apple-seq: 0.0 < apple-tk: false < apple-originating-system: CommerceGateway < x-responding-instance: CommerceGateway:010196::: < apple-timing-app: 3 ms < strict-transport-security: max-age=31536000; includeSubDomains < x-daiquiri-instance: daiquiri:45824002:st44p00it-hyhk15104701:7987:21HOTFIX14 < Does it mean there is no related transaction history or something wrong with API usage? Thanks
Posted
by
Post not yet marked as solved
0 Replies
420 Views
https://developer.apple.com/documentation/appstoreserverapi/send_consumption_information If the customer provided consent, respond by calling this API and sending the consumption data in the ConsumptionRequest to the App Store. If not, respond by calling this API and setting the customerConsented value to false in the ConsumptionRequest; don't send any other information. Since our server would be receiving CONSUMPTION_REQUEST server notifications and will be the one calling the Consumption API, how do we know if the user has provided consent? That info doesn't seem to be in the server notification or anywhere else.
Posted
by
Post not yet marked as solved
0 Replies
410 Views
We have a case to show users a message that their subscription was in Billing Retry. Current Entitlements documentation says only active subscription transactions will be returned. Our doubt is whether the current entitlements will also provide billing retry subscriptions. If not what is the best way to get the billing retry subscriptions?
Post not yet marked as solved
1 Replies
611 Views
We have a case to show users a message that their subscription was in Billing Retry. But we were unable to test it in Sandbox Environment. In the Sandbox environment, we were enabled interrupted purchase after initial purchase but that's not working as expected. In Xcode environment tried interrupted purchase and fail transactions with error. But Nothing works for renewal transactions. These all are working as expected only for the Initial purchase. Please provide a proper way to test the Billing Retry case.
Post not yet marked as solved
2 Replies
882 Views
In the latest Product object we are unable to get the price locale of the current product. Even though the display price string available with currency symbol, we need to display discount price of current product by comparing with other products. Earlier in SKProduct we had price locale property to achieve this.But in latest Product object we are missing this. Is there a way to get the price locale of the current storefront?. There is a countryCode property in Storefront enum. But there is no option to create locale using country code.
Posted
by
Post marked as solved
1 Replies
806 Views
Hey all, I've been using StoreKit 2 lately and I'm so deeply impressed with how much easier it has become. For a better user experience regarding transaction history, I've built a view that would show every transaction made in the past with the option to request a refund natively. The only problem I'm facing at the moment is that I can't seem to get a list with all transactions, my app has Non-consumable, Consumable and Subscription IAP and I only get the Non-consumable + Subscriptions when I use the Transaction.all Does anyone have any idea how I can get the Consumable transactions as well? Current code @MainActor     func getPurchasedProducts() async {         //Iterate through all of the user's purchased products.         for await result in StoreKit.Transaction.all {             if case .verified(let transaction) = result {                 if !self.transactions.contains(transaction) {                     self.transactions.append(transaction)                     self.transactions = sortByDate(self.transactions)                 }             }         }     }
Posted
by
Post not yet marked as solved
1 Replies
732 Views
Using the Implementing a Store In Your App Using the StoreKit API sample code, I've successfully integrated my new APP with StoreKit 2. There is one problem though: when I call the method Transaction.latest(for:) to get the user’s latest transaction, it always returns nil. Here's the code snippet: guard let result = await Transaction.latest(for: myProductId) else { return false } Is this a bug with StoreKit 2, or am I doing something wrong? This happens on a physical device, running from Xcode. Thanks in advance.
Posted
by
Post not yet marked as solved
4 Replies
1.1k Views
It seems that subscription status gives different results with XCode testing and Sandbox testing. I am using StoreKit2 to implement an IAP of an autorenewable subscription. I want to determine whether the subscription has been cancelled, so that the UI reflects that the subscription will stop after the expiry date and not be renewed. the 'willAutoRenew' property of the subscription status renewalInfo seems to do exactly what is required, and works fine in XCode testing. My setup is very similar to the StoreKit demo associated with the WWDC21 session available here: https://developer.apple.com/documentation/storekit/in-app_purchase/implementing_a_store_in_your_app_using_the_storekit_api/ To demonstrate its use, add: print(renewalInfo.willAutoRenew) after line 79 of the SubscriptionsView in the demo project. When you run the app, and purchase a Standard Navigation assistance subscription, the console shows 'true'. If you then cancel the subscription in XCode (Debug:StoreKit:Manage Transactions), the console will show 'false' as expected So far so good. My problem is that when I move to Sandbox testing, and cancel the subscription in another way (eg using the .manageSubscriptionsSheet view modifier, or in Settings:App Store:Sandbox Account), the willAutoRenew property remains true, even though the subscription is in fact cancelled (ie it disappears after the expiry date) Does anyone know a workaround to determine cancellation status?
Posted
by
Post not yet marked as solved
2 Replies
555 Views
our app integrates Apple Pay, and after the user pays successfully, our server obtains the original_transaction_id. after we get the original_transaction_id, we call. The https://api.storekit.itunes.apple.com/inapps/v1/history/ interface. 3.Url of request: https://api.storekit.itunes.apple.com/inapps/v1/history/30000931965136 4.The response result is: {"revision":"0","bundleId":"live.vshow.live.dfgsdb2","appAppleId":1552575331,"environment":"Production","hasMore":false,"signedTransactions":[]} 5.Why is the result of signedTransactions empty?
Posted
by
Post not yet marked as solved
0 Replies
386 Views
I am using StoreKit2 to offer an auto-renewable subscription in an iOS app, and have observed inconsistent results from the currentEntitlement function during sandbox testing To illustrate the issue I have created a function which prints out a timestamped entitlement, as well as subscription status using the following code. @MainActor func printSubscriptionInfo() async { let productId = "com.mycompany.autorenewablesubscription.subscription.id" print() print(Date.now) // Current entitlement if let currentEntitlement = await Transaction.currentEntitlement(for: productId) { if case .verified(let transaction) = currentEntitlement { print("Purchase date: \(transaction.purchaseDate)") print("Expiry date: \(transaction.expirationDate!)") } else { print("Transaction not verified") } } else { print("No entitlement for \(productId)") } // Subscription information let product = (availableProducts.filter { $0.id == productId }).first! guard let statuses = try? await product.subscription?.status, let status = statuses.first else { print("Unable to get subscription status") return } print("Subscription status: \(status.stateDescription)") } The following shows the output of this function at various time points after a purchase, as well as the associated server notifications received. Purchase completed: at 12:23:20 Function output at 12:23:40: Purchase date: 12:23:09 Expiry date: 12:26:09 Subscription status: subscribed Server notifications: 12:23:13 - INTERACTIVE_RENEWAL 12:25:20 - INITIAL_BUY Function output at 12:26:18: No entitlement for com.mycompany.autorenewablesubscription.subscription.id Subscription status: subscribed Server notifications: 12:28:26 - INITIAL_BUY Function output at 12:31:03 Purchase date: 12:29:09 Expiry date: 12:32:09 Subscription status: subscribed The output of the function called after the first subscription renewal (ie at 12:26:09) shows no current entitlement, even though the subscription status is correct and there are no server notifications indicating expiry. After the next expiry period has passed, the function returns correct results once more. Has anyone else noticed this behaviour?
Posted
by
Post marked as solved
2 Replies
2.2k Views
How can I validate an in-app purchase JWS Representation from StoreKit2 on my backend in Node? Its easy enough to decode the payload, but I can't find public keys that Apple uses to sign these JWS/JWTs anywhere. Any other time I've worked with JWTs, you simply used the node jsonwebtoken library and passed in the signers public key or shared secret key, either configured or fetched from a JWK. I do see the docs about validating the certificate chain in the x5c field but am at a bit of a loss on how to verify that its from Apple. Anyone can create a JWT and sign one. Thank you!
Posted
by
Post not yet marked as solved
1 Replies
542 Views
Hello,The Storekit 2 is pretty good, i want to Use it in my app right now,but i found some question? 1、unfinished transation never Automatic trigger on any where,such as when I start up again, how can i fetch the unfinished transations on some way? 2、i call transaction.finish(),but, when i user Transaction.unfinished to fetch all unfinished transations, the transaction i just finished, alway be funded 3、how can i verified jws myself, not call the autoVafified api you provide,can you has some demo?
Posted
by
Post not yet marked as solved
0 Replies
274 Views
Hello, I have a markdown editor app, the model is in-app purchases, User download it for free,  and it have a 14-days trails for free (0 tier). a PRO 5$ unlock all the features. And it is not a subscribe model app. In 14-days trails, user could use all the features too, after the 14-days trails, if user do not buy the PRO, it return to the view mode. Example, user buy the 14-days trails in Oct 12, in Oct 15, user could use all features, how to get the purchase date, and validate the current date , if less than 14, user could still use all feature, and after 14 days, It return to view mode. I find some document that build a server connect to App Store server may validate the receipt, but I see the StoreKit 2 documents says it could validate in local, my small project maybe use local validate is more simple. How to check the user’s device date in or out of trails? Thanks for any advice.
Posted
by
Post not yet marked as solved
1 Replies
396 Views
My app uses the new API for in-app purchase rather than the original API as listed here: https://developer.apple.com/documentation/storekit/choosing_a_storekit_api_for_in-app_purchase But in App Store connect I'm getting this error/warning: These in-app purchases can’t be promoted on the App Store because your latest approved binary doesn’t include the SKPaymentTransactionObserver method. SKPaymentTransactionObserver is a part of the old API and I believe I shouldn't need to implement it anymore. Anyone know what I should do here?
Posted
by
Post not yet marked as solved
0 Replies
375 Views
On an app that was using the old API for In-App Purchases (StoreKit 1). The app is already published on the App Store. The purchase is non-consumable. While trying to migrate to StoreKit 2, I'm unable to restore purchases. Specifically displaying and purchasing products works as expected, but when deleting and reinstalling the app, and then trying to restore purchases I can't do it. I'm trying to restore them using the new APIs but it doesn't seem to be working. What I have tried so far: I'm listening for transaction updates during the whole lifetime of the app, with: Task.detached { for await result in Transaction.updates { if case let .verified(safe) = result { } } } I have a button that calls this method, but other than prompting to log in again with the Apple ID it doesn't seem to have any effect at all: try? await AppStore.sync() This doesn't return any item for await result in Transaction.currentEntitlements { if case let .verified(transaction) = result { } } This doesn't return any item for await result in Transaction.all { if case let .verified(transaction) = result { } } As mentioned before I'm trying this after purchasing the item and deleting the app. So I'm sure it should be able to restore the purchase. Am trying this both with a Configuration.storekit file on the simulator, and without it on a real device, in the Sandbox Environment. Has anyone being able to restore purchases using StoreKit 2? PD: I already filed a feedback report on Feedback Assistant, but so far the only thing that they have replied is: Because StoreKit Testing in Xcode is a local environment, and the data is tied to the app, when you delete the app you're also deleting all the transaction data for that app in the Xcode environment. The code snippets provided are correct usage of the API. So yes, using a Configuration.storekit file won't work on restoring purchases, but if I can't restore them on the Sandbox Environment I'm afraid that this won't work once released, leaving my users totally unable to restore what they have already purchased.
Posted
by