Can you use App Store Sandbox testing (verifyReceipt specifically) before submitting the app or the related IAPs? - swift

I've been trying to use the verifyReceipt endpoint to verify in-app purchase transactions with no success.
Here's how I'm doing it.
1. Read the receipt data in iOS (this is copy paste from Apple's documentation):
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
do {
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
print(receiptData)
let receiptString = receiptData.base64EncodedString(options: [])
// sendReceiptToBackEnd(receiptString)
}
catch { print("Couldn't read receipt data with error: " + error.localizedDescription) }
}
2. In the backend, I followed this advice from Apple:
As a best practice, always call the production URL for verifyReceipt first, and proceed to verify with the sandbox URL if you receive a 21007 status code.
So first I try this (python code):
data = {
'receipt-data': receipt_string, # this is what we get from the frontend
'password': settings.APP_STORE_SHARED_SECRET
}
url = 'https://buy.itunes.apple.com/verifyReceipt'
response = requests.post(url, json=data)
response_data = response.json()
status = response_data['status']
Status here is 21007, which is expected according to the advice above. So then I try the sandbox:
url = 'https://sandbox.itunes.apple.com/verifyReceipt'
response = requests.post(url, json=data)
response_data = response.json()
status = response_data['status']
And I get 21002 - which means there's probably something wrong with what I sent.
Initially I was trying to test this using Xcode locally, but I found out later that verifyReceipt doesn't work with this feature because app's are not signed by the App Store. So I deleted the local configs and set the StoreKit Configuration in scheme back to none, and verified the app is using the App Store Connect as it was picking up any changes I made there in the price.
I'm out of ideas on how to debug this further, so I'm wondering could it be because the App & IAPs are not submitted yet? Currently the app is in the "Prepare for Submission" state, and the IAPs are in the "Ready to Submit" state. Or maybe there is something else I'm missing?
Edit: The answer to my question is "yes, you can." I copied and pasted the json and sent it manually using Postman and it worked fine so something is happening in the backend. I'm leaving the question for reference but if someone recommends deleting please let me know.

I saw your edit, but just as a follow-up code 21002 is a "malformed body" response (or theoretically an Apple server issue, but I've only seen it when mangling the receipt data).
You can see the full list of codes and meanings here:
https://developer.apple.com/documentation/appstorereceipts/status
Incidentally, as you build our your server to process iTunes responses you may find this guide useful for processing the receipt on the server:
https://www.namiml.com/blog/app-store-verify-receipt-definitive-guide

Related

ASWebAuthenticationSession on macOS 12.4 do not work properly

I'm using ASWebAuthenticationSession authorization for my application.
It works fine before macOS12.4, but occur unresponse's issue.
After "session.start success", Click Cancel or Continue and there is no response.
var session = ASWebAuthenticationSession.init(url: url, callbackURLScheme: "http", completionHandler: completionHandler)
guard let provider = NSApplication.shared.keyWindow!.contentViewController as? FlutterViewController else {
result(FlutterError(code: "FAILED", message: "Failed to aquire root FlutterViewController" , details: nil))
return
}
session.presentationContextProvider = provider
if(!session.start()) {
NSLog("session.start fail");
} else {
NSLog("session.start success");
}
Edit: I just noticed I did not read correctly and missed the fact that this question was for macOS, not iOS. I still hope one of those answers might lead to a solution.
I have noticed 2 things that seem incorrect to me.
The first one is that you need a strong reference for your session. This limitation is only valid for iOS version < 13.0.
This means, the session variable must "outlive" the invoked method it was started in, e.g. setting a session attribute for the whole class.
I cannot see something similar in the code snippet you provided, it looks like a scoped variable to me.
From Apples documentation for this feature:
For iOS apps with a deployment target earlier than iOS 13, your app
must keep a strong reference to the session to prevent the system from
deallocating the session while waiting for authentication to complete.
The second thing is your callback URL scheme seems to be set to https.
The scheme should match your apps bundle ID.
It must be configured in your Info.plist file. Here's an example how to achieve this. It should actually be something like com.company.myappname instead of http. Your authentication provider would also need to redirect to com.company.myappname://someurl to make this work. Please note the :// must not be part of the callback URL scheme, only the part before that.

Why is my app store receipt returning error code 21002

I'm generating an app store receipt to test via the sandbox link for receipt validation. For some reason, It's telling me the receipt is malformed (error code 21002)
How could this be? I don't actually use any In-App Purchases, I'm trying to turn my Paid app to free, and may introduce some in the future, but for now, I just need the original_purchase_version and original_purchase_date of the entire app purchase.
This is the receipt that is generated, please note that for some reason I have to call SKReceiptRefreshRequest() the first time I launch the app, or no receipt actually exists:
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
I'm following the Apple docs on validation, so I'm sure the receipt is generated correctly, like so:
// Get the receipt if it's available
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
do {
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
print(receiptData)
let receiptString = receiptData.base64EncodedString(options: [])
// Read receiptData
}
catch { print("Couldn't read receipt data with error: " + error.localizedDescription) }
}
So what could be the reasons for this receipt not being valid? Thank you!

How to open and verify WalletConnect link via iOS app?

I currently have an iOS application where I want users to be able to import their crypto wallets via WalletConnect. I try using the WalletConnect example app and I get as far as generating a url that opens metamask but it does not ask for any kind of verification, it just opens metamask and nothing else. I see others with the same issue on their github too with no resolution. I don't see any other service/package performing these functions besides WalletConnect either, I would appreciate any suggestions on what I may be doing wrong or other options that I can try out
You can connect, reconnect & disconnect with meta mask using walletconnect swift lib. Here is the link of it: https://github.com/WalletConnect/WalletConnectSwift
There have two section:
Server
Client
Server is used for wallet side functionalities. i.e. metamask, trustwallet and so on
Client is used to implement dApps where User initiate connect request and metamask/trustwallet app gets and request and approve it.
You should take a look at clientside code, which helpful to implement dapps.
Verfication is depends on the bridgeURL you used in the demo. Here is the bridge link, you should use: https://safe-walletconnect.gnosis.io/
Here is the code which help to have verification on metamask:
func connect() -> String {
let wcUrl = WCURL(topic: UUID().uuidString,
bridgeURL: URL(string: "https://safe-walletconnect.gnosis.io/")!,
key: try! randomKey())
let clientMeta = Session.ClientMeta(name: "ExampleDemoApp",
description: "WalletConnectDemo",
icons: [],
url: URL(string: "https://safe.gnosis.io")!)
let dAppInfo = Session.DAppInfo(peerId: UUID().uuidString,
peerMeta: clientMeta,
chainId: ViewController.chainID)
client = Client(delegate: self, dAppInfo: dAppInfo)
print("WalletConnect URL: \(wcUrl.absoluteString)")
try! client.connect(to: wcUrl)
return wcUrl.absoluteString
}
Hope it will help you. Its working on my side.

Firebase Offline Support: upload posts while user is offline and sync when user comes online in iOS Swift app

I am using firebase in an iOS-Swift project in which I have to enable offline support for uploading posts, in the post there is a picture and caption just like Instagram, so what I want is when user is offline and he/she wants to upload a post, his/her picture get saved in cache and when user comes online that photo get uploaded and give back a download url that we can use for saving posts-details it in database.
sample code is:
let photoIDString = UUID().uuidString
let storageRef = Storage.storage().reference(forURL: "storage ref URL").child("posts").child(photoIDString)
storageRef.putData(imageData, metadata: nil, completion: { (metadata, error) in
guard let metadata = metadata else {
return
}
if error != nil {
return
}
storageRef.downloadURL(completion: { ( url, error ) in
guard let downloadURL = url else {
return
}
let photoUrl = downloadURL.absoluteString
self.sendDataToDatabase(photoUrl: photoUrl)
})
}
)
I want to know what changes should I make in my code to provide the offline capability. Any code snippet will help more.
The problem is better view as re-send to server when there is an error.
For your offline case, you can check if the error return is a network error, or manually check network connection availability.
You can create a re-send array of object
e.g
var resendList : [YourObjectType]
// when failed to send to server
resendList.append(yourFailedObject)
And then, 2 solutions:
Check the network connectivity manually and reupload in when the app become active in func applicationDidBecomeActive(_ application: UIApplication) in appDelegate. For checking connectivity you can try the method here: Check for internet connection with Swift But this has a problem that, the user has to go out the app and back again with network connected
Keep track(listen to notification) on the connectivity change, using a suggestion method by https://stackoverflow.com/a/27310748/4919289 and reupload it to server
and loop through all objects in resendList and re-upload again.
I am not an iOS developer, but I can share logical flow and some references.
When user clicks on upload: Check if network is available?
if yes: upload the post.
if no:
save the post to app storage or offline database
set broadcast receiver to receive broadcast when device comes online. This link may be helpful.
upload post when device comes online.
If you are looking for solution that is offered by Firebase, you may find more details here.
Firebase offers you plenty of ways to do this in their documentation. https://firebase.google.com/docs/database/ios/offline-capabilities
When uploading to the firebase server, it will queue itself and wait until it has a internet connection again to upload. If this happens to timeout or you want to do it your own way just attempt to upload with a completionHandler on the setValue or updateChild functions - if not successfully and the error message is because of internet, add it to a local cache to the phone with the data and the path to the firebase server.
onLoad, attempt the same upload again until it succeeds, once it succeeds - clear the local cache.

Passwordless Email Auth Firebase, Dynamic Links (FDL)

I'm trying to setup password-less email authentication through firebase for my iOS app. I'm using the method send signInLink which require actionCodeSettings and in turn a url. I've discovered that this url has to be a dynamic link created on Firebase. I've gone to the firebase console and white-listed a domain, but when I try to create the dynamic link on the console, I get "An error occurred when creating new dynamic link".
I'm a little confused as to how I'm supposed to construct this dynamic link especially the deep link. I've been through Firebase's documentation, added a dummy App Store ID and App prefix (as I was told by Firebase support), but I can't seem to get a proper diagnosis behind this
If I try sending the sign-in email there is no issue, but when I click on the link I get a 400 error saying "The requested URL was not found on this server".
Can anyone help me out with this?
actionCodeSettings.handleCodeInApp = true
actionCodeSettings.url = URL(string: String(format: "my_dynamic_link", email.text!))
actionCodeSettings.setIOSBundleID(Bundle.main.bundleIdentifier!)
actionCodeSettings.setAndroidPackageName("", installIfNotAvailable: false, minimumVersion: "12")
Auth.auth().sendSignInLink(toEmail: email.text!, actionCodeSettings: actionCodeSettings, completion: { error in
if error != nil {
print("Link Error: " + (error?.localizedDescription)!)
let castedError = error as NSError?
let FBError = AuthErrorCode(rawValue: (castedError?.code)!)
switch FBError {
case .invalidEmail?:
print("invalid email")
case .emailAlreadyInUse?:
print("in use")
default:
print("Create User Error: \(error!)")
}
}
else {
print ("No Error")
}
})
You could try following the troubleshooting steps mentioned in the Firecasts video "Getting started with Firebase Dynamic Links on iOS - Pt.1 (Firecasts)" at 7:54 (https://youtu.be/KLBjAg6HvG0?t=474).
I was having the same issue and everything worked fine after:
deleting the app
restarting my phone
reinstalling the app
Currently the bug radar (http://www.openradar.me/radar?id=4999496467480576) is still open.