React-native-iap: IOS-Überprüfung, ob das Abonnement mit automatischer Verlängerung noch aktiv ist

Erstellt am 27. Sept. 2018  ·  61Kommentare  ·  Quelle: dooboolab/react-native-iap

Version von reaktiv-native-iap

2.2.2

Plattformen, bei denen der Fehler aufgetreten ist (IOS oder Android oder beides?)

IOS

Erwartetes Verhalten

Mit der RNIAP-API könnten wir überprüfen, ob noch ein Auto-Renewal-Abonnement aktiv ist

Tatsächliches Verhalten

Auf Android mache ich dies, um das zu überprüfen:

export const isUserSubscriptionActive = async (subscriptionId) =>{
    // Get all the items that the user has
    const availablePurchases = await getAvailablePurchases();
    if(availablePurchases !== null && availablePurchases.length > 0){
        const subscription = availablePurchases.find((element)=>{
            return subscriptionId === element.productId;
        });
        if(subscription){
             // check for the autoRenewingAndroid flag. If it is false the sub period is over
              return subscription["autoRenewingAndroid"] == true;
            }
        }else{
            return false;
        }
    }
}

Unter iOS gibt es kein Flag, um dies zu überprüfen, und die Methode getAvailablePurchases gibt alle getätigten Käufe zurück, auch die Abonnements, die derzeit nicht aktiv sind.

Gibt es eine Möglichkeit dies zu überprüfen?

Grüße,
Marcos

📱 iOS 🚶🏻 stale

Hilfreichster Kommentar

Hier ist die Funktion, die ich verwende, die sowohl auf Android als auch auf iOS funktioniert und auf iOS richtig sortiert, um sicherzustellen, dass wir die neuesten Belegdaten erhalten:

import * as RNIap from 'react-native-iap';
import {ITUNES_CONNECT_SHARED_SECRET} from 'react-native-dotenv';

const SUBSCRIPTIONS = {
  // This is an example, we actually have this forked by iOS / Android environments
  ALL: ['monthlySubscriptionId', 'yearlySubscriptionId'],
}

async function isSubscriptionActive() {
  if (Platform.OS === 'ios') {
    const availablePurchases = await RNIap.getAvailablePurchases();
    const sortedAvailablePurchases = availablePurchases.sort(
      (a, b) => b.transactionDate - a.transactionDate
    );
    const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;

    const isTestEnvironment = __DEV__;
    const decodedReceipt = await RNIap.validateReceiptIos(
      {
        'receipt-data': latestAvailableReceipt,
        password: ITUNES_CONNECT_SHARED_SECRET,
      },
      isTestEnvironment
    );
    const {latest_receipt_info: latestReceiptInfo} = decodedReceipt;
    const isSubValid = !!latestReceiptInfo.find(receipt => {
      const expirationInMilliseconds = Number(receipt.expires_date_ms);
      const nowInMilliseconds = Date.now();
      return expirationInMilliseconds > nowInMilliseconds;
    });
    return isSubValid;
  }

  if (Platform.OS === 'android') {
    // When an active subscription expires, it does not show up in
    // available purchases anymore, therefore we can use the length
    // of the availablePurchases array to determine whether or not
    // they have an active subscription.
    const availablePurchases = await RNIap.getAvailablePurchases();

    for (let i = 0; i < availablePurchases.length; i++) {
      if (SUBSCRIPTIONS.ALL.includes(availablePurchases[i].productId)) {
        return true;
      }
    }
    return false;
  }
} 

Alle 61 Kommentare

Ich arbeite auch daran, das herauszufinden. Ich habe es noch nicht getestet, aber aus dem, was ich lese, gehe ich davon aus, dass dies möglich ist, indem Sie in iTunes Connect ein "gemeinsames Geheimnis" erstellen und dieses mit dem Schlüssel "Passwort" an valideReceiptIos übergeben. Ich glaube, dies wird dann ein JSON-Objekt zurückgeben, das einen Code enthält, der den Validierungsstatus des Abonnements angibt, sowie die Schlüssel Latest_receipt, Latest_Receipt_info und Latest_expired_receipt info, unter anderem, mit denen Sie den Abonnementstatus ermitteln können. Ich bin buchstäblich gerade dabei, das herauszufinden, also muss ich es noch testen. Es ist das, was ich aus den Ausgaben und den Dokumenten von Apple zusammengestellt habe. Wenn das funktioniert, sollte es wirklich in der Dokumentation sehr deutlich gemacht werden, anstatt in den Problemen begraben zu werden.
Ich halte die folgenden Links für relevant:
https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html

203 #237

EDIT : Ich kann bestätigen, dass der oben erwähnte Prozess funktioniert. Ich denke, wir sollten daran arbeiten, eine vollständige Erklärung in den Dokumenten zu erhalten. Ich würde beim App-Start so etwas implementieren, um festzustellen, ob ein Abonnement abgelaufen ist:

RNIap.getPurchaseHistory()
        .then(purchases => {
                RNIap.validateReceiptIos({
                   //Get receipt for the latest purchase
                    'receipt-data': purchases[purchases.length - 1].transactionReceipt,
                    'password': 'whateveryourpasswordis'
                }, __DEV__)
                    .then(receipt => {
                       //latest_receipt_info returns an array of objects each representing a renewal of the most 
                       //recently purchased item. Kinda confusing terminology
                        const renewalHistory = receipt.latest_receipt_info
                       //This returns the expiration date of the latest renewal of the latest purchase
                        const expiration = renewalHistory[renewalHistory.length - 1].expires_date_ms
                       //Boolean for whether it has expired. Can use in your app to enable/disable subscription
                        console.log(expiration > Date.now())
                    })
                    .catch(error => console.log(`Error`))
        })

@kevinEsherick

Brillant!

Nur einige Dinge, die an Ihrem Code nicht so trivial sind;

  1. Es sollte sein

const Expiration = RenewalHistory [renewalHistory.length - 1].expires_date_ms

statt (wahrscheinlich nur ein Tippfehler)

const Expiration = LatestRenewalReceipt [latestRenewal.length - 1].expires_date_ms

2) Für diejenigen, die nicht wissen, was 'Passwort' ist: 'whateveryourpasswordis':

Sie können Ihren gemeinsamen geheimen Schlüssel für Ihre In-App-Zahlungen erstellen und in Ihrer App verwenden

Schritte sind hier: https://www.appypie.com/faqs/how-can-i-get-shared-secret-key-for-in-app-purchase

+1 um die Dokumente zu aktualisieren, vielleicht können Sie

Grüße

@kevinEsherick

Was passiert mit Abonnements mit automatischer Verlängerung?

Scheint, dass das Expires_date_ms das erste Ablaufdatum ist, es wird nicht aktualisiert

Ich habe das getestet:

  1. Abo-Artikel abonnieren
  2. Überprüfen Sie das Ablaufdatum (z. B. 5 Minuten später)
  3. Warte 10 Minuten
  4. Das Abonnement, das noch aktiv ist, scheint ein Abonnement mit automatischer Verlängerung zu sein
  5. Wenn ich überprüfe, dass der Ablauf des letzten Verlängerungsbelegs immer noch derselbe ist wie bei Punkt 2

Irgendwelche Ideen?

Mein Fehler, das Ablaufdatum scheint aktualisiert zu sein, es dauert nur eine Weile.

Ich werde diesen Kommentar hier hinterlassen, falls jemand in der gleichen Situation ist.

Grüße

  1. Es sollte sein

const Expiration = RenewalHistory [renewalHistory.length - 1].expires_date_ms

statt (wahrscheinlich nur ein Tippfehler)

const Expiration = LatestRenewalReceipt [latestRenewal.length - 1].expires_date_ms

Ups ja du hast recht, das war ein Tippfehler. Ich hatte den Namen geändert, um seine Bedeutung deutlicher zu machen, aber vergessen, alle Vorkommen zu ändern. Ich habe es bearbeitet, danke.

Und ich hatte nicht genau dieses Szenario mit Ablaufdatum, aber es gab einige Dinge, bei denen es überhaupt nicht automatisch verlängert wurde, obwohl es sich irgendwie von selbst gelöst hat (was mich beunruhigt, aber ich kann es nicht reproduzieren, also weiß ich, was sonst noch zu tun ist). Das Problem, das Sie hatten, ist ziemlich häufig. Es ist das erwartete Verhalten von Apple, daher sollten Sie eine Pufferfrist für die Verlängerung des Abonnements einplanen, bevor Sie den Benutzer abmelden. Dieses Problem auf SO wird genauer erklärt: https://stackoverflow.com/questions/42158460/autorenewable-subscription-iap-renewing-after-expiry-date-in-sandbox

Danke! Super Info, könnte der Grund sein

Zu diesem Problem denke ich, dass der @kevinEsherick- Code in den Dokumenten enthalten sein sollte 👍 Es ist eine großartige Möglichkeit, Abonnenten zu überprüfen!

Ja. Ich würde mich über ein ordentliches Dokument in readme freuen, wenn jemand von Ihnen ein PR anfordert. Danke.

Hallo Leute. Ich habe der Readme-Datei einen Abschnitt mit PR für dieses Problem geben.

Ich werde in den nächsten Tagen mal eine PR machen :)

Ich möchte diese Bibliothek auch speziell für Abonnements verwenden. Sowohl iOS als auch Android. Es wäre sehr wünschenswert, den richtigen Weg zu dokumentieren, um festzustellen, ob ein Benutzer ein gültiges Abonnement hat. 😄

@curiousdustin Apples Implementierung dazu ist wirklich schrecklich, wenn Sie sich auf medium beziehen. Da Sie so etwas in Ihrem Backend abwickeln müssen, konnten wir dies in unserem Modul nicht vollständig unterstützen. Sie können jedoch availablePurchases in Android mit unserer Methode getAvailablePurchases die mit einem iOS-Abonnementprodukt nicht funktioniert.

Wollen Sie damit sagen, dass die Lösung, an der @kevinEsherick in diesem Thread arbeiten, KEINE Lösung ist und dass ein anderer Backend-Server als der von Apple erforderlich ist, um den Status eines iOS-Abonnements zu bestätigen?

Wenn Sie den Medium-Artikel lesen, den Sie zusammen mit den Apple-Dokumenten gepostet haben, scheint es, dass die Verwendung eines Servers bevorzugt wird, aber es ist nicht unmöglich, den Abonnementstatus nur mit dem Gerät zu bestimmen.

Es tut mir leid, dass ich in meinem Schreiben einige Informationen übersehen habe. Ich werde das jetzt schaffen.
Apple schlägt vor, die receipt auf Ihrem eigenen Server zu speichern, um einen mittleren Angriff zu verhindern. Daher können Sie diese Quittung später überprüfen, um herauszufinden, ob die Quittung gültig ist oder das Abonnement noch verfügbar ist.

Es ist jedoch nicht unmöglich, es mit react-native-iap noch auszuprobieren, da wir den direkten Abruf der Bestätigungsquittung für den eigenen Apple-Server (Sandbox & Produktion) anbieten, was @kevinEsherick glücklicherweise geklappt haben.

Ich denke, dies ist derzeit die Lösung, um das ios Abonnement zu überprüfen.

Hey Leute, es tut mir leid, dass es mehr als ein paar Tage her ist, ich war mit der Arbeit beschäftigt, aber ich werde diese PR trotzdem einreichen, um die README-Datei zu aktualisieren. @curiousdustin Die Methode, über die ich oben geschrieben habe, funktioniert definitiv und ich werde sie den Dokumenten hinzufügen. Ich schicke die PR heute Abend oder morgen :)

Danke für das Update.

An deinem Beispiel ist mir noch etwas aufgefallen.

'Quittungsdaten': Einkäufe[Einkäufe.Länge - 1].TransactionReceipt,
...
const Expiration = RenewalHistory[renewalHistory.length - 1].expires_date_ms

In meinen Tests sind Käufe und Erneuerungsverlauf nicht unbedingt in einer bestimmten Reihenfolge. Müssen wir sie nicht sortieren oder etwas, um zu überprüfen, ob wir das verwenden, was wir beabsichtigen?

Hier gilt das gleiche. Käufe und ErneuerungHistorie sind definitiv nicht geordnet, also müssen wir zuerst sortieren. In der Entwicklung sind das viele Iterationen.

Hier ist die Funktion, die ich verwende, die sowohl auf Android als auch auf iOS funktioniert und auf iOS richtig sortiert, um sicherzustellen, dass wir die neuesten Belegdaten erhalten:

import * as RNIap from 'react-native-iap';
import {ITUNES_CONNECT_SHARED_SECRET} from 'react-native-dotenv';

const SUBSCRIPTIONS = {
  // This is an example, we actually have this forked by iOS / Android environments
  ALL: ['monthlySubscriptionId', 'yearlySubscriptionId'],
}

async function isSubscriptionActive() {
  if (Platform.OS === 'ios') {
    const availablePurchases = await RNIap.getAvailablePurchases();
    const sortedAvailablePurchases = availablePurchases.sort(
      (a, b) => b.transactionDate - a.transactionDate
    );
    const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;

    const isTestEnvironment = __DEV__;
    const decodedReceipt = await RNIap.validateReceiptIos(
      {
        'receipt-data': latestAvailableReceipt,
        password: ITUNES_CONNECT_SHARED_SECRET,
      },
      isTestEnvironment
    );
    const {latest_receipt_info: latestReceiptInfo} = decodedReceipt;
    const isSubValid = !!latestReceiptInfo.find(receipt => {
      const expirationInMilliseconds = Number(receipt.expires_date_ms);
      const nowInMilliseconds = Date.now();
      return expirationInMilliseconds > nowInMilliseconds;
    });
    return isSubValid;
  }

  if (Platform.OS === 'android') {
    // When an active subscription expires, it does not show up in
    // available purchases anymore, therefore we can use the length
    // of the availablePurchases array to determine whether or not
    // they have an active subscription.
    const availablePurchases = await RNIap.getAvailablePurchases();

    for (let i = 0; i < availablePurchases.length; i++) {
      if (SUBSCRIPTIONS.ALL.includes(availablePurchases[i].productId)) {
        return true;
      }
    }
    return false;
  }
} 

@andrewze

Wenn das Abonnement im Android-Szenario automatisch erneuerbar ist, wird es bei verfügbaren Käufen zurückgegeben

@curiousdustin @andrewzey Du hast recht, Einkäufe werden nicht bestellt. ErneuerungHistory/latest_receipt_info wird jedoch immer für mich bestellt. Ich erinnere mich, dass ich bemerkte, dass Einkäufe nicht bestellt wurden, dachte aber, ich hätte einen Grund gefunden, warum dies nicht wirklich von Bedeutung war. Ich werde mir das in ein paar Stunden anschauen, wenn ich zu Hause bin. Ich möchte keine PR einreichen, bis wir das herausgefunden haben.
BEARBEITEN: Der Grund, warum Sie die Einkäufe meiner Meinung nach nicht sortieren müssen, ist, dass Expires_date_ms dasselbe zurückgibt, unabhängig davon, welchen Kauf Sie abgefragt haben. Ist das bei euch nicht dasselbe? Oder benötigen Sie einige Informationen, die sortiert werden müssen? Lass mich wissen was du denkst. Ich stimme zu, dass die Dokumente immer noch klarstellen sollten, dass Käufe nicht chronologisch sortiert sind, aber soweit ich das beurteilen kann, ist keine Sortierung erforderlich, um das Ablaufdatum zu erhalten.

@kevinEsherick Danke! In meinem Fall wurden weder die Käufe noch die RenewalHistory bestellt.

@kevinEsherick @andrewzey Können Sie PR für diese Lösung geben? Wir möchten mit anderen, die leiden, teilen.

Natürlich werde ich es vorbereiten, sobald wir mit unserem öffentlichen App-Start fertig sind =).

Ich habe auf weitere Gespräche zu meinem letzten Kommentar gewartet. Ich habe einige Probleme angesprochen, die ungelöst bleiben. Siehe oben als Referenz. Sobald wir das geklärt haben, kann einer von uns eine PR machen.

@kevinEsherick Ah Entschuldigung, das habe ich übersehen.

expires_date_ms ist für mich definitiv auf jedem Verlängerungsbeleg einzigartig. Vielleicht habe ich das falsch verstanden? Mir ist aufgefallen, dass das jedem dekodierten Beleg beigefügte Array für Verlängerungsbelege gleich ist, unabhängig davon, welcher Beleg ausgewählt ist.

const sortedAvailablePurchases = availablePurchases.sort(
      (a, b) => b.transactionDate - a.transactionDate
    );

Aber ich habe es trotzdem getan, um zu versuchen, genau zu sein und mögliche Diskrepanzen in der tatsächlichen API-Antwort von den Dokumenten von Apple auszugleichen. Ich glaube nicht, dass eine Sortieroperation dort sehr teuer sein wird, angesichts der Gesamtzahl der Belege, die Sie wahrscheinlich für automatisch erneuernde Abonnements erhalten werden.

Irgendeine ETA zum PR? Ich erwäge, von diesem Paket abzuweichen, da es diesen Fall überhaupt nicht behandelt.

@schumannd Da die PR nur dazu dient, die Dokumente zu aktualisieren, können Sie jetzt zu react-native-iap wechseln.

Ich habe bestätigt, dass mein Code-Snippet tatsächlich ohne Probleme in der Produktion mit unserer kürzlich gestarteten App funktioniert: https://itunes.apple.com/us/app/rp-diet/id1330041267?ls=1&mt=8

EDIT Ich werde definitiv die PR machen (ich habe es in meiner "Give back to OSS"-Liste in unserem Trello), aber nach dem Black-Friday-Wochenende-Wahnsinn 😄

@schumannd Ich unterstütze, was @andrewzey gesagt hat. Alles, was ein PR sagen würde, ist hier in diesem Beitrag enthalten. Ich wollte mich schon damit befassen, aber es hat eine Weile gedauert, bis wir genau herausgefunden haben, was zu tun ist, und das gemischt mit Reisen und hektischen Startstunden bedeutet, dass ich noch keine Zeit hatte, es zu veröffentlichen. Ich habe immer noch vor, es bald zu tun, aber jeder andere kann in der Zwischenzeit einsteigen, wenn er möchte. Und @andrewzey gratuliert Mann! Sieht toll aus, ich werde es herunterladen!

Ich bin nicht davon überzeugt, dass dieser Thread den korrekten Weg zur Verwaltung von automatisch erneuernden Abonnements in iOS genau dokumentiert, sondern stattdessen einen "Workaround" -Workflow für die von dieser Bibliothek bereitgestellte API beschreibt. Dies ist jedoch ausschließlich Spekulation meinerseits und ich bin mir bewusst, dass ich mich irren könnte, daher hätte ich gerne Feedback zu den Beobachtungen, die ich unten skizziert habe.

Ich habe wochenlang vergeblich nach der richtigen Methode gesucht, um sich automatisch verlängernde Abonnements in React Native zu verwalten, also wechselte ich den Gang und begann zu recherchieren, wie man sie nativ in Objective-C / Swift verwaltet. Dies gab mir einen Anhaltspunkt, um die native(n) StoreKit-API(s) und die dokumentierte Verwendung mit dem zu vergleichen, was in dieser Bibliothek implementiert wurde.

Diese Beschreibung ist die beste Erklärung, die ich bisher dazu gefunden habe, wie der Kauf, die Validierung und die Wiederherstellung von Abonnements von einer nativen iOS-App gehandhabt werden sollten: https://www.raywenderlich.com/659-in-app-purchases-auto -erneuerbare-Abonnements-Tutorial und während alle APIs in react-native-iap , implementieren sie einen anderen Workflow.

Ein wesentlicher Unterschied, den ich sehe, ist die Verwendung von appStoreReceiptUrl .

StoreKit bietet die appStoreReceiptUrl die nach meinem Verständnis der richtige Weg ist, um die aktuellen Kauf- / Quittungsinformationen für eine iOS-App zu laden. Obwohl ich sehe, dass dies im react-native-iap Code verwendet und referenziert wird, scheint es sich in einem anderen Kontext zu befinden als der im referenzierten Artikel dokumentierte Workflow.

Ich bin mir nicht sicher, ob/wie dieser Implementierungsunterschied problematisch ist, aber die react-native-iap API scheint eine unnötige Abhängigkeit von StoreKits restoreCompletedTransactions zu erzeugen, die schließlich von getPurchaseHistory aufgerufen wird.

Mir scheint, dass die react-native-iap API den folgenden Workflow für iOS unterstützen sollte:

  • Versuchen Sie, den aktuellen App-Beleg (über appStoreReceiptUrl ) beim Start der Anwendung zu laden
  • Geben Sie die Möglichkeit, den Beleg zu validieren
  • Wenn appStoreReceiptUrl null ist, initiieren Sie eine "Wiederherstellung" auf dem aktuellen Gerät, die dann den Wert von appStoreReceiptUrl und die Validierungslogik kann erneut versucht werden

Gedanken?

Auch hier könnte ich die Implementierung dieser Bibliothek oder die referenzierte native Implementierung missverstehen.

@sellmeadog

Ich denke, ein wichtiger Punkt, der auch in dem von Ihnen verlinkten Artikel erwähnt wird, ist dieser:

Hinweis: Die Projekt-App führt SelfieService aus, der Belege akzeptiert und direkt an den Belegvalidierungsdienst von Apple hochlädt. Dies ist ein "Cheat", damit sich das Tutorial auf das Einrichten der Abonnements konzentriert. In einer echten App sollten Sie dafür einen Remote-Server verwenden – nicht die App.

Es hat einige Zeit gedauert, bis ich die ganze Sache mit dem automatisch erneuerbaren Abonnement herausgefunden habe, aber soweit ich weiß, ist der beste Weg, damit umzugehen, Ihr eigenes Backend als einzige Quelle der Wahrheit zu verwenden:

  • senden Sie alle Transaktionsdaten sofort an Ihr Backend
  • Bereitstellung einer Möglichkeit zum erneuten Senden im Falle von Übertragungsproblemen (z. B. Wiederherstellen von Einkäufen)
  • Überprüfen Sie regelmäßig (zB einmal täglich) alle gespeicherten Belege in Ihrem Backend
  • app fragt das Backend nach dem Abonnementstatus ab, z. B. bei der Rückkehr aus dem Hintergrund.

Dies schien mir der einzig vernünftige Weg, mit Abonnements umzugehen. Ich habe diesen Prozess auch mit anderen Entwicklern überprüft, die seit einiger Zeit kommerziell erneuerbare Abonnements verwenden.

Meine Antwort auf die Frage von OPs wäre also: Überprüfen Sie die Abonnements regelmäßig im Backend, nicht im Frontend

@schumand

Ich bin mir nicht sicher, ob wir das gleiche Problem verstehen oder darüber sprechen.

Empfang Validierung sollte auf dem Server durchgeführt werden. Laut Apples eigener Dokumentation :

Rufen Sie den Endpunkt des App Store-Servers /verifyReceipt von Ihrer App aus auf.

Ich habe den "Cheat" im referenzierten Artikel als direkte Aufrufe der Beispiel-App an /verifyReceipt direkt aus der App interpretiert, anstatt sie an einen Back-End-Server zu delegieren.

Mir scheint, dass Apple / der App Store die einzige Quelle der Wahrheit ist und sein sollte. Apple verarbeitet letztendlich Käufe, Verlängerungen, Stornierungen usw. und erstellt entsprechende Belege und stellt diese Daten über die StoreKit APIs zur Verfügung.

react-native-iap wickelt den Kauf und die Wiederherstellung von Einkäufen ohne Probleme ab, aber ich denke immer noch, dass es eine Lücke beim Zugriff auf die App Store-Quittungsdaten gibt, die lokal unter appStoreReceiptUrl verfügbar sind, was wiederum Apples dokumentierter Weg ist, auf Quittungen zuzugreifen Daten :

Um die Quittungsdaten abzurufen, verwenden Sie die Methode appStoreReceiptURL von NSBundle , um die Quittung der App zu finden...

Der Arbeitsablauf, den ich durch das Durchlesen der Apple-Dokumentation und den Verweis auf native Object-C / Swift-Implementierungen verstehe, ist wie folgt:

  • Produkte zum Kauf laden
  • Ein Produkt kaufen (Verbrauchsmaterial, Nicht-Verbrauchsmaterial, Abonnement, egal)
  • Eine Quittung für diesen Kauf wird lokal bei appStoreReceiptUrl von StoreKit hinter den Kulissen gespeichert
  • Die App sollte bei Bedarf Empfangsdaten zur Validierung an einen Backend-Server (nicht den App Store /verifyReceipt direkt) senden
  • appStoreReceiptUrl wird von StoreKit aktualisiert, wenn Stornierungen und/oder Verlängerungen auftreten (vorausgesetzt, die richtigen StoreKit Delegierten sind korrekt registriert)
  • Wenn appStoreReceiptUrl null ist, weil der Benutzer ein neues Gerät verwendet usw., verwenden Sie den Workflow zum Wiederherstellen des Kaufs, der aktuelle Belegdaten von Apple / AppStore abruft und StoreKit diese lokal bei appStoreReceiptUrl speichert.

Auch hier übernimmt react-native-iap all dies, mit Ausnahme des Ladens von Belegdaten aus appStoreReceiptUrl . Ich denke, eine getReceipt Methode, die den lokalen Beleg abfragt, würde die Verwaltung von Abonnements und/oder anderen Einkäufen erleichtern.

Apple hat es viel schwieriger gemacht, darüber nachzudenken, als es sein sollte.

appStoreReceiptUrl wird von StoreKit aktualisiert, wenn Stornierungen und/oder Verlängerungen auftreten (vorausgesetzt, die richtigen StoreKit-Delegierten sind korrekt registriert)

Dies beschreibt das Problem, das ich mit diesem Ansatz habe: Sie werden nicht über eine Verlängerung / Kündigung / Ablauf usw. benachrichtigt, wenn der Benutzer Ihre App anschließend nicht mit einer funktionierenden Internetverbindung öffnet.

Ansonsten scheint es eine gute Alternative zu dem von mir beschriebenen Ansatz zu sein. Ich frage mich, welche am häufigsten für Produktions-Apps verwendet wird.

@sellmeadog Ich denke, das ist die gleiche Idee, die ich hier gepostet habe: #356
Die lokale Validierung von Belegen ist eine Methode, empfiehlt Apple. Das ist völlig unabhängig von jeglicher Server-/Netzwerkanfrage.

Ja. Es gibt Nachteile in Bezug auf "keine Netzwerkverbindung - keine Validierung".
Aber dafür gibt es Anwendungsfälle.
Ich möchte eine schnelle Bestätigung der Quittung bei jedem App-Start und möchte nicht selbst den Überblick über In-App-Käufe über UserDefaults oder Keychain behalten. StoreKit bietet bereits einen Speichermechanismus.

Wie sollen Sie überprüfen, ob der IOS-Benutzer sein Abonnement verlängert oder gekündigt hat, ohne ihn aufzufordern, sich über etwas wie getAvailablePurchases beim iTunes-Konto anzumelden?

Zum Beispiel:
Beim ersten Kauf eines Abonnements validieren wir den Empfang und speichern das Ablaufdatum in ihrem Firebase-Profil. Wir verwenden diesen Firebase-Knoten, um beim Start zu überprüfen, ob sie ein aktives Abonnement haben. Das Problem tritt auf, wenn dieses Ablaufdatum überschritten ist und der neue Abrechnungszeitraum beginnt. Wie können wir überprüfen, ob sie automatisch verlängert oder storniert wurden? Wie bereits erwähnt, haben wir getAvailablePurchases verwendet, aber das fordert die iTunes-Anmeldung auf, wie es eine Wiederherstellungsschaltfläche tun würde - was eine schreckliche Benutzeroberfläche ist

Das gleiche frage ich mich auch.

In unserer App brauchen wir dies nicht, da wir eine serverseitige API haben, die direkt mit Apple kommuniziert, um den Status zu überprüfen. Nach dem ursprünglichen Kauf werden die Belegdaten auf unseren Servern gespeichert und später verwendet, um den aktualisierten Status abzufragen.

Allerdings denke ich, dass dies geräteseitig noch möglich sein sollte. Ich bin kein Experte dafür, aber so wie ich es verstehe, erhält die App bei jeder Verlängerung aktualisierte Transaktionen/Quittungen vom Betriebssystem. Ich vermute, dass dieses Plugin diese Quittungen beim Start der App verarbeitet. (Ich sehe eine Reihe von Protokollen, wenn ich von Xcode aus ausgeführt werde, die anscheinend mehrere Transaktionen verarbeiten.) ABER, soweit ich das beurteilen kann, gibt es keine Möglichkeit, diese Ereignisse im JS-Code abzugreifen.

Könnten wir dazu noch ein paar Infos bekommen?

@csumrell @curiousdustin Haben Sie dieses Verhalten in einer Produktionsumgebung bestätigt? Ich frage mich, ob es nur an Sandboxing liegen könnte, bei dem sich das Sandbox-Konto anmelden muss, da sich vermutlich ein anderes Nicht-Sandbox-Konto auf Ihrem Gerät befindet, während der Benutzer in der Produktion vermutlich bei seinem normalen Konto angemeldet ist und dies Modul kann möglicherweise getPurchases ohne Aufforderung zur Anmeldung abrufen.

Entschuldigung, ich habe NICHT bestätigt, dass getAvailablePurchases() Passwortabfragen in der Produktion verursacht.

Ich habe es nur angenommen, weil die Dokumentation darüber spricht, dass diese Methode für die Funktion verwendet wird, die allgemein als Wiederherstellen von Käufen bezeichnet wird und meiner Erfahrung nach zumindest unter iOS eine Authentifizierung beinhaltet.

@hyochan @JJMoon Könnten wir auch mehr Informationen dazu bekommen? Gibt es insbesondere eine Möglichkeit, zuverlässig verfügbare oder historische Käufe abzurufen, ohne dass das Betriebssystem einen Authentifizierungsversuch auslöst? Bitte beachten Sie auch meine obigen Kommentare zu Frage 2 bezüglich der Möglichkeit, auf die Käufe zu reagieren, die beim Start der App erkannt und verarbeitet werden.

@curiousdustin ja, wenn ich mich von meinem normalen iTunes-Konto abmelde , werde ich nicht mehr aufgefordert, mich anzumelden, wenn diese Methode aufgerufen wird. Meine Vermutung/Hoffnung ist also, dass das Problem in der Produktion, in der die Benutzer kein Sandbox-Konto verwenden, nicht auftauchen sollte. Betatests sollten genauso funktionieren wie die Produktion, da die Benutzer keine Sandbox-Konten auf dem Gerät eingerichtet haben, es ist nur ein Problem der Entwicklungsgeräte. Also werde ich das morgen mit Beta-Benutzern testen und euch wissen lassen, was ich finde.
EDIT: Ich kann bestätigen, dass dies auf Geräten ohne Sandbox passiert. Dies ist eine schreckliche UX und ziemlich problematisch. Jemand, irgendwelche Ideen?

Wenn Sie also auf Ihrem eigenen Server validieren, indem Sie den App Store-Server /verifyReceipt-Endpunkt von Ihrer App aus aufrufen, werden immer die neuesten Aktualisierungsinformationen für diesen Benutzer mit nur dem ursprünglichen ersten Kaufbeleg zurückgegeben.

Weiß jemand, ob die Verwendung der lokalen Validierungsmethode auch immer die aktuellsten Informationen zurückgibt? Oder gilt es ausschließlich für diesen Beleg, mit dem Sie ihn lokal validieren?

Hat jemand Tipps zum Einrichten Ihres eigenen Servers, um ihn mit Apple zu validieren? damit habe ich keine erfahrung

@csumrell Sie können die Quittung für den letzten Einkauf erhalten, indem Sie getPurchaseHistory anrufen und danach sortieren. In den obigen Kommentaren wird beschrieben, wie dies zu tun ist. Dies sollte die aktuellsten Informationen enthalten.

Und ich bin dabei, die Möglichkeit zu prüfen, auf dem Server zu validieren, damit wir uns gegenseitig helfen können. Würde aber gerne hören, wie andere es gemacht haben.

@kevinEsherick ja, aber getPurchaseHistory löst das

@csumrell Gotcha. Nun, das ist schnell und einfach testbar. Hast du es nicht probiert? Wenn nicht, schaue ich heute später nach.
BEARBEITEN : @csumrell ValidateReceiptIos verfolgt tatsächlich die neuesten automatischen Verlängerungen bei diesem Kauf, daher ist dies eine gültige Methode. Ich würde sagen, dass dies besser ist als die oben vorgeschlagenen Methoden zum Überprüfen von Abonnements, da die iTunes-Anmeldeaufforderung vermieden wird (es sei denn, jemand kann eine Lösung dafür finden). Der Nachteil ist, dass Sie die Belege lokal speichern müssen. Dies sollte nicht zu viel Aufwand sein. Möglicherweise möchten Sie auch nicht, dass diese Quittungen aus Sicherheitsgründen oder der Tatsache, dass sie sehr umfangreich sind (~60.000 Zeichen) an Ihr Backend gesendet werden. Wenn sich ein abonnierter Benutzer abmeldet oder ein neues Gerät verwendet, müssen Sie dies also tun fordern Sie sie auf, sich anzumelden, um ihren neuesten Kaufbeleg zu erhalten. Sie können ihren Abonnementstatus intern speichern, sodass Sie nur Benutzer fragen, die zuletzt abonniert wurden, die Sie überprüft haben.

@hyochan hat 20,00 USD für diese Ausgabe Sehen Sie es auf IssueHunt

@hyochan, welche konkrete Lösung finanzieren Sie? Wir haben einige hier in diesem Thread bereitgestellt, und ich denke, mein letzter Kommentar bietet die bisher stärkste Lösung mit den wenigsten Problemen. Finanzieren Sie jemanden, um diese Lösungen in einer prägnanten PR für die README-Datei zusammenzufassen? Oder ist dies ein tatsächliches Codeproblem, das Sie noch gelöst sehen möchten?

@kevinEsherick

Finanzieren Sie jemanden, um diese Lösungen in einer prägnanten PR für die README-Datei zusammenzufassen?

Ja. Das ist absolut richtig. Außerdem wollte ich nur testen, wie issue hunt out of the box funktionieren könnte. Mein nächster Plan ist es, Testfälle zu finanzieren.

@kevinEsherick Sie haben ValidateReceiptIos gibt tatsächlich die neuesten Informationen über das Abonnement zurück, wobei nur die ursprüngliche erste Transaktions-ID verwendet wird (im Redux-Zustand gespeichert).

Ich werde diese Methode verwenden, um zu vermeiden, dass ich meinen eigenen Server einrichten muss, und um das iTunes-Anmelde-Popup zu vermeiden, während ich überprüfe, ob das Abonnement aktiv ist.

Ein Hinweis für diejenigen, die erwägen, diesen Validierungsablauf zu verwenden, den @csumrell und ich ausgelegt haben: Ich habe festgestellt, dass in der Entwicklung die iTunes-Anmeldeaufforderung immer noch beim ersten Start angezeigt wird. In der Produktion passiert dies nicht. Solange Sie die von uns beschriebenen Schritte befolgen, sollte alles in Ordnung sein. Warum es dies tut, wenn iOS sieht, dass IAP verwendet wird / Teil der Modulzuordnung in der Entwicklung ist, fordert es automatisch zur Anmeldung auf. Ich bin mir nicht ganz sicher, aber es scheint nur eine Funktion von Sandbox-Tests zu sein.

Hallo, anscheinend gab es in letzter Zeit keine Aktivitäten zu diesem Thema. Wurde das Problem behoben oder erfordert es immer noch die Aufmerksamkeit der Community? Dieses Problem kann geschlossen werden, wenn keine weitere Aktivität auftritt. Sie können dieses Problem auch als "Zur Diskussion" oder "Gute erste Ausgabe" kennzeichnen und ich werde es offen lassen. Vielen Dank für Ihre Beiträge.

Schließen dieses Problems nach längerer Inaktivität. Wenn dieses Problem in der neuesten Version noch vorhanden ist, können Sie gerne eine neue Ausgabe mit aktuellen Informationen erstellen.

Vielleicht muss dieser ganze Ablauf in der Dokumentation geklärt werden?

Vor allem, wenn es um das Abonnement mit automatischer Verlängerung geht? Es ist für einen Neuling sehr unklar, ob Sie in der expires_date_ms überprüfen können, ob das Abonnement noch aktiv ist oder nicht?

Ich schlage vor, der beste Ort dafür wäre ein vollständiger funktionierendes Beispiel?

Wären die Leute daran interessiert? Wenn ich etwas Zeit habe, könnte ich daran arbeiten?

@alexpchin ja, das würde definitiv eine :D

Zustimmen! Wäre jemand an #856 interessiert?

Hallo,

Die Methode von @andrewzey hat für uns bis gestern hervorragend funktioniert, als wir plötzlich keine Quittungen für unsere Abonnements mehr validieren konnten - wir erhalten einen JSON-Parse-Fehler von iOS zurück, wenn validReceiptIos() aufgerufen wird. Ich nehme an, niemand sonst ist darauf gestoßen...? Die einzige Änderung, die seit gestern vorgenommen wurde, war das Hinzufügen eines Promo-Codes auf ITC, den wir inzwischen entfernt haben, um Variablen zu eliminieren. Gibt es einen Grund, warum eine Quittung beim JSON-Parsing fehlschlagen sollte? Es schlägt für jede zurückgegebene Quittung fehl – ​​nicht nur die Quittung am Index 0 von sortiertenAvailablePurchases. Der Code ist im Grunde identisch mit Andrews Beispiel - ich habe alles nach valideReceiptIos weggelassen, da der Fehler dort geworfen wird.

Wir verwenden RN 0.61.4, RNIap: 4.4.2 und iOS 13.3.1

Wir haben versucht:

  • App neu installieren
  • Neues Benutzerkonto
  • Verwenden eines neuen App-Geheimnisses
  • Testen aller Belege für jeden Benutzer - kein einziger Beleg kann nicht geparst werden

Wir fragen uns, ob wir finishTransactionIOS nicht richtig verwendet haben, als wir die Sandbox-Käufe getätigt haben?

Was wirklich seltsam ist, ist, wenn wir den Beleg mit diesem Online-Tool validieren, alles normal aussieht und wir alle Beleg-Metadaten sehen können.

  isSubscriptionActive = async () => {
      const availablePurchases = await RNIap.getAvailablePurchases();
      const sortedAvailablePurchases = availablePurchases.sort(
        (a, b) => b.transactionDate - a.transactionDate
      );
      const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;
      const isTestEnvironment = __DEV__;

      try {
        const decodedReceipt = await RNIap.validateReceiptIos(
          {
            'receipt-data': latestAvailableReceipt,
            password: Config.IN_APP_PURCHASE_SECRET,
          },
          isTestEnvironment
        );
        console.log('response!', decodedReceipt)
      } catch (error) {
        console.warn('Error validating receipt', error) // JSON PARSE ERROR HERE
      }
...

Frage zum Code, den @andrewzey platziert hat. Wäre Date.now() nicht die aktuelle Gerätezeit und ein Benutzer könnte dies möglicherweise ändern und ein endloses Abonnement haben? 🤔

Ist nicht auch davon abzuraten, den Empfang von der App selbst zu überprüfen?

@doteric Könnten Sie mich darauf hinweisen, wo sie erwähnen, dass sie entmutigt sind? Ich weiß, dass Sie serverseitige Benachrichtigungen einrichten können, aber aus meiner Sicht scheint es viel einfacher zu sein, diese Validierung auf dem Client statt auf dem Server durchzuführen.
Ich habe Mühe, die richtige Dokumentation von Apple oder anderen nativen Solid Reaction-Quellen zu finden.

@doteric ja Date.now () ist die Gerätezeit, sodass es umgangen werden könnte. Die Chancen, dass ein Benutzer dies tut, sind jedoch winzig, und sogar andere Methoden könnten für ein endloses Abonnement umgangen werden. Wenn sie beispielsweise eine einfache serverseitige Validierung verwenden, könnten sie vor dem Öffnen der App in den Flugzeugmodus wechseln, um zu verhindern, dass die App erkennt, dass das Abonnement abgelaufen ist. Natürlich gibt es andere Schutzmaßnahmen, die Sie einrichten könnten, aber mein Punkt ist, dass die Verwendung von Date.now() auf der Clientseite sicherlich funktionsfähig ist. @captaincole Ich habe die Dokumente nicht zur Hand, kann aber bestätigen, dass auch ich gelesen habe, dass von der clientseitigen Validierung abgeraten wird. Ich habe es in Apple-Dokumenten gelesen, glaube ich. Davon abgesehen denke ich, dass die Kundenseite die Arbeit erledigt.

Hallo,

Ich habe einen der in diesem Thread besprochenen Ansätze ausprobiert, indem ich valideReceiptIos und die neuesten_receipt_data verwendet habe (Vergleich von expires_date_ms mit dem tatsächlichen Datum). Es hat während der Entwicklung in Xcode hervorragend funktioniert. Als ich es jedoch in Testflight getestet habe, funktionierte es nicht mehr. Es ist schwer zu debuggen, daher kann ich das genaue Problem nicht identifizieren, es scheint, dass es keine Empfangsdaten erhält. Hatte jemand ein ähnliches Problem mit Testflight?

Vielen Dank im Voraus!

Hallo,

Ich habe einen der in diesem Thread besprochenen Ansätze ausprobiert, indem ich valideReceiptIos und die neuesten_receipt_data verwendet habe (Vergleich von expires_date_ms mit dem tatsächlichen Datum). Es hat während der Entwicklung in Xcode hervorragend funktioniert. Als ich es jedoch in Testflight getestet habe, funktionierte es nicht mehr. Es ist schwer zu debuggen, daher kann ich das genaue Problem nicht identifizieren, es scheint, dass es keine Empfangsdaten erhält. Hatte jemand ein ähnliches Problem mit Testflight?

Vielen Dank im Voraus!

+1

@asobralr @Somnus007 Ich kann mich irren, aber ich glaube nicht, dass Sie IAPs mit Testflight testen können, da Benutzer keine Einkäufe über Testflight tätigen können. Apple bietet keine großen Möglichkeiten zum Testen von Käufen in produktionsähnlichen Umgebungen

@asobralr @Somnus007 Ich kann mich irren, aber ich glaube nicht, dass Sie IAPs mit Testflight testen können, da Benutzer keine Einkäufe über Testflight tätigen können. Apple bietet keine großen Möglichkeiten zum Testen von Käufen in produktionsähnlichen Umgebungen

Richtig. Sie können in der Sandbox nicht einmal Fehlerszenarien simulieren, was schade ist. 😞

Hallo @kevinEsherick , danke für deine Antwort. Sie können falsch liegen. Der Benutzer kann über teslflight einkaufen. Bei Testflight muss sich der Benutzer anscheinend bei einem Sandbox-Konto anmelden, das dieselbe E-Mail-Adresse wie sein echtes Apple-Konto hat. Dann funktioniert alles gut. BTW, wird getPurchaseHistory das iTunes-Login-Popup in der Produktionsumgebung auslösen?

Es sieht so aus, als würde iOS 14 diese Lösung aufgrund der Tatsache stören, dass es Probleme gibt, wenn Sie getAvailablePurchases() aufrufen, bevor Sie requestSubscription() aufrufen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen