Files
tekton/server/nakama/lua/economy.lua
T
2026-05-19 17:30:29 +08:00

245 lines
10 KiB
Lua

local nk = require("nakama")
local utils = require("lua.utils")
local economy = {}
local SHOP_CATALOG_DEFS = {
{ id = "oldpop-blue-hat", name = "Oldpop Blue Hat", category = "head", gold = 100, star = 0, rarity = "Common", character = "Oldpop" },
{ id = "oldpop-green-hat", name = "Oldpop Green Hat", category = "head", gold = 100, star = 0, rarity = "Common", character = "Oldpop" },
{ id = "oldpop-red-hat", name = "Oldpop Red Hat", category = "head", gold = 100, star = 0, rarity = "Common", character = "Oldpop" },
{ id = "oldpop-yellow-hat", name = "Oldpop Yellow Hat", category = "head", gold = 100, star = 0, rarity = "Common", character = "Oldpop" },
{ id = "oldpop-og-pant", name = "Copper OG Pant", category = "costume", gold = 0, star = 0, rarity = "Common", character = "Oldpop" },
{ id = "oldpop-grey-pant", name = "Copper Grey Pant", category = "costume", gold = 150, star = 0, rarity = "Common", character = "Oldpop" },
{ id = "oldpop-red-pant", name = "Copper Red Pant", category = "costume", gold = 150, star = 0, rarity = "Common", character = "Oldpop" },
{ id = "oldpop-yellow-pant", name = "Copper Yellow Pant", category = "costume", gold = 150, star = 0, rarity = "Common", character = "Oldpop" },
{ id = "oldpop-blue-gloves", name = "Oldpop Blue Gloves", category = "glove", gold = 75, star = 0, rarity = "Common", character = "Oldpop" },
{ id = "oldpop-green-gloves", name = "Oldpop Green Gloves", category = "glove", gold = 75, star = 0, rarity = "Common", character = "Oldpop" },
{ id = "oldpop-red-gloves", name = "Oldpop Red Gloves", category = "glove", gold = 75, star = 0, rarity = "Common", character = "Oldpop" },
{ id = "oldpop-yellow-gloves", name = "Oldpop Yellow Gloves", category = "glove", gold = 75, star = 0, rarity = "Common", character = "Oldpop" }
}
local function build_shop_catalog()
local catalog = {}
for _, def in ipairs(SHOP_CATALOG_DEFS) do
local cat = def.category
if not catalog[cat] then catalog[cat] = {} end
local entry = {
id = def.id,
name = def.name,
gold = def.gold or 0,
star = def.star or 0,
rarity = def.rarity or "Common",
character = def.character
}
table.insert(catalog[cat], entry)
end
return catalog
end
function economy.rpc_get_shop_catalog(context, payload)
if not context.user_id then error("Not authenticated") end
local result = { catalog = build_shop_catalog(), featured_banners = {} }
local status, objs = pcall(nk.storage_read, {{ collection = "shop_config", key = "featured_banners", user_id = "00000000-0000-0000-0000-000000000000" }})
if status and objs and #objs > 0 then
local val = objs[1].value
if val.banners then result.featured_banners = val.banners end
end
return nk.json_encode(result)
end
function economy.rpc_buy_currency(context, payload)
if not context.user_id then error("Not authenticated") end
local request = nk.json_decode(payload)
local packageId = request.package_id
local receipt = request.receipt
local idempotencyKey = request.idempotency_key
if not packageId or packageId == "" then error("Package ID required") end
if not idempotencyKey or idempotencyKey == "" then error("Idempotency key required") end
local status, existing = pcall(nk.storage_read, {{ collection = "receipts", key = idempotencyKey, user_id = context.user_id }})
if status and existing and #existing > 0 then
return nk.json_encode({ success = true, package_id = packageId, duplicate = true, status = existing[1].value.status })
end
local changeset = { gold = 0, star = 0 }
local requiresVerification = false
if packageId == "gold_100" then changeset.gold = 100; requiresVerification = true
elseif packageId == "gold_500" then changeset.gold = 550; requiresVerification = true
elseif packageId == "gold_1000" then changeset.gold = 1150; requiresVerification = true
elseif packageId == "gold_2000" then changeset.gold = 2400; requiresVerification = true
elseif packageId == "gold_5000" then changeset.gold = 6250; requiresVerification = true
elseif packageId == "gold_10000" then changeset.gold = 13000; requiresVerification = true
elseif packageId == "star_100" then changeset.star = 100; changeset.gold = -500
elseif packageId == "star_250" then changeset.star = 250; changeset.gold = -1100
elseif packageId == "star_600" then changeset.star = 600; changeset.gold = -2500
else error("Invalid package ID") end
if requiresVerification and not receipt then
nk.storage_write({{
collection = "receipts",
key = idempotencyKey,
user_id = context.user_id,
value = { type = "currency", package_id = packageId, status = "pending", created_at = os.date("!%Y-%m-%dT%H:%M:%S.000Z") },
permission_read = 1,
permission_write = 0
}})
return nk.json_encode({ success = true, status = "pending", package_id = packageId })
end
local s, err = pcall(function()
if changeset.gold ~= 0 or changeset.star ~= 0 then
nk.wallet_update(context.user_id, changeset, {}, true)
end
nk.storage_write({{
collection = "receipts",
key = idempotencyKey,
user_id = context.user_id,
value = { type = "currency", package_id = packageId, changeset = changeset, receipt = receipt or nk.json_null(), status = "verified", processed_at = os.date("!%Y-%m-%dT%H:%M:%S.000Z") },
permission_read = 1,
permission_write = 0
}})
end)
if not s then
nk.logger_error("Currency purchase failed: " .. tostring(err))
error("NotEnoughFunds")
end
nk.logger_info("User " .. context.user_id .. " bought currency package " .. packageId)
return nk.json_encode({ success = true, status = "verified", package_id = packageId })
end
function economy.rpc_purchase_item(context, payload)
if not context.user_id then error("Not authenticated") end
local request = nk.json_decode(payload)
local itemId = request.item_id
local quantity = request.quantity or 1
local idempotencyKey = request.idempotency_key
if not itemId or itemId == "" then error("Item ID required") end
if quantity < 1 then error("Invalid quantity") end
if not idempotencyKey or idempotencyKey == "" then error("Idempotency key required") end
local status, existing = pcall(nk.storage_read, {{ collection = "receipts", key = idempotencyKey, user_id = context.user_id }})
if status and existing and #existing > 0 then
return nk.json_encode({ success = true, item = itemId, duplicate = true })
end
local itemDef = nil
for _, def in ipairs(SHOP_CATALOG_DEFS) do
if def.id == itemId then
itemDef = def
break
end
end
if not itemDef then error("ItemNotFound") end
local priceGold = (itemDef.gold or 0) * quantity
local priceStar = (itemDef.star or 0) * quantity
local category = itemDef.category or "accessory"
local s, err = pcall(function()
local changeset = {}
if priceGold > 0 then changeset.gold = -priceGold end
if priceStar > 0 then changeset.star = -priceStar end
if priceGold > 0 or priceStar > 0 then
nk.wallet_update(context.user_id, changeset, {}, true)
end
end)
if not s then
nk.logger_error("Wallet update failed: " .. tostring(err))
error("NotEnoughFunds")
end
local s2, err2 = pcall(function()
local writes = {
{
collection = "inventory",
key = itemId,
user_id = context.user_id,
value = { category = category, purchased_at = os.date("!%Y-%m-%dT%H:%M:%S.000Z"), quantity = quantity },
permission_read = 1,
permission_write = 0
},
{
collection = "receipts",
key = idempotencyKey,
user_id = context.user_id,
value = { type = "item", item_id = itemId, quantity = quantity, cost = { gold = priceGold, star = priceStar }, processed_at = os.date("!%Y-%m-%dT%H:%M:%S.000Z") },
permission_read = 1,
permission_write = 0
}
}
nk.storage_write(writes)
end)
if not s2 then
nk.logger_error("Purchase failed: " .. tostring(err2))
error("PurchaseFailed")
end
nk.logger_info("User " .. context.user_id .. " purchased " .. itemId)
return nk.json_encode({ success = true, item = itemId })
end
function economy.rpc_admin_set_featured_banners(context, payload)
utils.require_admin(context)
local req = nk.json_decode(payload or "{}")
local banners = req.banners or {}
local finalBanners = {}
for i = 1, math.min(#banners, 3) do
table.insert(finalBanners, banners[i])
end
for _, b in ipairs(finalBanners) do
local itemId = b.item_id or ""
if itemId ~= "" then
local found = false
for _, def in ipairs(SHOP_CATALOG_DEFS) do
if def.id == itemId then found = true; break end
end
if not found then error("Item not found in catalog: " .. itemId) end
end
end
nk.storage_write({{
collection = "shop_config",
key = "featured_banners",
user_id = "00000000-0000-0000-0000-000000000000",
value = { banners = finalBanners },
permission_read = 2,
permission_write = 0
}})
nk.logger_info("Featured banners updated by admin " .. context.user_id)
return nk.json_encode({ success = true, banners = finalBanners })
end
function economy.rpc_admin_get_featured_banners(context, payload)
utils.require_admin(context)
local status, objs = pcall(nk.storage_read, {{ collection = "shop_config", key = "featured_banners", user_id = "00000000-0000-0000-0000-000000000000" }})
if status and objs and #objs > 0 then
return nk.json_encode({ banners = objs[1].value.banners or {} })
end
return nk.json_encode({ banners = {} })
end
nk.register_rpc(economy.rpc_get_shop_catalog, "get_shop_catalog")
nk.register_rpc(economy.rpc_buy_currency, "buy_currency")
nk.register_rpc(economy.rpc_purchase_item, "purchase_item")
nk.register_rpc(economy.rpc_admin_set_featured_banners, "admin_set_featured_banners")
nk.register_rpc(economy.rpc_admin_get_featured_banners, "admin_get_featured_banners")
nk.logger_info("LUA TEST: economy module loaded successfully")
return economy