All the MarketplaceService prompt finished events fire on both the server and client, which makes sense for security reasons. However, MarketplaceService.PromptBulkPurchaseFinished must be called from the client which is acknowledged by the documentation as well. It is recommended to use PlayerOwnsAsset to verify whether or not the user has successfully purchased the assets however this can result in false positives:
• A limited item will return true on a PlayerOwnsAsset if the user already owned a copy
• A non-limited item that was owned before the prompt was given to the user will return true on PlayerOwnsAsset, even if the asset was not successfully bought as part of the prompt
repro-marketplaceservice-promptbulkpurchasefinished.rbxl (58.7 KB)
In this reproduction example, I am prompting the user on join to buy the free Roblox Baseball Cap and the verification awarded Verified, Bonafide, Plaidified. Upon finishing, the event is connected to from both the Server and the Client, however the Server-sided connection throws the following error:
MarketplaceService.PromptBulkPurchaseFinished can only be subscribed to from a local script
References:
https://devforum.roblox.com/t/the-problem-with-promptbulkpurchasefinished-and-its-security/3152535
https://create.roblox.com/docs/reference/engine/classes/MarketplaceService#PromptBulkPurchaseFinished
Disclaimer:
I’m not sure if this should be considered a engine feature request as the documentation suggests this event should have server support. Therefore, I’m filing this as a bug.
Expected behavior
The first parameter of MarketplaceService.PromptBulkPurchaseFinished is player: Player. This suggests that the event was originally designed for server-sided use. To give a clear overview of how PromptBulkPurchaseFinished is inconsistent, I’ve outlined all the events and their parameters below:
- PromptBulkPurchaseFinished( player , status , results ) (CLIENT ONLY) (?)
- PromptBundlePurchaseFinished( player , bundleId , wasPurchased ) (SERVER & CLIENT)
- PromptGamePassPurchaseFinished( player , gamePassId , wasPurchased ) (SERVER & CLIENT)
- PromptPremiumPurchaseFinished() (SERVER (?) & CLIENT)
- PromptProductPurchaseFinished( userId , productId , isPurchased ) (SERVER & CLIENT)
- PromptPurchaseFinished( player, assetId , isPurchased ) (SERVER & CLIENT)
- PromptSubscriptionPurchaseFinished( user , subscriptionId , didTryPurchasing ) (SERVER & CLIENT)
MarketplaceService.PromptBulkPurchaseFinished being called on the client only poses a unmitigable risk with validating the result of a bulk purchase prompt securely. I believe this event needs to be given the ability to be called from the server so we can safely validate its responses.