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

For my recent sponsorship of AppStories’ 99th episode with 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 StackOverflow questions, 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 🙂

Yoink v1.1 for iPad and iPhone now available

Yoink for iOS Icon

I’m happy to announce the immediate availability of Yoink v1.1 for iPad and iPhone.
It’s currently being featured on the App Store under “New Apps We Love”.

What’s Yoink for iOS 11?

Yoink strives to improve and simplify drag and drop and speed up your workflow.
It accepts almost anything you can drag, copy or share on your iPad and stores it for later use. This way, your fingers are free for more important things.

It’s a storage place for anything you might need later – an image from a website, a text snippet from a document, a URL, etc.

Here’s a quick YouTube video of how it works (check your sound, there’s some music) :

And a second one (with music, again) :

What’s New in Yoink v1.1?

Aside from numerous improvements and bug fixes, like an improved keyboard extension, the ability to create stacks by dragging items onto one another, smarter Spotlight indexing and a new take on the Share extension (it doesn’t need confirmation anymore for adding items), there are a lot of new features:

Clipboard Integration
When you open Yoink and have something new on your clipboard, Yoink will automatically offer to store it for you.
(You can configure this so Yoink doesn’t ask at all, and anything new gets automatically stored when you launch the app).

Download URLs
When you use Yoink’s Action extension to send something from another app to Yoink, the extension automatically detects if it’s a URL you are saving.
If that’s the case, you have the option of downloading the file the URL points to instead of saving the link to the URL in Yoink.
An optional notification can inform you about finished or failed downloads.

Today Widget
From the Today Widget, you can quickly copy the most recent items stored in Yoink to your clipboard, and save content from your clipboard in Yoink.

Quicker Sharing, Copying
Instead of having to tap Edit any time you’d like to share an item from Yoink with another app, each item and stack in Yoink now has a button that lets you quickly access those options.

Better iPhone Awareness
Quick Actions (via 3D Touch on the Home screen) for quickly adding your clipboard contents from your home screen, or to start a download.
Peek and Pop for items and stacks in Yoink
Full iPhone X  support

URL Scheme
You can now communicate with Yoink from other apps using Yoink’s URL scheme.
For more information, please visit the Yoink for iOS Usage Tips website and read Tip #6 🙂

File Provider
Finally. After being rejected by Apple for the initial version of Yoink (over and over again), the file provider extension has now been approved and is part of the Yoink experience on your iOS device.
Any app that uses the documents browser can now access items stored in Yoink.

Pricing and Availability

Yoink for iOS is available on the App Store right now, a little longer for the introductory price of $2.99 (€3,49). An iPad or iPhone with iOS 11 is required.

Yoink is also available on the Mac.

Links

Yoink for iPad and iPhone Website: https://eternalstorms.at/yoink/ios
Yoink for iOS Usage Tips: https://eternalstorms.at/yoink/ios/tips
Yoink for iPad and iPhone on the App Store: https://itunes.apple.com/app/id1260915283?mt=8
Yoink for iOS Press Kit: https://eternalstorms.at/press/Yoink-iPad-1-Press-Kit.zip

Yoink for Mac Website: https://eternalstorms.at/yoink/mac
Yoink for Mac Usage Tips: https://eternalstorms.at/yoink/mac/tips
Yoink on the Mac App Store: https://itunes.apple.com/app/id457622435?mt=12
Yoink for Mac Press Kit: https://eternalstorms.at/press/Yoink-3-Press-Kit.zip

Eternal Storms Software Logo

– – – Do you enjoy my blog and/or my software? – – –
Stay up-to-date on all things Eternal Storms Software and join my low-frequency newsletter (one mail a month at most).
Thank you 🙂

Yoink for iPad and iPhone now available – Improve and Simplify Drag and Drop

Yoink for iOS Icon

It’s here.
After an unnecessarily long, extended time in App Review, I’m so happy to be able to tell you that Yoink for iPad and iPhone with iOS 11 is now finally available on the App Store.

What’s Yoink for iPad and iPhone?

Yoink strives to improve and simplify drag and drop and speed up your workflow.
It accepts almost anything you can drag, copy or share on your iPad and stores it for later use. This way, your fingers are free for more important things.

Here’s a quick YouTube video of how it works (check your sound, there’s some music) :

How can I add stuff to Yoink?

Obviously, especially on iPad, Yoink was designed for drag’n’drop use, and using it as a Slide-Over or Side-by-Side app, I’d say that’s the best way to use it. Just slide Yoink over any app you’re in and drag to it anything you need later. Then slide it back out, if it’s in your way.
There are, however, other ways to add items to Yoink. It can grab the contents from your clipboard and it offers a Action / Share extension, so that whenever you use a Share sheet in iOS, you can add that shared item to Yoink – no drag and drop required. The app doesn’t even have to run.

How do I get stuff out of Yoink?

That’s just as easy. Either use drag and drop, or copy the items, or share them from within the app.
Yoink comes with a custom keyboard extension, which lets you use items you’ve stored in Yoink without having to leave the active app you’re currently editing text in – just switch to Yoink’s keyboard and drag out (or copy, if you’re on iPhone) the items to the destination in your text.
All items in Yoink are indexed by Spotlight, so you can use the system-wide search to find items. The results are draggable as well.

Yoink Custom Keyboard

How are items in Yoink represented?

Yoink creates a rich preview for every item you add, so that, at a glance, easy identification is possible (for example, web link items show part of the website, map locations show a preview using Apple Maps).
A full look at the item is available by tapping onto the item.

Deleting Items

Yoink doesn’t delete items right away. Instead, like in Photos.app, items you drag out of Yoink or delete are put into the Trash, where they remain for a specified amount of time, after which they’re really deleted.
So should you find you still need an item, you can restore it right away.

Pricing and Availability

Yoink for iOS is available on the App Store right now, for the introductory price of $2.99 (€3,49) – it will rise in November.
An iPad or iPhone with iOS 11 is required.

Yoink is also available on the Mac, and for the occasion, its price has also been reduced for a limited time! 🙂

Links

Yoink for iPad and iPhone Website: https://eternalstorms.at/yoink/ios
Yoink for iOS Usage Tips: https://eternalstorms.at/yoink/ios/tips
Yoink for iPad and iPhone on the App Store: https://itunes.apple.com/app/id1260915283?mt=8
Yoink for iOS Press Kit: https://eternalstorms.at/press/Yoink-iPad-1-Press-Kit.zip

Yoink for Mac Website: https://eternalstorms.at/yoink/mac
Yoink for Mac Usage Tips: https://eternalstorms.at/yoink/mac/tips
Yoink on the Mac App Store: https://itunes.apple.com/app/id457622435?mt=12
Yoink for Mac Press Kit: https://eternalstorms.at/press/Yoink-3-Press-Kit.zip

Delay of Release

I had planned and was ready for releasing Yoink for iPad together with iOS 11, on September 19th, 2017. As you may know, that didn’t work out. The app was ready, I was set, but App Review wasn’t happy with the app and so I missed the date, while other, let’s say similar apps, were allowed in.
After a lot of discussion by mail and phone with the App Review team, and having to remove some things from the app, Yoink was finally approved yesterday.

Thank you for your patience, and I hope you enjoy the app 🙂 Please spread the word and if you have the time and like the app, please consider leaving a little review on the App Store – it would mean a lot to me!

Eternal Storms Software Logo

– – – Do you enjoy my blog and/or my software? – – –
Stay up-to-date on all things Eternal Storms Software and join my low-frequency newsletter (one mail a month at most).
Thank you 🙂