
Tutorial
September 7, 2022
Updated: Mai 19, 2025
Die Servervalidierung (serverseitige Empfangsvalidierung) ist eine Methode, um die Echtheit eines Kaufs zu bestätigen. Im Gegensatz zur gerätebasierten Validierung geschieht die Servervalidierung auf dem, Sie ahnen es, Server. Die Validierung umfasst nichts anderes als dass das Gerät oder der Server eine Anfrage an Apple schickt, um herauszufinden, ob der Kauf tatsächlich stattgefunden hat und gültig ist.
Wir sollten erwähnen, dass die Servervalidierung keine Pflicht ist. In-App-Käufe würden auch ohne sie funktionieren. Sie bietet Ihnen jedoch einige Vorteile:
Allein der erste Vorteil reicht aus unserer Erfahrung aus, um eine Kaufabwicklung über den Server einzurichten.
In der Regel sieht der Vorgang der Kaufvalidierung auf iOS wie folgt aus:
Sie müssen das gemeinsame Geheimnis angeben, um eine Anforderung zur Zahlungsvalidierung zu autorisieren. Sie können es im App Store Connect generieren.
Das gemeinsame Geheimnis kann für eine bestimmte App (App-spezifisches Geheimnis) oder für alle Apps im Konto (primäres Geheimnis) erstellt werden.
Öffnen Sie die Seite der App in App Store Connect, gehen Sie zu In-App-Käufe → Verwalten und klicken Sie auf App-spezifisches gemeinsames Geheimnis, um ein App-spezifisches Geheimnis zu generieren. In dem sich nun öffnenden Fenster können Sie einen neuen Token generieren oder das vorhandene kopieren.
Öffnen Sie die Seite „Benutzer und Zugriff“ und wählen Sie die Registerkarte „Gemeinsames Geheimnis“ aus, um das Geheimnis für alle Apps in Ihrem Konto zu erhalten.
Sobald Sie das gemeinsame Geheimnis erhalten, können Sie die Quittungen versenden, um diese auf den Apple Servern zu validieren. Das erfolgt über eine verifyReceipt Anfrage. Senden Sie eine POST Anfrage an https://buy.itunes.apple.com/verifyReceipt. Im JSON Body der Anfrage geben Sie das gemeinsame Geheimnis im Password Feld ein und die Quittung bei receipt-data. Optional gibt es auch den exclude-old-transactions Parameter. Ist dieser true, erhalten Sie für jedes automatisch verlängerte Abonnement die letzte Transaktion statt des kompletten Verlaufs.
Hier ist der Payload der Anfrage zur Kaufvalidierung:
{
"password": "f4d35830e3...52aae",
"receipt-data": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
"exclude-old-transactions": false
}
Wenn Sie in einer Sandbox-Umgebung arbeiten (also zu Testzwecken), senden Sie die Validierungsanfragen an https://sandbox.itunes.apple.com/verifyReceipt. Das gemeinsame Geheimnis sowie der Payload und die Antwortformate bleiben gleich.
Beachten Sie, dass Sie eine in der Sandbox-Umgebung erstellte Quittung nicht auf einem Produktionsserver validieren können und umgekehrt. Aus diesem Grund ist es in realen Systemen am besten, die erste Anfrage an den Produktionsserver zu leiten und sie an den Sandbox-Server umzuleiten, falls der Status Key den Fehlercode 21007 zurückgibt. Dieses Verhalten ist ein Muss während der App-Überprüfung. Nur so können Apple-Mitarbeiter Käufe testen, während auch echte Nutzern die Chance haben, Ihre App zu kaufen.
Unter den vielen zu beachtenden Fehlern gibt es den 21004 Fehlercode. Dieser bedeutet, dass wir das falsche Geheimnis verwenden. Behalten Sie immer den Überblick, da es sich sowohl auf die Benutzererfahrung als auch auf die Genauigkeit der Analyse auswirkt. Im schlimmsten Fall kann die App aus dem App Store entfernt werden, wenn der Nutzer nach der Bezahlung nie Zugriff auf die Premium-Features erhält.
War die Validierung erfolgreich (Status=0), enthält die Antwort die Details der Transaktionen des Nutzers.
Hier ist die Antwort auf die Anforderung zur Zahlungsvalidierung:
{
"environment": "Production",
"receipt": {
"receipt_type": "Production",
"adam_id": 123,
"app_item_id": 123,
"bundle_id": "com.adapty.sample_app",
"application_version": "1",
"download_id": 123,
"version_external_identifier": 123,
"receipt_creation_date": "2021-04-28 19:42:01 Etc/GMT",
"receipt_creation_date_ms": "1619638921000",
"receipt_creation_date_pst": "2021-04-28 12:42:01 America/Los_Angeles",
"request_date": "2021-08-09 18:26:02 Etc/GMT",
"request_date_ms": "1628533562696",
"request_date_pst": "2021-08-09 11:26:02 America/Los_Angeles",
"original_purchase_date": "2017-04-09 21:18:41 Etc/GMT",
"original_purchase_date_ms": "1491772721000",
"original_purchase_date_pst": "2017-04-09 14:18:41 America/Los_Angeles",
"original_application_version": "1",
"in_app": [
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "1000000831360853",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1619638918000",
"purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-05-05 19:41:58 Etc/GMT",
"expires_date_ms": "1620243718000",
"expires_date_pst": "2021-05-05 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000397200750",
"is_trial_period": "true",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED"
}
]
},
"latest_receipt_info": [
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001020690335",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-08-04 19:41:58 Etc/GMT",
"purchase_date_ms": "1628106118000",
"purchase_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-11 19:41:58 Etc/GMT",
"expires_date_ms": "1628710918000",
"expires_date_pst": "2021-08-11 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000438372383",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
},
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001017218955",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-07-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1627501318000",
"purchase_date_pst": "2021-07-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-04 19:41:58 Etc/GMT",
"expires_date_ms": "1628106118000",
"expires_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000849023623",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
}
],
"latest_receipt": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
"pending_renewal_info": [
{
"auto_renew_product_id": "basic_subscription_1_month",
"product_id": "basic_subscription_1_month",
"original_transaction_id": "1000000831360853",
"auto_renew_status": "1"
}
],
"status": 0
}
Die Antwort ist ziemlich umständlich und wurde in der neuen Version der App Store Server API vereinfacht. Die aktuelle Implementierung ist jedoch nicht allzu umzusetzen.
Sie benötigen eine Möglichkeit, den Abonnementstatus zu ermitteln, damit Sie wissen, ob der Nutzer Zugriff auf die Premium-Funktionen der App hat oder nicht. In der aktuellen Version der API gibt es keine spezielle Anfrage zum Abrufen des Abonnementstatus, sodass Sie in jedem Fall mit dem Transaktionsverlauf arbeiten müssen.
Der latest_receipt_info Array enthält standardmäßig alle In-App-Käufe des jeweiligen Nutzers, außer verbrauchbare Produkte, die direkt innerhalb der App erfolgen. So können Sie den gesamten Kaufverlauf des Nutzers abrufen. Das ist sowohl für Analysen als auch für die Bestimmung des aktuellen Abonnementstatus sehr nützlich.
Die Transaktionen werden immer schon sortiert, sodass die neuesten zuerst angezeigt werden. Um sicherzugehen, empfehle ich dennoch, eine eigene Sortierung nach Datum für Transaktionen zu implementieren.
Der Transaktions-Payload:
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "1000000831360853",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1619638918000",
"purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-05-05 19:41:58 Etc/GMT",
"expires_date_ms": "1620243718000",
"expires_date_pst": "2021-05-05 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000397200750",
"is_trial_period": "true",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
}
Es reicht aus, die letzte Transaktion der Kette abzurufen und sich die Eigenschaft expires_date anzusehen, um den aktuellen Abonnementstatus zu überprüfen. Die Ausnahme davon wäre die Gnadenfrist, auf die wir später noch eingehen werden.
Zur Analyse empfehle ich, die folgenden Eigenschaften zu speichern:
Apple hat auf der WWDC 2021 angekündigt, den Transaktionsinformationen ein appAccountToken Feld hinzuzufügen. Es enthält die Kennung des Nutzers in Ihrem System. Diese Kennung muss im UUID-Format vorliegen und wird auf der App-Seite definiert, wenn der Kauf initialisiert wird. Ist sie definiert, wird sie bei allen Transaktionen dieser Kette zurückgegeben (Abonnementverlängerungen, Abrechnungsprobleme usw.), sodass Sie leicht nachvollziehen können, welcher Nutzer den Kauf getätigt hat.
Behalten Sie auch den Parameter subscription_group_identifier im Auge. Wenn der Benutzer zuvor Transaktionen mit aktiven Test- oder Einführungsangeboten hatte, sollte er keinen Zugriff mehr auf diese Art Abonnements haben. Dies sollte serverseitig nachverfolgt werden.
Das Array ‚pending_renewal_info‘ speichert die Daten zur Abonnement-Verlängerung. Sie können so nachvollziehen, was mit dem Abonnement in der nächsten Abrechnungsperiode geschehen soll. Wenn Sie beispielsweise festgestellt haben, dass sich der Nutzer gegen die automatische Verlängerung entschieden hat, können Sie ihm vorschlagen, zu einem anderen Plan zu wechseln, oder ihm ein Werbeangebot unterbreiten. Diese Ereignisse können bequem mit Serverbenachrichtigungen nachverfolgt werden, auf die ich in Kürze eingehen werde.
Daten zur Abonnementverlängerung:
{
"auto_renew_product_id": "basic_subscription_1_month",
"product_id": "basic_subscription_1_month",
"original_transaction_id": "1000000831360853",
"auto_renew_status": "1"
}
Wenn der Nutzer keine Produkte mit automatischer Verlängerung hat, werden die Keys latest_receipt_info und pending_renewal_info nicht zurückgegeben. In diesem Fall finden Sie Transaktionen in Receipt → in_app. Das Transaktionsformat ähnelt den automatisch erneuerbaren Transaktionen, hat aber keine Felder für Ablauf, Verlängerungen, Angebote und andere Eigenschaften, die nur für automatisch erneuerbare Transaktionen gelten.
Beachten Sie, dass auch bei Transaktionen mit automatischer Verlängerung receipt → in_app ausgegeben wird. Besser ist es jedoch, latest_receipt_info zu verwenden, da die darin enthaltenen Abonnementdaten die aktuellen sind.
Vor einiger Zeit mussten Sie noch ein komplexes System entwickeln, um Änderungen des Abonnementstatus zu verfolgen. Um beispielsweise zu verstehen, ob das Abonnement verlängert wurde oder nicht, mussten Sie zuvor ab 24 Stunden vor Ablauf des Abonnements stündlich eine Abonnement-Statusanfrage an Apple-Server senden. Apple fügte im Laufe der Zeit immer mehr Serverbenachrichtigungen hinzu. Diese decken jetzt praktisch alle wichtigen Abonnement-Ereignisse ab. Das ist wirklich praktisch: Sobald es auf Apples Seite Änderungen gibt, werden Sie auf Ihrem eigenen Server darüber benachrichtigt. Sie werden über neue Käufe, Abonnementverlängerungen, Abrechnungsprobleme usw. benachrichtigt. Dadurch können Sie viel genauere Analysen durchführen und den Abonnementstatus viel einfacher verwalten.
Sie können die Serverbenachrichtigungen in App Store Connect aktivieren. Öffnen Sie die Seite der App und gehen Sie zu Allgemein -> App-Informationen. Fügen Sie als Nächstes den Link in das Feld URL für App Store-Serverbenachrichtigungen ein und speichern Sie Ihre Änderungen.
Serverbenachrichtigung:
{
"notification_type": "DID_RENEW",
"password": "f4d35830e3...52aae",
"environment": "PROD",
"auto_renew_product_id": "basic_subscription_1_month",
"auto_renew_status": "true",
"unified_receipt": {
"status": 0,
"environment": "Production",
"latest_receipt_info": [
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001020690335",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-08-04 19:41:58 Etc/GMT",
"purchase_date_ms": "1628106118000",
"purchase_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-11 19:41:58 Etc/GMT",
"expires_date_ms": "1628710918000",
"expires_date_pst": "2021-08-11 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000438372383",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
},
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001017218955",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-07-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1627501318000",
"purchase_date_pst": "2021-07-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-04 19:41:58 Etc/GMT",
"expires_date_ms": "1628106118000",
"expires_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000849023623",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
}
],
"latest_receipt": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
"pending_renewal_info": [
{
"auto_renew_status": "1",
"auto_renew_product_id": "basic_subscription_1_month",
"product_id": "basic_subscription_1_month",
"original_transaction_id": "1000000831360853"
}
]
},
"bid": "com.adapty.sample_app",
"bvrs": "0"
}
Das Format der Serverbenachrichtigung ähnelt der Zahlungsvalidierungsantwort. Die Transaktionsdetails werden in unified_receipt → Latest_receipt_info gespeichert. Der password key speichert das gemeinsame Passwort für Ihre App, wodurch Sie die Echtheit der Anfrage überprüfen können. Der notification_type Key speichert den Ereignistyp. Die nützlichsten sind meiner Meinung nach:
Die Servervalidierung verbessert die Analysen für Daten, die Sie von Ihrer App sammeln. Es erschwert Betrügern auch den Zugriff auf Ihre Premium-Inhalte und ermöglicht es Ihnen, Ihre Abonnements plattformübergreifend abzuschließen. Gleichzeitig kann die Implementierung der Servervalidierung ziemlich viel Zeit in Anspruch nehmen, insbesondere wenn Sie präzise Daten benötigen. Dazu müssten viele Nebenfälle berücksichtigt werden: Abonnement-Upgrades, Abonnement-Crossgrades, Testzeiträume, Promo-/Intro-Angebote, Schonfristen, Rückerstattungen, Familienabonnements usw. Sie müssen auch verschiedene Nuancen kennen und berücksichtigen. Apple bietet eine Reduzierung der Provision von 30 % auf 15 % für Abonnements, die regelmäßig um mehr als ein Jahr verlängert werden.
Further reading
Tutorial
September 7, 2022