@pcreative/commerce-contract 1.3.1 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -2
- package/package.json +1 -1
- package/src/adapters/api.js +165 -0
- package/src/index.d.ts +212 -0
package/README.md
CHANGED
|
@@ -44,6 +44,31 @@ al tema. Un carrito que ya no existe devuelve `null`, no una excepción.
|
|
|
44
44
|
**`checkout.complete()` puede devolver `{ order }` o `{ cart, error }`** — las
|
|
45
45
|
dos con HTTP 200. Si el cobro falla, el carrito vuelve intacto y con el motivo.
|
|
46
46
|
|
|
47
|
+
**El autor de una pieza y su soporte van juntos.** `getAuthor(handle)` trae la
|
|
48
|
+
ficha pública de quien vende en el mercado, con sus piezas y con lo que calcula
|
|
49
|
+
la plataforma: cuántas consultas resuelve y cuánto tarda en contestar. Lo que
|
|
50
|
+
todavía no tiene dato viene en `null` —nunca en cero— para que el tema pueda
|
|
51
|
+
enseñar «autor nuevo» en vez de un 0 % que espanta.
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
const autor = await commerce.getAuthor("estudio-demo")
|
|
55
|
+
autor.support?.resolvedPct // 80, o null si aún no hay tres valoraciones
|
|
56
|
+
autor.sales // 12, o null si todavía no ha vendido
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Para escribir al autor hace falta la clave de licencia**: solo da soporte a
|
|
60
|
+
quien ha comprado. El hilo es privado y el correo del comprador viaja siempre en
|
|
61
|
+
el cuerpo, nunca en la dirección.
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
const { id, covered } = await commerce.support.open({
|
|
65
|
+
licenseKey, subject: "No instala", message: "Me da un error", email,
|
|
66
|
+
})
|
|
67
|
+
// covered === false: su soporte incluido ya caducó. Se avisa, no se bloquea.
|
|
68
|
+
await commerce.support.get(id, email)
|
|
69
|
+
await commerce.support.rate(id, email, true) // una sola pregunta al cerrar
|
|
70
|
+
```
|
|
71
|
+
|
|
47
72
|
## Escribir otro adaptador
|
|
48
73
|
|
|
49
74
|
Implementa la interfaz `CommerceClient` de [`src/index.d.ts`](src/index.d.ts) y
|
|
@@ -54,8 +79,8 @@ import { assertCommerceClient } from "@pcreative/commerce-contract"
|
|
|
54
79
|
const faltan = assertCommerceClient(miCliente) // [] si cumple
|
|
55
80
|
```
|
|
56
81
|
|
|
57
|
-
`account` y `
|
|
58
|
-
cliente ni blog.
|
|
82
|
+
`account`, `content`, `getAuthor` y `support` son opcionales: un backend puede
|
|
83
|
+
no tener cuentas de cliente, ni blog, ni mercado de autores.
|
|
59
84
|
|
|
60
85
|
## Tests
|
|
61
86
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pcreative/commerce-contract",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "Contrato de datos de commerce: los tipos que consume un tema y los adaptadores que los resuelven. Sin dependencias ni SDK del backend.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
package/src/adapters/api.js
CHANGED
|
@@ -6,6 +6,26 @@ const ORDEN_DEL_CONTRATO = {
|
|
|
6
6
|
price_desc: "precio_desc",
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
const DE_QUIEN = { comprador: "buyer", autor: "author", tienda: "store" }
|
|
10
|
+
|
|
11
|
+
function aHilo(h) {
|
|
12
|
+
return {
|
|
13
|
+
id: h.id,
|
|
14
|
+
subject: h.asunto,
|
|
15
|
+
item: h.pieza,
|
|
16
|
+
status: h.estado,
|
|
17
|
+
covered: h.con_soporte !== false,
|
|
18
|
+
solved: h.resuelto ?? null,
|
|
19
|
+
messages: (h.mensajes ?? []).map((m) => ({
|
|
20
|
+
from: DE_QUIEN[m.de] ?? "store",
|
|
21
|
+
text: m.texto,
|
|
22
|
+
createdAt: m.creado_en,
|
|
23
|
+
})),
|
|
24
|
+
createdAt: h.creado_en,
|
|
25
|
+
updatedAt: h.movido_en,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
9
29
|
export function createCommerce({
|
|
10
30
|
baseUrl,
|
|
11
31
|
publishableKey,
|
|
@@ -108,11 +128,13 @@ export function createCommerce({
|
|
|
108
128
|
tiers: v.tiers ?? [],
|
|
109
129
|
digital: Boolean(v.digital),
|
|
110
130
|
requiresShipping: v.requiresShipping ?? !v.digital,
|
|
131
|
+
code: v.code ?? null,
|
|
111
132
|
bundle: v.bundle ?? null,
|
|
112
133
|
bundleGroups: v.bundleGroups ?? null,
|
|
113
134
|
measure: v.measure ?? null,
|
|
114
135
|
unitPrice: v.unitPrice ?? null,
|
|
115
136
|
})),
|
|
137
|
+
market: p.market ?? null,
|
|
116
138
|
categories: p.categories ?? [],
|
|
117
139
|
quantityRules: p.quantityRules ?? null,
|
|
118
140
|
customization: p.customization ?? [],
|
|
@@ -203,6 +225,7 @@ export function createCommerce({
|
|
|
203
225
|
if (params.q) qs.set("q", String(params.q))
|
|
204
226
|
if (params.category) qs.set("categoria_handle", params.category)
|
|
205
227
|
if (params.categoryId) qs.set("categoria", params.categoryId)
|
|
228
|
+
if (params.type) qs.set("tipo", params.type)
|
|
206
229
|
if (params.ids?.length) qs.set("ids", params.ids.join(","))
|
|
207
230
|
if (ORDEN_DEL_CONTRATO[params.sort]) qs.set("orden", ORDEN_DEL_CONTRATO[params.sort])
|
|
208
231
|
const r = await api(`/tienda/productos?${qs}`)
|
|
@@ -571,6 +594,148 @@ export function createCommerce({
|
|
|
571
594
|
throw e
|
|
572
595
|
}
|
|
573
596
|
},
|
|
597
|
+
async getLicenses(orderId) {
|
|
598
|
+
try {
|
|
599
|
+
const { licencias } = await api(`/tienda/pedidos/${encodeURIComponent(orderId)}/licencias`)
|
|
600
|
+
return (licencias ?? []).map((l) => ({
|
|
601
|
+
key: l.key,
|
|
602
|
+
status: l.status,
|
|
603
|
+
title: l.title ?? null,
|
|
604
|
+
sites: Number(l.sites ?? 0),
|
|
605
|
+
limit: Number(l.limit ?? 1),
|
|
606
|
+
supportedUntil: l.supportedUntil ? String(l.supportedUntil).slice(0, 10) : null,
|
|
607
|
+
}))
|
|
608
|
+
} catch (e) {
|
|
609
|
+
if (e.status === 404) return []
|
|
610
|
+
throw e
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
async getCodes(orderId) {
|
|
614
|
+
try {
|
|
615
|
+
const { codigos } = await api(`/tienda/pedidos/${encodeURIComponent(orderId)}/codigos`)
|
|
616
|
+
return codigos ?? []
|
|
617
|
+
} catch (e) {
|
|
618
|
+
if (e.status === 404) return []
|
|
619
|
+
throw e
|
|
620
|
+
}
|
|
621
|
+
},
|
|
622
|
+
async revealCode(orderId, codeId) {
|
|
623
|
+
const { codigo } = await api(
|
|
624
|
+
`/tienda/pedidos/${encodeURIComponent(orderId)}/codigos/${encodeURIComponent(codeId)}/revelar`,
|
|
625
|
+
{ method: "POST", body: {} },
|
|
626
|
+
)
|
|
627
|
+
return codigo ?? null
|
|
628
|
+
},
|
|
629
|
+
newsletter: {
|
|
630
|
+
async subscribe(email, consent = {}) {
|
|
631
|
+
await api("/tienda/boletin", {
|
|
632
|
+
method: "POST",
|
|
633
|
+
body: {
|
|
634
|
+
email,
|
|
635
|
+
texto: consent.text,
|
|
636
|
+
version: consent.version,
|
|
637
|
+
idioma: consent.locale,
|
|
638
|
+
origen: consent.source,
|
|
639
|
+
ip: consent.ip,
|
|
640
|
+
navegador: consent.userAgent,
|
|
641
|
+
},
|
|
642
|
+
})
|
|
643
|
+
return { status: "pendiente" }
|
|
644
|
+
},
|
|
645
|
+
async confirm(token) {
|
|
646
|
+
try {
|
|
647
|
+
await api("/tienda/boletin/confirmar", { method: "POST", body: { token } })
|
|
648
|
+
return true
|
|
649
|
+
} catch (e) {
|
|
650
|
+
if (e.status === 400) return false
|
|
651
|
+
throw e
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
async unsubscribe(token) {
|
|
655
|
+
await api("/tienda/boletin/baja", { method: "POST", body: { token } })
|
|
656
|
+
return true
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
async applyAsAuthor(a) {
|
|
660
|
+
const vende = [...new Set((a.sells ?? []).filter((x) => x === "tema" || x === "extension"))]
|
|
661
|
+
const r = await api("/tienda/autores/solicitudes", {
|
|
662
|
+
method: "POST",
|
|
663
|
+
body: {
|
|
664
|
+
nombre: a.name,
|
|
665
|
+
email: a.email,
|
|
666
|
+
portafolio: a.portfolio,
|
|
667
|
+
vende: vende.length === 2 ? ["los-dos"] : vende,
|
|
668
|
+
slug: a.handle,
|
|
669
|
+
pais: a.country,
|
|
670
|
+
web: a.website,
|
|
671
|
+
stacks: a.stacks,
|
|
672
|
+
experiencia: a.experience,
|
|
673
|
+
},
|
|
674
|
+
})
|
|
675
|
+
return { id: r.id }
|
|
676
|
+
},
|
|
677
|
+
async getAuthor(handle) {
|
|
678
|
+
try {
|
|
679
|
+
const { autor } = await api(`/tienda/autores/${encodeURIComponent(handle)}`)
|
|
680
|
+
if (!autor) return null
|
|
681
|
+
return {
|
|
682
|
+
handle: autor.handle,
|
|
683
|
+
name: autor.name,
|
|
684
|
+
bio: autor.bio ?? null,
|
|
685
|
+
website: autor.website ?? null,
|
|
686
|
+
country: autor.country ?? null,
|
|
687
|
+
links: autor.links ?? {},
|
|
688
|
+
logo: autor.logo ?? null,
|
|
689
|
+
since: autor.since,
|
|
690
|
+
items: (autor.items ?? []).map((i) => ({
|
|
691
|
+
handle: i.handle,
|
|
692
|
+
title: i.title,
|
|
693
|
+
type: i.type,
|
|
694
|
+
summary: i.summary ?? null,
|
|
695
|
+
cover: i.cover ?? null,
|
|
696
|
+
price: money(i.price?.amount ?? 0, i.price?.currency ?? "eur"),
|
|
697
|
+
})),
|
|
698
|
+
sales: autor.sales ?? null,
|
|
699
|
+
support: autor.soporte
|
|
700
|
+
? {
|
|
701
|
+
resolvedPct: autor.soporte.porcentaje ?? null,
|
|
702
|
+
firstReplyHours: autor.soporte.horasPrimeraRespuesta ?? null,
|
|
703
|
+
ratings: autor.soporte.valorados ?? 0,
|
|
704
|
+
}
|
|
705
|
+
: null,
|
|
706
|
+
}
|
|
707
|
+
} catch (e) {
|
|
708
|
+
if (e.status === 404) return null
|
|
709
|
+
throw e
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
support: {
|
|
713
|
+
async open({ licenseKey, subject, message, email }) {
|
|
714
|
+
const r = await api("/tienda/soporte", {
|
|
715
|
+
method: "POST",
|
|
716
|
+
body: { clave: licenseKey, asunto: subject, texto: message, email },
|
|
717
|
+
})
|
|
718
|
+
return { id: r.id, covered: r.conSoporte !== false }
|
|
719
|
+
},
|
|
720
|
+
async get(id, email) {
|
|
721
|
+
try {
|
|
722
|
+
const { hilo } = await api(`/tienda/soporte/${encodeURIComponent(id)}`, { method: "POST", body: { email } })
|
|
723
|
+
return hilo ? aHilo(hilo) : null
|
|
724
|
+
} catch (e) {
|
|
725
|
+
if (e.status === 404 || e.status === 400) return null
|
|
726
|
+
throw e
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
async reply(id, email, message) {
|
|
730
|
+
await api(`/tienda/soporte/${encodeURIComponent(id)}`, { method: "POST", body: { email, texto: message } })
|
|
731
|
+
},
|
|
732
|
+
async rate(id, email, solved) {
|
|
733
|
+
await api(`/tienda/soporte/${encodeURIComponent(id)}`, {
|
|
734
|
+
method: "POST",
|
|
735
|
+
body: { email, resuelto: !!solved },
|
|
736
|
+
})
|
|
737
|
+
},
|
|
738
|
+
},
|
|
574
739
|
cart,
|
|
575
740
|
checkout,
|
|
576
741
|
account,
|
package/src/index.d.ts
CHANGED
|
@@ -53,6 +53,12 @@ export interface Variant {
|
|
|
53
53
|
tiers?: PriceTier[]
|
|
54
54
|
digital?: boolean
|
|
55
55
|
requiresShipping?: boolean
|
|
56
|
+
/**
|
|
57
|
+
* Cuando la variante se vende como codigo: donde se canjea, para que region
|
|
58
|
+
* vale y en que idiomas viene. Se ensena ANTES de pagar; una clave de la
|
|
59
|
+
* region equivocada no sirve y acaba en devolucion.
|
|
60
|
+
*/
|
|
61
|
+
code?: { platform: string | null; region: string | null; languages: string[]; instructions: string | null } | null
|
|
56
62
|
bundle?: BundleComponent[] | null
|
|
57
63
|
bundleGroups?: BundleGroup[] | null
|
|
58
64
|
measure?: { quantity: number; unit: string } | null
|
|
@@ -86,6 +92,73 @@ export interface BundleComponent {
|
|
|
86
92
|
image?: string | null
|
|
87
93
|
}
|
|
88
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Un codigo que se vende y se entrega: clave de juego, tarjeta, licencia.
|
|
97
|
+
* `code` solo llega cuando el comprador pide destaparlo; hasta entonces se
|
|
98
|
+
* ensena `hint` (los ultimos caracteres) y `revealed` dice si ya se destapo,
|
|
99
|
+
* que es lo que cuenta como entregado.
|
|
100
|
+
*/
|
|
101
|
+
/**
|
|
102
|
+
* La licencia de una pieza del mercado comprada en un pedido. La clave es la
|
|
103
|
+
* credencial con la que el comprador activa el tema o la extensión en su sitio
|
|
104
|
+
* y con la que abre soporte con el autor: por eso se enseña entera.
|
|
105
|
+
*/
|
|
106
|
+
export interface License {
|
|
107
|
+
key: string
|
|
108
|
+
status: "activa" | "caducada" | "anulada"
|
|
109
|
+
/** Nombre del producto al que da derecho. */
|
|
110
|
+
title: string | null
|
|
111
|
+
/** Sitios donde está activada ahora mismo. */
|
|
112
|
+
sites: number
|
|
113
|
+
/** Cuántos sitios admite. */
|
|
114
|
+
limit: number
|
|
115
|
+
/** Hasta cuándo incluye soporte y versiones nuevas (AAAA-MM-DD). null: sin fecha. */
|
|
116
|
+
supportedUntil: string | null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface ProductCode {
|
|
120
|
+
id: string
|
|
121
|
+
lineId: string
|
|
122
|
+
title: string
|
|
123
|
+
status: "libre" | "reservado" | "entregado" | "revocado" | "retirado"
|
|
124
|
+
hint: string
|
|
125
|
+
code: string | null
|
|
126
|
+
revealed: boolean
|
|
127
|
+
revealedAt: string | null
|
|
128
|
+
deliveredAt: string | null
|
|
129
|
+
reason: string | null
|
|
130
|
+
platform: string | null
|
|
131
|
+
region: string | null
|
|
132
|
+
languages: string[]
|
|
133
|
+
instructions: string | null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Lo que anade un producto que viene del mercado de temas y extensiones:
|
|
138
|
+
* version, cuando se actualizo, demo y changelog. Es lo que mira quien compra
|
|
139
|
+
* software y lo que un producto normal no tiene.
|
|
140
|
+
*
|
|
141
|
+
* En los listados llega un resumen: `screenshots` y `changelog` vienen vacíos
|
|
142
|
+
* y se rellenan en `getProduct`, que es donde se enseñan.
|
|
143
|
+
*/
|
|
144
|
+
export interface MarketInfo {
|
|
145
|
+
type: "tema" | "extension"
|
|
146
|
+
author: { name: string; handle: string }
|
|
147
|
+
version: string | null
|
|
148
|
+
updatedAt: string | null
|
|
149
|
+
stack: string | null
|
|
150
|
+
niche: string[]
|
|
151
|
+
license: string | null
|
|
152
|
+
demoUrl: string | null
|
|
153
|
+
screenshots: string[]
|
|
154
|
+
changelog: { version: string; date: string | null; notes: string | null }[]
|
|
155
|
+
/**
|
|
156
|
+
* Meses de soporte del autor que incluye la compra (0: sin soporte). Las
|
|
157
|
+
* versiones nuevas no tienen fecha: le llegan al comprador siempre.
|
|
158
|
+
*/
|
|
159
|
+
supportMonths?: number
|
|
160
|
+
}
|
|
161
|
+
|
|
89
162
|
export interface Download {
|
|
90
163
|
id: string
|
|
91
164
|
lineId: string
|
|
@@ -149,6 +222,8 @@ export interface Product {
|
|
|
149
222
|
/** Precio mínimo entre las variantes: el «desde X €» de la tarjeta. */
|
|
150
223
|
price: Money
|
|
151
224
|
variants: Variant[]
|
|
225
|
+
/** Solo cuando el producto es una pieza del mercado de temas y extensiones. */
|
|
226
|
+
market?: MarketInfo | null
|
|
152
227
|
categories: CategoryRef[]
|
|
153
228
|
tags?: string[]
|
|
154
229
|
rating?: number | null
|
|
@@ -430,6 +505,11 @@ export interface ListProductsParams {
|
|
|
430
505
|
categoryId?: string
|
|
431
506
|
ids?: string[]
|
|
432
507
|
tags?: string[]
|
|
508
|
+
/**
|
|
509
|
+
* Solo piezas del mercado de ese tipo. Sin él salen todos los productos,
|
|
510
|
+
* sean o no del mercado. Un adaptador sin mercado lo ignora.
|
|
511
|
+
*/
|
|
512
|
+
type?: "tema" | "extension"
|
|
433
513
|
limit?: number
|
|
434
514
|
offset?: number
|
|
435
515
|
sort?: SortProductos
|
|
@@ -445,6 +525,87 @@ export interface Page<T> {
|
|
|
445
525
|
|
|
446
526
|
// ── El cliente ────────────────────────────────────────────────────────────
|
|
447
527
|
|
|
528
|
+
|
|
529
|
+
/** Una pieza del autor tal y como se lista en su ficha. */
|
|
530
|
+
export interface AuthorItem {
|
|
531
|
+
handle: string
|
|
532
|
+
title: string
|
|
533
|
+
type: "tema" | "extension"
|
|
534
|
+
summary: string | null
|
|
535
|
+
price: Money
|
|
536
|
+
cover: string | null
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Lo que la plataforma calcula del soporte de un autor. Es lo que le da valor:
|
|
541
|
+
* no lo escribe él. Cuando todavía no hay datos suficientes viene en null, que
|
|
542
|
+
* se enseña como «autor nuevo» y nunca como un cero.
|
|
543
|
+
*/
|
|
544
|
+
export interface AuthorSupport {
|
|
545
|
+
/** Porcentaje de consultas que sus compradores dieron por resueltas. */
|
|
546
|
+
resolvedPct: number | null
|
|
547
|
+
/** Mediana de horas hasta su primera respuesta. */
|
|
548
|
+
firstReplyHours: number | null
|
|
549
|
+
/** Cuántas valoraciones sostienen el porcentaje. */
|
|
550
|
+
ratings: number
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** La ficha pública de quien vende una pieza en el mercado. */
|
|
554
|
+
export interface Author {
|
|
555
|
+
handle: string
|
|
556
|
+
name: string
|
|
557
|
+
bio: string | null
|
|
558
|
+
website: string | null
|
|
559
|
+
/** Código de dos letras, en minúsculas. */
|
|
560
|
+
country: string | null
|
|
561
|
+
/** Redes y sitios del autor, siempre https. */
|
|
562
|
+
links: Record<string, string>
|
|
563
|
+
logo: string | null
|
|
564
|
+
/** Desde cuándo publica. */
|
|
565
|
+
since: string
|
|
566
|
+
items: AuthorItem[]
|
|
567
|
+
/** Solo cuando hay ventas: un cero espanta y no se enseña. */
|
|
568
|
+
sales: number | null
|
|
569
|
+
support: AuthorSupport | null
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** Lo que manda quien quiere vender en el mercado. Lo revisa una persona. */
|
|
573
|
+
export interface AuthorApplication {
|
|
574
|
+
/** Nombre con el que publicará. */
|
|
575
|
+
name: string
|
|
576
|
+
email: string
|
|
577
|
+
/** Enlace a algo que haya hecho: sin eso no hay nada que revisar. */
|
|
578
|
+
portfolio: string
|
|
579
|
+
sells: ("tema" | "extension")[]
|
|
580
|
+
/** Identificador público; si falta, sale del nombre. */
|
|
581
|
+
handle?: string
|
|
582
|
+
/** Código de dos letras. */
|
|
583
|
+
country?: string
|
|
584
|
+
website?: string
|
|
585
|
+
stacks?: string[]
|
|
586
|
+
experience?: string
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export interface SupportMessage {
|
|
590
|
+
from: "buyer" | "author" | "store"
|
|
591
|
+
text: string
|
|
592
|
+
createdAt: string
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export interface SupportThread {
|
|
596
|
+
id: string
|
|
597
|
+
subject: string
|
|
598
|
+
item: string
|
|
599
|
+
status: "abierto" | "contestado" | "esperando" | "cerrado"
|
|
600
|
+
/** Si su soporte incluido sigue vigente. Caducado no cierra la puerta: avisa. */
|
|
601
|
+
covered: boolean
|
|
602
|
+
/** Lo que contestó al cerrar. null mientras no se le ha preguntado. */
|
|
603
|
+
solved: boolean | null
|
|
604
|
+
messages: SupportMessage[]
|
|
605
|
+
createdAt: string
|
|
606
|
+
updatedAt: string
|
|
607
|
+
}
|
|
608
|
+
|
|
448
609
|
export interface CommerceClient {
|
|
449
610
|
/** Identificador del adaptador: "pcreative", "static"… */
|
|
450
611
|
readonly adapter: string
|
|
@@ -461,10 +622,61 @@ export interface CommerceClient {
|
|
|
461
622
|
subirFichero?(archivo: File): Promise<{ id: string; nombre: string; tipo: string; bytes: number; firma: string }>
|
|
462
623
|
/** Opcional: las descargas de un pedido con contenido digital. */
|
|
463
624
|
getDownloads?(orderId: string): Promise<Download[]>
|
|
625
|
+
/** Opcional: las licencias de las piezas del mercado compradas en un pedido. */
|
|
626
|
+
getLicenses?(orderId: string): Promise<License[]>
|
|
627
|
+
/** Los codigos de un pedido, tapados. */
|
|
628
|
+
getCodes?(orderId: string): Promise<ProductCode[]>
|
|
629
|
+
/** Destapa uno y devuelve el codigo entero. Queda apuntado quien y cuando. */
|
|
630
|
+
revealCode?(orderId: string, codeId: string): Promise<string>
|
|
464
631
|
/** Opcional: estado del derecho de desistimiento de un pedido. */
|
|
465
632
|
getWithdrawal?(orderId: string): Promise<WithdrawalState | null>
|
|
466
633
|
/** Opcional: solicita el desistimiento de un pedido (botón obligatorio en la UE). */
|
|
467
634
|
requestWithdrawal?(orderId: string, datos?: { lineas?: string[]; motivo?: string }): Promise<{ solicitud: string; aceptadas: number }>
|
|
635
|
+
/**
|
|
636
|
+
* Opcional: pedir vender en el mercado. Devuelve el id de la solicitud; la
|
|
637
|
+
* cuenta se crea cuando alguien la acepta. Si el correo ya pidió, falla con
|
|
638
|
+
* `err.status` 400 y el motivo en `err.detalle`.
|
|
639
|
+
*/
|
|
640
|
+
applyAsAuthor?(application: AuthorApplication): Promise<{ id: string }>
|
|
641
|
+
/**
|
|
642
|
+
* Opcional: el boletín, con doble confirmación. `subscribe` NO apunta a nadie:
|
|
643
|
+
* manda un correo con un enlace, y solo al pulsarlo (`confirm`) queda dentro.
|
|
644
|
+
* Contesta siempre lo mismo para que no sirva para averiguar quién está.
|
|
645
|
+
*/
|
|
646
|
+
newsletter?: {
|
|
647
|
+
subscribe(
|
|
648
|
+
email: string,
|
|
649
|
+
consent: {
|
|
650
|
+
/** El texto EXACTO que vio la persona junto a la casilla. Es la prueba. */
|
|
651
|
+
text: string
|
|
652
|
+
/** Versión de ese texto: cambia cuando cambia el texto. */
|
|
653
|
+
version: string
|
|
654
|
+
locale?: string
|
|
655
|
+
source?: string
|
|
656
|
+
ip?: string
|
|
657
|
+
userAgent?: string
|
|
658
|
+
},
|
|
659
|
+
): Promise<{ status: "pendiente" }>
|
|
660
|
+
confirm(token: string): Promise<boolean>
|
|
661
|
+
unsubscribe(token: string): Promise<boolean>
|
|
662
|
+
}
|
|
663
|
+
/** Opcional: la ficha pública de un autor del mercado. */
|
|
664
|
+
getAuthor?(handle: string): Promise<Author | null>
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Opcional: hablar con el autor de una pieza. Es privado y hace falta la
|
|
668
|
+
* clave de licencia, así que solo escribe quien ha comprado.
|
|
669
|
+
*
|
|
670
|
+
* El correo va en el cuerpo y nunca en la dirección: identifica a una
|
|
671
|
+
* persona y una dirección acaba en registros y en el historial del navegador.
|
|
672
|
+
*/
|
|
673
|
+
support?: {
|
|
674
|
+
open(datos: { licenseKey: string; subject: string; message: string; email?: string }): Promise<{ id: string; covered: boolean }>
|
|
675
|
+
get(id: string, email: string): Promise<SupportThread | null>
|
|
676
|
+
reply(id: string, email: string, message: string): Promise<void>
|
|
677
|
+
/** Una sola pregunta al cerrar: si le sirvió. Alimenta el dato público del autor. */
|
|
678
|
+
rate(id: string, email: string, solved: boolean): Promise<void>
|
|
679
|
+
}
|
|
468
680
|
|
|
469
681
|
cart: {
|
|
470
682
|
get(id: string): Promise<Cart | null>
|