When I reinstall the app with my subscription secret unchanged ,
let receiptFileURL = Bundle.main.appStoreReceiptURL
let receiptData = try? Data(contentsOf: receiptFileURL!)
let receiptBase64String = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
guard let stringReceipt = receiptBase64String else { return }
Above section of code gives me the old and expired response before installing the new version. I swear whether I have logged in my sandbox account or not, the result gives me the response
transaction_id : 1000000558259116
original_transaction_id : 1000000558239264
productId : YP20190527_3
renewalDict : {
"auto_renew_product_id" = "YP20190527_3";
"auto_renew_status" = 0;
"expiration_intent" = 1;
"is_in_billing_retry_period" = 0;
"original_transaction_id" = 1000000558239264;
"product_id" = "YP20190527_3";
}
What should I do next if I want to simulate a completely new iPhone and proceed purchases? I guess this old response force me prompting the Apple sandbox login for all time
Updates: When I try to ping sandbox.itunes.apple.com , it gives 100% packet loss. When I login my sandbox account at iTunes and App Store Section of my iPhone setting , I cannot login even I have got correct password and email.
Still, the system status of Apple gives available service of Sandbox iTunes User? I am not sure whether my in-app subscription can be tested using sandbox account
https://developer.apple.com/system-status/
While visiting this link with browser, it say Service unavailable
Could you please tell us when I can test the in-app subscription now ?
Related
I have been using SwiftyStoreKit.verifyReceipt to make sure that the user is still subscribed to the auto renewable membership. I run this code at viewWillAppear, it works well, but the problem is that it keep asking for the Apple ID and password each time, is it because the app is still under development / the in app purchase have not been verified by Apple or I am using SwiftyStoreKit.verifyReceipt incorrectly.
Documentation: https://github.com/bizz84/SwiftyStoreKit
My Code in viewWillAppear:
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "123")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
switch result {
case .success(let receipt):
let productId = "123"
// Verify the purchase of a Subscription
let purchaseResult = SwiftyStoreKit.verifySubscription(
ofType: .autoRenewable, // or .nonRenewing (see below)
productId: productId,
inReceipt: receipt)
switch purchaseResult {
case .purchased(let expiryDate, let items):
print("\(productId) is valid until \(expiryDate)\n\(items)\n")
OneSignal.sendTag("isUserVIPMember", value: "true")
case .expired(let expiryDate, let items):
print("\(productId) is expired since \(expiryDate)\n\(items)\n")
OneSignal.sendTag("isUserVIPMember", value: "false")
case .notPurchased:
print("The user has never purchased \(productId)")
OneSignal.sendTag("isUserVIPMember", value: "false")
}
case .error(let error):
print("Receipt verification failed: \(error)")
}
}
My assumption would be that verifyReceipt doesn't use the local receipt, but requests it from the App Store. Requesting the receipt require the consent of the user and therefore would explain why he has to enter his credentials.
The code on Github states that the default value of forceRefresh is false, but could you add forceRefresh: false to the verifyReceipt call.
Also could you please check if receiptString contains any data?
let receiptData = SwiftyStoreKit.localReceiptData
let receiptString = receiptData.base64EncodedString(options: [])
And if so, please try to do SwiftyStoreKit.verifySubscription() with the local receipt. Do you still have to enter your credentials?
I have no idea what kind of app you are building, but I would recommend not to verify the receipt in viewWillAppear. The local receipt won't change anyway (except for renewals). So do it once on app start or if it is really crucial that the user loses access, to what ever he gets with your subscription, I would recommend to use a custom server. The server will be notified if something changes in the subscription status of a user.
Side note: At the moment I would advise against the usage of SwiftyStoreKit, because they verify the receipt against the server endpoint from Apple and this is only allowed from another server. Chances are that Apple will reject your app.
Warning
Do not call the App Store server verifyReceipt endpoint from your app. You can't build a trusted connection between a user’s device and the App Store directly, because you don’t control either end of that connection, which makes it susceptible to a man-in-the-middle attack.
Source
I am trying to enable In-App Purchases in Swift 4 for iOS and I can not get the Display Name and Description to properly show up (or load) in the app when I test. I do get the error handing message that I put in if (products.count == 0) {. I can't get the Product Title and Product Description that I set up in AppStoreConnect to properly load when I test my app. Instead the code thinks that there are no products because it runs the code in the if (products.count == 0) { closure. I quadruple checked everything else (i.e. Bundle ID, Product ID, StoreKit's imported, the delegates are set up, everything on the AppStoreConnect side is set up, etc.). There are no Xcode Warnings and the In-App Purchase status is "Ready To Submit". Any suggestions?
var product: SKProduct?
var productID = "myProductID"
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
var products = response.products
if (products.count == 0) {
productTitle.text = "Warning" // GETS DISPLAYED
productDescripton.text = "Unable to connect to In-App Purchase." // GETS DISPLAYED
} else {
product = products[0]
productTitle.text = product?.localizedTitle
productDescripton.text = product?.localizedDescription
buyButton.isEnabled = true
buyButton.alpha = 1.0
}
let invalid = response.invalidProductIdentifiers
for product in invalid {
print("\(product)")
}
}
If you are a rookie at integrating In-App Purchases, then you should read this. If you have clean code and you think that you set everything up properly in App Store Connect, but still can't test properly. Specifically, if your products array is showing empty when it shouldn't be. Then, make sure that you have done the following:
1) Set up a Sandbox Tester via Users and Access in App Store Connect. This needs to be set up with an email that is not already used with iTunes, App Store, or Apple.
2) Filled out all of the necessary forms in Agreements, Tax, and Banking via App Store Connect. You'll know that everything is filled out when Paid Applications shows an Active Status.
3) Are testing on a real device that has been signed out of iTunes and App Store.
Common things that will cause this without warning are:
Your products aren't in the 'Ready to Submit' stage. This is most often from a missing screenshot, which is required even for Sandbox (you can upload a blank image for sandbox testing).
You haven't signed the 'Paid Applications' agreement in App Store Connect
Here's a good blog post that covers everything in a little more detail: Configuring In-app Products is Hard
There is one thing I don't quite understand when it comes to In-App Subscription purchase.
I obtain the receipt on iOS client like this:
private func loadReceipt() -> Data? {
guard let url = Bundle.main.appStoreReceiptURL else {
return nil
}
do {
let receipt = try Data(contentsOf: url)
return receipt
} catch {
print("Error loading receipt data: \(error.localizedDescription)")
return nil
}
}
And send it for verification to my server (written n Python).
def verify_receipt(self, receipt):
r = requests.post(config.APPLE_STORE_URL, json=receipt)
request_date_ms = DateUtils.generate_ms_from_current_time()
for item in r.json()['latest_receipt_info']:
expires_date_ms = int(item['expires_date_ms'])
if expires_date_ms > request_date_ms:
return True
return False
I'm not sure if this is the correct way of verifying if a subscription is still valid.
I get the expires_date_ms from latest_receipt_info, and if it's greater than the current time in milliseconds, then the subscription counts as still valid.
However what I noticed is that the separate latest_receipt, which is supposed to be equal to the one I have just sent in, is actually changing every time I call the API. But why? I haven't subscribed to anything new, why is the latest receipt changing?
According to the docs:
latest_receipt
Only returned for receipts containing auto-renewable subscriptions.
For iOS 6 style transaction receipts, this is the base-64 encoded
receipt for the most recent renewal. For iOS 7 style app receipts,
this is the latest base-64 encoded app receipt.
If this is against the sandbox then the subscription is auto-renewing on a predefined period. See:
https://help.apple.com/app-store-connect/#/dev7e89e149d
I am using braintree in my app for payment(Credit card & Paypal). I am using custom UI. When click on pay by Paypal button, i am using the following code.
braintreeClient = BTAPIClient(authorization: tokenizationKey)!
let payPalDriver = BTPayPalDriver(APIClient: braintreeClient)
payPalDriver.viewControllerPresentingDelegate = self
//payPalDriver.appSwitchDelegate = self // Optional
// Specify the transaction amount here. "2.32" is used in this example.
let request = BTPayPalRequest(amount: "2.32")
request.currencyCode = "USD" // Optional; see BTPayPalRequest.h for more options
payPalDriver.requestOneTimePayment(request) { (tokenizedPayPalAccount, error) in
if let tokenizedPayPalAccount = tokenizedPayPalAccount {
print("Got a nonce: \(tokenizedPayPalAccount.nonce)")
// Access additional information
// See BTPostalAddress.h for details
} else if error != nil {
// Handle error here...
} else {
// Buyer canceled payment approval
}
}
It opens this link https://checkout.paypal.com/one-touch-login-sandbox/index.html?action=setup_billing_agreement&ba_token=BA-HERMES-SANDBOX-TOKEN&cancel_url=com.pronto.btreeInteg.payments%3A%2F%2Fonetouch%2Fv1%2Fcancel&controller=client_api%2Fpaypal_hermes&experience_profile%5Baddress_override%5D=false&experience_profile%5Bno_shipping%5D=1&merchant_id=qkd2xjhc84nhd5b3&return_url=com.pronto.btreeInteg.payments%3A%2F%2Fonetouch%2Fv1%2Fsuccess&version=1 . It gives me dummy nonce. i want check with my Paypal a/c by login. Right now i am using Braintree sandbox a/c.
Full disclosure: I work at Braintree.
What you have described is the expected behavior for sandbox accounts. See the Braintree docs:
PayPal transactions initiated with sandbox API keys cannot be used for full end-to-end testing. The goal of sandbox testing is to ensure your client and server side configurations are correct and that you are receiving appropriate responses for your requests. If you wish to do end-to-end testing, you will need to do that in your production account.
However, if you have further questions about testing PayPal with your sandbox account, please contact Braintree support to see how your needs can be met.
I'm implementing the In-App Purchase feature in my iOS app for the first time. I passed all test phases correctly and I submitted the app to review, but it was rejected for this reason:
Your app offers a content subscription but does not have a mechanism in place to support the requirement that the subscription content be available to the user on all of their iOS devices.
To fix the issue I have to:
include an optional user registration feature to deliver subscription content to all of the user's iOS devices.
My app don't require a user registration, so I'm wondering if it's really necessary.
Assuming the answer is "YES!", I don't know what really I have to do.
After the user registration, I need to save the receipt data on my host and then retrieve the data if the user change device?
Actually I get the receipt data in this way:
func validateReceipt() {
let receiptUrl = NSBundle.mainBundle().appStoreReceiptURL
let isSandox = receiptUrl?.absoluteString.containsString("sandboxReceipt")
if let receipt: NSData = NSData(contentsOfURL: receiptUrl!) {
let receiptdata: NSString = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
let transaction = GTLAppleTransactionBeanApiAppleTransactionBean()
transaction.receiptData = receiptdata as String
transaction.sandbox = isSandox
ReceiptService().validate(transaction, completion: validated)
} else {
receiptDetected = false
delegate.subscriptionUpdate()
}
}
The "GTLAppleTransactionBeanApiAppleTransactionBean()" is my GAE Bean and "ReceiptService()" call my GAE Endpoint service. This service check the receipt data with Apple.
A Swift example will be very much appreciated. :P
Sorry for my English.
Thanks, Alessio.