Blog

Latest news and updates from the PlayFab developers

by BrendanVanous 2015-08-06

Wanna buy a duck? How to Set Up Trading in Your Game

More and more, games are providing ways for players to interact that go beyond the simple “boom, hi there!” of a headshot. Among them, gifting and trading are some of the most powerful social features, as they foster interaction between players, and can add an extra dimension to the way the player engages with the game itself. Whether it’s giving another player a thank-you gift or engaging in a series of clever trades to get a complete set of armor you’ve been aching to try out, these mechanics provide for additional interaction which can broaden the appeal of the game to more players.

In order to provide for this, PlayFab has introduced Trading API calls, which you can try out in your titles right away. In this post, I’ll walk you through the essentials of how to use these calls, provide a recommended way to build out a trading environment for your players, so that you can add this to your in-game ecosystem, and give you a look at where we plan to take this feature next.

For simplicity, all the examples below are shown as simple Web API calls, but they’re also available in all our SDKs. If you’re not seeing your preferred language or platform in the list, let us know in our forums, or use our SDK Generator to make one that precisely meets your needs.

Trading essentials

The trading system in PlayFab creates an offer which an authorized recipient can then choose to accept. Authorized recipients are simply other players designated as such by the player who originally created the trade. Potential recipients can be informed of the possible trade in a number of ways: In-game chat, a custom user interface, or even social media postings.

One method developers can use to quickly test out the notions in this post is to create Shared Group Data with an ID of [PlayFabID]_Incoming_Trades for each player. Then, the player offering the trade could write a Key/Value pair of [PlayFabId]/[TradeId] into that Shared Group Data, where the PlayFabId is his own, so that there aren’t any potential collision issues with other players. On start of the game, a player would check his own Shared Group Data, to see if any new trades are available, and present them to the player.

Also, note that many games with trading have certain items which cannot be traded. In order to provide for this, as well as protect existing titles in PlayFab from abuse of their in-game inventory systems, items must be marked as “tradable Item” in the Properties dialog for the item in the game Catalog, as shown below.

 

Once you have a few items in your Catalog defined as tradable, you’re ready to proceed.

Making an offer

Trading, at its core, is a way to specify an exchange of items for other items. You can allow any user to set up a trade in your title using the OpenTrade API call:

<code class="language-http hljs "><span class="hljs-attribute">POST https://aaa.playfabapi.com/Client/OpenTrade
Content-Type</span>: <span class="hljs-string">application/json</span>
<span class="hljs-attribute">X-Authentication</span>: <span class="hljs-string">1234567890ABCDEF-0-0-AAA-1234567890ABCDEF-1234567890ABCDEF.1234567890ABCDEF {</span>
  "<span class="hljs-attribute">OfferedInventoryInstanceIds"</span>: <span class="hljs-string">[</span>
    "<span class="hljs-attribute">1359947",
    "6354819"
  ],
  "RequestedCatalogItemIds"</span>: <span class="hljs-string">[</span>
    "<span class="hljs-attribute">rare_hat_7"
  ]
}
</span></code>

 

In this case, the user is offering two items from his current inventory for trade, using their specific Instance IDs.

<code class="language-http hljs ">  "<span class="hljs-attribute">OfferedInventoryInstanceIds"</span>: <span class="hljs-string">[</span>
    "<span class="hljs-attribute">1359947",
    "6354819"
  ],</span></code>

 

And he’s asking for one item — a rare hat — using the ItemId from the game's catalog.

<code class="language-http hljs "> "<span class="hljs-attribute">RequestedCatalogItemIds"</span>: <span class="hljs-string">[</span>
    "<span class="hljs-attribute">rare_hat_7"
  ]
</span></code>

 

One other possible input parameter — AllowedPlayerIds — restricts the trade to specific users. Since this trade isn’t using that, it’s available for any player to accept.

In response, the PlayFab service returns all the details for the trade offer which has been opened. The key item to take note of here is the TradeId, which we’ll be using again later on.

<code class="language-http hljs "><span class="hljs-status">HTTP/1.1 <span class="hljs-number">200</span> OK</span>
<span class="hljs-attribute">Content-Type</span>: <span class="hljs-string">application/json; charset=utf-8</span>
{
  "<span class="hljs-attribute">code"</span>: <span class="hljs-string">200,</span>
  "<span class="hljs-attribute">status"</span>: <span class="hljs-string">"OK",</span>
  "<span class="hljs-attribute">data"</span>: <span class="hljs-string">{</span>
    "<span class="hljs-attribute">Trade"</span>: <span class="hljs-string">{</span>
      "<span class="hljs-attribute">Status"</span>: <span class="hljs-string">"Opening",</span>
      "<span class="hljs-attribute">TradeId"</span>: <span class="hljs-string">"98765FEDCBA54321",</span>
      "<span class="hljs-attribute">OfferingPlayerId"</span>: <span class="hljs-string">"1234567890ABCDEF",</span>
      "<span class="hljs-attribute">OfferedInventoryInstanceIds"</span>: <span class="hljs-string">[</span>
        "<span class="hljs-attribute">1359447",
        "635691"
      ],
      "RequestedCatalogItemIds"</span>: <span class="hljs-string">[</span>
        "<span class="hljs-attribute">rare_hat_7"
      ],
      "OpenedAt"</span>: <span class="hljs-string">"2015-07-29T09:04:28Z"</span>
    }
  }
}
</code>

 

Once the trade has been created, it’s worth noting that items offered as part of a trade no longer appear in the user’s inventory in a call to GetUserInventory (or GetUserCombinedInfo). That’s because those items have been placed in “escrow” pending the trade, and so are not usable by the player. In the Game Manager, those items show up in the Player’s inventory as being in the state “TradePending”. If the player later cancels the trade, those items will be removed from escrow, putting them back in the “Succeeded” state, at which point they will again be listed in the player’s active inventory via the API calls.

Sending a gift

But it’s not always about getting something in return. Sometimes, your players want to be nice and give each other gifts. To accomplish that, just leave out the RequestedCatalogItemIds, and the items in the OfferedInventoryInstanceIds are free to the player who accepts the offer.

<code class="language-http hljs "><span class="hljs-attribute">POST https://aaa.playfabapi.com/Client/OpenTrade
Content-Type</span>: <span class="hljs-string">application/json</span>
<span class="hljs-attribute">X-Authentication</span>: <span class="hljs-string">1234567890ABCDEF-0-0-AAA-1234567890ABCDEF-1234567890ABCDEF.1234567890ABCDEF {</span>
  "<span class="hljs-attribute">OfferedInventoryInstanceIds"</span>: <span class="hljs-string">[</span>
    "<span class="hljs-attribute">1359947",    
    "6354819”
  ],
  "AllowedPlayerIds"</span>: <span class="hljs-string">[</span>
    "<span class="hljs-attribute">74621AD74E8431FF1"
  ]
}
</span></code>

 

And since presents usually have an intended recipient, we’re also specifying the AllowedPlayerIds in this call, so that only the player we want to send this gift to can claim it.

<code class="language-http hljs "><span class="hljs-status">HTTP/1.1 <span class="hljs-number">200</span> OK</span>
<span class="hljs-attribute">Content-Type</span>: <span class="hljs-string">application/json; charset=utf-8</span>
{
  "<span class="hljs-attribute">code"</span>: <span class="hljs-string">200,</span>
  "<span class="hljs-attribute">status"</span>: <span class="hljs-string">"OK",</span>
  "<span class="hljs-attribute">data"</span>: <span class="hljs-string">{</span>
    "<span class="hljs-attribute">Trade"</span>: <span class="hljs-string">{</span>
      "<span class="hljs-attribute">Status"</span>: <span class="hljs-string">"Opening",</span>
      "<span class="hljs-attribute">TradeId"</span>: <span class="hljs-string">"12345ABCDEF67890",</span>
      "<span class="hljs-attribute">OfferingPlayerId"</span>: <span class="hljs-string">"1234567890ABCDEF",</span>
      "<span class="hljs-attribute">OfferedInventoryInstanceIds"</span>: <span class="hljs-string">[</span>
        "<span class="hljs-attribute">1359447",
        "635691"
      ],
      "OpenedAt"</span>: <span class="hljs-string">"2015-07-29T09:34:31Z"</span>
    }
  }
}
</code>

 

Accepting a trade

Once a trade has been initiated by one player, another player has to accept the trade for it to be completed. For this step, the AcceptTrade call is used to claim the trade for the current user — assuming that either her PlayFab ID is in the AllowedPlayerIds, or that no IDs have been specified, making this trade available to everyone. In accepting the trade, the specific item instances which are being used to fulfill the RequestedCatalogItemIds (if any) must be specified, so that PlayFab can finalize the deal.

For example, if the current player were to accept the first trade we set up, the call to AcceptTrade might look like this.

<code class="language-http hljs "><span class="hljs-attribute">POST https://aaa.playfabapi.com/Client/AcceptTrade
Content-Type</span>: <span class="hljs-string">application/json</span>
<span class="hljs-attribute">X-Authentication</span>: <span class="hljs-string">ABCDEF1234567890-0-0-AAA-ABCDEF1234567890-ABCDEF1234567890.ABCDEF1234567890 {</span>
  "<span class="hljs-attribute">OfferingPlayerId"</span>: <span class="hljs-string">"1234567890ABCDEF",</span>
  "<span class="hljs-attribute">TradeId"</span>: <span class="hljs-string">"98765FEDCBA54321",</span>
  "<span class="hljs-attribute">AcceptedInventoryInstanceIds"</span>: <span class="hljs-string">[</span>
    "<span class="hljs-attribute">C26738AE73882311F"
  ]
}
</span></code>

 

Here, the AcceptedInventoryInstanceIds specifies one item, which should be the rare_hat_7 the original player requested. If it isn’t, or the item specified isn’t in the current player’s inventory, the trade will be rejected.

For the second trade, where the items are a gift, the response would leave out the AcceptedInventoryInstanceIds, since the player offering the items didn’t ask for anything.

 

 

<code class="language-http hljs "><span class="hljs-attribute">POST https://aaa.playfabapi.com/Client/AcceptTrade
Content-Type</span>: <span class="hljs-string">application/json</span>
<span class="hljs-attribute">X-Authentication</span>: <span class="hljs-string">ABCDEF1234567890-0-0-AAA-ABCDEF1234567890-ABCDEF1234567890.ABCDEF1234567890 {</span>
  "<span class="hljs-attribute">OfferingPlayerId"</span>: <span class="hljs-string">"1234567890ABCDEF",</span>
  "<span class="hljs-attribute">TradeId"</span>: <span class="hljs-string">"12345ABCDEF67890"</span>
}
</code>

 

However, as the sharp-eyed will have spotted, this AcceptTrade call will actually fail, since the player who created it specified a recipient, which doesn’t match the current player.

<code class="language-http hljs "> "<span class="hljs-attribute">AllowedPlayerIds"</span>: <span class="hljs-string">[</span>
    "<span class="hljs-attribute">74621AD74E8431FF1"
  ]
</span></code>

 

So instead of claiming the trade in this case, the call would fail with a TradeAcceptingUserNotAllowed error.

Finally, it’s worth noting that trade acceptance is an atomic operation in PlayFab. Any given trade can be accepted only once, even if many players try to claim it simultaneously. The first valid AcceptTrade call to be processed will be the one that succeeds. The rest will receive the TradeAlreadyFilled error response, so that the game knows what to tell each user.

So if the current user is able to claim the trade — the player is valid, the items required to complete the trade are specified and are in that player’s inventory — and no other user has claimed it already, the call succeeds, transferring the items between the players and marking the trade as complete.

Cancelling a trade

Now, there’s always the chance that after creating a trade, a player may decide that he wants to change or even cancel the trade entirely. Maybe he realized he’s asking for too much — or not enough — or maybe he just decided he doesn’t want to give up the items at all. To do so, all the title needs to do is make a call to CancelTrade, specifying the TradeId.

<code class="language-http hljs "><span class="hljs-attribute">POST https://aaa.playfabapi.com/Client/CancelTrade
Content-Type</span>: <span class="hljs-string">application/json</span>
<span class="hljs-attribute">X-Authentication</span>: <span class="hljs-string">1234567890ABCDEF-0-0-AAA-1234567890ABCDEF-1234567890ABCDEF.1234567890ABCDEF {</span>
  "<span class="hljs-attribute">TradeId"</span>: <span class="hljs-string">"12345ABCDEF67890"</span>
}
</code>

 

If the trade has not yet been accepted, this call updates the trade to the Cancelled status, meaning that it can no longer be accepted by other users, though the information on the trade will be maintained, so that the player can review his trade history.

Viewing trade histories and status

And speaking of the trade history, the player can retrieve the complete history of his trades at any time via a call to GetPlayerTrades. For simplicity, there’s only one parameter to this call — an optional StatusFilter. Titles can use it to filter the returned trades down to just those they’re interested in for the current operation or, by leaving it out, they can get the complete trade history for the user. For example, if the title wanted to retrieve all the trades which are still outstanding for the current user, it could specify trades in the Open status.

<code class="language-http hljs "><span class="hljs-attribute">POST https://aaa.playfabapi.com/Client/GetPlayerTrades
Content-Type</span>: <span class="hljs-string">application/json</span>
<span class="hljs-attribute">X-Authentication</span>: <span class="hljs-string">1234567890ABCDEF-0-0-AAA-1234567890ABCDEF-1234567890ABCDEF.1234567890ABCDEF {</span>
  "<span class="hljs-attribute">StatusFilter"</span>: <span class="hljs-string">"Open"</span>
}
</code>

 

This way, the response would contain only information about trades that have been created but not yet accepted by any user, in case the player wants to make adjustments to his available trades. For the user who opened the two trades in this example, if his first trade has not yet been accepted by anyone, but his second (the gift) has, the response to this call would contain the information for just the open trade, since we’ve filtered for that status.

<code class="language-http hljs ">{
    "<span class="hljs-attribute">code"</span>: <span class="hljs-string">200,</span>
    "<span class="hljs-attribute">status"</span>: <span class="hljs-string">"OK",</span>
    "<span class="hljs-attribute">data"</span>: <span class="hljs-string">{</span>
        "<span class="hljs-attribute">OpenedTrades"</span>: <span class="hljs-string">[</span>
            {
                "<span class="hljs-attribute">Status"</span>: <span class="hljs-string">"Open",</span>
                "<span class="hljs-attribute">TradeId"</span>: <span class="hljs-string">"98765FEDCBA54321",</span>
                "<span class="hljs-attribute">OfferingPlayerId"</span>: <span class="hljs-string">"1234567890ABCDEF",</span>
                "<span class="hljs-attribute">OfferedInventoryInstanceIds"</span>: <span class="hljs-string">[</span>
                    "<span class="hljs-attribute">1359947",
                    "6354819"
                ],
                "OfferedCatalogItemIds"</span>: <span class="hljs-string">[</span>
                    "<span class="hljs-attribute">common_sword_3",
                    "uncommon_shield_1"
                ],
                "RequestedCatalogItemIds"</span>: <span class="hljs-string">[</span>
                    "<span class="hljs-attribute">rare_hat_7"
                ],
                "OpenedAt"</span>: <span class="hljs-string">"2015-07-29T09:04:28Z"</span>
            }
        ],
        "<span class="hljs-attribute">AcceptedTrades"</span>: <span class="hljs-string">[]</span>
    }
}
</code>

 

Note the AcceptedTrades array: as the name implies, the details for any trades which the player accepted, using the AcceptTrade call, would appear here. This way, the title can give the player a way to review his trades over time, and see how well (or poorly) he’s doing.

Now, if you want to get information on trades that were initiated by another player (especially for trades that are still open), the GetTradeStatus method lets you retrieve the info for any trade, regardless.

<code class="language-http hljs "><span class="hljs-attribute">POST https://aaa.playfabapi.com/Client/GetTradeStatus
Content-Type</span>: <span class="hljs-string">application/json</span>
<span class="hljs-attribute">X-Authentication</span>: <span class="hljs-string">ABCDEF1234567890-0-0-AAA-ABCDEF1234567890-ABCDEF1234567890.ABCDEF1234567890 {</span>
  "<span class="hljs-attribute">OfferingPlayerId"</span>: <span class="hljs-string">"1234567890ABCDEF",</span>
  "<span class="hljs-attribute">TradeId"</span>: <span class="hljs-string">"98765FEDCBA54321"</span>
}
</code>

 

In this case, we’ve chosen the second trade we set up — the gift. Since that one has been accepted by its intended player, the resulting response reflects the fact that the trade has been completed.

 

 

<code class="language-http hljs ">{
    "<span class="hljs-attribute">code"</span>: <span class="hljs-string">200,</span>
    "<span class="hljs-attribute">status"</span>: <span class="hljs-string">"OK",</span>
    "<span class="hljs-attribute">data"</span>: <span class="hljs-string">{</span>
        "<span class="hljs-attribute">Trade"</span>: <span class="hljs-string">{</span>
            "<span class="hljs-attribute">Status"</span>: <span class="hljs-string">"Accepted",</span>
            "<span class="hljs-attribute">TradeId"</span>: <span class="hljs-string">"12345ABCDEF67890",</span>
            "<span class="hljs-attribute">OfferingPlayerId"</span>: <span class="hljs-string">"1234567890ABCDEF",</span>
            "<span class="hljs-attribute">OfferedInventoryInstanceIds"</span>: <span class="hljs-string">[</span>
                "<span class="hljs-attribute">1359947",
                "6354819"
            ],
            "OfferedCatalogItemIds"</span>: <span class="hljs-string">[</span>
                "<span class="hljs-attribute">common_sword_3",
                "uncommon_shield_1"
            ],
            "OpenedAt"</span>: <span class="hljs-string">"2015-07-29T09:34:31Z"</span>
        }
    }
}
</code>

 

Managing trades across users

So now that you know how everything works, what’s the best way to use it? As with many situations where you need to manage data that’s originating from users in a non-user-specific way, the key is to use Shared Group Data. For an example of using Shared Group Data, have a look at our post on asynchronous matchmaking in PlayFab.

For one-to-one trades, you can create a Shared Group Data space using the PlayFab ID of the user who is the recipient for the trade request. Think of it as an “inbox” of sorts for the player, where other users can add a note concerning their own trade request. Then, you would add trade requests containing just the salient details. As an example, and using the trade requests we built in this sample, here’s a call to write information about the “gift” trade we created earlier into the recipient’s “inbox”.

<code class="language-http hljs "><span class="hljs-attribute">POST https://aaa.playfabapi.com/Client/UpdateSharedGroupData
Content-Type</span>: <span class="hljs-string">application/json</span>
<span class="hljs-attribute">X-Authentication</span>: <span class="hljs-string">1234567890ABCDEF-0-0-AAA-1234567890ABCDEF-1234567890ABCDEF.1234567890ABCDEF {</span>
  "<span class="hljs-attribute">SharedGroupId"</span>: <span class="hljs-string">"74621AD74E8431FF1",</span>
  "<span class="hljs-attribute">Data"</span>: <span class="hljs-string">{</span>
    "<span class="hljs-attribute">OfferingItems"</span>: <span class="hljs-string">"[\"common_sword_3\",</span>
                       \"<span class="hljs-attribute">uncommon_shield_1\"]",    
    "AskingItems:"</span>: <span class="hljs-string">""</span>
  }  
}
</code>

 

As you can see, we’re writing to a Shared Group with the ID of person who is able to accept the trade, along with the information about what we’re offering and requesting. In this case, I’ve put an AskingItems key in, though since the player isn’t asking for anything in return, we could just as easily leave that out.

For designs where you want to make the trade available more broadly, you could use a more global Shared Group Data object, as in the asynchronous matchmaking example, though as your user population grows — and especially if trading is a significant part of player interaction — this could rapidly grow to be an unwieldy dataset to be querying all at once.

Future plans

Which leads me then to our future development work for this feature. To support even more game styles, we will be adding the following in upcoming sprints:

  • A way to query for a filtered list of trades, based on items being offered or requested
  • “Make me an offer” trades, in which a player will be able to specify either side of the transaction as a base, allowing other players to fill in the rest of the trade, so that the original player can accept or decline the offer
  • The ability to track stackable items
  • The ability to specify Virtual Currencies in the trade, both as part of the offer and request
  • Game Manager integration

If any of the additions sound like ones you’ll need for your game, we’d like to hear from you, so that we can make sure we’re closely tracking on the needs of our community, and the timeframes for titles. We’re also very interested in feedback on ways we can further add to and improve our service in general. If you have suggestions, questions, or feedback of any kind,  we’d love to hear from you in our forums or via email.