DoConure

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?