Saltar a contenido

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

  1. registrar o reutilizar una tablet
  2. emitir codigo de activacion si la tablet se instala o se resetea
  3. crear una solicitud de firma con externalRef, deviceId y pdfBase64
  4. consultar el estado hasta signed, cancelled o expired
  5. 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, expired o error.
  • deviceId: tablet destino.
  • limit: entre 1 y 100; por defecto 50.

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 con GET /v1/signature-requests/{requestId}/result.
  • cancelled: mostrar cancelacion y no descargar.
  • expired: informar caducidad y permitir reenviar si procede.
  • error: mostrar error operativo usando errorCode, errorMessage y traceId.

Patron recomendado

Para una primera version, implementa dos mecanismos:

  • Accion manual Actualizar estado en 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 Ref
  • Device Id
  • Request Id
  • Status
  • Document Name
  • Created At
  • Signed At
  • Last Check At
  • Error Code
  • Error Message
  • Result 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 ausente
  • 404 NOT_FOUND: solicitud o tablet inexistente
  • 409 INVALID_STATE: la solicitud ya no admite esa operacion
  • 409 CONFLICT: conflicto operativo, por ejemplo eliminar una tablet con solicitudes abiertas
  • 413 PDF_TOO_LARGE: el PDF supera el limite configurado
  • 422 PDF_INVALID: Base64 o PDF invalidos
  • 500 INTERNAL_ERROR: error inesperado; revisar traceId

Tabla ampliada: API - Errores de integracion

Recomendaciones practicas para BC

  • guardar requestId y externalRef para trazabilidad
  • registrar siempre traceId cuando 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

Siguiente lectura recomendada