无法使用订阅优惠购买
Couldn't purchase with Subscription offer
我正在努力使 In-App Purchase
订阅优惠有效。所以我从我们的服务器获得了编码的签名、随机数、时间戳和密钥标识符。我创建了一个 SKPaymentDiscount
对象并将其设置为 SKMutablePayment
对象的 paymentDiscount
。
在第一个弹出窗口中,它按预期显示修改后的价格 -> 输入密码并继续 -> 第二个弹出窗口:确认订阅:好的 -> 第三个弹出窗口:显示以下错误 无法购买 联系开发商了解更多信息。
我尝试为产品传递一个不适用的报价标识符。然后它抛出正确的错误说:这不能应用于此。
PromoOfferAPI.prepareOffer(usernameHash: "name", productIdentifier: "bundleid.product", offerIdentifier: "TEST10") { (result) in
switch result {
case let .success(discount):
// The original product being purchased.
let payment = SKMutablePayment(product: option.product)
// You must set applicationUsername to be the same as the one used to generate the signature.
payment.applicationUsername = "name"
// Add the offer to the payment.
payment.paymentDiscount = discount
// Add the payment to the queue for purchase.
SKPaymentQueue.default().add(payment)
break
case let .customFail(message):
print(message)
break
case let .failure(error):
print(error.localizedDescription)
break
}
}
无论我尝试多少次,它总是给我同样的错误。 无法购买 有关详细信息,请联系开发人员。如何解决此问题。任何帮助深表感谢。
提前致谢!
编辑 1:它永远不会进入 updatedTransactions
函数。它只记录 Finishing transaction for payment "bundleid.product" with state: failed.
编辑 2:收到错误: code - 12 (invalidSignature)。无法连接到 iTunes Store
Node.JS 生成编码签名的代码。
const UUID = require("uuid-v4");
const microtime = require('microtime');
const express = require('express');
const router = express.Router();
const EC = require("elliptic").ec;
const ec = new EC("secp256k1");
const crypto = require('crypto');
const privateKey = `-----BEGIN PRIVATE KEY-----
key goes here
-----END PRIVATE KEY-----`;
//const key = ec.keyFromPrivate(privateKey,'hex');
router.post('/',(req, res)=>{
const bundle_id = "bundle.id";
const key_id = "keyed";
const nonce = String(UUID()).toLowerCase();// Should be lower case
const timestamp = microtime.now();
const product = req.body.product;
const offer = req.body.offer;
const application_username = req.body.application_username;
const payload = bundle_id + '\u2063' + key_id + '\u2063' + product + '\u2063' + offer + '\u2063' + application_username + '\u2063' + String(nonce) + '\u2063' + String(timestamp)
let shaMsg = crypto.createHash("sha256").update(payload).digest();
let signature = ec.sign(shaMsg, privateKey, {canonical: true});
let derSign = signature.toDER();
let buff = new Buffer(derSign);
let base64EncodedSignature = buff.toString('base64');
let response = {
"signeture": base64EncodedSignature,
"nonce": nonce,
"timestamp": timestamp,
"keyIdentifier": key_id
}
res.type('json').send(response);
});
module.exports = router;
经过多次尝试和错误,解决了这个问题。基本上是因为错误的算法以及一些小问题。这是 Node.js 中的完整代码,希望对您有所帮助。
// https://developer.apple.com/documentation/storekit/in-app_purchase/generating_a_signature_for_subscription_offers
// Step 1
const appBundleID = req.body.appBundleID
const keyIdentifier = req.body.keyIdentifier
const productIdentifier = req.body.productIdentifier
const offerIdentifier = req.body.offerIdentifier
const applicationUsername = req.body.applicationUsername
const nonce = uuid4()
const timestamp = Math.floor(new Date())
// Step 2
// Combine the parameters into a UTF-8 string with
// an invisible separator ('\u2063') between them,
// in the order shown:
// appBundleId + '\u2063' + keyIdentifier + '\u2063' + productIdentifier +
// '\u2063' + offerIdentifier + '\u2063' + applicationUsername + '\u2063' +
// nonce + '\u2063' + timestamp
let payload = appBundleID + '\u2063' + keyIdentifier + '\u2063' + productIdentifier + '\u2063' + offerIdentifier + '\u2063' + applicationUsername + '\u2063' + nonce+ '\u2063' + timestamp
// Step 3
// Sign the combined string
// Private Key - p8 file downloaded
// Algorithm - ECDSA with SHA-256
const keyPem = fs.readFileSync('file_name.pem', 'ascii');
// Even though we are specifying "RSA" here, this works with ECDSA
// keys as well.
// Step 4
// Base64-encode the binary signature
const sign = crypto.createSign('RSA-SHA256')
.update(payload)
.sign(keyPem, 'base64');
let response1 = {
"signature": sign,
"nonce": nonce,
"timestamp": timestamp,
"keyIdentifier": keyIdentifier
}
res.type('json').send(response1);
我 运行 在测试他们提供的新 WWDC2019 示例 Node.js 服务器文件时遇到了同样的问题。按照readme进行操作后,我能够成功生成签名。
然而,令我惊讶的是,一个无效的签名看起来就像一个有效的签名,我花了一段时间才意识到我的签名是无效的。
我的错误如下:我使用 Alamofire 向我的服务器发出 GET 请求,如下所示:
AF.request("myserver:3000/offer", parameters: parameters).responseJSON { response in
var signature: String?
var keyID: String?
var timestamp: NSNumber?
var nonce: UUID?
switch response.result {
case let .success(value):
let json = JSON(value)
// Get required parameters for creating offer
signature = json["signature"].stringValue
keyID = json["keyID"].stringValue
timestamp = json["timestamp"].numberValue
nonce = UUID(uuidString: json["nonce"].stringValue)
case let .failure(error):
print(error)
return
}
// Create offer
let discountOffer = SKPaymentDiscount(identifier: offerIdentifier, keyIdentifier: keyID!, nonce: nonce!, signature: signature!, timestamp: timestamp!)
// Pass offer in completion block
completion(discountOffer) // this completion is a part of the method where this snippet is running
}
}
在 WWDC2019 Video on Subscription Offers 中提供的文件中,在 index.js 文件中,它们正在加载我根据请求传递的参数,如下所示:
const appBundleID = req.body.appBundleID;
const productIdentifier = req.body.productIdentifier;
const subscriptionOfferID = req.body.offerID;
const applicationUsername = req.body.applicationUsername;
然而,我的 alamofire 请求并没有在正文中传递参数,而是作为查询参数传递。因此,服务器正在生成一个带有空 appBundleID 以及其他空字段的签名!所以我将 index.js 的上述部分更改为以下内容:
const appBundleID = req.query.appBundleID;
const productIdentifier = req.query.productIdentifier;
const subscriptionOfferID = req.query.offerID;
const applicationUsername = req.query.applicationUsername;
我希望这可以帮助那些忽略了这一点的人。请原谅我的不安全 swift,但我希望你明白我的意思!
在我的+Apple 的 WWDC19 node impl 和上面的 impl 之间交换几天后,我发现我的问题不是设置 applicationUsername iOS 端来匹配 node.js 中使用的 applicationUsername。具体来说,设置折扣的 SKMutablePayment 对象 iOS 端。希望一些幸运的患者在比我花费更少的时间后看到这个。
我正在努力使 In-App Purchase
订阅优惠有效。所以我从我们的服务器获得了编码的签名、随机数、时间戳和密钥标识符。我创建了一个 SKPaymentDiscount
对象并将其设置为 SKMutablePayment
对象的 paymentDiscount
。
在第一个弹出窗口中,它按预期显示修改后的价格 -> 输入密码并继续 -> 第二个弹出窗口:确认订阅:好的 -> 第三个弹出窗口:显示以下错误 无法购买 联系开发商了解更多信息。
我尝试为产品传递一个不适用的报价标识符。然后它抛出正确的错误说:这不能应用于此。
PromoOfferAPI.prepareOffer(usernameHash: "name", productIdentifier: "bundleid.product", offerIdentifier: "TEST10") { (result) in
switch result {
case let .success(discount):
// The original product being purchased.
let payment = SKMutablePayment(product: option.product)
// You must set applicationUsername to be the same as the one used to generate the signature.
payment.applicationUsername = "name"
// Add the offer to the payment.
payment.paymentDiscount = discount
// Add the payment to the queue for purchase.
SKPaymentQueue.default().add(payment)
break
case let .customFail(message):
print(message)
break
case let .failure(error):
print(error.localizedDescription)
break
}
}
无论我尝试多少次,它总是给我同样的错误。 无法购买 有关详细信息,请联系开发人员。如何解决此问题。任何帮助深表感谢。
提前致谢!
编辑 1:它永远不会进入 updatedTransactions
函数。它只记录 Finishing transaction for payment "bundleid.product" with state: failed.
编辑 2:收到错误: code - 12 (invalidSignature)。无法连接到 iTunes Store
Node.JS 生成编码签名的代码。
const UUID = require("uuid-v4");
const microtime = require('microtime');
const express = require('express');
const router = express.Router();
const EC = require("elliptic").ec;
const ec = new EC("secp256k1");
const crypto = require('crypto');
const privateKey = `-----BEGIN PRIVATE KEY-----
key goes here
-----END PRIVATE KEY-----`;
//const key = ec.keyFromPrivate(privateKey,'hex');
router.post('/',(req, res)=>{
const bundle_id = "bundle.id";
const key_id = "keyed";
const nonce = String(UUID()).toLowerCase();// Should be lower case
const timestamp = microtime.now();
const product = req.body.product;
const offer = req.body.offer;
const application_username = req.body.application_username;
const payload = bundle_id + '\u2063' + key_id + '\u2063' + product + '\u2063' + offer + '\u2063' + application_username + '\u2063' + String(nonce) + '\u2063' + String(timestamp)
let shaMsg = crypto.createHash("sha256").update(payload).digest();
let signature = ec.sign(shaMsg, privateKey, {canonical: true});
let derSign = signature.toDER();
let buff = new Buffer(derSign);
let base64EncodedSignature = buff.toString('base64');
let response = {
"signeture": base64EncodedSignature,
"nonce": nonce,
"timestamp": timestamp,
"keyIdentifier": key_id
}
res.type('json').send(response);
});
module.exports = router;
经过多次尝试和错误,解决了这个问题。基本上是因为错误的算法以及一些小问题。这是 Node.js 中的完整代码,希望对您有所帮助。
// https://developer.apple.com/documentation/storekit/in-app_purchase/generating_a_signature_for_subscription_offers
// Step 1
const appBundleID = req.body.appBundleID
const keyIdentifier = req.body.keyIdentifier
const productIdentifier = req.body.productIdentifier
const offerIdentifier = req.body.offerIdentifier
const applicationUsername = req.body.applicationUsername
const nonce = uuid4()
const timestamp = Math.floor(new Date())
// Step 2
// Combine the parameters into a UTF-8 string with
// an invisible separator ('\u2063') between them,
// in the order shown:
// appBundleId + '\u2063' + keyIdentifier + '\u2063' + productIdentifier +
// '\u2063' + offerIdentifier + '\u2063' + applicationUsername + '\u2063' +
// nonce + '\u2063' + timestamp
let payload = appBundleID + '\u2063' + keyIdentifier + '\u2063' + productIdentifier + '\u2063' + offerIdentifier + '\u2063' + applicationUsername + '\u2063' + nonce+ '\u2063' + timestamp
// Step 3
// Sign the combined string
// Private Key - p8 file downloaded
// Algorithm - ECDSA with SHA-256
const keyPem = fs.readFileSync('file_name.pem', 'ascii');
// Even though we are specifying "RSA" here, this works with ECDSA
// keys as well.
// Step 4
// Base64-encode the binary signature
const sign = crypto.createSign('RSA-SHA256')
.update(payload)
.sign(keyPem, 'base64');
let response1 = {
"signature": sign,
"nonce": nonce,
"timestamp": timestamp,
"keyIdentifier": keyIdentifier
}
res.type('json').send(response1);
我 运行 在测试他们提供的新 WWDC2019 示例 Node.js 服务器文件时遇到了同样的问题。按照readme进行操作后,我能够成功生成签名。
然而,令我惊讶的是,一个无效的签名看起来就像一个有效的签名,我花了一段时间才意识到我的签名是无效的。
我的错误如下:我使用 Alamofire 向我的服务器发出 GET 请求,如下所示:
AF.request("myserver:3000/offer", parameters: parameters).responseJSON { response in
var signature: String?
var keyID: String?
var timestamp: NSNumber?
var nonce: UUID?
switch response.result {
case let .success(value):
let json = JSON(value)
// Get required parameters for creating offer
signature = json["signature"].stringValue
keyID = json["keyID"].stringValue
timestamp = json["timestamp"].numberValue
nonce = UUID(uuidString: json["nonce"].stringValue)
case let .failure(error):
print(error)
return
}
// Create offer
let discountOffer = SKPaymentDiscount(identifier: offerIdentifier, keyIdentifier: keyID!, nonce: nonce!, signature: signature!, timestamp: timestamp!)
// Pass offer in completion block
completion(discountOffer) // this completion is a part of the method where this snippet is running
}
}
在 WWDC2019 Video on Subscription Offers 中提供的文件中,在 index.js 文件中,它们正在加载我根据请求传递的参数,如下所示:
const appBundleID = req.body.appBundleID;
const productIdentifier = req.body.productIdentifier;
const subscriptionOfferID = req.body.offerID;
const applicationUsername = req.body.applicationUsername;
然而,我的 alamofire 请求并没有在正文中传递参数,而是作为查询参数传递。因此,服务器正在生成一个带有空 appBundleID 以及其他空字段的签名!所以我将 index.js 的上述部分更改为以下内容:
const appBundleID = req.query.appBundleID;
const productIdentifier = req.query.productIdentifier;
const subscriptionOfferID = req.query.offerID;
const applicationUsername = req.query.applicationUsername;
我希望这可以帮助那些忽略了这一点的人。请原谅我的不安全 swift,但我希望你明白我的意思!
在我的+Apple 的 WWDC19 node impl 和上面的 impl 之间交换几天后,我发现我的问题不是设置 applicationUsername iOS 端来匹配 node.js 中使用的 applicationUsername。具体来说,设置折扣的 SKMutablePayment 对象 iOS 端。希望一些幸运的患者在比我花费更少的时间后看到这个。