<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Storekit · Loosely Decoupled</title>
    <link>https://looselydecoupled.com/topics/storekit/</link>
    <description>Answers to PowerShell, Windows Server and OpenSSH questions the docs skip, with the reasoning behind them. Answer first, discussion after.</description>
    <language>en-us</language>
    
    <copyright>© 2026 Emmanuel Tsouris</copyright>
    <lastBuildDate>Sun, 25 Oct 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://looselydecoupled.com/topics/storekit/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>StoreKit Purchase Verifies but currentEntitlements Is Empty in Xcode Testing</title>
      <link>https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/</link>
      <guid isPermaLink="true">https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/</guid>
      <pubDate>Sun, 25 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Emmanuel Tsouris</dc:creator>
      <category>Xcode</category>
      <category>Storekit</category>
      
      <description>StoreKit itself returned nothing, not the verification code, and deleting and reinstalling the app cleared what a new .storekit file didn&#39;t.</description>
      <content:encoded><![CDATA[<div class="answer" data-answer="true">
  <p class="answer__label" data-pagefind-ignore>TL;DR</p>
  <p>A StoreKit test purchase on a physical device can succeed and verify while <code>Transaction.currentEntitlements</code> comes back empty right after. First find out whether StoreKit returned nothing or your code dropped the result: count every result before you verify it. If the count is zero, delete the app from the device, reinstall it from Xcode and buy again. In the one case tested here, on one iPhone, that cleared it.</p>
<div class="code-block" data-copyable data-copy-label="Swift">
  <div class="code-block__head"><span class="code-block__lang">Swift</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="k">for</span> <span class="n">await</span> <span class="n">result</span> <span class="k">in</span> <span class="n">Transaction</span><span class="p">.</span><span class="n">currentEntitlements</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">entitlementCount</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">guard</span> <span class="kd">let</span> <span class="nv">transaction</span> <span class="p">=</span> <span class="k">try</span><span class="p">?</span> <span class="n">checkVerified</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c1">// The app&#39;s log line also prints the Pro state after this count.</span>
</span></span><span class="line"><span class="cl"><span class="bp">print</span><span class="p">(</span><span class="s">&#34;StoreManager: Entitlements refreshed - </span><span class="si">\(</span><span class="n">entitlementCount</span><span class="si">)</span><span class="s"> from StoreKit&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
  </div>
</div><div class="tested" id="tested">
  <p class="label label--seam">Tested with</p><dl class="tested__rows">
    <div class="tested__row"><dt>iOS</dt> <dd>27.0.1, iPhone</dd></div>
    <div class="tested__row"><dt>Xcode</dt> <dd>27.1 (27A9275)</dd></div>
    <div class="tested__row"><dt>Framework</dt> <dd>StoreKit 2, local .storekit configuration</dd></div>
    <div class="tested__row"><dt>Date</dt> <dd>October 2026</dd></div>
  </dl>
</div>

</div>

<p>I hit this while preparing a release of a small iPhone app I&rsquo;m building, which sells one non-consumable Pro upgrade. The purchase sheet said &ldquo;You&rsquo;re all set&rdquo;, and the app stayed locked. This page covers how I separated StoreKit from my own code, what turned out not to matter, and the reinstall that fixed it.</p>
<h2 id="purchase-succeeds-but-the-upgrade-stays-locked">Purchase succeeds, but the upgrade stays locked<a class="heading-anchor" href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#purchase-succeeds-but-the-upgrade-stays-locked"
     aria-label="Link to section: Purchase succeeds, but the upgrade stays locked" data-pagefind-ignore></a>
</h2>
<p>On a physical iPhone launched from Xcode with a StoreKit configuration file, the symptoms, in the order they appear:</p>
<ol>
<li>The purchase sheet said &ldquo;You&rsquo;re all set&rdquo;, and the app logged a verified, unrevoked transaction.</li>
<li>The entitlement refresh straight after it found nothing.</li>
<li>Xcode&rsquo;s transaction manager, under <strong>Debug</strong> &gt; <strong>StoreKit</strong> &gt; <strong>Manage Transactions</strong>, listed the purchase.</li>
<li>Restore Purchases reported no previous purchases.</li>
<li>Buying again failed with <code>ASDServerErrorDomain 2010</code>.</li>
</ol>
<p>The two log lines from steps 1 and 2:</p>
<div class="code-block code-block--wrap" data-copyable data-copy-label="Text">
  <div class="code-block__head"><span class="code-block__lang">Text</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">StoreManager: Purchase verified - &lt;product-id&gt;, revoked: false
</span></span><span class="line"><span class="cl">StoreManager: Entitlements refreshed - 0 from StoreKit, Pro: false</span></span></code></pre></div></div>
  </div>
</div><p>I don&rsquo;t know what <code>ASDServerErrorDomain 2010</code> means. I couldn&rsquo;t find it in Apple&rsquo;s documentation, so treat it as a symptom here, not a diagnosis.</p>
<h2 id="count-entitlements-before-you-verify-them">Count entitlements before you verify them<a class="heading-anchor" href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#count-entitlements-before-you-verify-them"
     aria-label="Link to section: Count entitlements before you verify them" data-pagefind-ignore></a>
</h2>
<p>An empty entitlement list can come from two places: StoreKit emitted nothing, or your code discarded what it emitted. A loop that only counts the transactions it keeps can&rsquo;t tell those apart. A failed verification, a revocation check or a product ID filter all look the same as an empty sequence.</p>
<p>Count each result as it arrives, before any check, as in the code in the answer. In that code, <code>checkVerified</code> is the app&rsquo;s own verification helper, not a StoreKit API. If that count is zero, the sequence itself was empty and your filtering isn&rsquo;t the cause. Mine was zero.</p>
<p>The product type matters too, because <code>currentEntitlements</code> doesn&rsquo;t include every product type. Apple&rsquo;s <a href="https://developer.apple.com/documentation/storekit/transaction/currententitlements" rel="external noopener" target="_blank"><code>currentEntitlements</code> reference<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#i-external-link"/></svg><span class="visually-hidden"> (opens in a new tab)</span></a> says it emits &ldquo;a transaction for each non-consumable Apple In-App Purchase&rdquo; and leaves out consumables and refunded or revoked products. Check the product&rsquo;s type in the <code>.storekit</code> file. Mine was <code>NonConsumable</code>, so it should have appeared.</p>
<h2 id="placeholder-team-id-wasnt-the-cause">Placeholder team ID wasn&rsquo;t the cause<a class="heading-anchor" href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#placeholder-team-id-wasnt-the-cause"
     aria-label="Link to section: Placeholder team ID wasn’t the cause" data-pagefind-ignore></a>
</h2>
<p>The <code>.storekit</code> file was an older one, written by hand. Its <code>_developerTeamID</code> was the placeholder <code>YOUR_TEAM_ID</code>, and its product and app IDs didn&rsquo;t match App Store Connect. <strong>Sync with App Store Connect</strong> failed with &ldquo;Could not sign in&rdquo;, even though Xcode was signed in to an Admin account.</p>
<p>That looked like the cause, and fixing it was still worth doing. Apple&rsquo;s <a href="https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode" rel="external noopener" target="_blank">StoreKit testing setup guide<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#i-external-link"/></svg><span class="visually-hidden"> (opens in a new tab)</span></a> has you pick the team and app when you create a synced configuration file. To sync a converted file again, it says to create a new synced file. I replaced the hand-written file with a new synced one. It wasn&rsquo;t enough on its own. A purchase made after the swap still left the entitlement count at zero.</p>
<h2 id="entitlement-count-by-environment">Entitlement count by environment<a class="heading-anchor" href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#entitlement-count-by-environment"
     aria-label="Link to section: Entitlement count by environment" data-pagefind-ignore></a>
</h2>
<p>Each run used the same build of the app:</p>
<div class="table-wrap" data-scrollable data-label="Table: Environment">
  <table>
    <thead>
      <tr><th scope="col">Environment</th><th scope="col">Entitlements after purchase</th></tr>
    </thead>
    <tbody>
      <tr><td>Simulator, iOS 26.5, synced <code>.storekit</code> file</td><td>1</td></tr>
      <tr><td>iPhone, iOS 27.0.1, synced <code>.storekit</code> file, same install as before the swap</td><td>0</td></tr>
      <tr><td>iPhone, iOS 27.0.1, App Store sandbox, scheme with no StoreKit configuration, fresh install</td><td>1</td></tr>
      <tr><td>iPhone, iOS 27.0.1, synced <code>.storekit</code> file, after deleting and reinstalling the app</td><td>1</td></tr>
    </tbody>
  </table>
</div>
<p>The Simulator result alone didn&rsquo;t separate the device from the OS version, because the Simulator ran an older iOS. The sandbox row answers a narrower question. On iOS 27, against the App Store sandbox, a fresh install logged <code>Entitlements refreshed - 1 from StoreKit, Pro: true</code>. Because that install was fresh too, it doesn&rsquo;t show whether the sandbox would have returned the entitlement on the install that returned zero. It shows only that a sandbox purchase unlocks on a clean install, in one run.</p>
<h2 id="delete-the-app-and-test-again">Delete the app and test again<a class="heading-anchor" href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#delete-the-app-and-test-again"
     aria-label="Link to section: Delete the app and test again" data-pagefind-ignore></a>
</h2>
<p>The last row of the table is the fix. On the one iPhone and iOS build tested, the empty result behaved like test state stored on the device: it survived replacing the configuration file, and deleting the app cleared it. I captured only the before and after, so what that state was, what put it there, and why replacing the file didn&rsquo;t reset it are unknown.</p>
<p>To clear it:</p>
<ol>
<li>On the iPhone, delete the app.</li>
<li>In Xcode, run the scheme that has the StoreKit configuration selected.</li>
<li>In the app, tap <strong>Restore Purchases</strong> before buying anything. With nothing bought, it reports no previous purchases.</li>
<li>Buy the product again.</li>
</ol>
<p>Check the console. A purchase that unlocks logs a verified transaction followed by a count of one:</p>
<div class="code-block code-block--wrap" data-copyable data-copy-label="Text">
  <div class="code-block__head"><span class="code-block__lang">Text</span>
  </div>
  <div class="code-block__scroll">
    <div data-copy-source><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">StoreManager: Purchase verified - &lt;product-id&gt;, revoked: false
</span></span><span class="line"><span class="cl">StoreManager: Entitlements refreshed - 1 from StoreKit, Pro: true</span></span></code></pre></div></div>
  </div>
</div><h2 id="resets-that-dont-reach-the-device">Resets that don&rsquo;t reach the device<a class="heading-anchor" href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#resets-that-dont-reach-the-device"
     aria-label="Link to section: Resets that don’t reach the device" data-pagefind-ignore></a>
</h2>
<p><code>xcrun simctl erase all</code> erases every simulator. It doesn&rsquo;t touch a physical device. For a device, Apple&rsquo;s <a href="https://developer.apple.com/documentation/storekit/testing-in-app-purchases-in-xcode" rel="external noopener" target="_blank">guide to testing in-app purchases in Xcode<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="https://looselydecoupled.com/storekit-purchase-verified-current-entitlements-empty/#i-external-link"/></svg><span class="visually-hidden"> (opens in a new tab)</span></a> resets a test by deleting the transaction under <strong>Debug</strong> &gt; <strong>StoreKit</strong> &gt; <strong>Manage Transactions</strong>. Whether that reset clears the same state isn&rsquo;t recorded here; deleting the app is what cleared it.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
