API - Business Central
Objetivo
Esta pagina explica como integrar Business Central con la API de firma para registrar tablets, crear solicitudes y recuperar el PDF firmado.
URLs utiles
- API base URL:
https://apifirma.jdmarquez.dev - Swagger UI:
https://apifirma.jdmarquez.dev/swagger - OpenAPI JSON:
https://apifirma.jdmarquez.dev/swagger/v1/swagger.json
Flujo de referencia para BC
- registrar o reutilizar una tablet
- emitir codigo de activacion si la tablet se instala o se resetea
- crear una solicitud de firma con
externalRef,deviceIdypdfBase64 - consultar el estado hasta
signed,cancelledoexpired - descargar el PDF firmado
La maquina de estados completa esta documentada en API - Estados de solicitud.
Requisitos previos en BC
- la API debe estar publicada por HTTPS
- la extension debe tener permitido
HttpClient - el token BC debe almacenarse como secreto o en configuracion segura
- el PDF debe convertirse a Base64 antes de llamar a
POST /v1/signature-requests
Autenticacion
text
Authorization: Bearer <BC_BEARER_TOKEN>
BC no usa X-Device-Token; ese header es solo para la tablet activada.
Inicializacion recomendada en AL
```text local procedure GetApiBaseUrl(): Text begin exit('https://apifirma.jdmarquez.dev'); end;
local procedure GetApiBearerToken(): Text begin exit('replace_me'); end; ```
Estructura sugerida en AL
```text codeunit 50100 "BC PDF Sign Mgt." { procedure RegisterDevice(DeviceId: Text; DeviceName: Text) begin end;
procedure IssueActivationCode(DeviceId: Text)
begin
end;
procedure DeleteDevice(DeviceId: Text)
begin
end;
procedure CreateSignatureRequest(PdfBase64: Text)
begin
end;
procedure GetSignatureStatus(RequestId: Guid)
begin
end;
procedure DownloadSignedPdf(RequestId: Guid; var TargetBlob: Codeunit "Temp Blob")
begin
end;
} ```
La recomendacion es centralizar toda la comunicacion HTTP con la API en una sola codeunit.
Helper base para enviar JSON
```text local procedure SendJson(Method: Text; Url: Text; Token: Text; BodyText: Text; var Response: HttpResponseMessage) var Client: HttpClient; Request: HttpRequestMessage; Content: HttpContent; Headers: HttpHeaders; begin Request.Method := Method; Request.SetRequestUri(Url);
if BodyText <> '' then begin
Content.WriteFrom(BodyText);
Content.GetHeaders(Headers);
Headers.Clear();
Headers.Add('Content-Type', 'application/json');
Request.Content := Content;
end;
Request.GetHeaders(Headers);
Headers.Add('Authorization', StrSubstNo('Bearer %1', Token));
if not Client.Send(Request, Response) then
Error('No se pudo contactar con la API de firma.');
end; ```
Ejemplo AL - eliminar tablet
```text procedure DeleteDevice(ApiBaseUrl: Text; Token: Text; DeviceId: Text) var Response: HttpResponseMessage; begin SendJson('DELETE', ApiBaseUrl + '/v1/devices/' + DeviceId, Token, '', Response);
if Response.HttpStatusCode() = 409 then
Error('No se puede eliminar la tablet porque tiene solicitudes abiertas.');
if not Response.IsSuccessStatusCode() then
Error('Error al eliminar tablet. Codigo HTTP: %1', Response.HttpStatusCode());
end; ```
Ejemplo AL - registrar tablet
```text procedure RegisterDevice(ApiBaseUrl: Text; Token: Text) var Payload: JsonObject; Response: HttpResponseMessage; BodyText: Text; begin Payload.Add('deviceId', 'tablet-recepcion-1'); Payload.Add('name', 'Recepcion 1'); Payload.WriteTo(BodyText);
SendJson('POST', ApiBaseUrl + '/v1/devices', Token, BodyText, Response);
if not Response.IsSuccessStatusCode() then
Error('Error al registrar tablet. Codigo HTTP: %1', Response.HttpStatusCode());
end; ```
Ejemplo AL - emitir codigo de activacion
```text procedure IssueActivationCode(ApiBaseUrl: Text; Token: Text; DeviceId: Text) var Response: HttpResponseMessage; begin SendJson('POST', ApiBaseUrl + '/v1/devices/' + DeviceId + '/activation-code', Token, '', Response);
if not Response.IsSuccessStatusCode() then
Error('Error al emitir codigo de activacion. Codigo HTTP: %1', Response.HttpStatusCode());
end; ```
Ejemplo AL - crear solicitud de firma
```text procedure CreateSignatureRequest(ApiBaseUrl: Text; Token: Text; PdfBase64: Text) var Payload: JsonObject; Response: HttpResponseMessage; BodyText: Text; begin Payload.Add('externalRef', 'BC-1001'); Payload.Add('deviceId', 'tablet-recepcion-1'); Payload.Add('documentName', 'autorizacion.pdf'); Payload.Add('pdfBase64', PdfBase64); Payload.Add('guestName', 'John Doe'); Payload.Add('reservationRef', 'RES-1001'); Payload.WriteTo(BodyText);
SendJson('POST', ApiBaseUrl + '/v1/signature-requests', Token, BodyText, Response);
if not Response.IsSuccessStatusCode() then
Error('Error al crear solicitud. Codigo HTTP: %1', Response.HttpStatusCode());
end; ```
Ejemplo AL - consultar estado
```text procedure GetSignatureStatus(ApiBaseUrl: Text; Token: Text; RequestId: Guid) var Response: HttpResponseMessage; begin SendJson('GET', ApiBaseUrl + '/v1/signature-requests/' + Format(RequestId), Token, '', Response);
if not Response.IsSuccessStatusCode() then
Error('Error al consultar estado. Codigo HTTP: %1', Response.HttpStatusCode());
end; ```
Listar solicitudes desde BC
Para paneles operativos o sincronizaciones, BC puede listar solicitudes sin descargar PDFs:
text
GET /v1/signature-requests?status=pending&deviceId=tablet-recepcion-1&limit=50
Authorization: Bearer <BC_BEARER_TOKEN>
Filtros disponibles:
status:pending,displayed,signing,signed,cancelled,expiredoerror.deviceId: tablet destino.limit: entre1y100; por defecto50.
La respuesta esta ordenada por createdAt descendente e incluye resultAvailable para saber si ya puede llamarse a GET /v1/signature-requests/{requestId}/result.
Ejemplo AL - descargar PDF final
```text procedure DownloadSignedPdf(ApiBaseUrl: Text; Token: Text; RequestId: Guid; var TargetBlob: Codeunit "Temp Blob") var Client: HttpClient; Request: HttpRequestMessage; Response: HttpResponseMessage; Headers: HttpHeaders; InStr: InStream; OutStr: OutStream; begin Request.Method := 'GET'; Request.SetRequestUri(ApiBaseUrl + '/v1/signature-requests/' + Format(RequestId) + '/result'); Request.GetHeaders(Headers); Headers.Add('Authorization', StrSubstNo('Bearer %1', Token));
if not Client.Send(Request, Response) then
Error('No se pudo descargar el PDF firmado.');
if not Response.IsSuccessStatusCode() then
Error('Error al descargar resultado. Codigo HTTP: %1', Response.HttpStatusCode());
Response.Content().ReadAs(InStr);
TargetBlob.CreateOutStream(OutStr);
CopyStream(OutStr, InStr);
end; ```
Ejemplo AL - flujo minimo de negocio
text
procedure StartCheckInSignature(PdfBase64: Text)
begin
RegisterDevice(GetApiBaseUrl(), GetApiBearerToken());
CreateSignatureRequest(GetApiBaseUrl(), GetApiBearerToken(), PdfBase64);
end;
Polling de estado en Business Central
La API no llama de vuelta a Business Central. BC debe guardar el requestId devuelto por POST /v1/signature-requests y consultar el estado con:
text
GET /v1/signature-requests/{requestId}
Authorization: Bearer <BC_BEARER_TOKEN>
Estados intermedios
Mientras el estado sea uno de estos, BC debe seguir esperando:
pending: solicitud creada, pendiente de tablet.displayed: la tablet ya reservo o mostro el documento.signing: el huesped empezo a firmar.
Estados finales
BC debe dejar de consultar cuando llegue uno de estos estados:
signed: descargar el PDF conGET /v1/signature-requests/{requestId}/result.cancelled: mostrar cancelacion y no descargar.expired: informar caducidad y permitir reenviar si procede.error: mostrar error operativo usandoerrorCode,errorMessageytraceId.
Patron recomendado
Para una primera version, implementa dos mecanismos:
- Accion manual
Actualizar estadoen la pagina de BC. - Job Queue que revise solicitudes abiertas cada 1 minuto.
Evita un bucle bloqueante en AL esperando la firma. Si necesitas una experiencia en pantalla con refresco rapido, usa un boton manual o un control add-in; no bloquees la sesion de BC con esperas largas.
Tabla sugerida en BC
Crea una tabla propia para persistir la relacion entre BC y la API, por ejemplo PDF Signature Request:
Entry No.External RefDevice IdRequest IdStatusDocument NameCreated AtSigned AtLast Check AtError CodeError MessageResult Downloaded
Pseudocodigo AL para polling
```text procedure PollPendingRequests() var SignReq: Record "PDF Signature Request"; begin SignReq.SetFilter(Status, '%1|%2|%3', 'pending', 'displayed', 'signing');
if SignReq.FindSet() then
repeat
RefreshSignatureStatus(SignReq);
if SignReq.Status = 'signed' then
DownloadSignedPdf(SignReq);
until SignReq.Next() = 0;
end; ```
Refrescar una solicitud
```text procedure RefreshSignatureStatus(var SignReq: Record "PDF Signature Request") var Response: HttpResponseMessage; ResponseText: Text; begin SendJson( 'GET', GetApiBaseUrl() + '/v1/signature-requests/' + SignReq."Request Id", GetApiBearerToken(), '', Response);
Response.Content().ReadAs(ResponseText);
if not Response.IsSuccessStatusCode() then
Error('Error consultando estado. HTTP %1: %2', Response.HttpStatusCode(), ResponseText);
// Parsear ResponseText como JSON y actualizar:
// - Status
// - Signed At
// - Error Code
// - Error Message
// - Last Check At
SignReq.Modify();
end; ```
Descargar automaticamente al firmar
Cuando Status = 'signed', BC puede descargar el resultado y marcarlo como descargado:
text
if SignReq.Status = 'signed' then begin
DownloadSignedPdf(SignReq);
SignReq."Result Downloaded" := true;
SignReq.Modify();
end;
Frecuencia recomendada
- Usuario esperando en pantalla: accion manual o polling controlado cada 5-10 segundos si se implementa con UI adecuada.
- Job Queue: cada 1 minuto suele ser suficiente para automatizar descargas sin saturar la API.
- No consultar cada segundo.
Ejemplo de payload JSON desde BC
json
{
"externalRef": "BC-1001",
"deviceId": "tablet-recepcion-1",
"documentName": "autorizacion.pdf",
"pdfBase64": "JVBERi0xLjQKJ...",
"guestName": "John Doe",
"reservationRef": "RES-1001"
}
Errores que BC debe manejar
401 UNAUTHENTICATED: token BC incorrecto o ausente404 NOT_FOUND: solicitud o tablet inexistente409 INVALID_STATE: la solicitud ya no admite esa operacion409 CONFLICT: conflicto operativo, por ejemplo eliminar una tablet con solicitudes abiertas413 PDF_TOO_LARGE: el PDF supera el limite configurado422 PDF_INVALID: Base64 o PDF invalidos500 INTERNAL_ERROR: error inesperado; revisartraceId
Tabla ampliada: API - Errores de integracion
Recomendaciones practicas para BC
- guardar
requestIdyexternalRefpara trazabilidad - registrar siempre
traceIdcuando una llamada falle - no hacer polling agresivo; para BC basta con consultar estado segun el proceso de negocio
- usar Swagger para validar el contrato antes de escribir codigo AL