Delphi
Muchos sistemas contables, ERP y puntos de venta de Ecuador están escritos en Delphi y corren en las propias computadoras de la oficina. Pueden emitir comprobantes electrónicos con Flashpi sin un SDK: Flashpi es una API HTTPS que recibe y devuelve JSON, y Delphi trae un cliente HTTP (THTTPClient) y una librería de JSON (System.JSON). Esta guía emite la factura del Inicio rápido desde Delphi, con el emisor demo compartido en el ambiente de pruebas del SRI.
Antes de empezar
Sección titulada «Antes de empezar»- Delphi 10.4 o posterior, para Windows. Los ejemplos usan
THTTPClient(System.Net.HttpClient) ySystem.JSON;TNetHTTPClient, el componente de la paleta, tiene las mismas propiedades y métodos. Para Indy, consulta Con Indy. - Windows 10 u 11, o Windows Server 2016 o posterior, en la computadora que llama a la API. En Windows,
THTTPClientusa el TLS del propio Windows (consulta TLS 1.2 y versiones de Windows). - Una API key de prueba (
fp_test_), creada en API keys de la consola con Pruebas elegido, con alcance Toda la cuenta (account) y acceso de lectura y escritura (consulta Autenticación y API keys), y el ID del emisor demo, del Resumen de la consola (consulta Sandbox y emisor demo).
Protege la API key
Sección titulada «Protege la API key»- Lo mejor: un servidor llama a Flashpi. Las PC le piden a un pequeño servicio en tu servidor (o en el servidor del ERP) que emita, y solo ese servidor tiene la llave. Las PC nunca la ven, y la dirección pública del servidor puede ser la única de la lista de IP permitidas de la llave.
- Si cada PC llama a la API, guarda la llave solo en esa PC, legible por el usuario de Windows que usa el sistema: una variable de entorno de ese usuario (los ejemplos leen
GetEnvironmentVariable('FLASHPI_API_KEY')) o, mejor, cifrada con la API de protección de datos de Windows (CryptProtectData), que solo ese usuario o esa computadora pueden descifrar. - Si vendes tu sistema a otras empresas, cada cliente crea su propia cuenta y sus llaves de Flashpi, e ingresa su llave al instalar el sistema. Nunca incluyas tu propia llave en el producto.
- Dale a cada llave solo lo que necesita: una llave con alcance de un emisor para el único RUC por el que emite el sistema, y una lista de IP permitidas con la dirección pública de la oficina cuando es fija.
- Empieza con una llave de prueba (
fp_test_): sus comprobantes no tienen efecto tributario. Usa una llave de producción (fp_live_) solo en las computadoras que emiten de verdad, cuando todo funcione. - Nunca escribas la llave en un log ni la muestres en pantalla. Si se filtra, revócala en la consola: deja de funcionar al instante.
Comprueba la conexión
Sección titulada «Comprueba la conexión»GET /v1/status no necesita llave, así que te dice si la computadora llega a la API:
var Client: THTTPClient;begin Client := THTTPClient.Create; try // 200 {"status":"operational"} ShowMessage(Client.Get('https://api.flashpi.com.ec/v1/status').ContentAsString); finally Client.Free; end;end;Si falla con A security error occurred (error 12175 de WinHTTP), Windows no pudo establecer TLS con la API: consulta TLS 1.2 y versiones de Windows.
La unidad Flashpi
Sección titulada «La unidad Flashpi»unit Flashpi;
interface
uses System.SysUtils, System.Classes, System.JSON, System.Net.URLClient, System.Net.HttpClient;
const FlashpiApi = 'https://api.flashpi.com.ec';
function NewIdempotencyKey: string;function Amount(const Value: Currency): string;function Decimal(const Value: Double; Decimals: Integer): string;function IssueDocument(const Path, Body, IdempotencyKey: string; out Error: string): TJSONObject;function GetDocument(const DocumentId: string; out Error: string): TJSONObject;function SaveDocumentFile(const DocumentId, Kind, FileName: string; out Error: string): Boolean;
implementation
// A new GUID without braces: the Idempotency-Key of one documentfunction NewIdempotencyKey: string;begin Result := Copy(TGUID.NewGuid.ToString, 2, 36);end;
// An amount of money as the API takes it: a string with 2 decimals and a// dot, whatever the regional settings of Windows say (a comma in Ecuador)function Amount(const Value: Currency): string;var Format: TFormatSettings;begin Format := TFormatSettings.Create; Format.DecimalSeparator := '.'; Result := CurrToStrF(Value, ffFixed, 2, Format);end;
// A quantity or a unit price, with up to 6 decimals: a string with a dot// (Currency keeps only 4 decimals, so this one takes a Double)function Decimal(const Value: Double; Decimals: Integer): string;var Format: TFormatSettings;begin Format := TFormatSettings.Create; Format.DecimalSeparator := '.'; Result := FloatToStrF(Value, ffFixed, 15, Decimals, Format);end;
function NewClient: THTTPClient;begin Result := THTTPClient.Create; Result.ConnectionTimeout := 10000; // milliseconds // Longer than the sync mode's wait (15 seconds by default) Result.ResponseTimeout := 30000;end;
function Headers(const IdempotencyKey: string): TNetHeaders;begin Result := [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('FLASHPI_API_KEY')), // Error messages in Spanish, to show them to the user TNetHeader.Create('Accept-Language', 'es')]; if IdempotencyKey <> '' then Result := Result + [TNetHeader.Create('Idempotency-Key', IdempotencyKey)];end;
// The text of an error answer (RFC 9457 problem details) to show or log:// detail and code, each field error, and the requestId for supportfunction ProblemText(const Response: IHTTPResponse; const Text: string): string;var Problem, Field: TJSONValue;begin Problem := TJSONObject.ParseJSONValue(Text); if Problem = nil then begin // Not problem details, such as a proxy's error page: the status, and the // request ID when the API sent one Result := 'HTTP ' + IntToStr(Response.StatusCode); if Response.HeaderValue['x-request-id'] <> '' then Result := Result + sLineBreak + 'requestId: ' + Response.HeaderValue['x-request-id']; Exit; end; try Result := Problem.GetValue<string>('detail') + ' (' + Problem.GetValue<string>('code') + ')'; for Field in Problem.GetValue<TJSONArray>('errors') do Result := Result + sLineBreak + Field.GetValue<string>('field') + ': ' + Field.GetValue<string>('message'); Result := Result + sLineBreak + 'requestId: ' + Problem.GetValue<string>('requestId'); finally Problem.Free; end;end;
// One member of an error answer, such as its code, to branch on it ('' when// it is missing)function ProblemValue(const Text, Name: string): string;var Problem: TJSONValue;begin Result := ''; Problem := TJSONObject.ParseJSONValue(Text); if Problem <> nil then try Result := Problem.GetValue<string>(Name, ''); finally Problem.Free; end;end;
// Issues a document, retrying safely: the same body and the same// Idempotency-Key on every attempt, so it is never issued twice. Returns the// document (free it), or nil with Error set.function IssueDocument(const Path, Body, IdempotencyKey: string; out Error: string): TJSONObject;var Client: THTTPClient; Source: TStringStream; Response: IHTTPResponse; Attempt, Wait, Status: Integer; Text, DocumentId: string;begin Result := nil; Error := ''; Client := NewClient; try Client.ContentType := 'application/json'; for Attempt := 1 to 5 do begin Wait := 1 shl (Attempt - 1); // 1, 2, 4 and 8 seconds between attempts Response := nil; // The JSON's UTF-8 bytes, without a byte order mark Source := TStringStream.Create(Body, TEncoding.UTF8); try try Response := Client.Post(FlashpiApi + Path, Source, nil, Headers(IdempotencyKey)); except // No answer: Flashpi may have received the request, so send it again on E: ENetException do Error := E.Message; end; finally Source.Free; end; if Response <> nil then begin Status := Response.StatusCode; Text := Response.ContentAsString(TEncoding.UTF8); if (Status = 200) or (Status = 202) then Exit(TJSONObject.ParseJSONValue(Text) as TJSONObject); Error := ProblemText(Response, Text); if Status < 500 then begin DocumentId := ''; if ProblemValue(Text, 'code') = 'idempotency_key_reused' then DocumentId := ProblemValue(Text, 'documentId'); if DocumentId <> '' then // The key already created a document: read it instead of issuing again Exit(GetDocument(DocumentId, Error)); if (Status = 429) and (StrToIntDef(Response.HeaderValue['Retry-After'], 0) <= 60) then Wait := StrToIntDef(Response.HeaderValue['Retry-After'], 1) else if ProblemValue(Text, 'code') <> 'idempotency_key_in_progress' then Exit; // Fix the request first: sent unchanged, it gets the same answer end; end; if Attempt < 5 then Sleep(Wait * 1000); end; finally Client.Free; end;end;
// Reads a document again: its status, its access key, its totals...function GetDocument(const DocumentId: string; out Error: string): TJSONObject;var Client: THTTPClient; Response: IHTTPResponse; Text: string;begin Result := nil; Error := ''; Client := NewClient; try try Response := Client.Get(FlashpiApi + '/v1/documents/' + DocumentId, nil, Headers('')); except on E: ENetException do begin Error := E.Message; Exit; end; end; Text := Response.ContentAsString(TEncoding.UTF8); if Response.StatusCode = 200 then Result := TJSONObject.ParseJSONValue(Text) as TJSONObject else Error := ProblemText(Response, Text); finally Client.Free; end;end;
// Saves the RIDE ('pdf') or the XML ('xml') of a document in a filefunction SaveDocumentFile(const DocumentId, Kind, FileName: string; out Error: string): Boolean;var Client: THTTPClient; Response: IHTTPResponse; Output: TFileStream;begin Result := False; Error := ''; Client := NewClient; try try Response := Client.Get(FlashpiApi + '/v1/documents/' + DocumentId + '/' + Kind, nil, Headers('')); except on E: ENetException do begin Error := E.Message; Exit; end; end; if Response.StatusCode <> 200 then begin Error := ProblemText(Response, Response.ContentAsString(TEncoding.UTF8)); Exit; end; // The file's bytes as they arrived Output := TFileStream.Create(FileName, fmCreate); try Output.CopyFrom(Response.ContentStream, 0); finally Output.Free; end; Result := True; finally Client.Free; end;end;
end.Emite la factura
Sección titulada «Emite la factura»-
Dale a la factura su Idempotency-Key y guárdala con la factura antes del primer intento. Un reintento, incluso después de reiniciar el programa o la PC, envía la misma clave, y Flashpi le responde con el comprobante que creó el primer intento en lugar de emitir otro (consulta Idempotencia). Aquí la clave vive en un campo
idem_keyde la tabla de facturas. Con actualizaciones en caché (TClientDataSet,CachedUpdatesde FireDAC), llama aApplyUpdatesdespués de cadaPost, para que la clave llegue a la base de datos antes de llamar aIssueDocument(). -
Arma el cuerpo JSON con
System.JSON, que escapa los textos por ti. Los montos y las cantidades son strings decimales con punto:Amount()para el dinero, con 2 decimales, yDecimal()para las cantidades y los precios unitarios, que admiten hasta 6. Envías el precio y la tarifa de IVA; Flashpi calcula el subtotal, el IVA (67.50) y el total (517.50), y los pagos deben sumar ese total. Para varias líneas, agrega un ítem por línea al arregloitems. -
Envíala con
IssueDocument(), que reintenta sola cuando es seguro hacerlo.
// 2. The body, with the values your system keeps in its tablesfunction InvoiceBody: string;var Invoice, Customer, Item, Tax, Payment, Options: TJSONObject; Items, Taxes, Payments: TJSONArray;begin Invoice := TJSONObject.Create; try Invoice.AddPair('issuerId', '<demo issuer id>'); // from the console's Overview Invoice.AddPair('establishment', '001'); Invoice.AddPair('emissionPoint', '001');
Customer := TJSONObject.Create; Customer.AddPair('idType', 'ruc'); Customer.AddPair('id', '1790000000001'); Customer.AddPair('name', 'Supermercados La Pradera S.A.'); Invoice.AddPair('customer', Customer);
Tax := TJSONObject.Create; Tax.AddPair('type', 'vat'); Tax.AddPair('rate', '15'); Taxes := TJSONArray.Create; Taxes.AddElement(Tax);
Item := TJSONObject.Create; Item.AddPair('code', 'SRV-001'); Item.AddPair('description', 'Monthly accounting services'); Item.AddPair('quantity', Decimal(1, 2)); Item.AddPair('unitPrice', Decimal(450, 2)); Item.AddPair('taxes', Taxes); Items := TJSONArray.Create; Items.AddElement(Item); Invoice.AddPair('items', Items);
Payment := TJSONObject.Create; Payment.AddPair('method', 'other_financial_system'); Payment.AddPair('amount', Amount(517.50)); Payments := TJSONArray.Create; Payments.AddElement(Payment); Invoice.AddPair('payments', Payments);
Options := TJSONObject.Create; Options.AddPair('waitForAuthorization', TJSONBool.Create(True)); Invoice.AddPair('options', Options);
Result := Invoice.ToJSON; finally Invoice.Free; // also frees every object and array added to it end;end;
procedure IssueInvoice(Invoices: TDataSet);var Key, Error: string; Document: TJSONObject;begin // 1. One key per invoice, saved before the first attempt Key := Invoices.FieldByName('idem_key').AsString; if Key = '' then begin Key := NewIdempotencyKey; Invoices.Edit; Invoices.FieldByName('idem_key').AsString := Key; Invoices.Post; end;
// 3. Send it Document := IssueDocument('/v1/invoices', InvoiceBody, Key, Error); if Document = nil then raise Exception.Create(Error); try // Keep the document's id: it reads the document, its RIDE and its XML Invoices.Edit; Invoices.FieldByName('flashpi_id').AsString := Document.GetValue<string>('id'); Invoices.FieldByName('doc_status').AsString := Document.GetValue<string>('status'); Invoices.FieldByName('doc_number').AsString := Document.GetValue<string>('number'); Invoices.FieldByName('access_key').AsString := Document.GetValue<string>('accessKey'); Invoices.Post; finally Document.Free; end;end;IssueInvoice() necesita Data.DB, System.SysUtils, System.JSON y la unidad Flashpi en su uses. La misma IssueDocument() emite los demás comprobantes: solo cambian la ruta y el cuerpo (/v1/credit-notes, /v1/withholdings y los demás, consulta Emitir comprobantes).
Lee la respuesta
Sección titulada «Lee la respuesta»| Respuesta | Qué significa | Qué hacer |
|---|---|---|
200 OK |
Solo en modo síncrono: el SRI resolvió el comprobante dentro de la espera. status es authorized, rejected o failed. |
authorized: descarga e imprime el RIDE. rejected: los motivos del SRI están en sriMessages. failed: failure dice por qué. |
202 Accepted |
El comprobante quedó guardado y sigue su curso: processing, o pending_sri cuando la espera terminó antes de que el SRI respondiera. |
Guarda su id y síguelo (consulta Modo síncrono, consultas o webhooks). No se pierde nada. |
4xx |
Un error que no se resuelve solo, como un objeto problem details. | Muestra detail, decide según code, corrige y vuelve a enviar. Solo vale la pena reintentar sin cambios 409 idempotency_key_in_progress y un 429 con un Retry-After corto. |
5xx, o sin respuesta |
La solicitud pudo llegar o no. | Envíala otra vez con la misma clave y el mismo cuerpo, esperando más cada vez. |
THTTPClient no lanza una excepción por un estado de error: la respuesta llega con su estado, y solo la falta de respuesta (un error de red, un tiempo de espera agotado o una falla de TLS) lanza una ENetException. El comprobante que devuelve la API es el del Inicio rápido: id, status, number (001-001-000004821), accessKey (la clave de acceso de 49 dígitos), totals, authorization, sriMessages y lo demás. Una respuesta de error se ve así, en español porque la unidad envía Accept-Language: es, y ProblemText() la convierte en un solo texto:
{ "type": "https://flashpi.com.ec/docs/errors/validation_failed", "title": "Error de validación", "status": 422, "detail": "Uno o más campos no son válidos. Revisa errors para ver el detalle.", "instance": "/v1/invoices", "code": "validation_failed", "errors": [ { "field": "customer.id", "code": "invalid_check_digit", "message": "El dígito verificador de este número de identificación es incorrecto." } ], "requestId": "01a1412b-9c03-7e55-b1a8-27f4d6e0c913"}Decide según code y errors[].code, que nunca cambian con el idioma; title, detail y message son textos para personas. Cada código tiene su página en Errores.
Reintentos y tiempos de espera
Sección titulada «Reintentos y tiempos de espera»IssueDocument() sigue las reglas de Idempotencia:
- Solo reintenta lo que es seguro reintentar: sin respuesta (un error de red o un tiempo de espera agotado), un
5xx,409idempotency_key_in_progressy un429cuyoRetry-Afteres de 60 segundos o menos. Un429que pide más, como la cuota diaria del emisor demo, se detiene y devuelve el error. - Siempre con la misma clave y el mismo cuerpo. Si el primer intento sí llegó, Flashpi responde al reintento con el mismo comprobante y el header
Idempotent-Replayed: true, nunca con una segunda factura. Con la misma clave y otro cuerpo, la respuesta es422idempotency_key_reused. - Después del último intento la factura conserva su clave, así que enviarla más tarde sigue siendo seguro. Dentro de las 24 horas en que Flashpi recuerda la clave, si el primer intento emitió el comprobante, la misma solicitud recibe otra vez esa respuesta. Después, la respuesta es
422idempotency_key_reusedcon eldocumentIddel comprobante que creó la clave, eIssueDocument()lee ese comprobante. Si cambiaste la factura dentro de las 24 horas, el422no traedocumentId: no le des una clave nueva, porque el primer intento pudo haberla emitido; búscala primero en la consola. ResponseTimeout(30 segundos aquí) debe ser mayor que la espera del modo síncrono: 15 segundos por defecto, hastawaitTimeoutSeconds(30 como máximo), más unos segundos. Si no, el programa se rinde justo antes de que llegue la respuesta.
Modo síncrono, consultas o webhooks
Sección titulada «Modo síncrono, consultas o webhooks»- El modo síncrono (
options.waitForAuthorization, como en el ejemplo) sirve para un punto de venta que imprime el RIDE enseguida: la solicitud espera hasta 15 segundos al SRI (waitTimeoutSeconds, de 1 a 30). Cuando el SRI tarda más, la respuesta es un202conpending_sri, y el comprobante sigue por su cuenta (consulta Modo síncrono y asíncrono). - Los webhooks envían cada autorización y cada rechazo a una dirección HTTPS tuya, así que necesitan un servidor al que se pueda llegar desde internet: una PC de escritorio no puede recibirlos. Si tu sistema tiene un servidor o un servicio web, recíbelos ahí (consulta Webhooks).
- Las consultas funcionan desde cualquier PC: lee el comprobante con
GetDocument()hasta que su estado sea final (authorized,rejectedofailed), por ejemplo desde unTTimer. Espera unos segundos entre lecturas al principio y luego más, por ejemplo un minuto, porque cuando el SRI está caído un comprobante puede tardar horas o días: Flashpi intenta entregarlo al SRI hasta 72 horas desde su creación y espera su decisión hasta 72 horas desde el envío (consulta Límites). Después de que el sistema estuvo cerrado,GET /v1/eventslista los eventos que te perdiste. Un comprobantefailedtodavía no genera evento: búscalo también conGET /v1/documents?status=failed.
Descarga el RIDE y el XML
Sección titulada «Descarga el RIDE y el XML»Cuando el comprobante está authorized, descarga su RIDE (el PDF) con GET /v1/documents/{id}/pdf y su XML autorizado con GET /v1/documents/{id}/xml. Flashpi todavía no se los envía por correo al comprador, así que los entrega el emisor (consulta RIDE y correo).
var Id, FileName, Error: string;begin Id := Invoices.FieldByName('flashpi_id').AsString; FileName := 'C:\Invoices\factura-' + Invoices.FieldByName('doc_number').AsString; if not SaveDocumentFile(Id, 'pdf', FileName + '.pdf', Error) then ShowMessage(Error); SaveDocumentFile(Id, 'xml', FileName + '.xml', Error);end;El RIDE se genera justo después de la autorización, normalmente unos segundos más tarde. Hasta entonces la respuesta es 409 ride_not_available: vuelve a intentarlo un momento después.
TLS 1.2 y versiones de Windows
Sección titulada «TLS 1.2 y versiones de Windows»La API solo acepta TLS 1.2 y TLS 1.3, con conjuntos de cifrado actuales (ECDHE con AES-GCM o ChaCha20). En Windows, THTTPClient y TNetHTTPClient usan WinHTTP, el TLS del propio Windows, así que lo que cuenta es la versión de Windows, no la de Delphi:
- Windows 10 y 11, y Windows Server 2016 o posterior se conectan tal como están.
- Windows 7 y Windows Server 2008 R2 no pueden conectarse, ni siquiera después de activar TLS 1.2 en WinHTTP (actualización KB3140245): les faltan esos conjuntos de cifrado, así que el handshake falla. Las versiones anteriores tampoco.
- Otras versiones sin soporte de Microsoft pueden fallar igual. Comprueba la conexión en cada computadora antes de confiar en ella.
En una computadora que no puede conectarse, mueve la llamada a un servicio en un Windows con soporte (la opción del servidor de Protege la API key), actualiza el Windows de esa computadora o usa Indy, que trae su propio TLS (consulta abajo).
Texto y UTF-8
Sección titulada «Texto y UTF-8»- La API lee y escribe UTF-8. Las strings de Delphi son Unicode:
TStringStream.Create(Body, TEncoding.UTF8)envía los bytes en UTF-8, yContentAsString(TEncoding.UTF8)lee la respuesta. - Declara
application/jsonoapplication/json; charset=utf-8. Un charset que no empiece porutf-, comowindows-1252olatin1, se rechaza con415unsupported_media_type. - Los campos de texto admiten hasta 300 caracteres, en una línea y sin emojis (consulta Límites). Recorta los valores de los campos de largo fijo.
Con Indy
Sección titulada «Con Indy»Muchos sistemas más antiguos llaman por HTTP con Indy (TIdHTTP). También funciona con Flashpi:
- TLS: dale a
TIdHTTPunTIdSSLIOHandlerSocketOpenSSLconSSLOptions.SSLVersions := [sslvTLSv1_2], y haz que verifique el certificado del servidor y su nombre exacto, como en el ejemplo: sin eso, Indy acepta cualquier certificado y la API key puede ser interceptada.cacert.pemes el paquete de autoridades de certificación de Mozilla (lo publica curl.se), que incluye las raíces de Amazon que firman el certificado de la API, yVerifyPeeres un método, por ejemplo del formulario, porqueOnVerifyPeeres un evento. El ejemplo necesita un Indy 10.6 que envíe SNI, como el actual de GitHub: la API lo exige, y un Indy más antiguo no se conecta. Indy usa OpenSSL en lugar del TLS de Windows, así que también se conecta desde versiones de Windows que WinHTTP no puede, siempre que sus DLL de OpenSSL (libeay32.dllyssleay32.dll) estén junto al EXE. Ese OpenSSL 1.0.2 ya no tiene soporte: prefiereTHTTPClientdonde Windows lo permita. - Respuestas de error: por defecto
TIdHTTPlanzaEIdHTTPProtocolExceptionante un4xxo5xx. AgregahoNoProtocolErrorExceptionyhoWantProtocolErrorContentaHTTPOptionspara leer el problem details, y el estado enResponseCode. - Headers y cuerpo:
Request.ContentType := 'application/json',Request.CharSet := 'utf-8',AuthorizationeIdempotency-KeyconRequest.CustomHeaders.AddValue(), y el cuerpo como unTStringStreamen UTF-8, como arriba.
Http.IOHandler := SSL; // a TIdSSLIOHandlerSocketOpenSSLSSL.SSLOptions.Mode := sslmClient;SSL.SSLOptions.SSLVersions := [sslvTLSv1_2];// Check the server's certificate: Indy accepts any certificate otherwiseSSL.SSLOptions.RootCertFile := ExtractFilePath(ParamStr(0)) + 'cacert.pem';SSL.SSLOptions.VerifyMode := [sslvrfPeer];SSL.SSLOptions.VerifyDepth := 9;SSL.OnVerifyPeer := VerifyPeer;Http.HTTPOptions := Http.HTTPOptions + [hoNoProtocolErrorException, hoWantProtocolErrorContent];Http.ConnectTimeout := 10000;Http.ReadTimeout := 30000;Http.Request.ContentType := 'application/json';Http.Request.CharSet := 'utf-8';Http.Request.CustomHeaders.AddValue('Authorization', 'Bearer ' + GetEnvironmentVariable('FLASHPI_API_KEY'));Http.Request.CustomHeaders.AddValue('Idempotency-Key', Key);Answer := Http.Post('https://api.flashpi.com.ec/v1/invoices', Source);Status := Http.ResponseCode;// A chain that cacert.pem trusts (AOk), ending in a certificate issued to// exactly api.flashpi.com.ec. EndsText is in System.StrUtils.function TForm1.VerifyPeer(Certificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean;begin Result := AOk and ((ADepth > 0) or EndsText('/CN=api.flashpi.com.ec', Certificate.Subject.OneLine));end;Siguientes pasos
Sección titulada «Siguientes pasos»- Sigue la vida de un comprobante: Ciclo de vida del comprobante.
- Emite con tu propio RUC: crea tu emisor desde su certificado de firma y usa su
issuerId. - Pasa a producción: una llave de producción (
fp_live_) emite con efecto tributario, con las mismas solicitudes. - ¿Prefieres Visual FoxPro? Consulta Visual FoxPro.