How can I allow in-app purchases through my .NET MAUI application? - maui

I'm looking for documentation on this and I'm not finding it.
The main things I need to accomplish are:
Allow the user to purchase an add-on through the Microsoft Store.
Determine if they've already purchased an add-on so the appropriate application features can be enabled.
These two things can be accomplished easily for a UWP app as described here:
https://learn.microsoft.com/en-us/windows/uwp/monetize/enable-subscription-add-ons-for-your-app#code-examples
But I can't find an equivalent for .NET MAUI.

So in fact you can use the UWP code examples from Microsoft. The trick is to wrap everything that's Windows specific in a statement like this:
#if WINDOWS
...
#endif
So my code looks like this:
#if WINDOWS
using Windows.Services.Store;
#endif
namespace MyProject.Data
{
public class AddOnService
{
#if WINDOWS
private StoreContext context = null;
StoreProduct subscriptionStoreProduct;
// Assign this variable to the Store ID of your subscription add-on.
private string subscriptionStoreId = "INSERT-STORE-ID";
public async Task<bool> SetupSubscriptionInfoAsync()
{
if (context == null)
{
context = StoreContext.GetDefault();
// If your app is a desktop app that uses the Desktop Bridge, you
// may need additional code to configure the StoreContext object.
// For more info, see https://aka.ms/storecontext-for-desktop.
}
bool userOwnsSubscription = await CheckIfUserHasSubscriptionAsync();
if (userOwnsSubscription)
{
// Unlock all the subscription add-on features here.
return true;
}
// Get the StoreProduct that represents the subscription add-on.
subscriptionStoreProduct = await GetSubscriptionProductAsync();
if (subscriptionStoreProduct == null)
{
return true;
}
// Check if the first SKU is a trial and notify the customer that a trial is available.
// If a trial is available, the Skus array will always have 2 purchasable SKUs and the
// first one is the trial. Otherwise, this array will only have one SKU.
StoreSku sku = subscriptionStoreProduct.Skus[0];
if (sku.SubscriptionInfo.HasTrialPeriod)
{
// You can display the subscription trial info to the customer here. You can use
// sku.SubscriptionInfo.TrialPeriod and sku.SubscriptionInfo.TrialPeriodUnit
// to get the trial details.
}
else
{
// You can display the subscription purchase info to the customer here. You can use
// sku.SubscriptionInfo.BillingPeriod and sku.SubscriptionInfo.BillingPeriodUnit
// to provide the renewal details.
}
// Prompt the customer to purchase the subscription.
await PromptUserToPurchaseAsync();
return false;
}
private async Task<StoreProduct> GetSubscriptionProductAsync()
{
// Load the sellable add-ons for this app and check if the trial is still
// available for this customer. If they previously acquired a trial they won't
// be able to get a trial again, and the StoreProduct.Skus property will
// only contain one SKU.
StoreProductQueryResult result =
await context.GetAssociatedStoreProductsAsync(new string[] { "Durable" });
if (result.ExtendedError != null)
{
System.Diagnostics.Debug.WriteLine("Something went wrong while getting the add-ons. " +
"ExtendedError:" + result.ExtendedError);
return null;
}
// Look for the product that represents the subscription.
foreach (var item in result.Products)
{
StoreProduct product = item.Value;
if (product.StoreId == subscriptionStoreId)
{
return product;
}
}
System.Diagnostics.Debug.WriteLine("The subscription was not found.");
return null;
}
private async Task<bool> CheckIfUserHasSubscriptionAsync()
{
StoreAppLicense appLicense = await context.GetAppLicenseAsync();
// Check if the customer has the rights to the subscription.
foreach (var addOnLicense in appLicense.AddOnLicenses)
{
StoreLicense license = addOnLicense.Value;
if (license.SkuStoreId.StartsWith(subscriptionStoreId))
{
if (license.IsActive)
{
// The expiration date is available in the license.ExpirationDate property.
return true;
}
}
}
// The customer does not have a license to the subscription.
return false;
}
private async Task PromptUserToPurchaseAsync()
{
// Request a purchase of the subscription product. If a trial is available it will be offered
// to the customer. Otherwise, the non-trial SKU will be offered.
StorePurchaseResult result = await subscriptionStoreProduct.RequestPurchaseAsync();
// Capture the error message for the operation, if any.
string extendedError = string.Empty;
if (result.ExtendedError != null)
{
extendedError = result.ExtendedError.Message;
}
switch (result.Status)
{
case StorePurchaseStatus.Succeeded:
// Show a UI to acknowledge that the customer has purchased your subscription
// and unlock the features of the subscription.
break;
case StorePurchaseStatus.NotPurchased:
System.Diagnostics.Debug.WriteLine("The purchase did not complete. " +
"The customer may have cancelled the purchase. ExtendedError: " + extendedError);
break;
case StorePurchaseStatus.ServerError:
case StorePurchaseStatus.NetworkError:
System.Diagnostics.Debug.WriteLine("The purchase was unsuccessful due to a server or network error. " +
"ExtendedError: " + extendedError);
break;
case StorePurchaseStatus.AlreadyPurchased:
System.Diagnostics.Debug.WriteLine("The customer already owns this subscription." +
"ExtendedError: " + extendedError);
break;
}
}
#else
public async Task<bool> SetupSubscriptionInfoAsync()
{
return false;
}
#endif
}
}
I'll be modifying this some to suit my needs but I was able to debug it and step through the code to verify that the code is indeed executing as expected on Windows.

Related

When I test an app integrated with the HMS Health Kit on my Huawei Mate 30 Pro, why cannot I obtain the number of steps?

What should I do if the error code 50005 is returned when querying the step count using the corresponding method under DataController? (The scopes of Health Kit I applied for from the Huawei Developers website have been approved.)
2020-05-26 11:41:21.195 17338-17338/com.hauwei.hmsdemo I/DataManager:
read failure 50005:Unknown authorization error
2020-05-26 11:41:21.203 17338-17338/com.hauwei.hmsdemo I/DataManager:
The following lines are used for querying the step count:
public void readSteps(View view) throws ParseException {
DataCollector dataCollector = new DataCollector.Builder().setPackageName(context)
.setDataType(DataType.DT_CONTINUOUS_STEPS_DELTA)
.setDataStreamName("STEPS_DELTA")
.setDataGenerateType(DataCollector.DATA_TYPE_RAW)
.build();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date startDate = dateFormat.parse(BEGIN_TIME);
Date endDate = dateFormat.parse(END_TIME);
ReadOptions readOptions = new ReadOptions.Builder().read(dataCollector)
.setTimeRange(startDate.getTime(), endDate.getTime(), TimeUnit.MILLISECONDS)
.build();
dataController.read(readOptions).addOnSuccessListener(new OnSuccessListener<ReadReply>() {
#Override
public void onSuccess(ReadReply readReply) {
……
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(Exception e) {
……
}
});
}
There is nothing wrong with the lines you used for querying the step count. The issue lies within authorization granting. It should be noted that the data that an app can access must be within the scopes granted to the app on the Huawei Developers website and within the range of data for which the user approval has been granted on the device side.
Regarding this issue, verify that the read/write permissions for the step count data have been granted when the app is displaying the data authorization screen.
You can refer to the following code, or refer to the sample code for Health Kit at
https://developer.huawei.com/consumer/en/doc/development/HMS-Examples/healthkit_Android_sample_code.
private void dataAuthorization() {
Log.i(TAG, "begin sign in");
// The data that can be used here and its read/write permissions can only be those you have applied for from the Huawei Developers website.
List < Scope > scopeList = new ArrayList < > ();
scopeList.add(new Scope(Scopes.HEALTHKIT_STEP_BOTH));
HuaweiIdAuthParamsHelper authParamsHelper =
new HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM);
HuaweiIdAuthParams authParams =
authParamsHelper.setIdToken().setAccessToken().setScopeList(scopeList).createParams();
final HuaweiIdAuthService authService =
HuaweiIdAuthManager.getService(this.getApplicationContext(), authParams);
Task < AuthHuaweiId > authHuaweiIdTask = authService.silentSignIn();
authHuaweiIdTask.addOnSuccessListener(huaweiId - > {
......
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(Exception exception) {
......
}
});
}

UWP trying to run background service throwing exception

I am trying to run background service in UWP application. I am first checking if application has background permission. If yes then I am registering the service for running.
This code was working fine until I updated Visual Studio along with Windows 10 SDK to Creators Update version. Now I can't figure out if this update changes things for registering background service.
using System;
using Windows.ApplicationModel.Background;
using BackgroundService;
using SampleApp.Config;
namespace SampleApp.Background
{
class BackgroundClass
{
LocalConfig LC = new LocalConfig();
public async void RequestBackgroundAccess()
{
var result = await BackgroundExecutionManager.RequestAccessAsync();
switch (result)
{
case BackgroundAccessStatus.AllowedMayUseActiveRealTimeConnectivity:
break;
case BackgroundAccessStatus.AllowedWithAlwaysOnRealTimeConnectivity:
break;
case BackgroundAccessStatus.Denied:
break;
case BackgroundAccessStatus.Unspecified:
break;
}
}
public async void RegisterBackgroundSync()
{
var trigger = new ApplicationTrigger();
var condition = new SystemCondition(SystemConditionType.InternetAvailable);
if (!LC.BackgroundSyncStatusGET())
{
var task = new BackgroundTaskBuilder
{
Name = nameof(BackgroundSync),
CancelOnConditionLoss = true,
TaskEntryPoint = typeof(BackgroundSync).ToString(),
};
task.SetTrigger(trigger);
task.AddCondition(condition);
task.Register();
LC.BackgroundSyncStatusSET(true);
}
await trigger.RequestAsync(); //EXCEPTION HAPPENS AT THIS LINE
}
public void RegisterBackgroundService(uint time)
{
var taskName = "BackgroundService";
foreach (var unregisterTask in BackgroundTaskRegistration.AllTasks)
{
if (unregisterTask.Value.Name == taskName)
{
unregisterTask.Value.Unregister(true);
}
}
if(time != 0)
{
var trigger = new TimeTrigger(time, false);
var condition = new SystemCondition(SystemConditionType.InternetAvailable);
var task = new BackgroundTaskBuilder
{
Name = nameof(BackgroundService),
CancelOnConditionLoss = true,
TaskEntryPoint = typeof(BackgroundService).ToString(),
};
task.SetTrigger(trigger);
task.AddCondition(condition);
task.Register();
}
}
}
}
Now while requesting I am checking if background service is registered keeping issues for re-registration. I am getting following exception
System.Runtime.InteropServices.COMException occurred
HResult=0x80004005
Message=Error HRESULT E_FAIL has been returned from a call to a COM component.
Source=Windows
 
StackTrace:
  
at Windows.ApplicationModel.Background.ApplicationTrigger.RequestAsync()
  
at SampleApp.Background.BackgroundClass.d__2.MoveNext()
Please Help
Had this same problem, was in my Windows 10 Privacy Settings.
System Settings => Privacy Settings
In the left-hand menu choose Background apps.
Check to make sure your app hasn't been blocked from running background tasks.

how to handle runtime permission auth in android M

I want to know the best practise to ask user for permission check and the code to run if user declines that particular permission access.
This example set states for CONTACTS permission
private static final int PERMISSIONS_REQUEST_READ_CONTACTS = 1;
private static String[] PERMISSIONS_CONTACT = {Manifest.permission.READ_CONTACTS}
if (checkSelfPermission(PERMISSIONS_CONTACT)) {
Log.i(TAG,
"Contact permissions have already been granted. Displaying contact details.");
} else {
Log.i(TAG, "Contact permissions has NOT been granted. Requesting permission.");
requestPermissions(PERMISSIONS_CONTACT, PERMISSIONS_REQUEST_READ_CONTACTS);
}
…
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case PERMISSIONS_REQUEST_READ_CONTACTS: {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! do the
// calendar task you need to do.
Log.d(TAG, "permission granted");
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
Log.d(TAG, "permission denied");
}
return;
}
}
}
You can follow the link
http://www.applicoinc.com/blog/android-m-permissions-review/
Dexter is an Android library that simplifies the process of requesting permissions at runtime.
Usage of Dexter
Some hint:
Always check for permission before calling operations on Google Play Services because they require permission but the management is in charge of you;
Don't ask for granted permissions: when you call request permissions with multiple permissions the system doesn't care if one or two permissions are already granted, the dialog is shown for all! So keep track only of permissions really needed. It could be strange for user grant already granted permissions;
Always check if it's needed to show a reason to the user using the method shouldShowRequestPermissionRationale() it could be not really obvious why you are asking a permission;
Send a notification to the user if you are checking for permission in background, in a service for example and allow it to grant the permission going with a tap in the app details system activity directly.
Take a look here - there's a flowchart that explains the whole process quite well.
Bottom line is that according to Android's documentation, you should always check and ask for permission if you don't have it (Android will automatically return DENIED in the callback if the user said to never ask again) and you should display a short message if the user has already declined you once in the past but hasn't marked the never ask again option.
For Example Camera Run time permission :
This Code you have to put in to camera button click
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
givePerMissons();
} else {
openCamera();
}
public void givePerMissons() {
if ((Utils.checkPermission(MainActivity.this, Manifest.permission.CAMERA)) {
openCamera();
} else {
Utils.givePermisson(MainActivity.this, Manifest.permission.CAMERA, Utils.PERMISSOIN);
if (!Utils.permissionsList.isEmpty()) {
requestPermissions(Utils.permissionsList.toArray(new String[Utils.permissionsList.size()]), Utils.PERMISSOIN_CODE);
}
}
}
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case Utils.PERMISSOIN_CODE: {
if ((Utils.checkPermission(MainActivity.this, Manifest.permission.CAMERA)) {
openCamera();
} else {
Toast.makeText(getApplicationContext(),"Write Your message",Toast.LENGTH_LONG).show();
}
}
}
}
This Code you have to put in your utils Class:
public static final List<String> permissionsList = new ArrayList<String>();
public static void givePermisson(Context context, String permisson, String permissonType) {
int per = context.checkSelfPermission(permisson);
if (per != PackageManager.PERMISSION_GRANTED) {
permissionsList.add(permisson);
} else if (per != PackageManager.PERMISSION_DENIED) {
}
}
public static boolean checkPermission(Context context, String permission) {
try {
PackageManager pm = context.getPackageManager();
if (pm.checkPermission(permission, context.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
return true;
} } catch (Exception e) {
e.printStackTrace();
}
return false;
}
if ( ActivityCompat.shouldShowRequestPermissionRationale (this, Manifest.permission.CAMERA) || ActivityCompat.shouldShowRequestPermissionRationale (this,
Manifest.permission.RECORD_AUDIO) ) {
Toast.makeText (this,
R.string.permissions_needed,
Toast.LENGTH_LONG).show ();
} else {
ActivityCompat.requestPermissions (
this,
new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO},
CAMERA_MIC_PERMISSION_REQUEST_CODE);
}

ASP.NET MVC5 Identity account lockout without Entity Framework

I have created an ASP.NET MVC5 application that allows users to access their financial data including invoices, services and products that they have acquired from our company.
The application gets all data from a set of WCF services including a list of all registered users that have access to the system. I'm using ASP.NET Identity object and claims to authorize users into the application, everything works fine I only have to use the credentials (email and password) to invoke the WCF service which returns an object containing the details about the User or a NULL value if there's no match.
However, there's a new requirement to implement an account lockout after 5 failed login attempts (the account will be locked for 20 minutes before allowing users to try again) which is a feature already included in ASP.NET identity 2.0. I have been "googling" for a couple of days, but couldn't find an example (or even a similar approach) of how to implement this requirement without storing users in Entity Framework and a local DB.
Is there any way of adding just the account lockout feature (with the 20 minutes lockout) using a WCF service as a datasource to my ASP.NET MVC5 application? Any ideas?
This is actually my first ASP.NET MVC5 application, so don't really know much about all features provided on it, any help will be appreciated.
This is how the Login (POST) looks like:
//Authentication handler
IAuthenticationManager Authentication
{
get { return HttpContext.GetOwinContext().Authentication; }
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
try
{
//Validate user from service
CWP_AccountService.User userObject = AccountClient.GetUserByCredentials(model.Email, model.Password);
if (userObject != null)
{
//Create Claims
var identity = new ClaimsIdentity(
new[] { new Claim(ClaimTypes.Name, userObject.Email) },
DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name, ClaimTypes.Role);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userObject.Email));
identity.AddClaim(new Claim(ClaimTypes.Sid, userObject.UserID.ToString()));
int cookieDurationPersistence = 20160;
int cookieDuration = 2;
Authentication.SignIn(new AuthenticationProperties { IsPersistent = model.RememberMe, ExpiresUtc = (model.RememberMe) ? DateTime.Now.AddMinutes(cookieDurationPersistence) : DateTime.Now.AddMinutes(cookieDuration) }, identity);
//Check if there is a local return URL
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return RedirectToLocal(returnUrl);
}
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("system-error", "The Email/Password provided is not valid.");
}
}
catch(Exception ex)
{
ModelState.AddModelError("system-error", "Error");
logger.Warn(ex.ToString());
}
}
return View(model);
}
The Startup.Auth.cs:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
}
}

How to use Prime31 Google In App Billing Plugin

I just bought "Google In App Billing Plugin for unity3d" by Prime31. I don't understand how to use it in the game that I want to develop.
Can you please show me a code example? I understand I need to use my app key, but I don't know what to do next.
And how to a I make test purchases with this plugin?
Please help as much as you can, because I am really stuck on this subject for quite a while.
This is some of my "purchase maker" object called MoneyTakerScript (inherits from MonoBehaviour):
void Start()
{
string key = "My App Key...";
GoogleIAB.init(key);
var skus = new string[] { "cl.48931", "tp.58932", "mmm.68393" };
GoogleIAB.queryInventory( skus );
TPS = GameObject.Find("TPBtn").GetComponent(typeof(TPScript)) as TPScript;
CLS = GameObject.Find("CLBtn").GetComponent(typeof(CLScript)) as CLScript;
MMM = GameObject.Find("MMBtn").GetComponent(typeof(MMMScript)) as MMMScript;
}
public void Purchase(string ProductId)
{
GoogleIAB.purchaseProduct(ProductId);
}
public void UseProduct(string ProductId)
{
if (ProductId.Contains("cl"))
{
CLS.MakeCL();
}
if (ProductId.Contains("tp"))
{
TPS.MakeTP();
}
if (ProductId.Contains("mmm"))
{
MMM.MakeMMM();
}
GoogleIAB.consumeProduct(ProductId);
}
And this is some of my "purchase listner" object code:
void purchaseSucceededEvent(GooglePurchase purchase)
{
//Debug.Log( "purchaseSucceededEvent: " + purchase );
MoneyScript.UseProduct(purchase.productId);
}
void Start()
{
MoneyScript = GameObject.Find("MoneyTaker").GetComponent(typeof(MoneyTakerScript)) as
MoneyTakerScript;
}
I found the problem and solved it!
This line was missing in my AndroidManifest.Xml for some reason:
<activity android:name="com.prime31.GoogleIABProxyActivity"></activity>
Just added the line and now I have In App Purchase!!