diff --git a/docs/api-v1-contrato.html b/docs/api-v1-contrato.html new file mode 100644 index 0000000..290f4cf --- /dev/null +++ b/docs/api-v1-contrato.html @@ -0,0 +1,505 @@ +
Referencia de integración · admin.u-site.app
++ Las 10 preguntas del equipo integrador, respondidas leyendo el código del + servidor en vez de inferirlas desde el cliente. Cada respuesta cita archivo y + línea para que se pueda auditar. Una es crítica y explica por qué la integración + probablemente nunca autenticó. +
+ +expires_in es un timestamp Unix absolutoEl campo se reusa: entra como duración en segundos y sale como timestamp.
+// config/token.go:26-49 +t.Expire = ninetyYears // duración, en segundos +expiresIn := time.Now().Add( + time.Duration(t.Expire)*time.Second).Unix() +claims["exp"] = expiresIn +t.Expire = expiresIn // ← se SOBREESCRIBE+
Lo que viaja en la respuesta es expiresIn: segundos desde epoch.
Pero hay algo más grave. Login llama a
+ CreateToken sin pasarle vencimiento (pkg/auth/user.go:98),
+ así que aplica el default: 90 años.
En la práctica ese token nunca expira, y toda la lógica de caché y renovación + del cliente no se ejerce jamás. Es un arreglo nuestro, no de ustedes.
+POST /oauth/token{
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+ "expires_in": 4626547200
+}
+ rest/controllers/api/auth_controller.go:42-45
+token, no access_token.expires_in está en el nivel raíz.token_type, ni refresh_token, ni envoltorio.Error de credenciales, siempre HTTP 401:
+{ "error": true, "message": "Invalid Credentials" }
+ | Endpoint | Método | Respuesta |
|---|---|---|
/generate-qr-tmp | POST | {"url": "https://…"} |
/generate-url-qr | POST | {"url": "https://…"} |
/generate-vcf | POST | {"success": true, "url": "https://…"} |
/generate-qr | POST | No es JSON — imagen binaria image/webp |
vcard_qr.go:133-135 · vcard_vcf.go:103-106 · vcard_qr.go:169-170
+Ojo con el último: generate-qr responde bytes de imagen. Un
+ json_decode sobre eso falla siempre.
Errores: {"error": "Datos inválidos"} con 400, o
+ {"error": "<detalle>"} con 500.
Nadie lo adivinó mal. La doble codificación existe:
+// rest/controllers/api/dlocal_controller.go:42-45 +return c.Status(200).JSON(fiber.Map{ + "message": "Plan creado exitosamente.", + "response": string(response), // ← el JSON, como STRING +})+
Lo que llega:
+{
+ "message": "Plan creado exitosamente.",
+ "response": "{\"id\":\"PLAN-123\",\"status\":\"active\"}"
+}
+ Hay que hacer json_decode dos veces: una del cuerpo y
+ otra del campo response. Aplica igual a los 7 endpoints dLocal
+ (líneas 44, 77, 110, 137, 162, 188, 221).
El texto de message cambia según el endpoint, así que no conviene
+ usarlo para decidir nada.
// rest/middlewares/auth.go:271-286 +token := c.Cookies("Verify-Rest-Token") +if token == "" { + return c.Status(401).JSON("Token not found") // falta la cookie +} +… ErrorHandler: + return ctx.Status(401).JSON("Invalid Attempt") // cookie inválida o vencida+
Los dos son JSON string, con comillas incluidas — el cuerpo
+ literal es "Invalid Attempt", 16 bytes, no el texto pelado.
Recomendación: reintentar ante cualquier 401, sin mirar el + texto. Es más robusto y no depende de una redacción que puede cambiar.
+Todo cuelga de https://admin.u-site.app/api/v1/. Lo correcto es
+ guardar la raíz y concatenar la ruta, en vez de guardar la URL del
+ token y derivar las demás con str_replace.
POST /api/v1/oauth/token
+POST /api/v1/generate-qr-tmp
+POST /api/v1/generate-qr (imagen)
+POST /api/v1/generate-vcf
+POST /api/v1/generate-url-qr
+POST /api/v1/dlocal/subscription/crear-plan
+GET /api/v1/dlocal/subscription/ver-plan/:planID
+PATCH /api/v1/dlocal/subscription/actualizar-plan/:planID
+GET /api/v1/dlocal/subscription/plan/all
+PATCH /api/v1/dlocal/subscription/plan/:planId/subscription/:subscriptionId/deactivate
+GET /api/v1/dlocal/subscription/:subscriptionId/execution/:invoiceId
+POST /api/v1/dlocal/payment/crear-pago
+POST /api/v1/rapyd/wallet/create
+ Cookie es obligatorio, y hoy está rotoLa API no acepta Bearer. Autentica exclusivamente por cookie:
+// rest/middlewares/auth.go:272-281 +token := c.Cookies("Verify-Rest-Token") +if token == "" { return c.Status(401).JSON("Token not found") } +TokenLookup: "cookie:Verify-Rest-Token"+
Con el placeholder literal ... sin completar, los 12
+ endpoints autenticados devuelven 401. Si algo funciona hoy, es porque el
+ cliente HTTP tiene cookie jar y reusa la cookie que POST /oauth/token
+ deja seteada en la respuesta (config/token.go:40-47).
Authorization: Bearer
+ no autentica nada.session_id no hace falta: el middleware no lo
+ mira. Se puede quitar.Lo correcto: tomar el token de la respuesta de oauth/token
+ y mandarlo como Cookie: Verify-Rest-Token=<token>, o dejar que el
+ cliente maneje cookies solo.
/api/v1Solo se valida la cookie JWT. No hay allowlist; no hace falta registrar la IP + del servidor de producción.
+Sí existe validación por IP, pero en otra API:
+ /api/v2 usa ApiKey con IP obligatoria y scopes, y
+ /api/v1/pagos-externos usa AuthServicioPago — ambas
+ excluidas de AuthApi (auth.go:266-268).
Si prefieren un esquema con IP fija y token que no viva en una cookie,
+ /api/v2 es el camino, y conviene migrar ahí en vez de
+ arreglar el actual.
No van acá. Son las de un usuario real de la tabla users
+ (login.CheckLogin(), auth_controller.go:28). Que se
+ entreguen por un canal seguro.
Dado que llevan 15 meses sin rotar, conviene crear un usuario dedicado a + la integración en vez de reusar el de una persona.
+Hay webhooks entrantes de pago ya montados
+ (rest/routes/publicas.go:22-33): /webhooks/bold,
+ /webhooks/dlocal, /webhooks/paypal, más Coolify y Telegram.
Pero sirven para que la pasarela nos avise a nosotros, no para + avisarle a un tercero. Un callback hacia su sistema es desarrollo nuevo.
+Mientras tanto, la alternativa ya construida es
+ /api/v1/pagos-externos, pensada justo para apps de terceros:
GET /api/v1/pagos-externos/pasarelas +POST /api/v1/pagos-externos/solicitar +GET /api/v1/pagos-externos/:referencia/estado+
Usa token Bearer + IP (no cookie), así que evita todo el problema del punto 07. + Para una integración nueva, recomendamos esta y no los endpoints + dLocal directos.
+No son preguntas: son cosas a corregir en admin.u-site.app.
config/token.go:26). Un token
+ filtrado es acceso permanente. Ponerle un vencimiento razonable — y recién ahí la
+ caché y el reintento del cliente van a tener sentido.Secure: false
+ (config/token.go:44), así que viaja también por HTTP plano./api/v2 ya lo resuelve bien.