Chap. 18 - Payment System

The Payment System

The purpose of the payment system is to allow you to make money selling games and in-game content.

To use the payment system, players must sign up for an account with Wild Pockets. The player uses his credit card (or another payment method) to purchase "coins." The player then enters your game. Your game must initially run in a feature-limited mode. You can limit access to any feature you like - for example, you could deny access to a dungeon, or to a cool costume, or to god-mode. If you want to sell your game as a whole, you could deny access to everything but the title screen. Your game must use the payment system API to offer the player a chance to unlock some restricted feature, for a price. If the player agrees, the purchase is recorded, and coins are transferred from the player's account to your developer account, from which they can be cashed out. Your game can then use the payment system API to detect that the user has paid, and it can unlock the desired feature.

Feature ID Strings

When you wall off a feature of your game as a paid feature, you must give that feature a name, which must be a string. For example, if your game contained three hats which must be individually paid for, you might give each hat a name: "Large Fedora", "Cute Beret", and "Wizard Hat."

Your customers will see these strings! They show up when the customer views his bill. Your customer might complain if he sees a charge that he doesn't understand, so be sure the ID string is self-explanatory.

These feature ID strings are used throughout the payment system API. They appear in the price-list file which you will upload to the server. They appear when you visit your statistics web-page that displays your revenues. They also appear in the Lua commands that verify whether or not the item has been paid for.

Purchase Records

Purchase records are stored on the Wild Pockets servers. The records look much like this:

    Item                    Paid Until
    ---------------------   --------------------
    Wizard Hat              Sep 9, 2010
    Cute Beret              Jan 1, 9999

The payment API is essentially a means to create and query these payment records.

The paid-until date is used to implement subscription services. It shows the date that your subscription expires and must be renewed by making another payment. Some purchases never expire - ie, the customer has purchased permanent access to the content. In that case, the paid-until date will be set to Jan 1, 9999.

The Pricelist File

You will need to upload a price list to our servers. This is an XML file that you upload as “yourusername/pricelist”. It must be named pricelist. Each user account has a single pricelist for everything that they are selling. Upload this XML document the same way you would a texture or sound, through the builder's upload file menu. The file must consist of a 'pricelist' block containing multiple 'item' blocks, like this:

    <item key="yourusername/Wizard Hat" type="subscription" >
      <offer price="200" days="5" />
      <offer price="300" days="10" />
    <item key="yourusername/Cute Beret" type="onetime" >
      <offer price="300" />
    <item key="yourusername/Large Fedora" type="subscription" >
      <offer price="200" months="1" />

Each item is priced in "coins." Coins are nominally worth 1/1000 American Dollar. However, there are certain factors that adjust the value of a coin, which will be explained later.

You can sell items on a one-time basis (your payment buys permanent access to the content), or on a subscription basis (your payment buys temporary access to the content). In the case of a subscription purchase, the pricelist file must specify how many days of access you get for your payment.

If today were midnight on Jan 1, 2000, and you were to pay for the Wizard Hat above for 5 days, the purchase would expire at midnight on Jan 6, 2000 (exactly 5 days from today). If you were to then pay again, the expiration date would be moved to Jan 11, 2000 (exactly 5 days after that). Each payment advances the expiration date by the amount specified in the pricelist file.

Instead of 'days', you may specify 'months'. In that case, the expiration is always the same day of the month - regardless of how many days there are in a month. If today were Jan 18, 2000, and you were to pay 200 coins for the Large Fedora (in the pricelist above), the expiration would be Feb 18, 2000. If you were to pay another 200 coins, the expiration would be advanced to Mar 18, 2000. The exception is if there is no such day in the month ahead. If you were to buy the Fedora on Jan 31, the expiration would be Feb 28. The next expiration after that would be Mar 28, and so forth.

Get the Player Logged In

The player cannot make any purchases unless he is logged into his Wild Pockets account. To put it differently, the system doesn't know if the user has purchased anything unless the user identifies himself. You can determine whether or not the user is logged in by using the function Player.getUsername. If it returns nil, the user is still anonymous.

If you try to use payment system functions when the user is not logged in, the payment system will automatically pop up the login dialog. To the player, this unexpected login prompt can seem "out of the blue." It is very disconcerting. The spontaneous popup is a useful development tool, but in a polished game, you should hand-hold the user. Explain to him why he should log in ("so you can store your high scores and achievements, and access cool features!"), then encourage him to click a login button. When he clicks the button, call Player.requestLogin. This will pop up the exact same login dialog, but since the user specifically asked for it, it not disconcerting. Your payment system code should always verify that the user is logged in. If not, the payment system code should not be used, and the login encouragement should be displayed instead.

When you use Player.requestLogin to pop up the login dialog, you must supply a callback function. When the user clicks the "ok" or "cancel" button on the login dialog, the callback will be invoked. The callback takes parameters that allow you to determine what happened in the login dialog. See the API reference for Player.requestLogin for more information.

The login status is stored in web-browser cookies. Therefore, being logged in is browser-wide state. If the user is running two Wild Pockets games at the same time in the same browser, they are either both logged in to the same account, or both logged out. The Wild Pockets website uses the same cookies. If the user visits the Wild Pockets website, and logs in via the website, he is logged in within the game as well.

Because of this, the user's login status can change "spontaneously." It's not actually spontaneous: the user is actively using another browser window. But as far as the game can tell, the return value of Player.getUsername changes without warning. Games need to take this into account.


Checking for Payment and Offering Purchases

Once the user is logged in, you can check to see whether or not he has paid for a particular item. To determine whether or not he has paid for the Wizard Hat, use code like this:

    Player.hasPurchased("yourusername/Wizard Hat", whCallback)


    function whCallback(paid, errormessage)
      if paid then
        unlock wizard hat
        if errormessage then
          display the error message

First, you call Player.hasPurchased, passing in the Feature ID string and a callback function. Because it takes time to query our servers, it may take as much as a second for the engine to retrieve the necessary information. When the information is available, the callback will be invoked. The callback takes two parameters - a paid-flag, and an error message string. The error message is usually nil - if not, the most likely error is that the internet might be down. Most errors will start with the following strings:

"LoginError" : The player has refused or can not log in
"PriceListError" : The pricelist file has an error or does not match
"InvalidInputError" : One of the passed parameters was invalid
"UnknownError" : There was a problem communicating with the server, possibly the nework was down

If there is no error, then the paid-flag might be true, in which case the Wizard Hat has been paid for.

Avoid making the following mistake: if you call 'hasPurchased' every frame, then each time you call it, you will schedule a callback to be invoked. It is possible to schedule literally hundreds of callbacks in a very short time. You probably don't want this - you probably just want to call the function once.

If the user hasn't paid for a given item, you can offer him the opportunity to buy it. Use the function Player.offerOneTimePurchase. This functions take the same parameters as Player.hasPurchased, and the callback is identical. The difference is that these offer-functions will pop up dialogs that offer the player a chance to buy the item, whereas Player.hasPurchased doesn't make any offers. To offer a subscription purchase use the function Player.offerSubscriptionPurchase. In addition to the first argument which is the item name, this function takes two more arguments , days and months. These arguments determine which offer from the pricelist the player is given. If you specify a number of days that is not in the pricelist, an error will be returned. You should specify days or months, leaving the one that you're not using nil. For example the function below will offer the player the chance to purchase a 5 day subscription for the amount specified in the pricelist file.

Player.offerSubscriptionPurchase("yourusername/Wizard Hat",5,nil,callbackFn)

Once again, it is disconcerting to pop up a dialog unless the player is expecting it. You shouldn't call either Player.offerOneTimePurchase unless you first present the user with a message and a button (ie, "click here to buy a wizard hat for your character"). Once the user clicks a purchase-button, he is going to be expecting the purchase-popup.

Micro Transactions

Game content is sold in 'coins.' Coins are nominally worth 1/1000 American Dollar. Currently, there is no minimum amount that you can charge. In time, we may implement a minimum, but it is going to remain very low - the minimum is not likely to exceed one penny. The overhead for these tiny sales is low - there is no problem selling things cheaply.

Collecting your Revenues

When you have charged for something in a game, a new page will appear on the Wild Pockets website: the "developer account" page. From this page, you can view your sales and your revenues. Periodically, Wild Pockets will write a check and mail it to you. This "cash-out" will be shown on your developer account page. To make cash-out possible, you will need to talk to the Wild Pockets staff and notify them of your mailing address.

Value of a Coin

When you charge for something, you charge the player in "coins." Nominally, a coin is worth 1/1000 of an American Dollar. However, because of expenses and incentives, you will not receive the full amount for each coin that you charge. There are two primary kinds of deductions:

  • Expenses. Wild Pockets takes a percentage of all coins collected to cover credit card fees, server operations, developer salaries, sales taxes, partner site fees, and other expenses. Because of expenses, the number of coins you receive will be reduced. Each coin will still be worth 1/1000 American Dollar, but you will receive fewer coins.
  • Incentives. Wild Pockets reserves the right to occasionally give out free coins to players, as promotions. Although the player can't tell the difference, these coins are not regular coins - they are "incentive coins." When you receive incentive coins, they show up in a different place on your developer account page. They cannot be cashed in, but they can be used to purchase services from Wild Pockets.

All of this is spelled out in detail in the Wild Pockets terms of service.

Avoiding Misunderstandings

It is worth it to spend a moment on practices that can avoid conflict and misunderstandings with the customer. Mainly, conflicts arise because of confusion - particularly, when the player buys one thing, but thinks he's buying something else. It's best to take great care to avoid this situation. It can be very frustrating to work hard, deliver a good game, and still have customers accuse you of trying to cheat them.

There are a lot of potentially bad situations. One is that the player might think he's buying an RPG, when in fact, the game is a platformer. Another is that the customer might find something on his bill that he doesn't remember buying. Another risk is that the player might think he's buying all the hats, when in fact, he's only buying one. It's worth it to go over your purchase-messages and item descriptions with a fine-tooth comb to avoid these situations. Here are some guidelines that might help:

  • When offering an item for sale, make it crystal clear what you're selling. Show your item-descriptions to your friends, and have them paraphrase it back to you. If there's any confusion, rephrase it.
  • Don't sell anything too abstract or confusing. Keep it simple.
  • Make sure the player has a clear picture of what your game is going to be like. Display screenshots before the actual purchase.
  • Make sure the text that appears on the customer's bill is self-explanatory and that it jogs the memory.