Side Project: Promo Codes (Part 1 – Intro and Promo Code Creation)

For my recent sponsorship of AppStories’ 99th episodewith Yoink for Mac, iPad and iPhone, I also offered a couple of promo codes to the lucky first people who got to them.
(Developers can create create promo codes for their app in the Mac- and iOS App Store, to give away to others so they can download their app for free. This way, members of the press can try out your app for free, or you can hold a give-away of your app).

Some users find promo code redemption a little clumsy – and I agree. You have to copy the promo code, navigate to the iOS App Store (or Mac App Store, or iTunes on Mac for iOS apps), paste the code and redeem.

I thought it would be nice to have a “landing page” for each promo code, where the user would be able to click a link and be taken to the correct redemption page with the code already filled in, or at least explain how to do it manually.
It also occurred to me it would be neat to track promo codes – like their redemption status and expiration dates.

Born was the idea for ‘Promo Codes’.

In detail, I wanted the following:

  • Have a Mac-, iPhone- and iPad app, well integrated into their respective OS
  • Be able to create promo codes without having to go to App Store Connect
  • Store those (and manually created) promo codes in a web database so I can access them from anywhere using the apps (basically, sync)
  • Track them
    – when were they created
    – what were they created for (or for whom)
    – when would they expire
    – whether they were redeemed or still unused
    – the popularity of a code (view count, last access date)
  • Have a landing page for users to make redemption easier

Of course, the whole thing would hang on my ability to be able to programmatically request promo codes from App Store Connect, but first, I wanted to check out the alternatives.

The Alternatives

“Alternatives” is saying too much, as there’s not a huge market around this, apparently. It’s more like “Alternative”, because I really found just one good solution:

Alternative 1 – Tokens.app

Tokens is an awesome app that lets you do exactly what I detailed above (apart from an iOS app), and – although I haven’t used it personally – I can only recommend it, as I hear only good things, and it’s being recommend all over the place by developers.
If you’re looking for a trusted, tested way of managing your promo codes – go try it out, they do have a demo!

Alternative 2 – No tracking at all (duh)

The other alternative was to forget about it and keep doing what I have been doing regarding promo codes – nothing.
After all, I’ve been doing just fine not worrying about them at all ever since I became an indie developer.

But what fun would that have been?

My Own Solution

I almost purchased Tokens, but for some reason saw a personal challenge in this, to see if I could pull off creating my own service.
I wasn’t worried about the Mac/iOS side of the project. Rather, I was apprehensive about my server-side “skills” – of which I have none.
I “know” a little PHP, and that just from piecing together code from tutorials and StackOverflowquestions, as well as the occasional, small service script here and there (for example, to handle newsletter sign-up from my apps).
Another big question mark was whether I could manage to programmatically create promo codes using App Store Connect – there wouldn’t have been much of a point in the apps if I wasn’t able to do that.

Having a clear vision of what I wanted to achieve, there would be four parts to this project:

  1. A PHP web service with a database (MySQL) for storing all necessary information:
    I wanted a JSON-based REST API that would allow the following:
    – Storing promo codes in a database (with all necessary metadata like dates, campaign titles and view counts)
    – Deleting promo codes
    – Updating specified promo codes
    – Retrieving all promo codes
    – APNs Push to my apps when codes are added or deleted
  2. A user-facing PHP script that would act as the landing page for the promo code they’re about to redeem:
    – Shows the promo code, the app the promo code is for and whether the code is still available
    – Optionally, if the promo code has already been claimed, offer another one from the same pool of that campaign
    – Allow for easy redemption by just clicking a link
  3. A Mac app that:
    – shows me all promo codes and their metadata
    – can request promo codes for any of my apps via App Store Connect
    – can add manually created promo codes
    – can delete promo codes
    – has deep system integration / services for convenience
    – reminds me about expiring promo codes via notifications
  4. An iPad and iPhone app that:
    – does the same as the Mac app

And with that, I started looking into promo code creation.

Programmatically Requesting Promo Codes

The credit regarding the REST APIs used here goes entirely to fastlane (github).
Those people are doing amazing work and without their open source code, I wouldn’t have been able to do any of this – my code was pieced together from theirs.

Requesting promo codes consists of these 7 steps:

  1. Request a “service key” to communicate with App Store Connect
  2. Log in to App Store Connect using the account’s username and password
  3. Retrieve the account’s session data
  4. Selecting a team
  5. Selecting an app for which to request promo codes and retrieving the promo code info (how many codes left?)
  6. Request a certain amount of promo codes
  7. Load the app’s promo code history to retrieve the newly created codes

1) Request a “service key”

We’re off to an easy start:
Call GET https://olympus.itunes.apple.com/v1/app/config?hostname=itunesconnect.apple.com
and get ‘authServiceKey’ from the JSON this returns – done.

2) Log in to App Store Connect

Custom UI for logging in to App Store Connect

This is where it gets a bit more complicated.
First, call POST ‘https://idmsa.apple.com/appleauth/auth/signin’ with this JSON body:

{
“accountName” : <username, apple id>,
“password” : <password>,
“rememberMe” : true
}

and these headers:

Content-Type: application/json
X-Requested-With: XMLHttpRequest
X-Apple-Widget-Key: <the “service key” you retrieved in step 1>
Accept: application/json, text/javascript
Connection: keep-alive

If you receive an HTTP Status of 200 in the response, you’re free to go to the next step (Retrieve the account’s session data) – the user either doesn’t have two-factor authorization set up, or the cookies are still valid from a previous session.
Chances are, though, that you won’t get 200, especially now that Apple is enforcing two-factor authentication.

So you’re more likely to receive an HTTP Status of 409.
– Remember the response’s “x-apple-id-session-id” and “scnt” header values, you’ll need them for the next few calls.
– Call GET ‘https://idmsa.apple.com/appleauth/auth‘ with these headers:

X-Apple-Id-Session-Id: <the according value you just saved>
X-Apple-Widget-Key: <the “service key” you got in step 1>
Accept: application/json
scnt: <the according value you just saved>

This will give us all the necessary information for proceeding – like the registered phone numbers (obfuscated, like you would see on the website, with ‘••••’ instead of digits).
Depending on the information we receive here, we can also figure out if a code has just been sent to the user’s devices (or to a phone number), or if we need to request one.

The user will have received a code at this point if:
– The returned dictionary contains a key “noTrustedDevices” with a boolean value of NO, or does not contain this key at all or
– The returned dictionary only contains one trusted phone number

Otherwise, the user hasn’t set up any devices to be trusted with their login credentials or the account has more than one phone number which the user has to pick from for verification.
In the latter case, we need to present the user with their obfuscated phone numbers, let them pick one and request a code via SMS for that number.
One phone number is represented in the response like this:

{
id = 1,
numberWithDialCode = “+1 •••• ••••••00”,
obfuscatedNumber = “•••• •••••00”,
pushMode = sms
}

The id is what we need for requesting a code via sms:
We call PUT ‘https://idmsa.apple.com/appleauth/verify/phone‘ with the following headers:

X-Apple-Id-Session-Id : <like before>
scnt : <like before>
X-Apple-Widget-Key : <like before>
Accept : application/json
Content-Type : application/json

and a JSON body like this:

{
phoneNumber : {id : <the id of the phone number the user picked>},
mode : sms
}

which, if everything goes well, will return an empty response and send a code via sms to the user’s chosen phone number.

Now that the user has a code, we can show the UI for entering the confirmation code, which we then again send back to Apple:
Depending on whether the code was sent to a trusted device, or by SMS, the URL to call and the JSON body to send are a bit different.
For a “trusted device code”, the URL is POST ‘https://idmsa.apple.com/appleauth/auth/verify/trusteddevice/securitycode‘, with this JSON body:

{
securityCode: {code : <the code the user received and then entered>}
}

For a code received by SMS, the URL is POST ‘https://idmsa.apple.com/appleauth/auth/verify/phone/securitycode‘, and this JSON body:

{
securityCode : {code : <the code the user received and then entered> },
phoneNumber : {id : <the id of the selected phone number>},
mode : sms
}

Call either of those with the usual headers and, if everything goes well, you’ll receive an empty response – otherwise, errors are returned (if the supplied code was wrong, for example).

3) Retrieve the account’s session data

Now that we’re logged in, we can continue with our actual objective – requesting promo codes.
First, we need to know what we’re dealing with – mainly, the teams the user is part of.
We do that by calling GET ‘https://olympus.itunes.apple.com/v1/session‘ with the headers

X-Requested-With : XMLHttpRequest
X-Apple-Widget-Key : <the auth key>
Accept : application/json, text/javascript

which will give us a JSON response and everything we need to know – the current user info (“user”), the currently signed-in team (“provider”) and all available teams (“availableProviders”).
In my implementation, I follow that call with another one to retrieve the currently selected team’s apps right away before updating the UI, but for the sake of this post, I’ll explain that in the following points.

4) Selecting a team

Custom UI for selecting a team and an app of that team

As the user might be part of several teams, we need to be able to change the currently selected team so we can retrieve its apps for promo code creation.
This is done by calling POST ‘https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/v1/session/webSession‘ with the usual headers and this JSON body:

{
contentProviderId : <the selected team id>,
dsId : <the logged in user’s personID>
}

This will update your session’s cookies so that the selected team is the active one.

5) Selecting an app and retrieving its promo code info

Custom UI with promo code info loaded for an app

Let’s retrieve the apps for the currently selected team.
It’s a simple GET call (with the usual headers) to ‘https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/manageyourapps/summary/v2‘ and it’ll return a JSON with all the infos we need about the apps. Depending on the UI implementation, we’re particularly interested in an app’s id (“adamId”), its name (“name”), its bundle ID (“bundleId”), vendor ID / sku (“vendorId”) and the platform it’s running on (“platformString”).
We can now present the apps to the user and let them select one.

With a selected app, we can retrieve its promo code info – front and foremost, if there are any promo codes left to be requested for the current version.
We call GET ‘https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/<adamIdOfTheSelectedApp>/promocodes/versions‘ and receive a JSON where we can
– the version and versionID of the app and
– calculate how many codes are left

6) Request a certain amount of promo codes

Knowing everything we need to know, we can finally request the desired amount of promo codes for the app.
We call POST ‘https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/<adamIdOfTheSelectedApp>/promocodes/versions/‘ with a JSON body like:

[
{
numberOfCodes : <amount of codes to request>,
agreedToContract : true
versionId : <the version id of the app retrieved earlier>
}
]

Careful, it’s an array this time, not a dictionary. Yup – that cost me a couple of hours.

It would be convenient if the response would contain the desired promo codes, but sadly, it doesn’t. All it contains is whether the creation was successful or not.
To actually get to the promo codes we need to

7) Load the app’s promo code history

The promo code history contains all promo codes created previously and just now, so we need to know when we started the request process so we can filter out codes we didn’t just create.
We call GET ‘https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/<adamIdOfTheSelectedApp>/promocodes/history‘ and parse the resulting JSON.
Specifically, we’re interested in the code itself, its creation- and expiration dates, its platform and version.

Source Code

You can find the source code (in form of a sample project) over at GitHub.
Enjoy :)

Advertisements

Zoom-Transition between NSViews

For Briefly, I needed a nice, subtle animation for switching between the detail soundtrack view and the reorderable list view. In OS X Yosemite 10.10.3’s Photos.app, I noticed something I liked very much.
When going into an album, for example, the current view is zoomed out of focus and the new view is zoomed in.

Zoomtransition Animation Gif

NSView+ESSViewCategory

I wrote a little category on NSView to do just that, it’s a one liner (ironically, in this pic, it’s more than one line) :

Line of code

It’s pretty self-explanatory. You pass in the view you want to transition from and the one you want to transition to, the type of transition (zooming in or out), the duration and an optional completionHandler that’s called when the animation ends.

Alternatively, it’s also available as an instance method where the view you call this on will be passed into the class method as fromView:

Instancemethod

The Views

For the transition to work, fromView has to be in a view hierarchy, toView shouldn’t. They should be the same size, otherwise more work on your part is necessary (which I had to do in Briefly because the NSPopover the views reside in resizes before / after the transition), but either way the code provided should give you a nice head start.

fromView’s superview is temporarily set to have a CALayer to make use of Core Animation during the transition. After the animation ends, the superview’s wantsLayer – state is reset to what it was before the animation. If we didn’t do this, the animation would appear sluggish.

ESSViewZoomTransition

As you can see in the gif above, there are two types of the transition:

ESSViewZoomTransitionZoomOut – the transition from the textView to the view with the checkboxes.
ESSViewZoomTransitionZoomIn the transition from the checkbox-view to the textView

How To Use NSView+ESSViewCategory

You’ll have to first add the NSView+ESSViewCategory.h and *.m files to your project.
Please note that the category imports <Quartz/Quartz.h> for Core Animation’s CAMediaTiming class, so you might have to add that framework to your project, too.

fromView
It has to be inside of a view hierarchy. Fades out during the transition.

toView
Can be in a different xib file (for example, a NSViewController) or in the same as fromView. It’s important that it is not already on screen somewhere. Fades in during the transition.

Once you have set up your views, either call the class method and pass fromView and toView as well as the other parameters or call the instance method on fromView.

How It Works

The method creates an NSImage of both toView and fromView, puts them into two NSImageViews that have the same frame as the views and animates those two NSImageViews accordingly (calling imageView.animator.frame = …; and imageView.animator.alphaValue = …; )
Because fromView’s superview temporarily gets a CALayer, .animator is powered by Core Animation, which makes for a much smoother animation than doing the same without a layer-backed view.

ImagecreationCreating an NSImage of toView.

So the views themselves aren’t actually resized, they’re just screenshotted, removed from view as we place the NSImageView on top of it, creating the illusion that nothing happened. Then we animate the NSImageViews and insert toView after the animation is done, removing both NSImageViews.

The Source Code

The repository (a sample OS X app) is available on Github.

It was developed (and tested) on OS X Yosemite 10.10.3 using Xcode 6.3.1, but should work on earlier versions of the operating system.

I have some more source code available here (or directly on my github profile page) if you’re interested. If you have any questions or feedback regarding my open source projects, please be sure to mail or tweet me – I’m looking forward to your feedback!

Enjoy!

ESSSquareProgressIndicator for iOS and OS X

ESSSquareProgressIndicator animating

What Is ESSSquareProgressIndicator?

It’s an indeterminate progress indicator originally developed for the iOS game ZEN.

It uses Core Animation (specifically, CAShapeLayers) to do its job and it’s pretty straight forward.

CGPathRefs, CAShapeLayer

CAShapeLayer has two animatable properties – -strokeStart and -strokeEnd (with a minimum value of 0.0 – start of path – and 1.0 – end of path). Going beyond 1.0 or below 0.0 doesn’t work.

So when trying to animate those values along a rectangular path – which was the first thing I tried when creating this – you do get a nice animation, but it ends at the 0.0/1.0 (which is, basically, the same) mark. So you end up with something like this:

glitchy progress indicator animation

The goal, then, was to animate beyond 1.0. The solution I came to – I’m sure there are other ways – was to use a second CAShapeLayer that animated alongside the first for the last/first part of the animation.

For a square, the values of the CAShapeLayer’s -strokeStart and -strokeEnd are as follows (starting at the top left corner):

CAShapeLayer strokeStart strokeEnd values

The CGPath begins and ends in the top left corner because I drew the path that way (you could make it start in the lower left corner or in the middle of a side, it all depends on where you start the CGPath with CGPathMoveToPoint).

The progress indicator starts at the middle of the left line and reaches to the middle of the top line. These are the two CAShapeLayers at work. The left part is one shape layer, the top line is another. I’ll do this in pictures I drew in code so it’s simpler to understand (the lines represent where the layer animated to, the dots are the invisible rest of the square).

Animating

Animation Part 1We start at this position. The line to the left is layer2(strokeStart:0.875, strokeEnd:1.0), the line at the top is layer1(strokeStart:0.0, strokeEnd:0.125).

Animation Part 2The transition to this is the reason we need two layers as we can not animate one layer beyond the 1.0 value.
So we animate layer2 to (1.0, 1.0) and at the same time animate layer1 to (0.0, 0.25) which makes it look like one line moving.

Animation Part 3This next part is easy. We animate layer1 to (0.75, 1.0).

Animation Part 4Lastly, we animate with two layers again to get to the initial position so we can repeat the whole thing from there on.
We animate layer2 from (0.75, 1.0) to (0.875, 1.0) and layer1 from (0.0, 0.0) to (0.0, 0.125) at the same time, again making it look like one line moving.

The Source Code

The repository (a sample iOS app, but the class works the same on OS X) is available on Github.

It was developed for iOS 7.1 but should work on earlier systems, and has been tested on OS X Yosemite, but should work on earlier systems as well.

You just drop in an ordinary UIView or NSView and set its class to ESSSquareProgressIndicator. Done. You can then set the color and stroke width right within Interface Builder thanks to the fairly new Xcode macros IB_Designable and IBInspectable.

Open Source

I have some more source code available here (or directly on my github profile page) if you’re interested. If you have any questions or feedback regarding this progress indicator, please be sure to mail or tweet me ;)

Enjoy!

—-
My name is Matt, I’m the developer of Eternal Storms Software. If you’d like to comment, you can catch me on twitter here: or by eMail.