使用 lambda 通过 Node.js 验证 iOS 收据

Posted

技术标签:

【中文标题】使用 lambda 通过 Node.js 验证 iOS 收据【英文标题】:iOS Receipt Validation through Node.js using lambda 【发布时间】:2017-02-07 01:04:13 【问题描述】:

我正在用 Swift 开发一个 ios 应用,并尝试为应用内购买实现收据验证。我不知道如何在 Swift 中实现这一点,所以在看到 this 问题中的 Giulio Roggero 示例后,我尝试让我的应用程序通过 Node.js 中的 Lambda 函数发送请求。我的 Swift 代码如下所示:

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
        )

我的 Lambda node.js 函数如下所示:

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();

但是,在运行此代码时,无论是通过 lambda 测试还是通过我的应用程序,我都会收到一条简单的错误消息 Response body: "errorMessage":"true"。我注意到,如果我调整代码,我可以创建更多预期的错误——例如,如果我对收据数据有一些其他值,我会得到一个 21002 错误代码作为响应,如果我将“生产”更改为 true ,我收到 21007 错误。部分问题是我不知道回调应该如何工作——https.request 中的块是否适合我在 Swift 中尝试做的事情?我的印象是收据数据格式正确,因为更改它会产生不同的结果,那么为什么最终结果仍然是错误?

编辑:

我之前没有注意到的是,当我运行 Lambda 函数时,会出现“body: (receipt data)”行,其中 (receipt data) 是我发送给函数的 base 64 编码数据。这让我怀疑我根本没有到达错误回调块,并且该错误与我将回调结果发送回我的应用程序的方式有关。这个块是什么:

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);
);
);

应该怎么做?我是否可能需要启用一些权限才能接收回调?

【问题讨论】:

您应该尝试对您发送的 base64 收据数据进行 % 编码。特别是 + 需要替换为 %2b。另外我建议将您的订阅密码放在 node.js 代码中,而不是从您的应用程序中传递它 我尝试使用receiptString =receiptString!.replacingOccurrences(of: "+", with: "%2b"),但我仍然得到相同的结果“errorMessage:true”。但是,我注意到在 Lambda 中,函数实际上到达了“console.log("body:" + chunk);" 行。我真的不明白这应该做什么,所以现在我想知道我是否真的得到了 Apple 的回复,但是我将它发送回我的应用程序的方式有问题。 【参考方案1】:

对于以后发现此问题的任何人,潜在问题:

    这里最大的问题是Content-Type 不正确。您需要发布 JSON,而不是 url 编码;虽然代码这样做,但它使用错误的类型来告诉服务器它正在使用什么格式。应该是 application/json 而不是 application/x-www-form-urlencoded。 发送到 Apple 服务器的 Base64不得(至少截至上周)包含行尾。我不知道这是这里的问题,但这是我过去遇到的问题,.endLineWithLineFeed 让我很怀疑。 用于在服务器之间切换的算法。该方法不应允许在生产和沙盒之间切换,而是应尝试验证生产服务器的收据。如果结果对象具有status21007,则代码应尝试对沙盒服务器进行相同的收据。一旦收据被解码和验证,客户端可以检查收据以查看它是针对哪个服务器进行验证的,并(根据需要)忽略沙盒收据。

【讨论】:

【参考方案2】:

这对我有用:

'use strict'
var AWS = require('aws-sdk')
var https = require('https')

exports.handler = (event, context, callback) => 

    var payload = JSON.stringify(
        'receipt-data': event.arguments.input.base64_receipt
    )

    function generateOptions(url) 
        return 
            host: url,
            port: 443,
            path: '/verifyReceipt',
            method: 'POST',
            headers: 
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(payload)
            
        
    

    function validateReceipt(url, payload, errorCallback) 
        var req = https.request(generateOptions(url), function(res) 
            res.setEncoding('utf8')
            res.on('data', function(chunk) 
                var data = JSON.parse(chunk)
                // success
                callback(null, event)
            )
            res.on('error', function(error) 
                //error
                errorCallback ? errorCallback() : callback('There was an error validating the transaction', event)
            )
        )
        req.write(payload)
        req.end()
    

    // attempt to validate on production and sandbox
    validateReceipt('buy.itunes.apple.com', payload, function() 
        validateReceipt('sandbox.itunes.apple.com', payload)
    )

【讨论】:

【参考方案3】:

最终我通过遵循this 示例并使用Python 而不是Node.js 解决了这个问题。我之前仍然不知道我的代码到底出了什么问题,但是在 Swift 中进行 base-64 编码,然后将收据数据和密码发送到 Python Lambda 函数给了我正确的结果。

【讨论】:

【参考方案4】:

在您的 Swift 代码中,检查“if task.error != nil”然后您遇到了错误

    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)
    

但在您的节点代码中,您以这种方式调用回调:

cb(true, chunk);

第一个参数是你的错误,它不会是零。您应该将此行替换为:

cb(null, chunk);

这样你就会得到正确的 Json

【讨论】:

以上是关于使用 lambda 通过 Node.js 验证 iOS 收据的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法从 node.js 同步调用 AWS Lambda?

从本地系统而不是S3上运行的node.js应用程序调用AWS Lambda

使用 Node.js 和 AWS Lambda 将 S3 文件的内容记录到 postgres 表

Node.js Lambda 函数从 REST 调用将“响应无效”返回给 Alexa 服务模拟器

使用 Node.JS 调用 AWS 胶水的 lambda 函数不使用 console.log 的原因是啥?

实现Lambda函数以通过SSM运行命令