Nach In-App-Kauf sende Daten sicher zu Webserver

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • DKCode schrieb:

    Du sagst es! So ist zumindest der Plan und ich hoffe, dass das Validieren per PHP irgendwann bei mir funktioniert.
    Ich bin nicht sicher, vielleicht etwas überlesen zu haben, aber reduziert sich Dein Vorgehen nicht auf die folgenden Schritte:
    1. User nimmt IAP vor
    2. App empfängt ein Receipt von Apple
    3. Deine App schickt das Receipt an Deinen Server
    4. Dein Server kommuniziert mit dem Apple-Backend, um das Receipt zu überprüfen
    5. Bei Erfolg wird auf dem Server die DB aktualisiert
    Damit bleibt doch nur der Punkt (4.) als offenes Thema, korrekt? Hierzu gibt es von Apple den "Receipt Validation Programming Guide", in dem genau dieses Überprüfungsverfahren im Abschnitt "Validating receipts with the App Store" beschrieben wird. Ein Sniffern des Receipts dürfte Dir egal sein und ein gefälschtes Receipt sollte in der Validierung scheitern.

    Du postest das Receipt base64-encoded und erhältst ein JSON von Apple, das Dir die Gültigkeit des Kaufes verrät. Ohne es gemacht zu haben (sic!) klingt das jetzt nicht nach Raketenwissenschaft :D

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • DKCode schrieb:

    gritsch schrieb:

    Und warum nicht der einfache schritt wie beschrieben mit überprüfung der transaktion (wenn Apple das schon anbietet).
    Weil ich nach einem Plan B suche während der einfache Schritt noch nicht funktioniert. Wenn ich eins mittlerweile mitbekommen habe, dann, dass man bei der APP-Entwicklung immer einen Plan B haben sollte :thumbsup:
    Nein. Man sollte die korrekte Lösung durchziehen auch wenn sich das nicht sofort von alleine löst.
    Du brauchst hier keinen Plan B. Nimm einfach den Plan A!
  • DKCode schrieb:

    NSObject schrieb:

    DKCode schrieb:

    Hat irgendjemand vielleicht eine Idee, wie ich den Kaufprozess anders aufbauen könnte, um dem Problem mit dem Mitlesen der Daten aus dem Weg gehen zu können?
    Wie sieht denn dein bisheriger Prozess aus?
    1. Klick auf Kaufbutton
    2. Kaufprozess von Apple
    3. Wenn Kauf durch Apple bestätigt ist:
    > Aufruf einer PHP bzw. REST-API (POST-Call) durch Alamofire mit der Übermittlung von 5 POST-Variablen
    > API validiert die POST-Variablen
    > API schreibt die Daten in die Datenbank

    Wenn nicht Abbruch
    4. App aktualisiert sich, lädt die Daten aus der Datenbank und zeigt sie an
    Hallo? Der Verschlüsselung- und/oder Authentifizierungsprozess natürlich.

    NSObject schrieb:

    Hast du eine Idee? Danke dir!
    Für die Transportverschlüsselung TLS mit Key Pinning / Certificate Pinning.
    Für die Autorisierung ein Challenge-Response-Verfahren. Oder einfach wie von Mattes vorgeschlagen.

    NSObject schrieb:

    NSObject schrieb:

    DKCode schrieb:

    Es wäre mir eigentlich völlig egal,s das jemand die Verbindung mitliest, wenn nicht der Sinn der App dadurch komplett zerstört werden würde. Daher hoffe ich auf eure Unterstützung! Vielen Dank!
    Das sollte Dir im Interesse deiner User und der Rechtslage nicht egal sein.
    Das wäre mir nicht egal, wenn es bei dem Problem um sensible Userdaten ginge,...
    Das definierst nicht Du, sondern der Gesetzgeber. Und Telefonnummern sind explizit personenbezogene Daten.

    DKCode schrieb:

    Aber in meinem Fall baut der gesamte Sinn der App auf einem In-App-Kauf auf heißt kann man diesen Umgehen hat jeder andere Nutzer einen riesigen Nachteil.

    Daher muss ich irgendwie den Aufruf des Servers also die URL, oder aber die Parameter schützen, oder irgendwie anders eine Verbindung herstellen, oder einen gesonderten Parameter übertragen... bin da schlichtweg ratlos.
    Nochmal: URLs sind öffentlich. Kein Schutz. - Die Parameter sind Daten. Via TLS können die geschützt werden.
    * Kann Spuren von Erdnüssen enthalten.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von NSObject ()

  • MyMattes schrieb:

    DKCode schrieb:

    Du sagst es! So ist zumindest der Plan und ich hoffe, dass das Validieren per PHP irgendwann bei mir funktioniert.
    Ich bin nicht sicher, vielleicht etwas überlesen zu haben, aber reduziert sich Dein Vorgehen nicht auf die folgenden Schritte:
    1. User nimmt IAP vor
    2. App empfängt ein Receipt von Apple
    3. Deine App schickt das Receipt an Deinen Server
    4. Dein Server kommuniziert mit dem Apple-Backend, um das Receipt zu überprüfen
    5. Bei Erfolg wird auf dem Server die DB aktualisiert
    Damit bleibt doch nur der Punkt (4.) als offenes Thema, korrekt? Hierzu gibt es von Apple den "Receipt Validation Programming Guide", in dem genau dieses Überprüfungsverfahren im Abschnitt "Validating receipts with the App Store" beschrieben wird. Ein Sniffern des Receipts dürfte Dir egal sein und ein gefälschtes Receipt sollte in der Validierung scheitern.

    Du postest das Receipt base64-encoded und erhältst ein JSON von Apple, das Dir die Gültigkeit des Kaufes verrät. Ohne es gemacht zu haben (sic!) klingt das jetzt nicht nach Raketenwissenschaft :D

    Mattes
    Moin Mattes,

    genau das probiere ich schon eine ganze Weile, aber ich bekomme das Receipt weder anzeigt noch gesendet. Wie gesagt das ist meine erste In-App-Kauf App seit bitte nachsichtig ^^
  • DKCode schrieb:

    genau das probiere ich schon eine ganze Weile, aber ich bekomme das Receipt weder anzeigt noch gesendet. Wie gesagt das ist meine erste In-App-Kauf App seit bitte nachsichtig ^^
    Dann sucht man aber nicht nach einer schlechteren Lösung sondern nach seinem Fehler.

    Und zu sagen "es funktioniert nicht" bringt gar nix wenn man hilfe benötigt.
    Man zeigt eventuell einen code-schnipsel der nicht funktioniert (inklusive eingabe-parameter und fehlermeldungen)...
  • NSObject schrieb:


    NSObject schrieb:

    Hast du eine Idee? Danke dir!
    Für die Transportverschlüsselung TLS mit Key Pinning / Certificate Pinning.Für die Autorisierung ein Challenge-Response-Verfahren. Oder einfach wie von Mattes vorgeschlagen.

    Das klingt total professionell aber ich kann mit den Begriffen leider so gar nichts anfangen =O
    Das klingt professionell, aber ich kann leider mit den Begriffen so gar nichts anfangen =O ;(
  • DKCode schrieb:

    NSObject schrieb:

    NSObject schrieb:

    Hast du eine Idee? Danke dir!
    Für die Transportverschlüsselung TLS mit Key Pinning / Certificate Pinning.Für die Autorisierung ein Challenge-Response-Verfahren. Oder einfach wie von Mattes vorgeschlagen.
    Das klingt total professionell aber ich kann mit den Begriffen leider so gar nichts anfangen =O
    Das klingt professionell, aber ich kann leider mit den Begriffen so gar nichts anfangen =O ;(
    Im prinzip ist es ja egal wenn jemand die daten auf seinem eigenen Gerät mitlesen kann.
    Das einzig wichtige ist doch dass du das receipt am server validierst und nur falls das gültig ist, fügst du die angehängten daten in die DB ein (das receipt darf natürlich nur einmal verwendet werden).
  • DKCode schrieb:

    NSObject schrieb:

    NSObject schrieb:

    Hast du eine Idee? Danke dir!
    Für die Transportverschlüsselung TLS mit Key Pinning / Certificate Pinning.Für die Autorisierung ein Challenge-Response-Verfahren. Oder einfach wie von Mattes vorgeschlagen.
    Das klingt professionell, aber ich kann leider mit den Begriffen so gar nichts anfangen =O ;(
    Certificate and Public Key Pinning
    Pinning Cheat Sheet
    Challenge-Response Authentication for Dummys

    P.S. Ein Video in deutsch:
    Macoun 2013 - NSURLConnection: Safety First!
    * Kann Spuren von Erdnüssen enthalten.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von NSObject () aus folgendem Grund: Video

  • gritsch schrieb:

    DKCode schrieb:

    genau das probiere ich schon eine ganze Weile, aber ich bekomme das Receipt weder anzeigt noch gesendet. Wie gesagt das ist meine erste In-App-Kauf App seit bitte nachsichtig ^^
    Dann sucht man aber nicht nach einer schlechteren Lösung sondern nach seinem Fehler.
    Und zu sagen "es funktioniert nicht" bringt gar nix wenn man hilfe benötigt.
    Man zeigt eventuell einen code-schnipsel der nicht funktioniert (inklusive eingabe-parameter und fehlermeldungen)...
    Ja da hast du recht, ich wollte nur bevor ich mit Codes komme, das erstmal selber schaffen und hier im Forum höchstens ein paar Denkanstöße sammeln ^^


    Hier der Code meines StoreManagers:

    Brainfuck-Quellcode

    1. import Foundation
    2. import StoreKit
    3. import UIKit
    4. import MobileCoreServices
    5. import Alamofire
    6. import MBProgressHUD
    7. import SwiftyJSON
    8. enum ProductType:String{
    9. case consumable
    10. }
    11. class StoreManager: NSObject {
    12. let receiptURL = Bundle.main.appStoreReceiptURL
    13. static var shared:StoreManager = {
    14. return StoreManager()
    15. }()
    16. var productsFromStore = [SKProduct]()
    17. let purchasableProductsIds:Set<String> = ["inappproduct1", "inappproduct2"] //For now we only have one product
    18. let consumablesProductsIds:Set<String> = ["inappproduct1", "inappproduct2"]
    19. func setup(){
    20. self.requestProducts(ids: self.purchasableProductsIds)
    21. SKPaymentQueue.default().add(self)
    22. }
    23. func requestProducts(ids:Set<String>){
    24. if SKPaymentQueue.canMakePayments(){
    25. let request = SKProductsRequest(productIdentifiers: ids)
    26. request.delegate = self
    27. request.start()
    28. }else{
    29. print("User can't make payments from this account")
    30. }
    31. }
    32. }
    33. extension StoreManager:SKProductsRequestDelegate{
    34. func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    35. let products = response.products
    36. if products.count > 0{
    37. for product in products{
    38. self.productsFromStore.append(product)
    39. }
    40. }else{
    41. print("Products now found")
    42. }
    43. NotificationCenter.default.post(name: NSNotification.Name.init("SKProductsHaveLoaded"), object: nil)
    44. let invalidProductsIDs = response.invalidProductIdentifiers
    45. for id in invalidProductsIDs{
    46. print("Wrong product id: ",id)
    47. }
    48. }
    49. func request(_ request: SKRequest, didFailWithError error: Error) {
    50. print("error requesting products from the store",error.localizedDescription)
    51. }
    52. func buy(product:SKProduct){
    53. let payment = SKPayment(product: product)
    54. SKPaymentQueue.default().add(payment)
    55. print("Buying product: ",product.productIdentifier)
    56. }
    57. }
    58. extension StoreManager:SKPaymentTransactionObserver{
    59. func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    60. for transaction in transactions{
    61. switch transaction.transactionState {
    62. case .purchased:
    63. self.purchaseCompleted(transaction: transaction)
    64. case .failed:
    65. self.purchaseFailed(transaction: transaction)
    66. case .restored:
    67. self.purchaseRestored(transaction: transaction)
    68. case .purchasing,.deferred:
    69. print("Pending")
    70. }
    71. }
    72. }
    73. func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
    74. }
    75. func purchaseCompleted(transaction:SKPaymentTransaction){
    76. self.unlockContentForTransaction(trans: transaction)
    77. SKPaymentQueue.default().finishTransaction(transaction)
    78. }
    79. func purchaseFailed(transaction:SKPaymentTransaction){
    80. if let error = transaction.error as? SKError{
    81. switch error {
    82. case SKError.clientInvalid:
    83. print("User not allowed to make a payment request")
    84. case SKError.unknown:
    85. print("Unkown error while proccessing SKPayment")
    86. case SKError.paymentCancelled:
    87. print("User cancaled the payment request (Cancel)")
    88. case SKError.paymentInvalid:
    89. print("The purchase id was not valid")
    90. case SKError.paymentNotAllowed:
    91. print("This device is not allowed to make payments")
    92. default:
    93. break
    94. }
    95. }
    96. SKPaymentQueue.default().finishTransaction(transaction)
    97. }
    98. func purchaseRestored(transaction:SKPaymentTransaction){
    99. self.unlockContentForTransaction(trans: transaction)
    100. SKPaymentQueue.default().finishTransaction(transaction)
    101. }
    102. func unlockContentForTransaction(trans:SKPaymentTransaction){
    103. print("Should unlock the content for product ID",trans.payment.productIdentifier)
    104. print("----------------------")
    105. print(trans)
    106. print("----------------------")
    107. // START SERVER CALL
    108. let parameters: Parameters = ["run": "iap"]
    109. Alamofire.request("https://dev-server.de/iap.php",method: .post, parameters: parameters).validate().responseJSON { response in
    110. debugPrint(response)
    111. switch response.result {
    112. case .success(let data):
    113. let json = JSON(data)
    114. for (key, subJson) in json["return"] {
    115. if let lastid = subJson["lastid"].string {
    116. print(lastid)
    117. }
    118. }
    119. case .failure(let error):
    120. print("Request failed with error: \(error)")
    121. }
    122. }
    123. if self.consumablesProductsIds.contains(trans.payment.productIdentifier){
    124. if trans.payment.productIdentifier == "inappproduct1"{
    125. print("inappproduct1 has been added to your account")
    126. }
    127. if trans.payment.productIdentifier == "inappproduct2"{
    128. print("inappproduct2 has been added to your account")
    129. }
    130. }
    131. }
    132. func getAppReceipt() {
    133. guard let receiptURL = receiptURL else { return }
    134. do {
    135. let receipt = try Data(contentsOf: receiptURL)
    136. validateAppReceipt(receipt)
    137. } catch {
    138. let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
    139. appReceiptRefreshRequest.delegate = self
    140. appReceiptRefreshRequest.start()
    141. }
    142. }
    143. func requestDidFinish(_ request: SKRequest) {
    144. do {
    145. let receipt = try Data(contentsOf: receiptURL!)
    146. validateAppReceipt(receipt)
    147. } catch {
    148. }
    149. }
    150. func validateAppReceipt(_ receipt: Data) {
    151. let base64encodedReceipt = receipt.base64EncodedString()
    152. print("-------------------")
    153. print(base64encodedReceipt)
    154. print("-------------------")
    155. let requestDictionary = ["receipt-data":base64encodedReceipt]
    156. guard JSONSerialization.isValidJSONObject(requestDictionary) else { print("requestDictionary is not valid JSON"); return }
    157. do {
    158. let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
    159. let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt" // this works but as noted above it's best to use your own trusted server
    160. guard let validationURL = URL(string: validationURLString) else { print("the validation url could not be created, unlikely error"); return }
    161. let session = URLSession(configuration: URLSessionConfiguration.default)
    162. var request = URLRequest(url: validationURL)
    163. request.httpMethod = "POST"
    164. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
    165. let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
    166. if let data = data , error == nil {
    167. do {
    168. let appReceiptJSON = try JSONSerialization.jsonObject(with: data)
    169. print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
    170. // if you are using your server this will be a json representation of whatever your server provided
    171. } catch let error as NSError {
    172. print("json serialization failed with error: \(error)")
    173. }
    174. } else {
    175. print("the upload task returned an error: \(error)")
    176. }
    177. }
    178. task.resume()
    179. } catch let error as NSError {
    180. print("json serialization failed with error: \(error)")
    181. }
    182. }
    183. }
    Alles anzeigen

    weiter in der nächsten Antwort, da das Forum motzt, es seien zu viele Zeichen :rolleyes:
  • Der StoreManager managed den In-App-Kaufprozess. Aktuell bin ich soweit, dass ich in der Konsole das Receipt auslesen kann, allerdings erst bei einem Neustart der App nach dem Kauf, was natürlich falsch ist.

    Führe ich einen Testkauf aus erhalte ich bei Neustart der App das Receipt in base64 packe ich dieses dann in mein PHP Script:


    Quellcode

    1. function isReceiptValid($purchase,$is_sandbox) {
    2. $data = array('receipt-data' => $purchase);
    3. $encodedData = json_encode($data);
    4. if ($is_sandbox) {
    5. $url = "https://sandbox.itunes.apple.com/verifyReceipt";
    6. } else {
    7. $url = "https://buy.itunes.apple.com/verifyReceipt";
    8. }
    9. $ch = curl_init();
    10. curl_setopt($ch, CURLOPT_URL, $url);
    11. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    12. curl_setopt($ch, CURLOPT_POST, true);
    13. curl_setopt($ch, CURLOPT_POSTFIELDS, $encodedData);
    14. $encodedResponse = curl_exec($ch);
    15. curl_close($ch);
    16. //echo "<pre>";print_r($encodedResponse);
    17. if ($encodedResponse == false) {
    18. //logError($purchase, "Payment could not be verified (no response data).");
    19. return false;
    20. }
    21. $response = json_decode($encodedResponse);
    22. //echo "<pre>";print_r($response);exit;
    23. if ($response->{'status'} != 0) {
    24. //logError($purchase, "Payment could not be verified (status != 0).");
    25. return false;
    26. }
    27. //$purchase->storeReceipt = $response->{'receipt'};
    28. return $encodedResponse;
    29. }
    30. echo isReceiptValid("BASE64-RECEIPT");
    Alles anzeigen
    Dann bekomm ich diese Antwort vom Server:

    SandboxInvalid receipt. Status code: 21002

    Anscheint ist mein base64-Receipt falsch oder aber es ist nach dem Neustart der App abgelaufen? Ich weiß noch nicht wie ich das interpretieren soll.

    Hinzu kommt, was mich etwas verwundert, dass das Receipt überhaupt noch nach einem Neustart angezeigt wird. Ich ging davon aus, dass der Ablauf in etwa so ist,
    dass der Kauf durch Apple bestätigt wird und das Receipt wie z.B. bei Paypal der Token nur für einen Aufruf gültig ist und das wäre dann eigentlich direkt nach dem Kauf.

    Hmm... Irgendwo ist der Wurm, aber jetzt bin ich erstmal gespannt, ob ihr mir helfen könnt bzw. Fehler im Code entdeckt
  • NSObject schrieb:

    DKCode schrieb:

    NSObject schrieb:

    NSObject schrieb:

    Hast du eine Idee? Danke dir!
    Für die Transportverschlüsselung TLS mit Key Pinning / Certificate Pinning.Für die Autorisierung ein Challenge-Response-Verfahren. Oder einfach wie von Mattes vorgeschlagen.
    Das klingt professionell, aber ich kann leider mit den Begriffen so gar nichts anfangen =O ;(
    Certificate and Public Key PinningPinning Cheat Sheet
    Challenge-Response Authentication for Dummys

    P.S. Ein Video in deutsch:
    Macoun 2013 - NSURLConnection: Safety First!
    Danke dir für die Links, das schau ich mir gleich alles einmal an :thumbsup:
  • gritsch schrieb:

    DKCode schrieb:

    Was meint ihr zu meinem Code in Antwort 32 & 33? Ist das so der richtige Weg oder würdet ihr das ganz anders machen? Entdeckt ihr Fehler oder mögliche Probleme?
    Du hast hier keinen code der das receipt validiert und das ist im prinzip der einzig wichtige code.
    Wie? Ich habe den StoreManager der mir das Receipt ausliest, dieses wird dann (später) an den Server gesendet.
    Im Code oben kopiere ich das manuell ins PHP-Script. Das PHP-Script oben schickt diesen Token an den Apple-Server, der eine Antwort im Browser anzeigt (die natürlich auch später automatisch verarbeitet wird).

    Aber in meinem Code wird anscheint das falsche Base64-Token aus dem Repeipt erstellt, wodurch dann bei dem PHP-Aufruf "SandboxInvalid receipt. Status code: 21002" erscheint oder hab ich hier schon einen Gedankenfehler?