fifa17 store: make My Packs reward tiles openable (free coins row)

Reward/My-Packs tiles were emitted with no currencies row (dropped to avoid an
"undefined" payment label). But FIFA 17 opens My Packs through the store PURCHASE
flow (My Packs is a client-side filter over the store catalogue; the open-vs-buy
fork is client-side and no response selects it), so a tile with no purchase path
is not actionable — clicking it navigates instead of opening, sending no request.

Fix: keep the coins currency at the pack price (0 for reward packs; no extPrice,
so no `or %1s` mtx bug). The client then treats the tile as free and clicking
sends POST /purchased, which the server opens for free (owned-only redeem, no
debit). The empty-My-Packs sentinel keeps no currency row so it stays
non-openable. pack70 golden regenerated.

LIVE-PROVEN 2026-08-18: a reward Silver Pack opened and revealed cards on the
retail client (host log: POST /purchased/items 200 -> GET /purchased/items).
This commit is contained in:
funman300
2026-08-19 01:50:51 +00:00
parent 1d6a6fffcc
commit b323244ac9
2 changed files with 17 additions and 3 deletions
@@ -207,9 +207,13 @@ pub fn pack_body(p: &PackDef, idx: u64, owned: bool) -> Value {
});
let obj = body.as_object_mut().expect("pack body is a JSON object");
if owned {
// Reward/My-Packs tiles have no purchase path; drop the coin row so the
// client never formats an unavailable payment label as literal "undefined".
obj.remove("currencies");
// Reward/My-Packs tiles KEEP the coins currency at the pack price (0 for
// reward packs). My Packs opens through the store purchase flow, so a tile
// with no currency row is not actionable — clicking navigates instead of
// opening. With a free coin row the client sends POST /purchased and the
// server (owned-only) opens it for free. No extPrice (that is the `or %1s`
// mtx bug), so the free coin row formats as "0", not an unavailable label.
// LIVE-PROVEN 2026-08-18: a reward Silver Pack opened and revealed cards.
obj.insert(
"displayGroup".into(),
json!({ "value": "mypacks", "priority": idx }),
@@ -242,6 +246,9 @@ pub fn sentinel_body(idx: u64) -> Value {
.expect("sentinel body is a JSON object");
obj.insert("state".into(), json!("active"));
obj.insert("unopened".into(), json!(false));
// The sentinel must stay non-openable (it is only a resolve-without-crash shim
// for an empty My Packs), so it keeps no purchase path.
obj.remove("currencies");
body
}
@@ -212,6 +212,13 @@
"isPremium": false,
"sortPriority": 1,
"displayGroupAssetId": 3,
"currencies": [
{
"name": "coins",
"funds": 0,
"finalFunds": 0
}
],
"packContentInfo": {
"bronzeQuantity": 0,
"silverQuantity": 0,