IAP-yhteistestauksessa ennen julkaisua törmäsimme tyypilliseen mutta erittäin hämmentävään ongelmaan:

  • Käyttäjä painaa ”Jatka ostoa”
  • Painikkeen tila muuttuu ”Käsitellään”
  • Muutaman sekunnin kuluttua tila palaa ”Jatka ostoa”
  • App Storen ostovahvistusikkunaa ei tule näkyviin

Tämä artikkeli käy läpi koko selvityspolun, tärkeimmät lokit, juurisyyn sekä käytännössä toimineen korjausstrategian.

1. Oire

Tuotelista latautui ostosivulla normaalisti, mutta oston painamisen jälkeen:

  • purchase() kutsuttiin
  • Järjestelmän vahvistusikkunaa ei näkynyt
  • Käyttöliittymä palasi lopulta ostamattomaan tilaan

Ensi katsomalta vaikutti siltä, ettei ostoprosessi käynnistynyt lainkaan. Lokit näyttivät kuitenkin muuta.

2. Ensimmäinen tarkistus: kulkeeko ostoketju oikeasti läpi?

Lisäsimme lokituksen kaikkiin ostopolun kriittisiin pisteisiin:

  • Painikeklikkauksen parametrit
  • purchaseSelectedProduct()-sisääntulo ja guard-haarat
  • Ennen/jälkeen product.purchase()
  • Transaction.updates
  • refreshEntitlements()
  • Transaction.currentEntitlements
  • Transaction.latest(for:)

Tulos:

  • purchase() palautti .success
  • Transaktio verifioitui onnistuneesti, ja transaction.finish() myös suoritettiin
  • entitledIDs jäi silti tyhjäksi

Tämä kertoo, että ongelma ei ollut ”osto ei käynnistynyt”, vaan ”osto ei päivittynyt käyttöoikeuksiksi”.

3. Syvemmän analyysin ratkaisevat havainnot

Kun lisäsimme tarkempaa lokitusta, esiin nousi useita ratkaisevia signaaleja:

  1. Transaction.currentEntitlements-iteraatioiden määrä oli 0
  2. Transaction.latest(for: monthly) sisälsi kelvollisen merkinnän, mutta se oli vanhentunut
  3. Vielä tärkeämpää: ennen ostoa Transaction.unfinished sisälsi vanhoja transaktioita
  4. Nykyinen purchase() palautti transaktion vanhasta ketjusta, ei uudesta interaktiivisesta ostosta

Tämä selittää, miksi vahvistusikkunaa ei näkynyt:

  • StoreKit palautti ensin käsiteltävän vanhan transaktion (unfinished / historiallinen ketju)
  • Koodi viimeisteli sen finish()-kutsulla
  • Pyyntö näytti ”onnistuneelta”, mutta ei kulkenut uuden ostovahvistuspolun kautta

4. Juurisyyn yhteenveto

Kyse ei ollut yhdestä viasta, vaan kahden tekijän yhdistelmästä:

  • Keskeneräiset transaktiot (unfinished transaction) häiritsivät ostovirtaa
  • Sandbox-tilausketju oli vanhentuneessa tilassa (vaikka latest transaction löytyisi, se ei välttämättä ole aktiivinen entitlement)

Lopputulos: ei ponnahdusikkunaa, näennäinen onnistuminen, käyttöoikeudet tyhjät.

5. Korjausstrategia (toimivaksi todettu)

5.1 Siivoa unfinished-transaktiot aktiivisesti ennen ostoa

Sovelluksen käynnistyksessä ja ennen ostoa käy läpi Transaction.unfinished ja aja finish() kaikille verifioiduille transaktioille, jotta vanhat transaktiot eivät kaappaa uutta ostovirtaa.

5.2 Säilytä ja vahvista diagnostiikkalokit (suositus ennen julkaisua)

Nämä lokit kannattaa pitää päällä julkaisuun asti:

  • unfinished transaction -tilannekuva
  • subscription status (state, renewal info)
  • currentEntitlements-iteraatioiden määrä
  • latest(for:)-transaktion ja vanhenemisen tiedot

5.3 Kaksikanavainen entitlement-tarkistus (valinnainen kovennus)

Jos currentEntitlements on joissain ympäristöissä hetkellisesti tyhjä, käytä latest(for:)-tietoa fallbackina (ja varmista silti aktiivisuus).

6. IAP-tarkistuslista ennen julkaisua

  1. Tarkista ennen jokaista testiä Sandbox-tilin tilaustila (onko vanhentunut vai ei)
  2. Lokita kriittisillä poluilla unfinished-määrä
  3. Testaa kolme skenaariota: ”ensimmäinen osto / uusinta vanhenemisen jälkeen / ostojen palautus”
  4. Varmista, että currentEntitlements ja latest ovat keskenään linjassa
  5. Jos ”ei vahvistusikkunaa” ilmenee, tarkista ensin osuiko suoritus vanhaan transaktiopolkuun

7. Yhteenveto

”Osta”-painallus ilman ponnahdusikkunaa ei tarkoita automaattisesti UI- tai ajoitusongelmaa.
StoreKit 2:ssa transaktioiden tila, unfinished-jono ja tilauksen elinkaari vaikuttavat suoraan lopputulokseen.

Kun lisäät nämä lokit ja siivousvaiheet ennen julkaisua, vähennät merkittävästi IAP-ongelmia, joita on vaikea toistaa tuotannossa.