[TRANSACTIONS] When player doesn't pass "GetMinPurchaseAge" verse Can crash.

Summary

When a player fails the “GetMinPurchaseAge” in an offer (for example by being in a region the offer isn’t available) the function where the buyoffer is located at, will prematurely end and if the function expected something to be returned it will receive a bugged value

Please select what you are reporting on:

Verse

What Type of Bug are you experiencing?

Verse

Steps to Reproduce

  1. Create a marketplace offer that can fail on a player (simplest way is to just restrict the offer for your region/platform)
  2. Create a verse script that will spawn a suspends function with a “Sleep(0.01)” and a “buyoffer()” for the offer and expect a value to be returned (In the example code I returned a fort_playspace)
  3. In the verse code have it use the returned value. (In my example it just does .GetPlayers() from the fort_playspace
  4. IF the player did not meet the requirements the function will exit prematurely and not reach the return part which returns the intended object, instead returning “empty”

Expected Result

I expected the BuyOffer to have a similar result for when a player declines the offer and when a player is ineligible to view the offer.

Observed Result

If a player is ineligible to see the offer verse will prematurely exit the function, with an invalid return object. If verse tries to use that object verse will crash.

Platform(s)

windows

Upload an image

Video

Additional Notes

In the comments I’ll attach the project file and the verse code.
Also for extra context in the video my region is “GR” so my account should be able to buy the global and “GR” offers BUT not the “IT” (Italian) offer.

Project File: TransactionTest000001.zip (686.8 KB)

The verse scripts are the following:

ProductDefinitions.verse

using { /Fortnite.com/Marketplace }
using { /Verse.org/Assets }


MyMessage<localizes>:message="PLACEHOLDER"
base_product:=class(product){}


MyProduct:=class<concrete>(base_product):
    MaxCount<override>:int=10
    Consumable<override>:logic=true
    var Icon<override>:texture=PlaceholderTexture
    var Name<override>:message=MyMessage
    var Description<override>:message=MyMessage
    var ShortDescription<override>:message=MyMessage

S_Global<localizes>:message="GLOBAL"
GlobalOffer:=class(product_offer):
    var Name<override>:message=S_Global
    var Description<override>:message=MyMessage
    var ShortDescription<override>:message=MyMessage
    var Icon<override>:texture=PlaceholderTexture
    Price<override>:price_dimension=MakePriceVBucks(950.0)
    ProductType<override>:concrete_subtype(product)=MyProduct
    GetMinPurchaseAge<override>(CountryCode:string, SubdivisionCode:string, PlatformFamily:string)<computes><decides>:int=
        0

S_Windows<localizes>:message="WINDOWS"
WindowsOffer:=class(product_offer):
    var Name<override>:message=S_Windows
    var Description<override>:message=MyMessage
    var ShortDescription<override>:message=MyMessage
    var Icon<override>:texture=PlaceholderTexture
    Price<override>:price_dimension=MakePriceVBucks(950.0)
    ProductType<override>:concrete_subtype(product)=MyProduct
    GetMinPurchaseAge<override>(CountryCode:string, SubdivisionCode:string, PlatformFamily:string)<computes><decides>:int=
        PlatformFamily="Windows"
        0

S_Greek<localizes>:message="GREEK"
GreekOffer:=class(product_offer):
    var Name<override>:message=S_Greek
    var Description<override>:message=MyMessage
    var ShortDescription<override>:message=MyMessage
    var Icon<override>:texture=PlaceholderTexture
    Price<override>:price_dimension=MakePriceVBucks(950.0)
    ProductType<override>:concrete_subtype(product)=MyProduct
    GetMinPurchaseAge<override>(CountryCode:string, SubdivisionCode:string, PlatformFamily:string)<computes><decides>:int=
        CountryCode="GR"
        0

S_Italy<localizes>:message="ITALY"
ItalianOffer:=class(product_offer):
    var Name<override>:message=S_Italy
    var Description<override>:message=MyMessage
    var ShortDescription<override>:message=MyMessage
    var Icon<override>:texture=PlaceholderTexture
    Price<override>:price_dimension=MakePriceVBucks(950.0)
    ProductType<override>:concrete_subtype(product)=MyProduct
    GetMinPurchaseAge<override>(CountryCode:string, SubdivisionCode:string, PlatformFamily:string)<computes><decides>:int=
        CountryCode="IT"
        0
        


OfferEnums:=enum{GLOBAL,WINDOWS,GREEK,ITALIAN}
EnumToOffer(OfferEnum:OfferEnums):offer=
    case(OfferEnum):
        OfferEnums.GLOBAL=>GlobalOffer{}
        OfferEnums.WINDOWS=>WindowsOffer{}
        OfferEnums.GREEK=>GreekOffer{}
        OfferEnums.ITALIAN=>ItalianOffer{}

singleoffertest.verse

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Fortnite.com/Marketplace }
using { /Fortnite.com/FortPlayerUtilities }
using { /Fortnite.com/Characters }
using { /Fortnite.com/Playspaces }
single_offer_test:=class(creative_device):
    
    @editable OfferToShow : OfferEnums = OfferEnums.GLOBAL
    @editable Button : button_device = button_device{}
    OnBegin<override>()<suspends>:void=
        Button.InteractedWithEvent.Subscribe(OnInteracted)

    OnInteracted(Agent:agent):void=
        Print("-----------------------NEW TRANSACTION ATTEMPT----------------------------")
        if(Player:=player[Agent]):
            spawn. StartOffer(Player)
    
    
    StartOffer(Player:player)<suspends>:void=
        Print("[StartOffer] START")
        # ATTEMPTS TO BUYOFFER, And returns a "fort_playspace" object,
        # if the BuyOffer is restricted for the player BuyAttempt will return a non-valid value that the compiler 
        # still thinks is a fort_playspace
        Result:=BuyAttempt(Player)  
        Print("[StartOffer] Stopped, Result WAS= "+ToDiagnostic(Result.GetPlayers()))
        # ^THIS WILL TRY TO GET THE PLAYERS IN THE PLAYSPACE RETURNED, Unexpected error will occur if the BuyAttempt failed and returned a non-valid value

    BuyAttempt(Player:player)<suspends>:fort_playspace=
        Sleep(0.01) # If Sleep is removed BuyOffer will ALSO return the StartOffer Function along with the BuyAttempt function
        Print("[BuyAttempt] START")
        Offer:=EnumToOffer(OfferToShow) # Converts ENUM to "offer"

        Result:=BuyOffer(Player,Offer) # IF THE OFFER IS RESTRICTED FOR THE PLAYER THE FUNCTION WILL PREMATURELY RETURN HERE AND RETURN A NON-VALID VALUE

        Print("[BuyAttempt] STOPPED, BUY Result="+ToDiagnostic(Result))
        return GetPlayspace()

FORT-1023121 has been created and its status is ‘Unconfirmed’. This is now in a queue to be reproduced and confirmed.

1 Like