The bounty expires in 23 hours. Answers to this question are eligible for a +100 reputation bounty.
JaSHin wants to draw more attention to this question.
In my WPF application which is packaged via Desktop Bridge I found a problem that some users can't buy addon via in-app purchase. It displays my "Canceled" alert which represents StorePurchaseStatus.NotPurchased where result.ExtendedError is null.
Target framework is:
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
Here is the simplified code that procures the purchase:
namespace MyApp {
public partial class MainWindow: Window {
private readonly StoreContext context;
public MainWindow(){
context = StoreContext.GetDefault();
}
private bool IsAdministrator()
{
var identity = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
private async void BuyButtonClick(object sender, RoutedEventArgs e) {
if (IsAdministrator())
{
ShowAlert("Cannot run under administrator rights");
return;
}
if (sender is Button button)
{
StoreProduct? storeProduct = ((Product)dataContext).storeProduct;
if (storeProduct != null)
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(async delegate
{
var hwnd = new WindowInteropHelper(this).Handle;
WinRT.Interop.InitializeWithWindow.Initialize(context, hwnd);
var result = await context.RequestPurchaseAsync(storeProduct.StoreId);
switch (result.Status)
{
case StorePurchaseStatus.Succeeded:
ShowAlert("Succeeded");
break;
case StorePurchaseStatus.AlreadyPurchased:
ShowAlert("AlreadyPurchased");
break;
case StorePurchaseStatus.NotPurchased:
var extendedError = result.ExtendedError;
if (extendedError != null)
{
ShowAlert(extendedError.Message);
}
else
{
ShowAlert("Canceled");
}
break;
case StorePurchaseStatus.NetworkError:
ShowAlert("NetworkError");
break;
case StorePurchaseStatus.ServerError:
ShowAlert("ServerError");
break;
}
}
}
}
}
}
It works everywhere on my devices (Windows 11 and Windows 10). The user who cannot buy has Windows 11.
This might be caused by the account type that the customer is using.
First of all, the store purchase will fail if the app is running as administrator.
Normal “admin” accounts (people in the administrators group with a split token) will just run your desktop bridge app as a standard user, unless they right-click and launch something elevated explicitly.
But if the customer is using the system built-in account on their device, the purchase will be failed as the app will be running as administrator. This is not allowed for Microsoft Store purchase API.
It's possible that the issue is related to the user's Windows account or some settings on their device. Here are some steps you can take to troubleshoot the issue:
Check if the user has signed in to the Windows Store app with their Microsoft account. They may need to sign in or create an account if they haven't already done so.
Check if the user has a valid payment method associated with their Microsoft account. They may need to add or update their payment information if it's not valid or up-to-date.
Check if the user has disabled in-app purchases on their device. They can enable this feature in the Windows Settings app under "Apps & features" > "Microsoft Store" > "Advanced options" > "In-app purchases".
Try to reproduce the issue on a test device with the same configuration as the user's device. This can help you isolate the issue and identify any device-specific settings or configurations that may be causing the problem.
Contact Microsoft support for further assistance. They can help you troubleshoot the issue and provide guidance on how to resolve it.
Here's an updated version of your code that includes error handling and logging:
public partial class MainWindow : Window
{
private readonly StoreContext context;
public MainWindow()
{
context = StoreContext.GetDefault();
}
private async void BuyButtonClick(object sender, RoutedEventArgs e)
{
if (IsAdministrator())
{
ShowAlert("Cannot run under administrator rights");
return;
}
var product = (Product)dataContext;
if (product == null || product.storeProduct == null)
{
ShowAlert("Product is null or storeProduct is null");
return;
}
try
{
var hwnd = new WindowInteropHelper(this).Handle;
WinRT.Interop.InitializeWithWindow.Initialize(context, hwnd);
var result = await context.RequestPurchaseAsync(product.storeProduct.StoreId);
switch (result.Status)
{
case StorePurchaseStatus.Succeeded:
ShowAlert("Purchase succeeded");
break;
case StorePurchaseStatus.AlreadyPurchased:
ShowAlert("Already purchased");
break;
case StorePurchaseStatus.NotPurchased:
if (result.ExtendedError != null)
{
ShowAlert($"Purchase failed: {result.ExtendedError.Message}");
}
else
{
ShowAlert("Purchase cancelled");
}
break;
case StorePurchaseStatus.NetworkError:
ShowAlert("Network error");
break;
case StorePurchaseStatus.ServerError:
ShowAlert("Server error");
break;
default:
ShowAlert("Unknown error");
break;
}
}
catch (Exception ex)
{
// Log the exception
Debug.WriteLine(ex.Message);
ShowAlert($"Purchase failed: {ex.Message}");
}
}
private bool IsAdministrator()
{
var identity = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
private void ShowAlert(string message)
{
MessageBox.Show(message, "Purchase Status", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
This code logs any exceptions that occur during the purchase process and displays a message box with the error message. This can help you troubleshoot the issue and provide more detailed information to the user if the purchase fails.
Related
I have a console application running on .net 4.5 (only).
I am trying to detect when the computer return from sleep/hibernate mode.
I tried using Win32.SystemEvents.PowerModeChanged but for some reason it doesn't work...
I am using ThinkPad laptop running windows 10, When I unplug the charging cable it does fire the event with the argument Mode = PowerModes.StatusChange.
class Program
{
static void Main(string[] args)
{
SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
Console.WriteLine("This application is waiting for system events.");
Console.WriteLine("Press <Enter> to terminate this application.");
Console.ReadLine();
}
private static void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
Console.WriteLine(Enum.GetName(typeof(PowerModes), e.Mode));
File.WriteAllText("test.txt", "test");
}
}
Tried printing to the screen and writing to a file, couldn't managed to make it work...
Please if someone have an idea or a different approach, in the end I need to catch when the computer returns from sleep or hibernate.
SOLVED:
From windows 10, microsoft added Modern Standby that expands the Windows 8.1 Connected Standby power model.
SystemEvents.PowerModeChanged in .net 4.5 only supports Traditional Sleep and Hibernate (S1-4).
From windows 10, version 2004 Modern Standby is forced and cannot be disabled, renders SystemEvents.PowerModeChanged useless in my case.
The new Win32 API for handling Modern Standby power mode changes is referenced here:
PowerRegisterSuspendResumeNotification function MSDN
Unfortunately I didn't managed to find a complete C# implementation for the new API.
Soo I made one myself using C# wrappers for User32.dll and PowrPorf.dll from Vanara Project By dahall (GitHub):
public static class SystemPowerNotifications
{
public static event SystemPowerNotificationEventHandler PowerModeChanged
{
add
{
_powerModeChanged += value;
if (_eventHandler == null)
{
var result = PowrProf.PowerRegisterSuspendResumeNotification(PowrProf.RegisterSuspendResumeNotificationFlags.DEVICE_NOTIFY_CALLBACK,
_dnsp, out _eventHandler);
if (result != Win32Error.ERROR_SUCCESS)
throw new Exception();
}
}
remove
{
_powerModeChanged -= value;
if(_powerModeChanged.GetInvocationList().Length == 0)
{
if (PowrProf.PowerUnregisterSuspendResumeNotification(_eventHandler) != Win32Error.NO_ERROR)
throw new Exception();
_eventHandler.Dispose();
_eventHandler = null;
}
}
}
private static PowrProf.SafeHPOWERNOTIFY _eventHandler;
private static PowrProf.DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS _dnsp = new PowrProf.DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
{
Callback = OnDeviceNotify,
Context = IntPtr.Zero
};
private static Win32Error OnDeviceNotify(IntPtr context, uint type, IntPtr setting)
{
_powerModeChanged?.Invoke(null,new PowerNotificationArgs((PowerBroadcastType)type));
return 0;
}
private static SystemPowerNotificationEventHandler _powerModeChanged;
}
Full source code:
SystemPowerModeNotification-dotnet4.5 (GitHub)
EDIT:
When using it inside a Windows Service, the callback function registered in PowerRegisterSuspendResumeNotification will fire only when going to Hibernate mode, and not in Modern Standby Sleep/Monitor Off.
There for you need to register to a different notification called RegisterPowerSettingNotification referenced here: Registering for Power Events MSDN
and when a PowerEvent check the monitor state.
Take in mind that it will happen even if the computer will enter Monitor Off/On state without entering sleep mode.
Example of registration:
public static event SystemPowerNotificationEventHandler PowerModeChanged
{
add
{
_powerModeChanged += value;
if (_powerEventHandler == null)
{
if (!string.IsNullOrEmpty(ServiceName))
{
if (_ssh.IsNull)
_ssh = AdvApi32.RegisterServiceCtrlHandlerEx(ServiceName, OnDisplayNotify);
if (_ssh.IsNull)
throw new Exception("Failed To Register ServiceCtrlHandlerEx");
_displayEventHandler = User32.RegisterPowerSettingNotification(((IntPtr)_ssh), PowrProf.GUID_MONITOR_POWER_ON, User32.DEVICE_NOTIFY.DEVICE_NOTIFY_SERVICE_HANDLE);
if (_displayEventHandler.IsNull)
throw new Exception("Failed To Register PowerSettingNotification");
}
var result = PowrProf.PowerRegisterSuspendResumeNotification(PowrProf.RegisterSuspendResumeNotificationFlags.DEVICE_NOTIFY_CALLBACK,
_dnsp, out _powerEventHandler);
if (result != Win32Error.ERROR_SUCCESS)
throw new Exception("Failed To Register PowerSuspendResumeNotification");
}
}
remove
{
_powerModeChanged -= value;
if (_powerModeChanged == null)
{
if (!string.IsNullOrEmpty(ServiceName))
{
if (!User32.UnregisterPowerSettingNotification(_displayEventHandler))
throw new Exception("Failed To Unregister PowerSettingNotification");
_displayEventHandler.Dispose();
_displayEventHandler = null;
}
if (PowrProf.PowerUnregisterSuspendResumeNotification(_powerEventHandler) != Win32Error.NO_ERROR)
throw new Exception("Failed To Unregister PowerSuspendResumeNotification");
_powerEventHandler.Dispose();
_powerEventHandler = null;
}
}
}
Example of the callback:
private static Win32Error OnDisplayNotify(AdvApi32.ServiceControl control,uint eventType,IntPtr eventData,IntPtr context)
{
var dataHandle = new HANDLE(eventData);
var contextHandle = new HANDLE(context);
if(control == AdvApi32.ServiceControl.SERVICE_CONTROL_POWEREVENT)
{
POWERBRODCAST_SETTING settings = (POWERBRODCAST_SETTING)Marshal.PtrToStructure(eventData, typeof(POWERBRODCAST_SETTING));
_powerModeChanged?.Invoke(null, new PowerNotificationArgs((PowerBroadcastType)eventType,settings.Data));
}
return 0;
}
I am not able to determine whether a customer using the Lync client left a conversation due to a timeout or if the person closed the window. I am using the following event handler to check. After looking through the data in the variables, I still have the question :
Is there a way in UCMA to check whether the customer timed out?
void ImCall_StateChanged(object sender, CallStateChangedEventArgs e)
{
if (e.State == CallState.Terminating || e.State == CallState.Terminated)
{
//Program Logic .....
}
}
The answer from Ankit tells you how to determine when a user left, but not why.
There is a way to determine if the user closed the window or if something else happened using the Ms-client-Diagnostics header from the SIP message.
// Called when the instant messaging session changes state
void instantMessagingCall_StateChanged(object sender, CallStateChangedEventArgs e)
{
string diagnostics = "";
foreach (SignalingHeader header in e.MessageData.SignalingHeaders)
{
// Capture all flavors of diagnostic headers
if (header.Name.Equals("ms-diagnostics", StringComparison.InvariantCultureIgnoreCase))
{
diagnostics = diagnostics + header.GetValue() + ";";
}
if (header.Name.Equals("ms-diagnostics-public", StringComparison.InvariantCultureIgnoreCase)) // These are the public diagnostics in case you go over edge
{
diagnostics = diagnostics + header.GetValue() + ";";
}
if (header.Name.Equals("Ms-client-diagnostics", StringComparison.InvariantCultureIgnoreCase)) // These are the client diagnostics
{
diagnostics = diagnostics + header.GetValue() + ";";
break;
}
}
if (diagnostics.Contains("51004") || diagnostics.Contains("Action initiated by user"))
{
// handle the case where the Action is initiated by the user, which is when the user closes the chat using the cross button
}
if (diagnostics.Contains("52094") || diagnostics.Contains("Instant Messaging conversation terminated on user inactivity"))
{
// the user left the window inactive for more than 10 minutes
}
if (diagnostics.Contains("52107") || diagnostics.Contains("Call terminated on signout") )
{
// the user signed out of Lync/Skype while the conversation is still active
}
if (diagnostics.Contains("52082") || diagnostics.Contains("Session is detached from conversation"))
{
// when the user clicks on "Deny" after receving the invite
}
You can view the full list of diagnostic header values here : https://lyncforbusiness.wordpress.com/2015/10/15/lync-diagnostic-codes/
and here :
https://msdn.microsoft.com/en-us/library/gg132446(v=office.12).aspx
Try ParticipantEndpointAttendanceChanged event like below:
this.conversation.ConferenceSession.InstantMessagingMcuSession
.ParticipantEndpointAttendanceChanged +=
this.McuSessionParticipantEndpointAttendanceChanged;
private void InstantMessagingMcuSessionParticipantEndpointAttendanceChanged(
object sender,
ParticipantEndpointAttendanceChangedEventArgs<InstantMessagingMcuParticipantEndpointProperties> e)
{
foreach (var joiningParticipant in e.Joined)
{
// check if the customer has joined
}
foreach (var departingParticipant in e.Left)
{
// Verify if the customer has left
}
}
I used this wrapper for store functions to add to my Windows Phone 7 project condition to find out if user hase WP8 and enabled him in-apps and if not then just tell him.
I am using it like this:
if (Environment.OSVersion.Version.Major >= 8)
{
_store = StoreLauncher.GetStoreInterface("InAppPurchaseWrapper.Store, InAppPurchaseWrapper, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
}
if (_store != null)
{
var productListAsync = _store.LoadListingInformationAsync();
productListAsync.Completed = (async, status) =>
{
var listingInformation = async.GetResults();
var removeAdItem = listingInformation.ProductListings[Constants.RemoveAddKey];
PurchaseItem(removeAdItem.ProductId, Result);
};
}
else
{
MessageBox.Show(AppResources.remove_ads_notworking);
}
public void PurchaseItem(string id, Action<bool> result)
{
var purchaseAsync = _store.RequestProductPurchaseAsync(id, false);
purchaseAsync.Completed = (async, status) =>
{
try
{
if (status == StoreAsyncStatus.Completed)
{
var licenseInfo = _store.LicenseInformation;
if (licenseInfo.ProductLicenses[id].IsActive)
{
MessageBox.Show(AppResources.remove_ad_success);
appSettings.IsAdVisible = false;
}
else
{
MessageBox.Show(AppResources.remove_ads_something_goes_wrong);
}
}
else
{
if (status == StoreAsyncStatus.Error)
{
var forceException = async.GetResults();
}
result(false);
}
}
catch (Exception)
{
throw;
}
};
}
The problem is when I am calling RequestProductPurchaseAsync then I get: Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED)). But It was working. For one time I can buy that in-app and when I downloaded licenses I get it there. But not anymore. I was trying to restart emulator, OS but so far nothing helps. Anyone knows where could be problem? Thanks
Edit:
I find out that if I first run some sample application with same ProductId and that app is target for WP8. Then it's okay and I can buy in-app. If then I run original app (target for WP7) then I can buy in-apps too. So where could be problem? Is it posible to have working wrapper for Store functions for WP7 project? Should I send first WP8 app to Beta test and then use it's productId for WP7?
WP7 is not allowed to use Windows Phone Store in-app purchases
You should implement your own in-app purchases service
I have this code for my in-app purchase
string receipt = await Store.CurrentApp.RequestProductPurchaseAsync(pID, false);
The documentation states that this method returns a success value even if:
there's no network connection available
the user cancels out of the dialog
the user's authentication fails
You should treat a success result as indicating the async process completed without
errors. To ensure that the transaction itself was successful, check the
LicenseInformation element in the returned receipt.
Is there a quick way to verify the receipt for the particular item actually successful before I unlocked the purchase feature? Currently if there's no error, the feature will always be unlocked because the receipt isn't verified.
My code:
private async void inAppPurchase(string key)
{
if (!Store.CurrentApp.LicenseInformation.ProductLicenses[key].IsActive)
{
try
{
// The customer doesn't own this feature, so
// show the purchase dialog.
MockIAPLib.ListingInformation li = await Store.CurrentApp.LoadListingInformationAsync();
string pID = li.ProductListings[key].ProductId;
//purchase successful
string receipt = await Store.CurrentApp.RequestProductPurchaseAsync(pID, false);
//Check the receipt for
switch (key)
{
case "IsAdsVisibleSetting":
settings.IsAdsVisibleSetting = false;
break;
case "style_2":
fontFolder[key] = true;
settings.AllowedFontSetting = fontFolder;
break;
case "backgrounds_2":
backgroundGroups[key] = true;
settings.AllowedBackgroundGroupsSetting = backgroundGroups;
break;
case "backgrounds_3":
backgroundGroups[key] = true;
settings.AllowedBackgroundGroupsSetting = backgroundGroups;
break;
}
RenderStoreItems();
}
catch (Exception)
{
MessageBox.Show("Sorry, the in-app purchase was not completed because an error occurred.", "Purchasing Error", MessageBoxButton.OK);
}
}
}
after purchase method call, make sure to check the license is active or not before full fulling the goods. If it is consumable type, call ReportProductFulfillment() method.
CurrentApp.LicenseInformation.ProductLicenses[key].IsActive
BTW, in your code includeReceipt value is set as false - will keeps 'receipt' as empty always.
I have the following scenario: A Windows Mobile 6.1 (.NET Compact Framwork 2.0) Forms application (running in several devices simultanisly) that consumes several WCF services from a server. To use the windows Form app, users must login.
The server consumes data from a SAP system. The WCF server must retrive some information from SAP based on the logged users. So, my Windows forms app must (from time to time) show that they`re still runing to my WCF server.
What can I do to accomplish this task? I was thinking in create a background task that update the SQL server of the WCF server. (like a session control).
You could listen for Power State changes in your application. The documentation shows it is only available for Windows Mobile 6.5, but I use it in my Windows Mobile 5.0 applications running .NET CF 3.5.
Like a cell phone, if the mobile device is not used for a long time, the OS will show that the battery is at the same level until it actually gets used. So, it isn't very reliable.
However, you could listen for events (like POWER_STATE_CRITICAL, etc) and have your software make changes that interact with your WCF server accordingly.
Below is an edited version of something like what I use.
This will not solve your problem 100%, but it should give you an idea of how to do what you need.
using Microsoft.WindowsMobile.Status;
BatteryLevel _batteryLevel;
BatteryState _batteryState;
void Mobile5_Load(object sender, EventArgs e) {
_batteryLevel = (BatteryLevel)SystemState.GetValue(SystemProperty.PowerBatteryStrength);
_batteryState = (BatteryState)SystemState.GetValue(SystemProperty.PowerBatteryState);
if (!BatteryCritical(false)) {
// Continue
}
}
/// <summary>
/// Sets the Battery Level and Battery State for the Mobile Device
/// <para><value>showDialog=True show the dialog box</value></para>
/// <para><value>Returns True <b>if</b> the battery is in a critical state</value></para>
/// </summary>
/// <param name="showDialog">Do you want a dialog box to be displayed or not?</param>
/// <returns>false if the Battery is NOT in the critical state</returns>
bool BatteryCritical(bool showDialog) {
_batteryAlert = false;
bool bad = false; // everything starts out ok. We are actually running, after all.
_batteryLevel = (BatteryLevel)SystemState.GetValue(SystemProperty.PowerBatteryStrength);
_batteryState = (BatteryState)SystemState.GetValue(SystemProperty.PowerBatteryState);
bool present = ((_batteryState & BatteryState.NotPresent) != BatteryState.NotPresent);
bool charging = ((_batteryState & BatteryState.Charging) == BatteryState.Charging);
bool critical = ((_batteryState & BatteryState.Critical) == BatteryState.Critical);
bool lowbatry = ((_batteryState & BatteryState.Low) == BatteryState.Low);
Color c;
if (present) {
if (charging) {
c = Color.Cyan;
} else {
if (critical) {
c = Color.Orange;
_batteryAlert = true;
} else if (lowbatry) {
c = Color.Yellow;
_batteryAlert = true;
} else {
c = Color.White;
}
}
} else {
c = Color.Silver;
}
StatusPanel.BackColor = c;
switch (_batteryLevel) {
case BatteryLevel.VeryHigh: // (81-100%)
BatteryPanel.BackColor = Color.Cyan;
break;
case BatteryLevel.High: // (61-80%)
BatteryPanel.BackColor = Color.Lime;
break;
case BatteryLevel.Medium: // 41-60%)
BatteryPanel.BackColor = Color.Yellow;
break;
case BatteryLevel.Low: // (21-40%)
BatteryPanel.BackColor = Color.Orange;
//WirelessUpdate(RadioState.Off, false);
break;
case BatteryLevel.VeryLow: // (0-20%)
BatteryPanel.BackColor = Color.Red;
//WirelessUpdate(RadioState.Off, false);
bad = (!charging && present);
break;
}
if (showDialog) {
string msg = string.Format("Level is {0}\r\nState is {1}", _batteryLevel, _batteryState);
if (_batteryLevel == BatteryLevel.Low) {
msg += "\r\n\r\nThe wireless radio will be inactive until it has been charged.";
} else if (bad == true) {
msg += "\r\n\r\nThis Application will now close to preserve the device. Please return it to a charging base immediately.";
}
MessageBox.Show(msg, "Battery Meter", MessageBoxButtons.OKCancel, MessageBoxIcon.None, 0);
}
if (!bad) {
StatusPanel.Refresh();
// You could signal your app here
} else {
// Tell your app this device needs to turn off now.
}
return bad;
}