Check subscription validity using Flutters in-app-purchase plugin? - flutter

I'm trying to implement in-app purchases using the official Flutter In-App-Purchase plugin. I've got things working, except I can't figure out how to tell if a users subscription is still active or if it expired. Even after I canceled my test subscription, the values I get after connecting and doing queryPastPurchases() are the same as when the subscription was active:
productId: test_subscription_1
transactiondate: 1565682346568
status: null
verificationData
source: IAPSource.GooglePlay
localVerificationData: {
"orderId":"GPA.1234-1234-1234-12345",
"packageName":"com.example.myapp",
"productId":"test_subscription_1",
"purchaseTime":1565682346568,
"purchaseState":0,
"purchaseToken":"<long string>",
"autoRenewing":false
}
serverVerificationData: "<long string>"
Am I supposed to simply hard code my subscription period and compare the current time to purchaseTime + the subscription period? Will that even work across auto-renewals? What if the user changes the date on his phone to a year ago? It seems like there should be some value that should either give me the expiration time or at least a boolean true/false to indicate if the subscription is still valid?

The official in-app purchase plugin handles making the purchase but doesn't supply all of the backend infrastructure you need to handle auto-renewing subscriptions specifically.
The short answer to your question is send this purchase info up to your server and manage subscription status there. Alternatively you can look into a managed solution like purchases_flutter: https://pub.dev/packages/purchases_flutter/

I have used ‘purchases_flutter‘ and the process is straightforward. You can check the status of the subscription by calling the methods which comes with the plugin. Check out this article which includes an example https://medium.com/flutter-community/in-app-purchases-with-flutter-a-comprehensive-step-by-step-tutorial-b96065d79a21

For anyone still having issues, there's a simple solution to validate the receipt on iOS
Here's a simple js snippet that you can use to fetch the actual receipt from Apple and use it to validate the subscription
Note
You will need to generate app specific password for the app from with apple developer account
Further help
https://developer.apple.com/documentation/appstorereceipts/expiration_intent
const axios = require('axios');
const iosValidateReceipt = async (transactionReceipt, isTest = true) =>
new Promise(async (resolve, reject) => {
const url = isTest ? 'https://sandbox.itunes.apple.com/verifyReceipt' : 'https://buy.itunes.apple.com/verifyReceipt';
const data = {
'receipt-data': transactionReceipt,
password: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
};
console.log('iosValidateReceipt - input - ', { url, data });
try {
const response = await axios.post(url, data);
console.log('iosValidateReceipt - success - ', JSON.stringify(response.data, null, 2));
resolve(response.data);
} catch (err) {
console.log('iosValidateReceipt - error -', err);
reject(err);
}
});

Related

Tell wether or not a mint call to a contract succeeded

I'm working on an NFT site in NextJS and trying to implement a redirect for the user after they successfully mint a token. Here's my mint code:
const mintToken = () => {
safeMint?.();
router.push('/success');
};
As you can see, after safeMint is called, I try to redirect to /success which is what happens. However, it redirects regardless of a successful mint, I want it to only redirect after the call to the smart contract succeeds. I've tried using callbacks and timeouts but nothing seems to work the way I've laid out above. Is there some way of getting or waiting for a success response before redirecting that I'm missing? Thanks!
Function return value is not available outside of EVM if you execute the function with a transaction.
You can wait for the transaction receipt. It contains the transaction status (success / revert), as well as event logs. Tx receipt is available only after the tx is included in a block.
Depending on your safeMint() implementation, it might mint tokens each time the transaction succeeds. But if your implementation allows for the function to succeed even without minting tokens, you might need to check the event logs to make sure that the NFT was really minted.
// transaction reverted
function safeMint() external {
require(failedCondition);
_mint(msg.sender, tokenId);
}
// transaction succeeded but no token was minted
function safeMint() external {
if (failedCondition) {
_mint(msg.sender, tokenId);
}
}
How to wait for the receipt with ethers:
const tx = await myContract.safeMint();
const txReceipt = await transaction.wait();
if (txReceipt.status) {
// not reverted
}
Docs:
https://docs.ethers.io/v5/api/providers/types/#providers-TransactionResponse
https://docs.ethers.io/v5/api/providers/types/#providers-TransactionReceipt
in safeMint function inside contract, you can return the tokenId (or you could return true)
const mintToken =async () => {
const result=await safeMint?();
if(result){
router.push('/success');
}
};

Google IAP get subscription details and store in DB

I have an app that receives subscriptions from the user. I have implemented methods to receive payments and I can complete the transaction. When storing all the details in my database I can able to get details such as purchase id, purchase date and validity (returned P1Y for a 1-year subscription) but I also want to get the subscription end date is it possible? and how do I check if the user cancels the subscription or renews the subscription?
Revenue Cat:
Instead of using in_app_purchase I started using purchases_flutter (RevenueCat)
You can refer to their documentation for more information.
I currently use this method for in-app subscriptions.
Google Api:
I also managed to use google API to get the subscription details with the help of googleapis and googleapis_auth
IAP Helper class
Follow this documentation to complete the initial setup
Google API Credentials initialisation
final _credentials = ServiceAccountCredentials.fromJson(r'''
{
"private_key_id": "keyid",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADAN<private key>Sf\nbB9OjCOGt7ybJmDkMBe2U5Tq\n-----END PRIVATE KEY-----\n",
"client_email": "mail",
"client_id": "clientid",
"type": "service_account"
}
''');
static const _scopes = [AndroidPublisherApi.androidpublisherScope];
Function to get Subscription details
Future getSubData({#required String token , #required String productId})async{
SubscriptionPurchase res;
final httpClient = await clientViaServiceAccount(_credentials, _scopes);
try {
final pubApi = AndroidPublisherApi(httpClient);
res = await pubApi.purchases.subscriptions.get('com.yourcompany.package', productId, token);
} finally {
httpClient.close();
}
return res;
}
Delivering the product
_deliverProduct({#required PurchaseDetails purchaseDetails})async{
await iap.completePurchase(purchaseDetails);
final firestore.DocumentReference userDocReference = firebase.doc("Users/$_id/userdata/data");
final firestore.CollectionReference historyDocReference = firebase.collection("Users/$_id/history/");
final SubscriptionPurchase apiRes = await getSubData(token:purchaseDetails.billingClientPurchase.purchaseToken,productId:purchaseDetails.productID);
var data = {
"TXN ID": purchaseDetails.billingClientPurchase.purchaseToken,
"Order ID":purchaseDetails.billingClientPurchase.orderId,
"Product ID":purchaseDetails.productID,
"TXN Date": firestore.Timestamp.fromMillisecondsSinceEpoch(int.parse(purchaseDetails.transactionDate)),
"Subscribed":true,
"Start Date":firestore.Timestamp.fromMillisecondsSinceEpoch(int.parse(apiRes.startTimeMillis)),
"Expiry Date": firestore.Timestamp.fromMillisecondsSinceEpoch(int.parse(apiRes.expiryTimeMillis)),
"Payment Status": apiRes.paymentState,
"isFreeTrail": (apiRes.paymentState==1)?false:true,
"acknowledgementState":apiRes.acknowledgementState
};
await userDocReference.set({"Subscription":data,"Sub_Raw":apiRes.toJson()},firestore.SetOptions(merge: true)); //set to main user
await historyDocReference.doc('${purchaseDetails.billingClientPurchase.purchaseToken}').set(data); //put in purchase history
}
Now you can check if the user is subscribed or not from your firstore BD
This method may provide temporary FIX (Google PlayStore only) but I do not recommend this method as it contains a lot of security issues.
Note: Google only provides 200K API call's per day avoid calling API to check for subscription repeatedly.
Note: Google API method only works with in_app_purchase: ^0.5.2
Note: Google API method only works for Android and doesn't work for IOS.
Other Alternates
You can write your own server-side code in node.js/cloud firestore and you can achieve the same.
Ref : Cook book in_app_purchase
Disclaimer
I do not receive any incentive/payment from revenuecat for mentioning their product in this answer.

How to get the Authentication Provider for actions-on-google on Node using account linking with Auth0?

I have Javascript App running under Node v8.11.2 which uses the Actions-On-Google library. I'm using the V2 API. I have account linking set up with Auth0 and am using the SignIn helper intent. Auth0 is set up to use Google, Facebook and Twitter.
The scopes I use are OPENID, OFFLINE_ACCESS, PROFILE and EMAIL.
Everything is working fine and when the User is authenticated I get an Access Token returned.
My question is, how do I get the Authentication Provider that was selected by the User so that I can use the Access Token correctly to retrieve profile elements such as the display name, email address etc??
The signin object passed to the Sign In Confirmation intent handler just contains the following regardless of the provider selected: -
{"#type":"type.googleapis.com/google.actions.v2.SignInValue","status":"OK"}
Any help greatly appreciated as I have a deadline and this is driving me a bit crazy now!
Thanks,
Shaun
If your question is about how to get the required information when you have your accessToken available then you could use what is shown in this answer.
In node this looks like that:
let link = "https://www.googleapis.com/oauth2/v1/userinfo?access_token="+accessToken;
return new Promise(resolve => {
request(link,(error, response, body) => {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body);
let name = data.given_name ? data.given_name : '';
conv.ask(new SimpleResponse({
speech: "Hello "+ name + "!",
text: "Hello "+ name + "!"
}));
resolve();
} else {
console.log("Error in request promise: "+error);
resolve();
}
})
})
Everything you need should be in the data object.
Hope it helps.

Can't retrieve the play store reviews for my app

I am trying to get the reviews for my app from the playstore using the new reviews api from the android publisher service.
The app key is me.jadi (https://play.google.com/store/apps/details?id=me.jadi) as you can see it have reviews posted for it.
Here is the code I'm using:
var google = require('googleapis');
var secrets = require('./secrets.json');
var androidpublisher = google.androidpublisher('v2');
var authClient = new google.auth.JWT(
secrets.client_email, null, secrets.private_key,
['https://www.googleapis.com/auth/androidpublisher'], null);
authClient.authorize(function (err, tokens) {
if (err) {
return console.log(err);
}
androidpublisher.reviews.list({ auth: authClient, packageName: 'me.jadi' }, function (err, resp) {
if (err) {
return console.log(err);
}
});
});
It doesn't contain any errors for the auth nor for the actual service request. But the result is always an empty object.
So I'm trying to identify the problem,
is there something wrong with the code
do I need to opt-in specifically somewhere to use the API
does the API have any limitations, like geographic (the service is allowed only for the US devs)
or maybe the service have some bugs because it is still in beta
I found the answer my self, and I'll post it here for future reference.
The problem with my specific case was that I didn't have reviews posted or modified in the last week.
And the API documentation clearly states that it will keep history of the reviews in the last seven days.
Once I get a new review I tried the code and the review was successfully retrieved.

Error 500 when calling payment creation via REST API

I trying the new Paypal REST API and want integrate it in sandbox mode into a simple site just to see is it possible to implement one of the following payment flows:
Flow1
1) user fills the payment form, including credit card information
2) user clicks Buy
3) browser stores credit card information in paypal vault via REST API for credit cards (card id used later).
4) browser creates payment for the purchase via REST API call.
5) browser calls app server to ensure that purchase is going on.
5) browser redirects user to url provided by paypal.
6) when user consent/approval received at paypal website, and site redirected back to welcome page,
7) under the hood the IPN callback receives the conformation of payment and marks purchase as paid
8) user obtains access to the service app provides
Flow 2
1) user fills the payment form, including credit card information
2) user clicks Buy
4) browser creates payment for the purchase via REST API call.
5) browser calls app server to ensure that purchase is going on.
5) browser redirects user to url provided by paypal.
6) when user consent/approval received at paypal website, and site redirected back to welcome page,
7) under the hood the IPN callback receives the conformation of payment and marks purchase as paid and uses a bit more pf purchase details like credit card type and last 4 digits to add this info to my purchase object.
8) user obtains the access to services the application provides
8) user obtains access to the service app provides
Reasons to implement all this in such way
Mostly I going this path because i don't want deal with all this PCI compliance, and hones;ty i don't care where customers of my app live, which exactly card numbers they used, or when the card expire, since i don't want use automated subscription like payment, i want use different model:
user gets some pats of service for free, but to use rest of service, user buys time intervals of improved access (e. g. X months or 1 year).
This is what i trying to implement without storing credit cards information or storing as less as possible (paypal credit card id is enough i guess).
Question
Can i implement all this with Paypal REST API?
What i trying to do:
PaypalAdapter.prototype.sendCreditCardPayment = function (amount, description, creditCard, success, failure) {
var ccNumberResult = this.impl.validator.validateCardNumber(creditCard.number);
if (!ccNumberResult.success) {
return this.impl.$q.reject(ccNumberResult);
}
var cardInfo = {
number: ccNumberResult.number,
type: ccNumberResult.type,
expire_month: creditCard.expireMonth,
expire_year: String(creditCard.expireYear),
cvv2: creditCard.securityCode,
first_name: creditCard.firstName,
last_name: creditCard.lastName
};
var self = this;
var defer = self.impl.$q.defer();
this.impl.storeCardAPI(cardInfo,
function cardTokenCreated(cardTokenReply) {
var paymentSettings = {
intent: self.PaymentIntents.Sale,
redirect_urls: {
return_url: self.urls.returnUrl,
cancel_url: self.urls.cancelUrl
},
payer: {
payment_method: self.PayMethods.CreditCard,
funding_instruments: [
{
credit_card_token: {
credit_card_id: cardTokenReply.id
}
}
]
},
transactions: [
{
amount: {
total: String(amount.toFixed(2)),
currency: self.currencyCode
},
description: description
}
]
};
self.impl.createPaymentAPI(paymentSettings, function paymentCreated(payment) {
defer.resolve(payment);
}, function failedToCreatePayment(pcErr) {
defer.reject(pcErr);
});
},
function failedToStoreCard(ctErr) {
defer.reject(ctErr);
});
return defer.promise.then(success, failure);
};
To keep context more clean, here some more pieces of code:
var defaultHeaders = {
Authorization: 'Bearer ' + securityContext.tokens.paypal.access_token,
Accept: 'application/json'
};
var createPaymentResource = $resource(securityContext.configuration.paypal.endPoint + 'payments/payment', {}, {
sendPayment : {
method:'POST',
headers: defaultHeaders
}
});
var saveCreditCardResource = $resource(securityContext.configuration.paypal.endPoint + "vault/credit-cards", {}, {
storeCreditCard: {
method: 'POST',
headers: defaultHeaders
}
});
function storeCardFunc(cardInfo, success, failure) {
return saveCreditCardResource.storeCreditCard(null, cardInfo, success, failure);
}
function createPaymentFunc(paymentInformation, success, failure) {
return createPaymentResource.sendPayment(null, paymentInformation, success, failure);
}
var adapter = new PaypalAdapter($q, securityContext, creditCardValidator, storeCardFunc, createPaymentFunc);
return adapter;
Current "Results"
What i getting:
1) I can store credit card into vault via REST API.
2) i getting error 500 on try to create payment whether i use plain credit card properties to fill credit card funding instrument or try to use credit card token instead.
What i'm doing wrong?
Update
My mistake is a sort of logical with a lack of knowledge.
1) The direct credit cards payments (weather user approves them manually (with intent 'order' and payment_method = 'credit_card'), or not ('sale' intent and payment method 'credit_card') are not available for my country (Republic of Georgia). This is a single reason for the error i see.
Discovered this via Paypal account pages at developer.paypal.com website... It is a frustrating thing. Especially with all this too cryptic "internal server error". I t would be VERY helpful if Paypal would provide at least an informative message "Requested feature not available for your account" with info link to that page with available/enabled features.
Also documentation s a bit broken - some fields like first_name or last_name of payer object of order/payment request are NOT expected by the /payments/payment endpoint...