I followed the tutorial video in Unity doc, Authenthication is initialized and Module already deployed into cloudcode. previously the code is
var myZenyResult = await gameApiClient.CloudSaveData.GetItemsAsync(
context,
context.AccessToken,
context.ProjectId,
context.PlayerId,
new List<string> { "PWZeny" });
but even I change it to
var myZenyResult = await gameApiClient.CloudSaveData.GetItemsAsync(
context,
context.AccessToken,
context.ProjectId,
idplayer,
new List<string> { "PWZeny" });
always have this error access TOkem is missing. Please Help been 2 days figuring out this but with no lock
My module
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;
using System.Linq;
using Newtonsoft.Json;
using Unity.Services.CloudSave.Model;
namespace Buy;
public class Buy
{
public class Player
{
public bool Compromised { get; set; }
public float GCash { get; set; }
public int PWZeny { get; set; }
public string Message { get; set; }
}
public class BuyListing
{
public string PlayerID { get; set; }
public float Price { get; set; }
public int Stock { get; set; }
public DateTime Timestamp { get; set; }
}
private const string GameVariablesId = "BuySell";
private const string key = "Buy";
private readonly int _defaultRetryAttempts = 3;
[CloudCodeFunction("ByCurrency")]
public async Task<Player> ByCurrency(
IExecutionContext context,
IGameApiClient gameApiClient,
string idplayer,
float price,
int stock)
{
var player = new Player();
try
{
// 1. Get buyer's current PWZeny (to check available stock)
var myZenyResult = await gameApiClient.CloudSaveData.GetItemsAsync(
context,
context.AccessToken,
context.ProjectId,
idplayer,
new List<string> { "PWZeny" });
int myCurrentZeny = 0;
if (myZenyResult.Data.Results.Count > 0)
{
var myZenyData = myZenyResult.Data.Results.First().Value;
if (!int.TryParse(myZenyData.ToString(), out myCurrentZeny))
{
player.Compromised = true;
player.Message = "Invalid PWZeny format";
return player;
}
}
// Check if buyer has enough PWZeny stock to list
if (stock > myCurrentZeny)
{
player.Compromised = true;
player.Message = $"Insufficient PWZeny stock. You have {myCurrentZeny}, trying to list {stock}";
return player;
}
// 2. Get buyer's GCash (for returning in response, not for checking)
var cashResult = await gameApiClient.CloudSaveData.GetItemsAsync(
context,
context.AccessToken,
context.ProjectId,
idplayer,
new List<string> { "GCash" });
float currentCash = 0;
if (cashResult.Data.Results.Count > 0)
{
var cashData = cashResult.Data.Results.First().Value;
float.TryParse(cashData.ToString(), out currentCash);
}
// 3. Create the buy listing
var buyListing = new BuyListing
{
PlayerID = idplayer,
Price = price,
Stock = stock,
Timestamp = DateTime.UtcNow
};
// Convert to JSON
string listingJson = JsonConvert.SerializeObject(buyListing);
// 4. Get existing listings to add new one
bool saved = false;
string writeLock = null;
for (int attempt = 0; attempt < _defaultRetryAttempts && !saved; attempt++)
{
try
{
// Get all existing listings using GetCustomItemsAsync
var existingListingsResult = await gameApiClient.CloudSaveData.GetCustomItemsAsync(
context,
context.ServiceToken,
context.ProjectId,
GameVariablesId,
new List<string> { key });
List<BuyListing> allListings = new List<BuyListing>();
if (existingListingsResult.Data.Results.Count > 0)
{
var existingData = existingListingsResult.Data.Results.First().Value;
if (existingData != null)
{
var existingListings = JsonConvert.DeserializeObject<List<BuyListing>>(existingData.ToString());
if (existingListings != null)
{
allListings = existingListings;
}
}
writeLock = existingListingsResult.Data.Results.First().WriteLock;
}
// Add new listing
allListings.Add(buyListing);
// Convert back to JSON
string updatedListingsJson = JsonConvert.SerializeObject(allListings);
// Save updated listings
await gameApiClient.CloudSaveData.SetCustomItemAsync(
context,
context.ServiceToken,
context.ProjectId,
GameVariablesId,
new SetItemBody(key, updatedListingsJson, writeLock));
saved = true;
}
catch (ApiException ex)
{
// Write conflict, try again with new writeLock
if (attempt < _defaultRetryAttempts - 1)
{
await Task.Delay(100 * (attempt + 1)); // Exponential backoff
writeLock = null; // Reset writeLock for next attempt
}
else
{
throw new Exception("Failed to save listing after multiple attempts due to write conflicts");
}
}
}
if (!saved)
{
player.Compromised = true;
player.Message = "Failed to save buy listing after multiple attempts";
return player;
}
// 5. Deduct the listed stock from buyer's PWZeny (reserve the zeny for sale)
int newBuyerZeny = myCurrentZeny - stock;
await gameApiClient.CloudSaveData.SetItemAsync(
context,
context.AccessToken,
context.ProjectId,
context.PlayerId,
new SetItemBody("PWZeny", newBuyerZeny));
// 6. Return success
player.Compromised = false;
player.GCash = currentCash; // Return current GCash (no deduction)
player.PWZeny = newBuyerZeny;
player.Message = $"Successfully listed {stock} PWZeny for {price} GCash each";
return player;
}
catch (ApiException ex)
{
Console.WriteLine($"API Error: {ex.Message}");
player.Compromised = true;
player.Message = $"API Error: {ex.Message}";
return player;
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
player.Compromised = true;
player.Message = $"Error: {ex.Message}";
return player;
}
}
my Script that call the module.
try
{
// Call the function within the module and provide the parameters we defined in there
var module = new BuyBindings(CloudCodeService.Instance);
var result = await module.ByCurrency(AuthenticationService.Instance.PlayerId, price, stock);
Debug.Log(result);
}
catch (CloudCodeException exception)
{
Debug.LogException(exception);
}

