Flutter (IOS) in app purchase receipt data - ios

I use the [in app purchase][1] library to make in-app purchases in the application I developed with Flutter. When a purchase is made, in order to verify in-app purchases for Android on the server side, I am sending the datas as follows, which I need to send by the server.
_verifyPurchase(PurchaseDetails purchase) async {
productID = purchase.productID;
//for android it works nice
if(Platform.isAndroid){
orderId = purchase.billingClientPurchase.orderId;
purchaseToken = purchase.billingClientPurchase.purchaseToken;
purchaseVerify(orderId, purchaseToken, productID);
//but it does not work for iOS and the data required for purchase verification does not go to the server
}else if(Platform.isIOS){
transactionId = purchase.skPaymentTransaction.originalTransaction;
verifData = purchase.verificationData.serverVerificationData;
purchaseVerify(transactionId, verifData, productID);
}
}
purchaseVerify(String orderId, String purchaseToken, String productID) async {
var data = {
'orderId' : orderId,
'purchaseToken' : purchaseToken,
'productId' : productID,
};
res = await Network().authData(data, 'purchaseVerify.php');
}
However, although I try to obtain the data required to verify the in-app purchases for iOS as follows, no data is sent to the server side.
How can I get the data needed to validate in-app purchases for iOS and send it to the server side?

Related

Flutter ios appstore validateReceipt on non-consumable in-app purchase

I seem to be stuck on this. Trying to validate the receipt (server side) on an in-app purchase on IOS (haven't tried with android, yet.)
I'm using the official in_app_purchase pub package.
This is the setup to initialize the purchase:
Future<bool> initiatePurchase() async {
...
(verify store is available)
..
print ("==> Store available, initiating purchase");
final PurchaseParam purchaseParam =
PurchaseParam(productDetails: _productDetails![0]);
await InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam);
return true;
}
Here's my verify purchase call:
Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) async {
PurchaseVerifRest purchaseRest = PurchaseVerifRest();
Map<String,dynamic> rsp = await purchaseRest.verifyPurchase(
{
"source": purchaseDetails.verificationData.source,
"vfdata": purchaseDetails.verificationData.serverVerificationData
});
// bundle up the source and verificationData in a map and send to rest
// call
return rsp['status'] == 200;
}
On the server side, the code looks like this (NodeJS/express app)
// (in router.post() call - 'purchaseData' is the map sent in the above code,
// the 'vfdata' member is the 'serverVerificationData'
//. in the 'purchaseDetails' object)
if (purchaseData.source == ('app_store')) {
const IOS_SHARED_SECRET = process.env...;
let postData = {
'receipt-data': purchaseData['vfdata'],
'password': IOS_SHARED_SECRET
};
try {
let verif_rsp = await execPost(postData);
retStatus = verif_rsp.statusCode;
msg = verif_rsp.data;
} catch (e) {
retStatus = e.statusCode;
}
}
What I get back, invariably is
210003 - Receipt could not be authenticated
... even though the purchase seems to go through, whether I validate or not.
Details/questions:
Testing with a sandbox account.
This is for a 'non-consumable' product purchase.
I'm assuming that purchaseDetails.verificationData.serverVerificationData is the payload containing the receipt to send to Apple for verification. Is this not correct? Is there another step I need to do to get the receipt data?
I've read in other posts that the verification step is only for recurring subscriptions and not for other types of products. Is this correct? I don't see anything in Apple's docs to indicate this.
Any thoughts appreciated.

How to implement the in-app purchase feature in xamarin forms ios project

I am trying to implement the in-app purchase inside my Xamarin forms ios application. I have created one in-app purchase product on the app store. I need to do the payment when subscribing the application. I choose Auto-Renewable Subscription as the in-app purchase type.
After that how can I implement that feature on the app side? Do I need to use any Dependency Service for this? Which NuGet package do we need to use, is it Plugin.InAppBilling? I researched this and was confused about the app side integration. Any other setup I need to do to implement this feature.
I am looking for the specific codes that connect the application and the in-app purchase property created on the AppStore.
Only for ios, I am planning to implement the in-app purchase and for android, I am planning to do the payment with stripe. Is google accept stripe payment for this or are they also forced to implement in-app purchases?
Following is my scenario:
The subscription is for the usage of applications from the app/play store. The admin will subscribe to it and all the other users can use it for free.
I tried the below codes from this blog. But it looks likes for the android part, and not mentioned the ios part implementation.
// Connect to the service here
await CrossInAppBilling.Current.ConnectAsync();
// Check if there are pending orders, if so then subscribe
var purchases = await CrossInAppBilling.Current.GetPurchasesAsync(ItemType.InAppPurchase);
if (purchases?.Any(p => p.State == PurchaseState.PaymentPending) ?? false)
{
Plugin.InAppBilling.InAppBillingImplementation.OnAndroidPurchasesUpdated = (billingResult, purchases) =>
{
// decide what you are going to do here with purchases
// probably acknowledge
// probably disconnect
};
}
else
{
await CrossInAppBilling.Current.DisconnectAsync();
}
As per the same blog, I have updated AppDelegate like below:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
//initialize current one.
Plugin.InAppBilling.InAppBillingImplementation.OnShouldAddStorePayment = OnShouldAddStorePayment;
var current = Plugin.InAppBilling.CrossInAppBilling.Current;
return base.FinishedLaunching(app, options);
}
bool OnShouldAddStorePayment(SKPaymentQueue queue, SKPayment payment, SKProduct product)
{
// true in app purchase is initiated, false cancels it.
// you can check if it was already purchased.
return true;
}
On the MainPage, they have added the below code to purchase:
private async void ButtonNonConsumable_Clicked(object sender, EventArgs e)
{
var id = "iaptest";
try
{
await CrossInAppBilling.Current.ConnectAsync();
var purchase = await CrossInAppBilling.Current.PurchaseAsync(id, ItemType.InAppPurchase);
if (purchase == null)
{
await DisplayAlert(string.Empty, "Did not purchase", "OK");
}
else
{
if (!purchase.IsAcknowledged && Device.RuntimePlatform == Device.Android)
await CrossInAppBilling.Current.AcknowledgePurchaseAsync(purchase.PurchaseToken);
await DisplayAlert(string.Empty, "We did it!", "OK");
}
}
catch (Exception ex)
{
await DisplayAlert(string.Empty, "Did not purchase: " + ex.Message, "OK");
Console.WriteLine(ex);
}
finally
{
await CrossInAppBilling.Current.DisconnectAsync();
}
}
On this code they are checking the platform is android: Device.RuntimePlatform == Device.Android. This code is on the portable project, so how I can do the same for ios? And my purchase type is auto renewable subscription, that part is empty on this blog.
private async void ButtonRenewingSub_Clicked(object sender, EventArgs e)
{
}

URL for cancelling an in App purchase in Apple IOS

I am using Apple with Java to validate an IN app Purchase this way
public static void validateProductPurhcaseReceipt(String receiptData, String VERIFICATION_URL)
{
Map outPut = new HashMap();
HttpClient httpClient = new DefaultHttpClient();
try {
HttpPost request = new HttpPost(VERIFICATION_URL);
JSONObject requestData = new JSONObject();
requestData.put("receipt-data", receiptData);
requestData.put("password", "f1ebdc2f49664d7188b4d83f90131ecf");
StringEntity requestEntity = new StringEntity(requestData.toString());
request.addHeader("content-type", "application/x-www-form-urlencoded");
request.setEntity(requestEntity);
HttpResponse response = httpClient.execute(request);
String responseBody = EntityUtils.toString(response.getEntity());
JSONObject responseJSON = new JSONObject(responseBody);
System.out.println(responseJSON);
}
catch (Exception ex) {
ex.printStackTrace();
}
finally {
httpClient.getConnectionManager().shutdown();
}
}
The URL i am using is for verifyReceipt is
Development mode = https://sandbox.itunes.apple.com/verifyReceipt
Production mode = https://buy.itunes.apple.com/verifyReceipt
Could you please tell me what is the URL for Cancel an in app purchase
There aren't any to cancel an IAP. If it is an auto-renewing subscription, the user has an option to cancel the subscription before next renewal from their iTunes settings. If it is a one-time purchase, the IAP either goes through or fails.
Here's an Apple support doc explaining the different types of IAPs
And if you want to find out how to cancel an IAP if verification fails, this SO question discusses it (and this).

User id in iOS to store profiles and check IAP

My app has in-app currency and non-consumable products, it stores user profiles and posts values to leaderboards on my server.
In Android (pure java) I have LVL user ID - it is unique for pair developer-customer, so I easily manage user profiles on all his devices, and I can distinguish between devices using IMEI or Android ID.
In Windows/Windows Phone (monogame) I have LiveID, but devices have no id except self-generated UUID for statistics/ads. Can't be sure it persists reinstalls and updates.
And what about iOS (and maybe OSX) (Xamarin.iOS/monogame)?
As far as I remember in iOS was device id, but then api was deprecated.
What do you use as device/user id?
Maybe there is some user-unique-id that StoreKit has behind the scenes?
Or something related to cloud id, to distinguish users, not devices?
If none is available - is there a way to keep random UUID persistent on device, even if user reinstalls app?
When Apple removed the UUID, they provided the identifierForVendor method (In UIDevice) to replace it. It provides a UUID that is unique for you (the developer) for a particular device. I can't tell you how to call that from xamarin, but would assume it's possible.
If you want something that will persist across app deletes you could create your own UUID and save it to the keychain. You can use app groups to have a shared keychain for all of your apps, and keychain entries DO persist if you delete and reinstall an app.
Tertium: Here is the example code (tested). If you store it in cloud you can use it on all user's devices.
void SaveValueToKeychain(string key, String value)
{
var s = new SecRecord(SecKind.GenericPassword)
{
ValueData = NSData.FromString(value),
Generic = NSData.FromString(key),
Invisible = true,
CreationDate = NSDate.Now
};
var err = SecKeyChain.Add(s);
}
public String GetValueFromKeychain(string key)
{
String ret = null;
SecStatusCode res;
var rec = new SecRecord(SecKind.GenericPassword)
{
Generic = NSData.FromString(key)
};
var match = SecKeyChain.QueryAsRecord(rec, out res);
if (match != null)
{
ret = match.ValueData.ToString();
}
return ret;
}
...
string UUID_KEY = "com.my.app";
String id = GetValueFromKeychain(UUID_KEY);
if (id == null)
{
Guid g = Guid.NewGuid();
String gs = g.ToString().Replace("-", "");
Debug.Write("ID not found, generating: " + gs);
SaveValueToKeychain(UUID_KEY, gs);
id = GetValueFromKeychain(UUID_KEY);
}
else
{
Debug.Write("ID found: " + id);
}

Combining Woocommerce with ios in app purchase

I am trying to sync ios in app purchase with woocommerce purchase(web). So when user purchases something in apple store(product lists are same on both side) i would like to view it in woocommerce that item was purchased by that user. FYI, I have a json api plugin that i am using to make request between ios app and site. Any idea how i can do this?
This is how i resolved it
global $woocommerce;
$user = get_user_by('id',$shopId);
if ($user){
wp_set_current_user($shopId,$user->user_login);
wp_set_auth_cookie($shopId);
do_action('wp_login',$user->user_login);
}
if (is_user_logged_in()){
$cart = new WC_Cart();
$checkout = new WC_Checkout();
if ($cart->add_to_cart($productId)){
$orderId = $checkout->create_order(intval($shopId));
}
if (!empty($orderId)){
$order = new WC_Order($orderId);
if (!empty($order)){
$order->update_status("completed","ios app purchase");//I HAD TO SET IT TO COMPLETE SINCE USER BOUGHT IT FROM IOS IN APP PURCHASE
}
//
}
else{
$order = array('fail');
}
}
else{
$order = array();
}
return array($order);

Resources