Meet StoreKit 2

RSS for tag

Discuss the WWDC21 session Meet StoreKit 2.

View Session

Posts under wwdc21-10114 tag

19 results found
Sort by:
Post marked as Apple Recommended
642 Views

"“Test Apple Root CA - G3” certificate is not trusted" when verifying renewal info

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
Asked
by nosedive.
Last updated
.
Post not yet marked as solved
114 Views

Transaction.latest(for:) returns nil in StoreKit 2

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.
Asked Last updated
.
Post not yet marked as solved
271 Views

Testing declined purchase

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
Asked
by pdarcey.
Last updated
.
Post not yet marked as solved
296 Views

Price Locale not available in Product

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.
Asked Last updated
.
Post marked as solved
440 Views

Storekit: get transaction history return 404

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
Asked
by zangw.
Last updated
.
Post marked as solved
218 Views

How do I get all Consumable IAP Transactions with StoreKit 2

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)                 }             }         }     }
Asked Last updated
.
Post not yet marked as solved
228 Views

How to test Billing Retry subscriptions case in Sanbox or Xcode Environment?

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.
Asked Last updated
.
Post not yet marked as solved
208 Views

Does currentEntitlement will contain a Billing Retry Subscription?

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?
Asked Last updated
.
Post not yet marked as solved
202 Views

How do you get user consent for ConsumptionRequest?

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.
Asked
by crauss77.
Last updated
.
Post not yet marked as solved
283 Views

Transaction.updates weird behaviors

I ran the SKDemoApp using Xcode 13.0 beta (13A5155e) and found that even if the transactions were finished in the detach task, all transactions were presented again in the next renewal. 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)                 print("tid: \(transaction.id!)")                 //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")             }         }     } } With above code snippet I printed the transaction id and it looked like this: tid: 0 tid: 0 tid: 1 tid: 0 tid: 1 tid: 2 tid: 0 tid: 1 tid: 2 tid: 3 tid: 0 tid: 1 tid: 2 tid: 3 tid: 4 tid: 0 tid: 1 tid: 2 tid: 3 tid: 4 tid: 5 Is this the expected behavior?
Asked
by darkes.
Last updated
.
Post marked as solved
289 Views

Transaction.all complains of "Bag value missing"

When querying Transaction.all, the output posts the following error: 2021-06-27 20:00:17.765248-0400 Test App[1203:12345] [Default] Error enumerating all transactions: Error Domain=AMSErrorDomain Code=204 "Bag Value Missing" UserInfo={NSLocalizedDescription=Bag Value Missing, NSLocalizedFailureReason=The bag does not contain in-app-history-max-age nor did anyone register a default value. <AMSBagFrozenDataSource: 0x107e5d370; profile: AMSFrozenBagValue; version: 1>} How might one provide an "in-app-history-max-age" or set a default?
Asked Last updated
.
Post not yet marked as solved
577 Views

Error thrown when querying for Subscription Products

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})
Asked Last updated
.
Post not yet marked as solved
357 Views

SK2 - why so overly complicated?

SK2 sounds so easy. I understand they're many different use cases, but why is it made to be so overly complicated? For instance: When getting currentEntitlements, why does it return every transaction for this item, even if it is expired? Shouldn't currentEntitlements just return me one transaction which is valid, not expired? If I want all of the transactions for X Product, I should be able to query Transactions.all, or how about Transactions(for: Product)? Why does Transaction.all not conform to Sequence? Meaning, why cant I just iterate through all Transactions like: for transaction in await Transacion.all { //do stuff }, yet for await transaction in Transaction.all { //do dtuff } does work It seems so convoluted to get a status of an auto-renewing subscription. Why cant it just be: get Product X, for Product X, get the RenewalInfo, then what is the autoRenewStatus of this product? sounds simple, right? yet in reality, its much more complicated, with verifying responses and guard case and plenty of other extra nonsense that just makes the code grow for no reason. What would be nice here is:  get product from app store: let product = await Product(for: "com.sample.product") (Now, I know that this product is an auto-renewable subscription item, so I want to know what the auto-renew status for it is, so I can display to the user their status) get autoRenew status from this Product: let willAutoRenew = product.subscription.isAutoRenew Now, the user may not own this product, so even better would be to be able to get this info from the currentEntitlement, latestTransaction:  if let mySubEntitlement = try await Transaction.currentEntitlement(for: "this.productID") { //user must have an entitlement to this item //ensure that the response is verified: guard case .verified(let verified) = mySubEntitlement else { return } //give me the the RenewalInfo details of this verified response: let myRenewalnfo = verified.subscription.renewalInfo //print out the willAutoRenew details: print("Will this auto-renew?: \(myRenewalInfo.willAutoRenew)") In reality, it is really not this simple, but there is no reason that it should not be. Hoping for better in upcoming releases.  Also, why is it so difficult to get access to the JSON data packed into the product and transaction data? I can see it all there when I print it out to the output, but I can't for instance say something like: print(product.payload.thisField) I look forward to adding so many of the new iOS15 features to my app to assist my customers in managing their purchases, but implementation is just much more complicated than it really needs to be. I hope in the coming betas and final release, some of this will be made much more straight forward.
Asked Last updated
.
Post marked as solved
303 Views

for await result in Transaction.listener

Beta 2 seems to have removed Transaction.listener, it seems that it may have been replaced with Transaction.updates, but not sure because it does not seem to grab incoming transactions like the listener did.. Is there a special way to implement the updates method as opposed to the Transaction.listener method?
Asked Last updated
.
Post marked as solved
398 Views

fruta app full sample code

During a few sessions, the Fruta app is an example. is there a download for the full sample project? Ie after the edits in the project Made in for example the meet store kit 2, or write a DSL in swift session? in that way, I can “follow along(ie look along)” the project without having to have multiple Fruta project. Thanks!
Asked
by BSMandel.
Last updated
.