iOS Receipt Validation through Node.js using lambda

Question!

I am developing an iOS app in Swift and attempting to implement receipt validation for an in-app purchase. I couldn't figure out how to achieve this in Swift, so instead I tried having my app send the request through a Lambda function writtin in Node.js, after seeing Giulio Roggero's example in this question. My Swift code looks like this:

let receiptPath = Bundle.main.appStoreReceiptURL?.path
    if FileManager.default.fileExists(atPath: receiptPath!){
        var receiptData:NSData?
        do{
            receiptData = try NSData(contentsOf: Bundle.main.appStoreReceiptURL!, options: NSData.ReadingOptions.alwaysMapped)
        }
        catch{
            print("ERROR: " + error.localizedDescription)
        }
        let receiptString = receiptData?.base64EncodedString(options: .endLineWithLineFeed)
        let invocationRequest = AWSLambdaInvokerInvocationRequest()
        invocationRequest?.functionName = "sendReceiptRequest"
        invocationRequest?.invocationType = AWSLambdaInvocationType.requestResponse
        invocationRequest?.payload = ["receipt-data" : receiptString!, "password" : SUBSCRIPTION_SECRET]

        let lambdaInvoker = AWSLambdaInvoker.default()
        lock()
        lambdaInvoker.invoke(invocationRequest!).continue(with: AWSExecutor.mainThread(), with: { (task:AWSTask!) -> AnyObject! in
            if task.error != nil {
                self.sendErrorPopup("Error: \(task.error?.localizedDescription)")
            } else {
                print("TOKEN: ", task.result)
            }
            self.unlock()
            return nil
        })}

My Lambda node.js function looks like this, following the example:

function (receiptData_base64, password, production, cb)
{
var url = production ? 'buy.itunes.apple.com' : 'sandbox.itunes.apple.com'
var receiptEnvelope = {
    "receipt-data": receiptData_base64,
    "password":password
};
var receiptEnvelopeStr = JSON.stringify(receiptEnvelope);
var options = {
    host: url,
    port: 443,
    path: '/verifyReceipt',
    method: 'POST',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength(receiptEnvelopeStr)
    }
};

var req = https.request(options, function(res) {
    res.setEncoding('utf8');
    res.on('data', function (chunk) {
        console.log("body: " + chunk);
        cb(true, chunk);
    });
    res.on('error', function (error) {
        console.log("error: " + error);
        cb(false, error);
    });
});
req.write(receiptEnvelopeStr);
req.end();
}

However, when running this code, either through a lambda test or through my app, I get an error message that simply says Response body: {"errorMessage":"true"}. I've noticed that if I tweak the code I can create more expected errors- For instance, if I have some other value for the receipt-data, I get a 21002 error code in response, and if I change "production" to true, I get a 21007 error. Part of the problem is that I don't know exactly how the callback is supposed to work-- is the block inside https.request correct for what I'm trying to do in Swift? I get the impression that the receipt-data is correctly formatted since changing it yields a different result, so why is the end result still an error?

EDIT:

Something I previously didn't notice is that when I run the Lambda function, the line "body: (receipt data)" appears, where (receipt data) is the base 64 encoded data I sent to the function. This makes me suspect I'm not reaching the error callback block at all, and that the error has something to do with the way I send the result of the callback back to my app. What is this block:

var req = https.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
    console.log("body: " + chunk);
    cb(true, chunk);
});
res.on('error', function (error) {
    console.log("error: " + error);
    cb(false, error);
});
});

supposed to do? Is it possible I need to enable some permission to receive the callback?



Answers

Ultimately I solved the problem by following this example and using Python instead of Node.js. I still don't know exactly what was wrong with my code before, but base-64 encoding in Swift and then sending the receipt data and password to a Python Lambda function gave me the correct result.



You're first argument is the path to the executable. -> c://.../watcher.exe. The second is the directory to watch.

It's crashing when you print args[2] because there isn't a 3rd argument. If you wanted to add one in VS2015, you would click on the solution->properties->debug. You can set the command line arguments here under "Command line arguments." You should also be able to debug just by clicking on a line and adding breakpoints.

By : Ryan


I would do a little pre-processing before drawing the numbers to get a list of possible ranges. So let's assume we have a Range structure like so:

/// <summary> A possible range of values. </summary>
public struct Range
{
    /// <summary> Min value, inclusive. </summary>
    public readonly double Min;
    /// <summary> Max value, inclusive. </summary>
    public readonly double Max;
    public Range(double min, double max) { Min = min; Max = max; }
    /// <summary> Range length, distance between Min and Max. </summary>
    public double Length { get { return Max - Min; } }
}

And another structure RangeList which holds several ranges together. Range list also contains a cumulative length array of successive length sums of your Ranges, like so:

/// <summary> All possible ranges grouped together. </summary>
public struct RangeList
{
    /// <summary> Possible range. </summary>
    public readonly Range[] Ranges;
    /// <summary> Sum of each range length. </summary>
    public readonly double Length;
    /// <summary> Cumulative lengths values of each ranges. </summary>
    public readonly double[] CumulLengths;
    public RangeList(Range[] ranges)
    {
        Ranges = ranges;
        Length = 0;
        CumulLengths = new double[ranges.Length];
        for (var i = 0; i < ranges.Length; ++i)
        {
            Length += ranges[i].Length;
            CumulLengths[i] = Length;
        }
    }
}

We can then write easily a function that creates a RangeList from a given list of excluded ranges:

    /// <summary> Get possible ranges to draw from, considering exclusions. </summary>
    public static RangeList GetRangeList(Range range, params Range[] exclusions)
    {
        var ranges = new List<Range>();
        ranges.Add(range);
        if (exclusions != null)
        {
            foreach (var exclusion in exclusions)
            { // progressively eat latest range added to the list, cutting exclusions.
                var lastRange = ranges[ranges.Count - 1];
                if (exclusion.Min < lastRange.Max)
                {
                    ranges[ranges.Count - 1] = new Range(lastRange.Min, exclusion.Min);
                    if (exclusion.Max < lastRange.Max)
                    {
                        ranges.Add(new Range(exclusion.Max, lastRange.Max));
                    }
                }
            }
        }
        return new RangeList(ranges.ToArray());
    }

This method relies on several assumptions, including that not all space is excluded, exclusions are not overlapping, and exclusions are given in ascending order. It is then straight-forward to draw a number from the possible ranges:

    /// <summary> Assume exclusions are also given in ranges. </summary>
    public static double RangeWithExclusions(this Random random, Range range, params Range[] exclusions)
    {
        var rangeList = GetRangeList(range, exclusions);
        var rnd = random.NextDouble() * rangeList.Length;
        var rangeIndex = Array.BinarySearch(rangeList.CumulLengths, rnd);
        if (rangeIndex < 0)
        { // 'unlucky', we didn't hit a length exactly
            rangeIndex = ~rangeIndex;
        }
        var previousLength = rangeIndex > 0 ? rangeList.CumulLengths[rangeIndex - 1] : 0;
        var rndRange = rangeList.Ranges[rangeIndex]; // result range of our random draw
        return rndRange.Min + (rnd - previousLength); // scale rnd back into range space
    }

The following NUnit test demonstrate how to use the solution:

[TestFixture]
public class TestRandom
{
    [Test]
    public void Tests()
    {
        var random = new Random();
        double rnd;
        rnd = random.RangeWithExclusions(new Range(0, 1));
        Assert.IsTrue(rnd >= 0 && rnd <= 1);
        rnd = random.RangeWithExclusions(new Range(-100, 1));
        Assert.IsTrue(rnd >= -100 && rnd <= 1);
        rnd = random.RangeWithExclusions(new Range(0, 1), new Range(0.1, 0.9));
        Assert.IsTrue(rnd >= 0 && rnd <= 1 && (rnd <= 0.1 || rnd >= 0.9));
        rnd = random.RangeWithExclusions(new Range(0, 1), new Range(0, 0.9));
        Assert.IsTrue(rnd >= 0 && rnd <= 1 && (rnd >= 0.9));
        rnd = random.RangeWithExclusions(new Range(0, 1), new Range(0.2, 0.4), new Range(0.6, 0.8));
        Assert.IsTrue(rnd >= 0 && rnd <= 1 && (rnd <= 0.2 || rnd >= 0.4) && (rnd <= 0.6 || rnd >= 0.8));
    }
}

Hope this helps

By : pstrato


This video can help you solving your question :)
By: admin