16

I'd like to be notified when someone makes an In-App Purchase in my App rather than wait until the next day to check iTunes Connect to see wether or not I had any sales.

Does anyone know of a way to do this? If not, it would be really cool!

Thanks

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Creagen
  • 478
  • 5
  • 17
  • 3
    Of course your app knows. You could send a message to your server. But why? What's wrong with knowing once a day? – rmaddy Aug 15 '15 at 05:30
  • When you say 'send a message to your server', do you mean something like Parse or something different? Please elaborate. – Creagen Aug 15 '15 at 06:21
  • Also, it's just nice getting notifications when you sell something. An online business gets emails every time they get a sale. I'd like that but for an app/in-app sale. – Creagen Aug 15 '15 at 06:27
  • If you try to implement something along these lines, I'd predict that it will fail Apple's app store review. The app purchase process is supposed to be anonymous. I don't want my phone to use up my expensive 3G data because you are to lazy to look at iTunes connect. And I most certainly don't want that app to send _anything_ to your server without a very, very good reason that benefits _me_. – gnasher729 Aug 15 '15 at 08:18
  • 5
    @gnasher729, I disagree. There's nothing in the guidelines about in app purchases being anonymous. In my old job, we would regularly tie users to their purchases. Indeed it's the thing we wanted to analyse most! – James Webster Aug 15 '15 at 08:39
  • 1
    Thanks @JamesWebster! :) – Creagen Aug 15 '15 at 15:02
  • @gnasher729, you make a good point about the data plan. Some reporting services, including *but not limited to* Mixpanel, have a provision allowing the host application to only post data when on WiFi network. – SwiftArchitect Aug 26 '15 at 20:19
  • Would you be able to point me in the direction of a guide for mixpanel that would allow me to do this? Or do you know of a way I could send a message to my VPS that I have with dreamhost and then I can receive a notification from my server. – Creagen Aug 26 '15 at 22:06

4 Answers4

17

Track StoreKit Purchase Events

When a purchase takes place, send yourself a datapoint (Track here...)

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    for transation in transactions {
        switch transation.transactionState {

            case .purchased:
                queue.finishTransaction(transation)
                // Track here...

            case .purchasing: break
            case .restored: break
            case .deferred: break
            case .failed: break
        }
    }
}

Leverage Libraries

Use analytics. Replace the // Track here... comment above by any of the blocks below. Non exhaustive list in alphabetical order:

Accengage

NSString *currencyCode = [<SKProduct.priceLocale>
                          objectForKey:NSLocaleCurrencyCode];
BMA4SPurchasedItem* item =
    [BMA4SPurchasedItem itemWithId(t.payment.productIdentifier)
        label:t.payment.productName
        category:<product.type>
        price:@(<SKProduct.price>)
        quantity:t.payment.quantity
];
[BMA4STracker trackPurchaseWithId:transaction.identifier
                         currency:currencyCode
                            items:@[item]];

Branch

NSDictionary *state = @{ 
    @"itemId": @(t.payment.productIdentifier),
    @"price": <SKProduct.price>, 
    @"itemName": <SKProduct.name>, 
    @"currency":currencyCode };
[[Branch getInstance] userCompletedAction:@"purchase" withState:state];

Fabric (Crashlytics)

NSString *currencyCode = [<SKProduct.priceLocale>
                          objectForKey:NSLocaleCurrencyCode];
[Answers logPurchaseWithPrice:<SKProduct.price>
                     currency:currencyCode
                      success:@YES
                     itemName:<product name>
                     itemType:@"Purchase"
                       itemId:@(t.payment.productIdentifier)
             customAttributes:@{}];

FlightRecorder

FlightRecorder.sharedInstance().trackEventWithCategory(
    "Actions",
    action: "Purchase",
    label: "productIdentifier",
    value: t.payment.productIdentifier)

Flurry Analytics

let properties = ["productIdentifier":t.payment.productIdentifier]
Flurry.logEvent("Purchase", withParameters: properties)

Google Analytics

#import "GAI.h"
#import "GAIDictionaryBuilder.h"

id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
NSString *currencyCode = [<SKProduct.priceLocale>
                          objectForKey:NSLocaleCurrencyCode];
[tracker send:[[GAIDictionaryBuilder
                createItemWithTransactionId:transactionIdentifier
                name:<product.localizedTitle>
                sku:t.payment.productIdentifier
                category:@"Purchase"
                price:<SKProduct.price>
                quantity:@(t.payment.quantity)
                currencyCode:currencyCode]
               build]];

See In-app purchase tracking with Google Analytics iOS SDK.

Heap Analytics

[Heap track:@"Purchase"
    withProperties:@{@"productIdentifier":@(t.payment.productIdentifier)}
];

Mixpanel Analytics(*)

Mixpanel.sharedInstance().track("Purchased",
                                properties: ["productIdentifier":transation.payment.productIdentifier])
     properties:@{@"productIdentifier":@(t.payment.productIdentifier)};

(*) Provides support for WiFi reporting (allows to postpone all reporting until WiFi network is available, as to not use cellular data). See mixpanelWillFlush below.

Parse.com

NSDictionary *dimensions =
    @{@"productIdentifier":@(t.payment.productIdentifier)};
[PFAnalytics trackEvent:@“Purchase” dimensions:dimensions];

Send an email from a server

POST purchase to a URL, and have in turn the server send you a mail or other notification.

iOS implementation using URLSession:

if let url = URL(string: "https://<yoursite>/php/purchase.php") {
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.httpBody =
        "{\"productIdentifier\":\"\(transaction.payment.productIdentifier)\"}"
        .data(using: .utf8)
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")
    let task = URLSession.shared.dataTask(with: request as URLRequest,
                                          completionHandler: {_,_,_ in })
    task.resume()
}

purchase.php email sender:

<?php
try {
    header('Content-type: application/json');
    $to = 'bounce@stackoverflow.com';
    $subject = 'Purchase';
    $message = $_POST['productIdentifier'];
    $headers = "From: " . $to . "\n";
    @mail($to, $subject, $message, $headers)
} catch (Exception $e) {}
?>

► Find this solution on GitHub and additional details on Swift Recipes.

Community
  • 1
  • 1
SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179
10

Fabric (formerly Crashlytics), in addition to being a fantastic (and free) crash logging system, also includes a component called Answers, which tracks usage stats in real time:

enter image description here

Recently, they added the ability to add custom event tracking, so it's a simple matter to add a "Product Purchased" event to you app. Adding Crashlytics to your app takes seconds (and they walk you through the process), and adding a custom event like that takes a single line of code. From then on, you'll be able to track all sorts of info about purchases made, number of users, and any other metadata you want to record, all with a delay of about 3 seconds.

I've been using Crashlytics for years (actually based on a StackOverflow question of my own), and I CANNOT recommend it highly enough. It's free, easy, and incredibly effective.

Community
  • 1
  • 1
Nerrolken
  • 1,975
  • 3
  • 24
  • 53
  • @SwiftArchitect The link is in the answer, but [here you go](https://get.fabric.io/). It's all a part of a single framework called Fabric, which includes the crash reporting, the event tracking, and several more modules. You just install the framework in your app (they have a very easy and intuitive guide for installation), and then you have access to any of the modules you care to use. – Nerrolken Sep 01 '15 at 17:48
  • 1
    I also use Fabric for all the above mentioned but I was wondering how do you track a purchase vs repurchase? Currently it tracks both for me and doesn't differentiate between either. – Ace Green Nov 23 '15 at 14:40
  • By the way AWESOME statistics I would LOVE to reach that. How long did it take you? – Ace Green Nov 23 '15 at 17:49
  • @AceGreen I wish those were my stats, but that's just a screenshot from the walkthrough on the Fabric website. :) But as for differentiating, if you place the Answers tracking code in the `-paymentQueue:updatedTransactions:` method for your in-app purchases, you can use the `transactionState` property of the transaction to determine whether it's a new purchase or a restored purchase. – Nerrolken Nov 23 '15 at 19:59
  • Ahhh we shall both retrieve to achieve these numbers. As for the transactionState, I don't think that can be used or? I think a repurchase doesn't count as a restore. You just get the message that it was already purchased but the code transactionState.Purchased is executed here too. – Ace Green Nov 23 '15 at 20:01
  • @AceGreen Oh, you mean a second purchase of a consumable item? You'd need to develop your own system for that, I'm afraid. Tracking purchases per user or per device, and then only logging the Answers event if the current user/device hasn't made any purchases before, etc. Most of my experience is with non-consumable items that can't be repurchased, so I don't have any specific experience with that. – Nerrolken Nov 23 '15 at 20:03
6

I record all IAP purchases to table in Parse.com. It is really easy to push all of the data from an IAP receipt up to parse. I am also doing non-renewable subscriptions and I use parse to sync the data between a user's device since StoreKit does not automatically do that for non renewable subscriptions.

Stephen Johnson
  • 5,293
  • 1
  • 23
  • 37
0

Add Parse to your project. To do so, follow the Quick Start Guide: https://parse.com/apps/quickstart#parse_data/mobile/ios/native/existing

Once parse is setup, add PFObject *testObject = [PFObject objectWithClassName:@"TestObject"]; testObject[@"foo"] = @"bar"; [testObject saveInBackground]; to your completeTransaction code for each in-app purchase. For example:

- (void)completeTransaction:(SKPaymentTransaction *)transaction {
NSLog(@"completeTransaction...");

[self provideContentForProductIdentifier:transaction.payment.productIdentifier];
// NEW CODE
if ([transaction.payment.productIdentifier isEqualToString:@"company.app.iapra"]){
    [[NSUserDefaults standardUserDefaults] setObject: @"No" forKey:KEY];
    [[NSUserDefaults standardUserDefaults] synchronize];
    PFObject *testObject = [PFObject objectWithClassName:@"IAP"];
    testObject[@"TEST"] = @"Purchase Successful";
    [testObject saveInBackground];
}
if ([transaction.payment.productIdentifier isEqualToString:@"company.app.iap"]){
    [[NSUserDefaults standardUserDefaults] setObject: @"YES" forKey:KEY];
    [[NSUserDefaults standardUserDefaults] synchronize];
    PFObject *testObject = [PFObject objectWithClassName:@"IAP"];
    testObject[@"TEST"] = @"Purchase Successful";
    [testObject saveInBackground];
}
if ([transaction.payment.productIdentifier isEqualToString:@"company.app.iap"]){
    [[NSUserDefaults standardUserDefaults] setObject: @"YES" forKey:KEY];
    [[NSUserDefaults standardUserDefaults] synchronize];
    PFObject *testObject = [PFObject objectWithClassName:@"IAP"];
    testObject[@"TEST"] = @"Purchase Successful";
    [testObject saveInBackground];
}
if ([transaction.payment.productIdentifier isEqualToString:@"company.app.iap"]){
    [[NSUserDefaults standardUserDefaults] setObject: @"YES" forKey:KEY];
    [[NSUserDefaults standardUserDefaults] synchronize];
    PFObject *testObject = [PFObject objectWithClassName:@"IAP"];
    testObject[@"TEST"] = @"Purchase Successful";
    [testObject saveInBackground];
}
if ([transaction.payment.productIdentifier isEqualToString:@"company.app.iap"]){
    [[NSUserDefaults standardUserDefaults] setObject: @"YES" forKey:KEY];
    [[NSUserDefaults standardUserDefaults] synchronize];
    PFObject *testObject = [PFObject objectWithClassName:@"IAP"];
    testObject[@"TEST"] = @"Purchase Successful";
    [testObject saveInBackground];
}
if ([transaction.payment.productIdentifier isEqualToString:@"company.app.iap"]){
    [[NSUserDefaults standardUserDefaults] setObject: @"YES" forKey:KEY];
    [[NSUserDefaults standardUserDefaults] synchronize];
    PFObject *testObject = [PFObject objectWithClassName:@"IAP"];
    testObject[@"TEST"] = @"Purchase Successful";
    [testObject saveInBackground];
}
if ([transaction.payment.productIdentifier isEqualToString:@"company.app.iap"]){
    [[NSUserDefaults standardUserDefaults] setObject: @"YES" forKey:KEY];
    [[NSUserDefaults standardUserDefaults] synchronize];
    PFObject *testObject = [PFObject objectWithClassName:@"IAP"];
    testObject[@"TEST"] = @"Purchase Successful";
    [testObject saveInBackground];
}
// NEW CODE ^^

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

Don't forget to add #import <Parse/Parse.h> to the top of your header.h file.

I'm not quite sure there are ANY other methods out there like this. It's pretty cool, so enjoy and have fun watching your in-app purchase notifications appear in real time!

Creagen
  • 478
  • 5
  • 17