Salesforce (Apex)
Una classe di servizio minima. In produzione sposta URL e chiave in una Named Credential, così la chiave non sta nel codice.
public with sharing class DocumentComposer {
private static final String BASE_URL = 'https://your-host';
private static final String API_KEY = 'lpdc_xxxxxxxxxxxxxxxxxxxxxxxxxxxx';
public static Blob generateDocument(String templateId, Map<String, Object> data) {
HttpRequest req = new HttpRequest();
req.setEndpoint(BASE_URL + '/api/v1/compose/documents/generate');
req.setMethod('POST');
req.setHeader('Authorization', 'Bearer ' + API_KEY);
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(new Map<String, Object>{
'data' => data,
'settings' => new Map<String, Object>{ 'templateId' => templateId }
}));
req.setTimeout(120000);
HttpResponse res = new Http().send(req);
if (res.getStatusCode() != 200) {
// Branch on the code, not the message.
Map<String, Object> err =
(Map<String, Object>) JSON.deserializeUntyped(res.getBody());
throw new CalloutException(
'DoConure ' + err.get('code') + ': ' + err.get('message'));
}
return res.getBodyAsBlob();
}
}
Ricorda di aggiungere l'host in Setup → Remote Site Settings, oppure usa una Named Credential, altrimenti Salesforce blocca la chiamata.
Comporre e mandare in firma con una chiamata —
POST /api/v1/flow — ha la stessa
forma, solo un corpo più ricco e un endpoint diverso. La firma va dove
il modello ha {{ signature.A }}, quindi il firmatario non ha bisogno di
coordinate (Dove va la firma sulla
pagina: un tag nel documento, oppure in alternativa frazioni della pagina dall'angolo in alto
a sinistra):
public static Map<String, Object> composeAndSign(
String templateId, Map<String, Object> data,
String signerEmail, String signerName,
String requestKey) { // one per piece of work, e.g. 'contract-' + record.Id
// Placed by the template's {{ signature.A }} tag. Without a tag, give
// 'fields' => [{'kind' => 'signature', 'page' => 1, 'x' => 0.1, 'y' => 0.78}].
Map<String, Object> signer = new Map<String, Object>{
'email' => signerEmail, 'fullName' => signerName
};
Map<String, Object> body = new Map<String, Object>{
'data' => data,
'settings' => new Map<String, Object>{ 'templateId' => templateId },
'signature' => new Map<String, Object>{
'subject' => 'Please sign',
'signers' => new Map<String, Object>{ 'A' => signer },
// Told when it closes, at your Apex REST class, authenticated by
// the "prod" callback environment (OAuth2 client credentials).
'callbackUrl' => new Map<String, Object>{
'env' => 'prod', 'onSuccess' => '/doconure/ok', 'onError' => '/doconure/ko'
}
}
};
HttpRequest req = new HttpRequest();
req.setEndpoint(BASE_URL + '/api/v1/flow');
req.setMethod('POST');
req.setHeader('Authorization', 'Bearer ' + API_KEY);
req.setHeader('Content-Type', 'application/json');
// Retried Apex callouts (a platform event replay, a batch retry) must not
// compose and email a signer twice. The key names the work, not the
// attempt: the same record gives the same key, so a retry is recognised
// (see /docs/flow). A fresh random key per attempt would defeat it.
req.setHeader('Idempotency-Key', requestKey);
req.setBody(JSON.serialize(body));
req.setTimeout(120000);
HttpResponse res = new Http().send(req);
return (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
// Or poll GET /api/v1/flow/{flowId} until output.status is not "pending".
}
Essere richiamati nella tua org
Apex REST sul dominio della tua org vuole un token OAuth, quindi un
callback non autenticato non può raggiungerla senza un Site e un utente ospite.
Invece, aggiungi un ambiente dei callback nella console (credenziali client
OAuth2 verso l'URL del token della tua org, con la consumer key e il secret di una External Client App)
e manda env e i percorsi, come sopra. Prendiamo noi
il token, lo teniamo finché scade e lo mandiamo con ogni callback, accanto alla nostra
HMAC: Come faccio autenticare i vostri callback
presso il mio sistema?