question

Ruben Heeren avatar image
Ruben Heeren asked

Unity C# client API: PayPal GetPurchaseRequest returns TransactionStatus "Succeeded" even though the user did not pay in PayPal.

Title. Is this a bug or am I slowly going insane? How can the TransactionStatus ever be set to succeeded if the user never actually did the payment in PayPal?

My code:

	/// <summary>
	/// Step 3: Start the purchase request in PlayFab. Triggered by the player press	 ing the buy button. 
        /// </summary>
        public void StartPurchase()
        {
            ClientController.clientController.ToggleCanvasGroupSmooth(content, false);
            ClientController.clientController.ToggleCanvasGroupSmooth(contentIsPurchasing, true);
            ClientController.clientController.ToggleCanvasGroupSmooth(contentIsPurchased, false);


            var startPurchaseRequest = new StartPurchaseRequest()
            {
                Items = new List<ItemPurchaseRequest>()
                {
                    new ItemPurchaseRequest()
                        {
                            ItemId = selectedChestItemId,
                            Quantity = 1,
                            Annotation = "Purchased via in-game store"
                        }
                }
            };


            PlayFabClientAPI.StartPurchase(startPurchaseRequest, OnStartPurchaseSuccess, StratUtilityMethods.LogPlayFabError);
        }


        /// <summary>
        /// Step 4: The PlayFab purchase request was successfully created.
        /// A PayForPurchaseRequest is created and on success this class goes to OnPayForPurchaseSuccess()
        /// </summary>
        /// <param name="startPurchaseResult"></param>
        void OnStartPurchaseSuccess(StartPurchaseResult startPurchaseResult)
        {
            Debug.Log($"{name}: OnStartPurchaseSuccess(): orderId: {startPurchaseResult.OrderId}");


            var payForPurchaseRequest = new PayForPurchaseRequest()
            {
                OrderId = startPurchaseResult.OrderId,
                ProviderName = "PayPal",
                Currency = "RM"
            };


            PlayFabClientAPI.PayForPurchase(payForPurchaseRequest, OnPayForPurchaseSuccess, StratUtilityMethods.LogPlayFabError);
        }


        /// <summary>
        /// Step 5: The PlayFab pay for purchase request was sucessfully created. Does NOT mean the payment/purchase was successful, just that the payment process started.
        /// The user's browser is opened with their PayPal URL so they can actually do the payment.
        /// </summary>
        /// <param name="payForPurchaseResult"></param>
        void OnPayForPurchaseSuccess(PayForPurchaseResult payForPurchaseResult)
        {
            Debug.Log($"{name}: OnPayForPurchaseSuccess(): orderId: {payForPurchaseResult.OrderId}");


            string payPalConfirmUrl = payForPurchaseResult.PurchaseConfirmationPageURL;


            Application.OpenURL(payPalConfirmUrl);


            StartCoroutine(TryConfirmPurchasePayment(payForPurchaseResult.OrderId));
        }


        /// <summary>
        /// Step 6: (repeats): This method tries to confirm the payment for the purchase.
        /// Sends out PlayFab ConfirmPurchaseRequests repeatedly, and when those requests successfully go out the OnConfirmPurchaseSuccess() method runs.
        /// OnConfirmPurchaseSuccess() does a PlayFab GetPurchaseRequest which returns a transaction state, and that state is used for validation (validating if the payment completed successfully).
        /// Pending purchases get cancelled after 5 minutes of no payment or if the player hits the cancel button.
        /// </summary>
        /// <param name="orderId"></param>
        /// <returns></returns>
        IEnumerator TryConfirmPurchasePayment(string orderId)
        {
            for (int i = 0; i < 300; i++)
            {
                if (isPurchaseSuccessfull)
                    yield break;


                Debug.Log($"{name}: TryConfirmPurchasePayment();");


                var confirmPurchaseRequest = new ConfirmPurchaseRequest()
                {
                    OrderId = orderId
                };


                PlayFabClientAPI.ConfirmPurchase(confirmPurchaseRequest, OnConfirmPurchaseSuccess, StratUtilityMethods.LogPlayFabError);


                yield return new WaitForSeconds(1f);
            }


            // If this code is reached, it means the purchase failed after waiting for 5 minutes.
            CancelPendingPurchase();
        }


        /// <summary>
        /// Pending purchases get cancelled after 5 minutes of no payment or if the player hits the cancel button.
        /// </summary>
        public void CancelPendingPurchase() => OnShowPopUp();


        /// <summary>
        /// Step 6.1: Runs when a PlayFab ConfirmPurchaseRequest() successfully gets sent out. 
        /// Doesn't mean the purchase was actually successful (payment completed)! 
        /// See step 6 for more info.
        /// </summary>
        /// <param name="confirmPurchaseResult"></param>
        void OnConfirmPurchaseSuccess(ConfirmPurchaseResult confirmPurchaseResult)
        {
            var getPurchaseRequest = new GetPurchaseRequest()
            {
                OrderId = confirmPurchaseResult.OrderId
            };


            if (isPurchaseSuccessfull)
                return;


            PlayFabClientAPI.GetPurchase(getPurchaseRequest, OnGetPurchaseRequestSuccess, StratUtilityMethods.LogPlayFabError);
        }


        /// <summary>
        /// Step 6.2: The purchase payment completion gets validated here based on the transactionStatus returned by PlayFab.
        /// If the transactionStatus == 'Succeeded', PlayFabItems.playFabItems.GetPlayerInventory() gets triggered, which in turn triggers CompletePurchaseAndUnlockSGChest()
        /// CompletePurchaseAndUnlockSGChest() checks if their is and item with item class 'sgchest' in PlayFabItems.playFabItems.cachedPlayFabUserInventory and end the purchase process if so.
        /// See step 6 for more info.
        /// </summary>
        /// <param name="getPurchaseResult"></param>
        void OnGetPurchaseRequestSuccess(GetPurchaseResult getPurchaseResult)
        {
            Debug.Log($"{name}: OnGetPurchaseRequestSuccess(): order id: {getPurchaseResult.OrderId} transaction status: {getPurchaseResult.TransactionStatus}");


            if (isPurchaseSuccessfull)
                return;


            if (getPurchaseResult.TransactionStatus == "Succeeded")
                PlayFabItems.playFabItems.GetPlayerInventory();            
        }        


        /// <summary>
        /// Step 7: Player paid for their purchase and now the purchase is completed and the SG chest is unlocked.
        /// </summary>
        /// <param name="unUsedLastPurchasedItemId"></param>
        void CompletePurchaseAndUnlockSGChest(string unUsedLastPurchasedItemId)
        {
            foreach (var item in PlayFabItems.playFabItems.cachedPlayFabUserInventory)
            {
                if (item.ItemClass == "sgchest")
                {
                    isPurchaseSuccessfull = true;


                    PlayFabItems.playFabItems.UnlockContainer(item.ItemId);


                    ClientController.clientController.ToggleCanvasGroupSmooth(content, false);
                    ClientController.clientController.ToggleCanvasGroupSmooth(contentIsPurchasing, false);
                    ClientController.clientController.ToggleCanvasGroupSmooth(contentIsPurchased, true);


                    GameObject instantiatedChest = Instantiate
                    (
                        Resources.Load<GameObject>("Client/FullScreenPopUps/BuySGPopUp/" + item.ItemId + "/Prefab"),
                        contentIsPurchased.transform.Find("ChestContainer")
                    );


                    instantiatedChest.GetComponent<RectTransform>().anchoredPosition = new Vector2(0f, -407f);
                }       
            }   
        }
Partner Add-onsPlayer Inventory
1 comment
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Rick Chen avatar image Rick Chen ♦ commented ·

I cannot reproduce this issue using Postman. According to my test, if there is no payment in Paypal, the ConfirmPurchase API will return "FailedByPaymentProvider" error. I will do some test on Unity. Meanwhile, could you please reproduce this issue and provide the fiddler trace for us to diagnose?

0 Likes 0 ·

1 Answer

·
Ruben Heeren avatar image
Ruben Heeren answered

Nevermind, I'll just try with XSolla PlayFab intergration.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.