Subscription APIs are too obtuse

As a Roblox developer, it is currently too hard to implement a fully fleshed out product using the new subscription services.

If Roblox is able to address this issue, it would improve my development experience because I would be able to move quickly and confidently ship subscriptions in my experiences.

I’m going to stray from the usual request format here, because I think the problem is best illustrated by walking you through the existing development process. The APIs seem fine on first glance (I had even given positive feedback in early focus groups) but upon a deep dive in actually using them, it became clear that the implementation needs smoothing when I noticed that my code was about 60% just comments explaining the case and state that was being handled.

Let’s begin by defining what the product is. I am building an AI Tutor into Lua Learning, and users can subscribe to receive a monthly allowance of AI usage. In addition to the subscription, users can purchase a DevProduct for a few robux to get a small one-time allowance to try it out before they (hopefully) decide to subscribe. This seems like a very reasonable use case for subscriptions in my opinion, and I am excited to release it whenever subscriptions are rolled out.

Now to actually build this feature into my experience, I need to be able to determine and handle every relevant state and case. Let’s map out what those are.

  • First time unpaid subscribers
    These are people who have just subscribed, but payment hasn’t gone through yet
  • First time paid subscribers
    These are the above but after payment has cleared
  • Renewing unpaid subscriber
    These are subscribers whose payment this month is still processing
  • Renewed paid subscriber
    These are the above but after payment for the current cycle clears
  • Churned users
    Subscribed in the past, but not currently
  • Refunded users
    Subscribed, but refunded it
  • Resurrected unpaid subscribers
    These folks have subscribed in the past, unsubbed, and now are resubbed but payment is processing
  • Resurrected paid subscribers
    These are the above after payment clears
  • Not subscribed but bought trial credit
    These are people who bought the DevProduct but not the subscription
  • Bought trial credit and unpaid subscriber before finishing trial
    These people have trial credit remaining, but also bought the subscription but payment is processing
  • Bought trial credit and paid subscriber before finishing trial
    These people have trial credit remaining, and also have a paid up sub this cycle

Now let’s figure out how we want to handle them. Note that we’d want to display and permit different things for these different users, so it really does matter. For example, someone who is subscribed but not paid shouldn’t see a subscribe button, but rather a message about payment pending. Churned users should see different prompts than new users, etc. I cannot allow subbed users to access the AI until after their subscription payment successfully processes, otherwise abusers could subscribe, utilize, and cancel before payment succeeds thus leaving me with big server bills and no money. However, I don’t want renewing customers to have an annoying downtime each month while payment goes through, so I’d like “trusted” customers (ie: subscribers with a history of successful payments) to retain access even when this month’s payment is processing to ensure a nice experience. Furthermore, for those who purchase the trial DevProduct, I need that allowance to add to subscription allowance if they subscribe before using it up in order to fully provide what they paid for. When their sub payment cycle ends, their AI usage for the new cycle should reset to 0 to get their new allowance for the month. As you can see, we’re already picking up complexity and we haven’t even started talking about the API yet. Subscription services are a beast.

So, we need to determine what state a user is. This is where the API comes in.
The first thing is a web call to GetUserSubscriptionStatusAsync which lets us know if they have subscribed (but we don’t know if that is paid or unpaid) and if their subscription is set to renew when the cycle ends (which we don’t know when that is). So we make another web call to GetUserSubscriptionPaymentHistoryAsync, which gives us their payment history. Processing payments are not included in this history, which means we have to do more work to figure out their current status. I also need to do a Datastore call to get their trial status.

I was gonna make a flow chart to show how to determine the user’s state, but it was too much of a chore so I wrote it out instead.

EDIT: This is list WRONG, and was based upon vaguely worded documentation. I am now extra glad I wrote this report, because I would have mishandled things.

  • First time unpaid subscribers
    IsSubscribed = true, IsRenewing = any, no trial credit, empty payment history
  • First time paid subscribers
    IsSubscribed = true, IsRenewing = any, no trial credit, one payment history with status Paid
  • Renewing unpaid subscriber
    IsSubscribed = true, IsRenewing = true, no trial credit, latest payment history has end date in past and status Paid
  • Renewed paid subscriber
    IsSubscribed = true, IsRenewing = true, no trial credit, latest payment history has end date in future and status Paid
  • Churned users
    IsSubscribed = false, IsRenewing = false, no trial credit, latest payment history has end date in past and status Paid
  • Refunded users
    IsSubscribed = false, IsRenewing = false, no trial credit, latest payment history has end date in future and status Refunded
  • Resurrected unpaid subscribers
    IsSubscribed = true, IsRenewing = any, no trial credit, latest payment history has end date in past and status Paid
  • Resurrected paid subscribers
    IsSubscribed = true, IsRenewing = any, no trial credit, latest payment history has end date in future and status Paid, prior payment history has end date before latest start date and status Paid
  • Not subscribed but bought trial credit
    IsSubscribed = false, IsRenewing = false, has trial credit, payment history irrelevant
  • Bought trial credit and unpaid subscriber before finishing trial
    IsSubscribed = true, IsRenewing = any, has trial credit, either empty payment history OR latest payment history has end date in past
  • Bought trial credit and paid subscriber before finishing trial
    IsSubscribed = true, IsRenewing = any, has trial credit, latest payment history has end date in future and status Paid

This. Is. Painful. Even with a utility module to wrap away most of this complexity, I still have to maintain that. I also have to ensure correctness since money is on the line, and I can’t actually test this stuff since the mock values returned in studio with the allowlisted IDs don’t let me control the cycle dates to test all these cases.

One thing that would ease the pain in the short term while we figure out a better API would be to have a SubscriptionPaymentStatus.Processing so that we don’t need to derive that status from the lack of history/checking datetimes. It would also make it possible to get the cycle dates before payment completes. letting us initialize our systems while payment is processing.

20 Likes

Hey Zack,

Thanks for the feedback! The team and I spoke internally and there’s a few things we think it may be fruitful to clear up:

The purchase of a subscription from an unsubscribed state won’t update the status to isSubscribed:true unless the payment goes through. Renewing unpaid subscriber and renewing paid subscriber are the only payment processing states that need to be considered, the other payment processing states will simply have isSubscribed:false. Payment processing is quick and there shouldn’t be downtime from month to month; we’ve also included a grace period to give some lenience to payments that fail on first attempt.

As for trials, that’s something we’re looking to flesh out in the future but is not currently offered with the initial launch of Subscriptions within Experiences.

Thanks again for keeping us in the loop on your development, we plan to update documentation to make sure the above information is more clear :slight_smile:

5 Likes

Oh, I see. The documentation did not specify that.

User payments for subscriptions can have some processing time. Payment history doesn’t return a table for this period. However, in order to preserve a user’s subscription experience during the processing period, GetUserSubscriptionStatusAsync() returns IsSubscribed: true for the given user.

I still have concerns, however. A first time pending subscriber should be shown a “hey, your payment is processing” message instead of a “consider subscribing” message- how do I determine which to show if IsSubscribed is false and history is empty?

However, in order to preserve a user’s subscription experience during the processing period, GetUserSubscriptionStatusAsync() returns IsSubscribed: true for the given user. Don’t grant consumable or currency type subscription benefits to the user until after payment has been confirmed.

The grace period makes consumable type subscriptions more complex, since you can’t just trust IsSubscribed.

3 Likes

I do agree with all of the remarks that boatbomber has to say. Though I do want to point out my use-case and maybe this will help guide y’all along.

  • The current functions don’t return whether or not they are subscribed, on the grace period, or not subscribed. To combat this issue, I would suggest using like an enum like boatbomer said or just adding it into payment history. Maybe have Payment history function return if they are subscribed or not subscribed?

  • Communicating externally with an API or OpenCloud API, this is a topic that myself and many other developers/communities struggle with. Many of us developers have external communities (either on Guilded or Discord), that we like to chat, post changelogs, post teasers, or anything to get our community interested in our game. That means, maybe we would want the people who subscribed to get a fancy role in the discord server for us to thank them for subscribing or a private channel to communicate with us developers and other subscribers, the possibilities could be endless.

I’m currently in the process of making my own subscription-based system, as Roblox’s one is not fully done yet.

3 Likes