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 v3.5 for Mac – Handoff and Clipboard History

Yoink for Mac App Icon

I’m very happy to announce the immediate availability of Yoink v3.5 for Mac.
It’s a free upgrade for everyone who has already purchased the app and brings two great new features, as well as compatibility improvements and bug fixes.

Yoink Overview

Yoink running on a MacBook Pro

What is Yoink?

Yoink offers a temporary place for file- and app-content drags to free your mouse so you can more easily and quickly navigate to the actual destination of your drag.
This is especially helpful when it comes to moving and copying files between different windows, spaces or (fullscreen-) apps.

How does Yoink fit into my workflow?

Yoink stays in the background most of the time, waiting for you to drag someting. The app fades in at the edge of your screen when you start a drag, like a file in Finder, or app-content like an image from a website, or text from a document.
Drag your files to Yoink, and your mouse is free for you to navigate more easily and comfortably.
Yoink will hold on to the files you drag to it until you drag them out again.

The app can be customized in a number of ways. You can set up where it should appear (at either side of your screen, top, center or bottom; or at the mouse cursor), when it should appear (when a drag starts, or when a drag reaches the edge of your screen) and what apps it should (or should not) appear in.
If a file drag contains multiple files, a Stack is created so you can drag them out together again easily. Stacks can also be split up if you’d like to drag out one specific file in that drag.
QuickLook is available for all files you add to Yoink, as icon previews for quick identification and as full previews for a detailed look.
A keyboard shortcut lets you hide Yoink if you currently don’t need it, and show it again when you do.

What’s New in Yoink v3.5?

Yoink Clipboard History Today Widget

Today Widget in macOS' Notification Center

Yoink offers you a history of your clipboard’s contents with a convenient, out-of-the-way Today Widget. With it, you can copy previous items back to your clipboard, or send them straight to Yoink.

Handoff

Yoink's Handoff on macOS

You can now transfer files between Macs, iPads and iPhones (separate Yoink for iOS app required, available on the App Store) using Handoff. Selected files are transferred right away, whereas if there’s no selection, you can pick specific items on the receiving device.

Compatibility Improvements

Aside from improvements regarding the compatibility with various apps, Yoink also now fully supports macOS Mojave 10.14, including its Dark and Light appearances.

Yoink on macOS Mojave in Dark and Light Modes

This comes with an override where you can explicitly choose Yoink’s dark or light appearance, ignoring the setting in System Preferences.

Pricing and Availability

Yoink for Mac is available on the Mac App Store for the price of $7.99 / £7.99 / €8,99, with a free, 15-day trial available on its website.
It requires at least macOS Lion 10.7.3, macOS High Sierra 10.13 or newer is recommended.
The app is localized in English, German, French, Italian, Chinese (Simplified), Korean, Japanese, Portuguese (Portugal) and Portuguese (Brazil).

Yoink is also available for iPad and iPhone, exclusively available on the App Store for the price of $5.99 / £5.99 / €6,99.

Links

Yoink for Mac – Website
Yoink for Mac – Mac App Store
Yoink for Mac – Usage Tips
Yoink for Mac – Press Kit
Yoink for Mac – App Preview Video (Basic Functionality)
Yoink for Mac – App Preview Video (Today Widget)

Yoink for iPad and iPhone – Website
Yoink for iPad and iPhone – App Store

Eternal Storms Software – Website
Eternal Storms Software – Blog
Eternal Storms Software – Twitter
Eternal Storms Software – YouTube
Eternal Storms Software – Facebook
Eternal Storms Software – Instagram

I’m looking forward to seeing and hearing what you think about this update. I hope you’ll enjoy it 🙂

If you have any feedback or questions, please don’t hesitate to write me!

With warm regards
– Matt

Maintenance-Updates for Yoink for Mac and ScreenFloat

Today, I released two quick updates to my apps Yoink for Mac and ScreenFloat. Here’s what’s changed.

Yoink for Mac v3.4.3

Yoink for Mac app icon

– Fixed a rare crash when trying to share files
– Fixes a bug where the keyboard shortcut would act up after deleting a file held by Yoink in Finder or using the app’s Share extension
– Fixed a bug where, after changing the screen’s resolution, Yoink would be misplaced

Links:

Yoink for Mac Website (with free, 15-day demo)
Yoink on the Mac App Store

ScreenFloat v1.5.15

ScreenFloat App Icon

– Fixes a rare crash when dragging the mini-icon of a floating shot


Links:

ScreenFloat Website (with free, 15-day demo)
ScreenFloat on the Mac App Store