- What do you want to achieve?
I would like to know what’s the proper way to verify if a player successfully purchased a Gamepass.
- What solutions have you tried so far?
I searched the Dev Forum, youtube, etc. I believe I already did a lot of research… to no avail (currently).
- What is the issue?
In every post I see, they are all using the wasPurchased
param from the PromptGamePassPurchaseFinished
event. The Roblox documentation explicitly told us not to rely on it: “This might not accurately reflect if the purchase itself has been successfully processed.”
Then, in a few posts, they decide to use UserOwnsGamePassAsync
, which is a little better than the other posts I mentioned above. Nevertheless, the Roblox documentation for UserOwnsGamePassAsync
says this: “If the user has purchased the pass inside an experience through PromptGamePassPurchase, the UserOwnsGamePassAsync function might return false because of caching behavior.”
What am I supposed to use then? I believe there must be a way to do it without using a proxy.
For me, UserOwnsGamePassAsync
works fine but if you’re actually worried about it then you could save the gamepasses they own/bought inside a datastore. Saving it inside a data store would avoid the caching issue. If they bought it for the first time, you could add UserOwnsGamePassAsync
on top of the PromptGamePassPurchaseFinished
event for extra safety. Because you want to prioritize reliability over convenience, I’d recommend a data manager module so other scripts that access the data
1 Like
Cool! Thanks for the reply!
So you’re saying that instead of using UserOwnsGamePassAsync
when they join, I can check using a DataStore, and that I only use the UserOwnsGamePassAsync
for verifying when they buy it for the first time (and then store it to the DataStore if they bought it). That way, I can dodge the caching behavior of UserOwnsGamePassAsync
?
Yes. Unless some other person comes up with a better idea or disapproves of my idea then it works
1 Like
I’m not sure if that’s worth the effort. As you’ve stated, the documentation contradicts the reliability of either method, so if @sonic_848 says UserOwnsGamePassAsync
has yet to fail them, know that wasPurchased
has yet to fail me. I am not aware of any resource that elaborates on the edge cases in which these warnings come true. I designed my system to lean towards UserOwnsGamePassAsync
being unreliable
1 Like
Thanks for your reply!
I checked out your module (great job on the clean code and documentation, I rarely see devs like this!) and you seem to trust the wasPurchased
and even trust it more than UserOwnsGamePassAsync
. Any explanations, any example of live games you used with this module, and any thoughts on why Roblox would write that if it’s actually not that unrealiable?
Considering PromptGamePassPurchaseFinished
does not fire until the transaction menu has been cleared, it’s hard to imagine why wasPurchased
would be marked unreliable. You’d assume Roblox would only allow the boolean to be true upon receiving an HTTP 200 status code from their API endpoint. It could be that some catastrophic error could occur within their monetization pipeline that is disconnected from said API endpoint, which could lead to the transaction being cancelled or corrupted on Roblox’s end. It is easier to believe UserOwnsGamePassAsync
would be unreliable due to a given reason: caching behaviour. What Roblox means specifically is that prior ownership confirmation requests that come back false could be cached, where future requests after purchase could dig up the false cache. This is why I decided to develop my own dynamic cached based on wasPurchased
, as UserOwnsGamePassAsync
’s flaws are controllable.
I have used my system in projects before, and it’s served me and my coworkers very well. It streamlines implementing developer products and game-passes, while providing niceties like instant good/service activation upon purchase and objective control over in-game monetization
1 Like
I went through your module again, and I have one more question: How do you check if a player owns a gamepass when they just joined? I see you have a cache and use setGamePassOwned
for actions when they buy a gamepass, studio ownership, unofficial ownership, etc., but again, How do you check if a player owns a gamepass when they just joined?
@sonic_848 mentioned the use of datastores, and correct me if I’m wrong, but I think you don’t recommend that. What do you recommend then?
The module in question is a service component of a feature in the Folder-per-Feature architecture. Services are meant to provide essential tools, while the overall functionality of the feature is scripted using those tools. This is what enables flexibility in the feature. I use the tryLoadGamePassAsync function for all current and future players and game-passes on server startup:
--!strict
local Players = game:GetService("Players")
local Feature = script.Parent
local Service = require(Feature.Service)
local Types = require(Feature.Types)
local function onPlayerAdded(player: Player)
for _, gamePass in Service.getGamePasses() do
Service.tryLoadGamePassAsync(player, gamePass)
end
end
for _, player in Players:GetPlayers() do
task.spawn(onPlayerAdded, player)
end
Players.PlayerAdded:Connect(onPlayerAdded)
Service.GamePassRegistered:Connect(Service.tryLoadGamePassForAllAsync)
A data-store can’t function as a solution since you’d be relying on wasPurchased
or UserOwnsGamePassAsync
to verify the transaction’s success. We’ve already weighed the uncertainty of both methods, and it seems that wasPurchase
is inductively more reliable. This means you need only solve the local caching issues with UserOwnsGamePassAsync
, which does not require a data-store. We can continue to let Roblox handle the recording of game-pass ownership
1 Like
Wow! Thanks for your explanation. I looked at your source for tryLoadGamePassAsync
and it eventually uses:
local owned = ownsGamePassAsync(userId, gamePassId)
and the source for that is
local function ownsGamePassAsync(userId: number, gamePassId: number): boolean
return ownsGamePassInCache(userId, gamePassId)
or ownsGamePassInStudio(userId, gamePassId)
or MarketplaceService:UserOwnsGamePassAsync(userId, gamePassId)
or ownsGamePassUnofficiallyAsync(userId, gamePassId)
end
So based on your implementation of the Players.PlayerAdded:Connect(onPlayerAdded)
, the line that would run would be MarketplaceService:UserOwnsGamePassAsync(userId, gamePassId)
assuming the cache found no results, correct?
Yep! That is precisely what happens. However, in my system, I added an objective control flag called “OwnsGamePassesInStudio”. Enabling this BoolValue
within the feature’s configuration allow you to own any current and future registered game-pass. It’s a super helpful testing tool. This check is made before querying Roblox. To think of it, I should even make this check before checking the cache
1 Like
Alrighty, thanks so much for all the help! @sonic_848 thanks too!!
1 Like
The system also comes built with developer product and game-pass gifting support. So even if the flag is not raised, no positive cache exists, and Roblox doesn’t acknowledge ownership, the user still could’ve been gifted the game-pass offline or online. This would be the final stage in understanding whether or not a user owns a game-pass
1 Like