Esta página ainda não foi traduzida: mostrando a versão em inglês.
Catalog & Commerce
Itens, preços, carteiras, lojas, compras e direitos de uso. Integração real com as lojas das plataformas que permitem isso (Stripe, App Store, Google Play, Steam, Xbox), lojas com agenda e público-alvo, e um fluxo de compra em que cada etapa aceita um hook.
Quando usar
- Você vende coisas: por dinheiro real, via Stripe, App Store, Google Play, Steam ou Xbox, ou por moeda da carteira (wallet).
- Cada loja (storefront) precisa ser resolvida por jogador: agenda, público e preço são calculados no servidor, e o cliente nunca faz a conta de quem tem direito.
- As regras de preço ficam num único hook que dá para testar: descontos, mudanças de preço e vetos rodam antes de qualquer cobrança.
- Um recibo não pode ser reaproveitado, e um reembolso (refund) precisa revogar o direito de uso (entitlement) pelos mesmos eventos que o concederam.
- Dispensável quando nenhum item é vendido. Mesmo assim, as recompensas chegam pelo único
Grantdo comércio, com origemreward(é assim que chegam os baús de fim de ciclo de Leaderboards), e até um jogo sem loja mantém um único registro auditável de tudo o que foi concedido.
Quem faz o quê
| Ator | O que faz nesta página |
|---|---|
player | navega pelas lojas, compra, gerencia a carteira e resgata códigos |
seller | configura o catálogo, os preços e a agenda das lojas |
backend-service | valida recibos; muda preços ou concede itens pelos hooks de compra |
Resumo
main, já resolvida para este jogador, e comprar com a carteira// client — the storefront arrives already resolved for this player
var front = await playserv.Commerce.Storefront("main");
var order = await playserv.Commerce.Purchase(front.Items.First(), pay: Pay.Wallet("gems"));// client — the storefront arrives already resolved for this player
const front = await playserv.commerce.storefront('main');
const order = await playserv.commerce.purchase(front.items[0], { pay: Pay.wallet('gems') });# client — the storefront arrives already resolved for this player
front = await playserv.commerce.storefront("main")
order = await playserv.commerce.purchase(front.items[0], pay=Pay.wallet("gems"))Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.
// client — the storefront arrives already resolved for this player
Client->Commerce->Storefronts->Select().Then(
TPSOnResult<TPSPage<FPSStorefront>>::CreateWeakLambda(this, [this](const TPSResult<TPSPage<FPSStorefront>>& Result)
{
if (!Result.HasValue()) { return; }
const FPSStorefront& Front = Result.Value().Rows[0];
Client->Commerce->Orders->Create(FPSIdempotencyKey(CartId), Front.Items[0], FPSPay::Wallet(TEXT("gems")));
}));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.// client — the storefront arrives already resolved for this player
var front = await playserv.Commerce.Storefront("main");
var order = await playserv.Commerce.Purchase(front.Items.First(), pay: Pay.Wallet("gems"));before muda o preço da primeira compra, after concede o item[Before(Commerce.Purchase)] // veto or reprice
public static Verdict FirstBuyDiscount(PurchaseIntent p) =>
p.Player.Purchases == 0 ? Hook.Continue(p.WithPrice(p.Price * 0.5m)) : Hook.Continue(p);
[After(Commerce.Purchase)] // grant — side effects only
public static Task Grant(Purchase done) =>
done.Player.Inventory.Grant(done.Item, done.Count);// veto or reprice
export const firstBuyDiscount = before(Commerce.purchase, (p: PurchaseIntent) =>
p.player.purchases === 0 ? Hook.continue(p.withPrice(p.price * 0.5)) : Hook.continue(p));
// grant — side effects only
export const grant = after(Commerce.purchase, (done: Purchase) =>
done.player.inventory.grant(done.item, done.count));@before(commerce.purchase) # veto or reprice
def first_buy_discount(p: PurchaseIntent) -> Verdict:
return Hook.continue_(p.with_price(p.price * 0.5)) if p.player.purchases == 0 else Hook.continue_(p)
@after(commerce.purchase) # grant — side effects only
async def grant(done: Purchase):
await done.player.inventory.grant(done.item, done.count)Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.
Um hook é uma cloud function: ele roda na plataforma, não na engine. Escreva em C#, TypeScript ou Python; o Unreal assina os eventos de compra e de mudança de direito de uso. Estão planejadas funções escritas na engine: grafos de Blueprint e C++ (traduzido para um formato de script validado pela plataforma). As duas são analisadas e publicadas como qualquer declaração, e a execução continua na plataforma.
Um hook é uma cloud function: ele roda na plataforma, não na engine. Escreva em C#, TypeScript ou Python; o Unity assina os eventos de compra e de mudança de direito de uso.
O modelo
O que um item do catálogo declara.
| Declara | O que é |
|---|---|
key | o item é conteúdo criado pela equipe, identificado por uma chave: renomear no código é só renomear |
kind | consumable, que se gasta; ou durable, que se possui uma única vez |
prices | um preço é uma quantia monetária: um inteiro em unidades menores mais um código de moeda, nunca um float. Um item pode ter vários, em moeda do jogo e em dinheiro real |
identificador externo por provedor | um campo declarado para cada provedor, porque cada loja conhece o item pelo próprio id |
para onde aponta | opcionalmente, uma entidade de qualquer tipo declarado; nesse caso, comprar o item concede a posse dessa entidade |
Que composições são permitidas.
| O que é | |
|---|---|
o que pode ser comprado | um item do catálogo, nunca uma entidade qualquer, porque uma compra precisa de alguém que conceda o direito e responda pelo reembolso |
dois níveis, sem terceiro | um pacote (bundle) é um item do catálogo formado por itens; uma loja é um conjunto de ofertas, e cada oferta aponta para um item e pode sobrescrever o preço dele e o conteúdo de um pacote |
um preço regional | é definido pela loja, não no item |
O que uma loja declara.
| Declara | O que é |
|---|---|
offers | o conjunto de ofertas, cada uma apontando para um item |
schedule | quando a janela abre e fecha, em horário de relógio, sempre em UTC |
audience | um predicado, não uma lista de jogadores: o público é uma regra que continua valendo, não um snapshot |
Para um jogador fora do público, a loja não existe.
Os estados de um pedido.
| De | Para |
|---|---|
created | awaiting payment |
awaiting payment | paid · declined · expired |
paid | granted |
paid ou granted | refunded |
| Sempre | O que é |
|---|---|
o preço | fica fixado no pedido (order) no momento em que ele é criado, então uma mudança de preço depois não altera o que foi combinado |
awaiting payment | tem um prazo declarado, e declarado por provedor, porque cada um tem o seu |
a concessão | é separada do pagamento: paid e granted são estados diferentes. O dinheiro chegar e a coisa aparecer são dois fatos, e misturar os dois esconde qual deles falhou |
um direito de uso | guarda a origem (uma compra, um código promocional, uma recompensa, um presente), então dá para responder “de onde veio isso?” um ano depois |
um direito de uso consumível | se acumula: muda por incremento com chave de idempotência, nunca sobrescrevendo o que foi lido |
a posse | é um predicado de dono: um direito de uso pertence a um jogador pelo mesmo mecanismo que qualquer linha com dono |
o catálogo | é declarado no código e chega ao painel, por padrão, no modo de posse seed: o código cria o que falta, e as edições de um designer sobrevivem à próxima publicação |
os segredos dos provedores | ficam no plano do operador, nunca na declaração e nunca num repositório |
as capacidades de um provedor | são declaradas: se ele tem uma API utilizável e o que consegue fazer, para que o catálogo não prometa um fluxo que a loja não atende |
Um reembolso pode chegar a qualquer momento
Um reembolso é uma transição externa: chega sem que o jogo tenha pedido nada. O que acontece com o que foi concedido é declarado: existem três respostas e nenhuma é padrão.
Cada ponto de extensão diz o tipo que entrega ao hook: antes da compra, a intenção de compra (jogador, oferta, provedor, preço); depois, a própria compra. Um hook nunca recebe um saco de dados sem tipo.
Erros
- Fora do público responde not found, e não adianta repetir. Fora da agenda também responde not found, mas vale repetir quando a janela abrir.
- O provedor está indisponível e o provedor recusou o pagamento são respostas diferentes de propósito: a primeira é unavailable e pode ser repetida com backoff; a segunda é um conflito que repetir não resolve.
- Um recibo inválido é uma recusa de validação; um recibo já consumido por outro pedido ou outro jogador é um conflito, e é isso que torna inútil reaproveitá-lo.
- O preço mudou entre a leitura da loja e a compra é uma precondition failure: leia de novo e decida outra vez, em vez de pagar o preço novo sem perceber.
- Moeda do jogo insuficiente é um conflito, não forbidden: a permissão para comprar existe, o saldo é que não basta. Vale repetir depois de recarregar.
- Um direito de uso durável que o jogador já tem é um conflito.
- A região ou a idade não permite a compra responde forbidden, e não adianta repetir.
- O prazo do pedido venceu é um conflito: crie um pedido novo.
- O limite de gastos se esgotou responde como conflito ou como limite de frequência, conforme o limite em questão, e informa quando ele reinicia.
Limites
Os números chegam com o capítulo de limites da plataforma; o que acontece em cada teto já está definido.
- Tamanho do catálogo: publicar mais um item é recusado como conflito.
- Lojas por projeto: a criação é recusada.
- Ofertas numa loja: a inclusão é recusada; a loja nunca é cortada em silêncio.
- Tempo de vida de um pedido aguardando pagamento: transição para
expired, com um evento. - Frequência de tentativas de compra: limite de frequência com prazo para tentar de novo.
- Limite de gastos por período: um conflito que informa quando o limite reinicia.
- Retenção de pedidos: passado o período declarado, o pedido deixa de poder ser lido, em vez de sumir sem explicação.
- Direitos de uso por jogador: a concessão é recusada, e os já concedidos nunca são removidos.
- A precisão de um preço não é um limite, é um tipo: um inteiro em unidades menores.