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.
Related
Goal : To save user ID and their properties, as he/she interacts with the flutter application.
Problem : Events are getting logged but i cant see user id and properties. I noticed it initially (that user id and properties arre not being logged) while working in debug mode, and then I also waited for 24-48 hours to check in the dashboard but no details for userId.
Documentation link : https://firebase.google.com/docs/analytics/userid#dart
and Yes the properties that I am trying to save in analytics, I have defined them in Custom definitions under User scope.
Code :
import 'package:firebase_analytics/firebase_analytics.dart';
class AnalyticsClass {
static final AnalyticsClass _singleton = AnalyticsClass._internal();
late FirebaseAnalytics fa = FirebaseAnalytics.instance;
factory AnalyticsClass() {
return _singleton;
}
AnalyticsClass._internal();
buttonTap(id, name) async {
await fa.logEvent(name: 'button_tap');
}
setUser(String id, name) async {
await fa.setUserId(id: id);
await fa.setUserProperty(name: 'referral', value: "test new : $name");
}
resetUser() async {
await fa.setUserId(id: null);
await fa.setUserProperty(name: 'referral', value: null);
}
}
If you're using the setUserId() in Firebase, this does not show up in the Analytics reports due to privacy concerns. What you can do is to export your data to BigQuery. This will allow you to query your raw data as well as build custom reports via Data Studio. You may also consider using the User explorer in Google Analytics Dashboard which will help you gain more insight on the behavior of your individual users.
i installed the .net sdk of paypal and created an app in sandbox environment
next i picked up the clientId and secret and used the following sample code to make a payment.
static void Main(string[] args)
{
// Get a reference to the config
var config = ConfigManager.Instance.GetProperties();
// Use OAuthTokenCredential to request an access token from PayPal
var accessToken = new OAuthTokenCredential(config["clientId"], config["clientSecret"]);
var apiContext = new APIContext(accessToken.GetAccessToken());
var payment = Payment.Create(apiContext, new Payment
{
intent = "sale",
payer = new Payer
{
payment_method = "paypal"
},
transactions = new List<Transaction>
{
new Transaction
{
description = "Test",
invoice_number = "009",
amount = new Amount
{
currency = "EUR",
total = "41.00",
details = new Details
{
tax = "0",
shipping = "0",
subtotal = "40",
handling_fee = "1"
}
},
item_list = new ItemList
{
items = new List<Item>
{
new Item
{
name = "Room 12",
currency = "EUR",
price = "10",
quantity = "4",
}
}
}
}
},
redirect_urls = new RedirectUrls
{
return_url = "https://google.de/",
cancel_url = "https://google.de/"
}
});
}
in the transaction i have to pass Tax information.
Is there was that i let paypal calculate the tax and i just pass amount information along with address and some other information if required ?
No, you must calculate the tax yourself.
By default the user will be able to select their shipping address at PayPal, which is recommended as this saves them from having to type it manually. Given that their address can change during the PayPal checkout, you may wish to calculate a new tax amount and/or shipping amount based on the selected address. You can do this using the JS SDK's onShippingChange callback.
Firstly, though, it appears you may be using a deprecated SDK and deprecated v1/payments API, and also a redirect away from your site to PayPal, all of which is old. Don't do any of this.
Instead: follow the PayPal Checkout integration guide and make 2 routes on your server, one for 'Create Order' and one for 'Capture Order' (see the optional step 5 in 'Add and modify the code'). Both of these routes should return only JSON data (no HTML or text). There is a Checkout-Java-SDK you can use, or integrate with your own direct HTTPS API calls (obtain an access_token first, it can be cached but expires in 9 hours).
Inside the 2nd capture route on your server, when the capture API call is successful you should store its resulting payment details in your database (particularly purchase_units[0].payments.captures[0].id, which is the PayPal transaction ID) and perform any necessary business logic (such as sending confirmation emails or reserving product) immediately before forwarding your return JSON to the frontend caller.
Pair those 2 routes with the frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server
I am having a difficult time finding halfway descent documentation or examples on how to send money to another Paypal account.
I have installed the Nuget package PaypalSDK version 1.0.4. I have read the documentation at https://developer.paypal.com/home. I have browsed and tried to implement the sample code at https://github.com/paypal/Checkout-NET-SDK.
The problem I am having is that I am having is that I am not seeing notifications of payments sent or received in my sandbox account. I can successfully execute a checkout with the Javascript button in my shopping cart view. But eventually I want to add the capability to send money from my Paypal business account to another Paypal business account, without the other Paypal Business Account owner having to be logged in to my website.
Does the money recipient have to authorize the money I send, or should it just get deposited into their account once I send it?
Here is my code:
namespace MyShoppingCart.Helpers.Paypal
{
public class CaptureOrderSample
{
static string PayPalClientID = Startup.StaticConfig.GetValue<string>("Paypal:ClientID");
static string PayPalClientSecret = Startup.StaticConfig.GetValue<string>("Paypal:ClientSecret");
public static HttpClient client()
{
// Creating a sandbox environment
PayPalEnvironment environment = new SandboxEnvironment(PayPalClientID, PayPalClientSecret);
// Creating a client for the environment
PayPalHttpClient client = new PayPalHttpClient(environment);
return client;
}
public async static Task<HttpResponse> createOrder(string Email)
{
HttpResponse response;
// Construct a request object and set desired parameters
// Here, OrdersCreateRequest() creates a POST request to /v2/checkout/orders
var order = new OrderRequest()
{
CheckoutPaymentIntent = "CAPTURE",
PurchaseUnits = new List<PurchaseUnitRequest>()
{
new PurchaseUnitRequest()
{
AmountWithBreakdown = new AmountWithBreakdown()
{
CurrencyCode = "USD",
Value = "100.00"
},
Payee = new Payee
{
Email = Email // "payee#email.com"
}
}
}
//,
//ApplicationContext = new ApplicationContext()
//{
// ReturnUrl = "https://www.example.com",
// CancelUrl = "https://www.example.com"
//}
};
// Call API with your client and get a response for your call
var request = new OrdersCreateRequest();
request.Prefer("return=representation");
request.RequestBody(order);
response = await client().Execute(request);
var statusCode = response.StatusCode;
Order result = response.Result<Order>();
Debug.WriteLine($"Status: {result.Status}");
Debug.WriteLine($"Order Id: {result.Id}");
Debug.WriteLine($"Intent: {result.CheckoutPaymentIntent}");
Debug.WriteLine("Links:");
foreach (LinkDescription link in result.Links)
{
Debug.WriteLine($"\t{link.Rel}: {link.Href}\tCall Type: { link.Method}");
}
return response;
}
}
}
And this is currently called from my Orders controller when an order is completed. This is just for testing purposes.
[Authorize]
public async Task<IActionResult> CompleteOrder()
{
var items = _shoppingCart.GetShoppingCartItems();
Models.Order order = await _ordersService.StoreOrderAsync(items);
PrepareSellerEmail(items, order, "You Have a New Order!");
PrepareBuyerEmail(items, order, "Thank You for Your Order!");
await _shoppingCart.ClearShoppingCartAsync(_serviceProvider);
DeleteCartIDCookie();
//OrderRequest request = Helpers.CreateOrderSample.BuildRequestBody("USD", "100.00", "sb-r43z1e9186231#business.example.com");
//var client = Helpers.Paypal.CaptureOrderSample.client();
var result = Helpers.Paypal.CaptureOrderSample.createOrder("sb-r43z1e9186231#business.example.com");
//var response = await PayPalClient.client().execute.(request);
return View("OrderCompleted");
}
The output of the result is:
Status: CREATED
Order Id: 51577255GE4475222
Intent: CAPTURE
Links:
self: https://api.sandbox.paypal.com/v2/checkout/orders/51577255GE4475222 Call Type: GET
approve: https://www.sandbox.paypal.com/checkoutnow?token=51577255GE4475222 Call Type: GET
update: https://api.sandbox.paypal.com/v2/checkout/orders/51577255GE4475222 Call Type: PATCH
capture: https://api.sandbox.paypal.com/v2/checkout/orders/51577255GE4475222/capture Call Type: POST
This is a screen capture from my sandbox account:
Am I supposed to do something else to actually execute the transfer?
Edit: I figured out how to use the Paypal Payouts API.
First I installed the Nuget Package. It's simply called PayoutsSdk. I'm using version 1.1.1.
For the payout to execute, you need the client() method that is listed above in this post, and this CreatePayout() method listed below.
public async static Task<HttpResponse> CreatePayout()
{
var body = new CreatePayoutRequest()
{
SenderBatchHeader = new SenderBatchHeader()
{
EmailMessage = "Congrats on recieving 1$",
EmailSubject = "You recieved a payout!!"
},
Items = new List<PayoutItem>()
{
new PayoutItem()
{
RecipientType="EMAIL",
Amount=new Currency()
{
CurrencyCode="USD",
Value="1",
},
Receiver="sb-r43z1e9186231#business.example.com",
}
}
};
PayoutsPostRequest request = new PayoutsPostRequest();
request.RequestBody(body);
var response = await client().Execute(request);
var result = response.Result<CreatePayoutResponse>();
Debug.WriteLine($"Status: {result.BatchHeader.BatchStatus}");
Debug.WriteLine($"Batch Id: {result.BatchHeader.PayoutBatchId}");
Debug.WriteLine("Links:");
foreach (PayoutsSdk.Payouts.LinkDescription link in result.Links)
{
Debug.WriteLine($"\t{link.Rel}: {link.Href}\tCall Type: {link.Method}");
}
return response;
}
Of course I'll add parameters to the method for email, amount, currency code, email message, and subject.
Right now, I am calling this method from the controller method like this: var result = Helpers.Paypal.CaptureOrderSample.CreatePayout(); where Helpers.Paypal are folders that contain a class called CaptureOrderSample, which I will probably rename.
To send money from your account to another account, there are several different options:
Automate the sending with the Payouts API or Payouts Web (spreadsheet upload). For live, payouts can only be used if the live account sending the payment is approved for payouts.
Log into the account that is going to send the money in https://www.paypal.com or https://www.sandbox.paypal.com and click on the menu for Pay & Get Paid -> Send Money .
Use a PayPal Checkout integration, with or without the Orders API, and specify a payee that is to receive the money. You must log in with the paying (sending) account to approve the sending, and finally the order must be captured (via API or client side actions.order.capture()) which is what results in a PayPal transaction. If the final capture step is not performed, no money will be sent and the order will merely remain created or approved and eventually expire (72 hours after creation or 3 hours after approval)
In the sandbox, no actual emails are sent with notifications. Instead, the developer.paypal.com dashboard has a "Notifications" tab on the left, and of course activity will also be visible in each sandbox account by logging into the account. Only captured activity is likely to be visible.
I need an AutoRefreshingAuthClient to use gsheet to play with authenticated users Google Sheet. I can get AuthClient using google_sign_in package.
So, How can I get AutoRefreshingAuthClient from AuthClient in a flutter application without any service account?
The reason I don't want to use a Service Account is,
If I use a Service Account, all files are saved into this Service Account. But, I want to read, write and use Google Sheet from the authenticated user's account.
I ended up using googleapis instead. Seems like gsheets was made to work specifically with Service Accounts so even if you create a valid AutoRefreshingAuthClient it'll check to see if it's a Service Account.
Here's how I did it with the SheetsApi (The GoogleAuthClient class was found after a lot of searching on GitHub and multiple users had the same thing):
import 'package:http/http.dart' as http;
class GoogleAuthClient extends http.BaseClient {
final Map<String, String> _headers;
final http.Client _client = new http.Client();
GoogleAuthClient(this._headers);
Future<http.StreamedResponse> send(http.BaseRequest request) {
return _client.send(request..headers.addAll(_headers));
}
}
final client = GoogleAuthClient((await _googleSignIn.currentUser.authHeaders));
_SheetApi = sheets.SheetsApi(client);
There is as decent amount of overhead on our side with SheetsApi compared to GSheets. Like appending rows or simply naming your Spreadsheet requires more lines of code and/or objects:
// Sheet creation
sheets.Spreadsheet request = sheets.Spreadsheet.fromJson({
'properties': {
'title': "$title",
},
});
sheets.Spreadsheet sh = getSheetApi().spreadsheets.create(request));
// Add first row
List<String> row = [...];
getSheetApi().spreadsheets.values.append(createSheetRows([columns]), sh.spreadsheetId, "Sheet1!$startLetter:$endLetter",
valueInputOption: 'USER_ENTERED');
...
static sheets.ValueRange createSheetRows(List<List<String>> row){
return sheets.ValueRange.fromJson({
"values": row,
"majorDimension": "ROWS"
});
}
static sheets.ValueRange createSheetRow(List<String> row){
return createSheetRows([row]);
}
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);
}
});