SDK Java - com.itzenata:efact-java

SDK officiel EFact pour Java 21+. Conçu pour Spring Boot mais utilisable dans tout projet Java.

Installation

Maven

pom.xml
<dependency>
  <groupId>com.itzenata</groupId>
  <artifactId>efact-java</artifactId>
  <version>0.1.0</version>
</dependency>

Gradle

build.gradle
implementation 'com.itzenata:efact-java:0.1.0'

Initialisation

Basique

Basic Initialization
import com.itzenata.efact.Itzenata;

Itzenata itz = new Itzenata(System.getenv("ITZENATA_SECRET_KEY"));

Configuration avancée

Advanced Configuration
Itzenata itz = Itzenata.builder()
    .apiKey(System.getenv("ITZENATA_SECRET_KEY"))
    .apiVersion("v1")
    .baseUrl("https://api.efact.itzenata.com")
    .timeout(Duration.ofSeconds(30))
    .maxRetries(2)
    .build();

Injection Spring (recommandé)

Spring Configuration
@Configuration
public class EFactConfig {

    @Value("${itzenata.secret-key}")
    private String secretKey;

    @Bean
    public Itzenata itzenata() {
        return new Itzenata(secretKey);
    }
}
application.yml
# application.yml
itzenata:
  secret-key: ${ITZENATA_SECRET_KEY}

invoices().create(params)

Create Invoice
import com.itzenata.efact.model.Invoice;
import com.itzenata.efact.param.InvoiceCreateParams;
import com.itzenata.efact.param.InvoiceCreateParams.Customer;
import com.itzenata.efact.param.InvoiceCreateParams.Line;

@Autowired
private Itzenata itz;

public String createInvoice() {
    Invoice invoice = itz.invoices().create(
        InvoiceCreateParams.builder()
            .amount(24000L)
            .currency("MAD")
            .customer(Customer.builder()
                .ice("002345678901234")
                .name("Société Exemple SARL")
                .address("12 Rue Hassan II, Casablanca")
                .email("finance@exemple.ma")
                .build())
            .addLine(Line.builder()
                .description("Licence logiciel annuelle")
                .quantity(2)
                .unitPrice(10000L)
                .taxRate(20)
                .build())
            .addLine(Line.builder()
                .description("Formation (exonéré TVA)")
                .quantity(1)
                .unitPrice(4000L)
                .taxRate(0)
                .build())
            .putMetadata("internal_id", "INV-2026-043")
            .putMetadata("client_ref", "CLI-789")
            .build()
    );

    return invoice.getClientSecret(); // à retourner au frontend
}

invoices().retrieve(id)

Retrieve Invoice
Invoice invoice = itz.invoices().retrieve("inv_01H8XYZABC");

switch (invoice.getStatus()) {
    case ACCEPTED -> {
        String dgiId = invoice.getDgiId();
        String xmlUrl = invoice.getXmlUrl();
        String pdfUrl = invoice.getPdfUrl();
        invoiceRepository.markAsAccepted(
            invoice.getMetadata().get("internal_id"),
            dgiId, xmlUrl, pdfUrl
        );
    }
    case REJECTED -> {
        String reason = invoice.getRejectionReason();
        notificationService.alertAccountant(reason);
    }
}

invoices().list(params?)

List Invoices
import com.itzenata.efact.param.InvoiceListParams;
import com.itzenata.efact.model.InvoiceCollection;

InvoiceCollection collection = itz.invoices().list(
    InvoiceListParams.builder()
        .status("accepted")
        .limit(50L)
        .build()
);

for (Invoice invoice : collection.getData()) {
    System.out.println(invoice.getId() + " - " + invoice.getMetadata().get("internal_id"));
}

// Pagination
if (collection.getHasMore()) {
    InvoiceCollection nextPage = itz.invoices().list(
        InvoiceListParams.builder()
            .startingAfter(collection.getNextCursor())
            .build()
    );
}

webhooks().constructEvent(payload, signature, secret)

Webhook Handler
import com.itzenata.efact.model.Event;
import com.itzenata.efact.exception.SignatureVerificationException;

@PostMapping("/webhooks/efact")
public ResponseEntity<String> handleWebhook(
        @RequestBody String payload,
        @RequestHeader("Itzenata-Signature") String signature) {

    Event event;
    try {
        event = itz.webhooks().constructEvent(
            payload,
            signature,
            System.getenv("ITZENATA_WEBHOOK_SECRET")
        );
    } catch (SignatureVerificationException e) {
        log.warn("Webhook signature invalide : {}", e.getMessage());
        return ResponseEntity.badRequest().body("Invalid signature");
    }

    switch (event.getType()) {
        case "invoice.accepted" -> {
            var data = event.getDataAs(InvoiceAcceptedData.class);
            invoiceRepository.markAsAccepted(
                data.getMetadata().get("internal_id"),
                data.getDgiId()
            );
        }
        case "invoice.rejected" -> {
            var data = event.getDataAs(InvoiceRejectedData.class);
            notificationService.notifyAccountant(data.getRejectionReason());
        }
        default -> log.info("Event non géré : {}", event.getType());
    }

    return ResponseEntity.ok("ok");
}

Gestion des exceptions

Exception Handling
import com.itzenata.efact.exception.*;

try {
    Invoice invoice = itz.invoices().create(params);
} catch (AuthenticationException e) {
    // 401 - clé API invalide
    log.error("Clé API EFact invalide", e);
} catch (InvalidRequestException e) {
    // 400 / 422 - payload invalide
    log.error("Champ invalide : {} - {}", e.getParam(), e.getMessage());
} catch (ApiException e) {
    // 5xx - erreur serveur EFact
    log.error("Erreur serveur EFact : {}", e.getStatus(), e);
} catch (EFactException e) {
    // Base exception pour tous les cas
    log.error("Erreur EFact", e);
}

Hiérarchie des exceptions

ExceptionCode HTTPDescription
AuthenticationException401Clé API invalide ou manquante
PermissionException403Scope insuffisant
ResourceNotFoundException404Ressource introuvable
InvalidRequestException400 / 422Payload invalide
IdempotencyException409Conflit clé d'idempotency
RateLimitException429Rate limit dépassé
ApiException5xxErreur côté EFact
NetworkException-Timeout ou erreur réseau

Exemple contrôleur Spring Boot complet

InvoiceController.java
@RestController
@RequestMapping("/api/invoices")
@RequiredArgsConstructor
public class InvoiceController {

    private final Itzenata itz;
    private final InvoiceService invoiceService;

    @PostMapping
    public ResponseEntity<Map<String, String>> createInvoice(
            @RequestBody @Valid CreateInvoiceRequest req) {

        Invoice invoice = itz.invoices().create(
            InvoiceCreateParams.builder()
                .amount(req.getAmountCentimes())
                .currency("MAD")
                .customer(Customer.builder()
                    .ice(req.getCustomerIce())
                    .name(req.getCustomerName())
                    .build())
                .addLines(req.getLines().stream()
                    .map(l -> Line.builder()
                        .description(l.getDescription())
                        .quantity(l.getQuantity())
                        .unitPrice(l.getUnitPriceCentimes())
                        .taxRate(l.getTaxRate())
                        .build())
                    .toList())
                .putMetadata("internal_id", req.getInvoiceId())
                .build()
        );

        // Sauvegarder l'ID EFact dans votre base
        invoiceService.saveEFactId(req.getInvoiceId(), invoice.getId());

        return ResponseEntity.ok(Map.of("clientSecret", invoice.getClientSecret()));
    }
}

Compatibilité

EnvironnementSupport
Java 21+Supporté
Spring Boot 3.xSupporté
Spring Boot 2.7.xSupporté
QuarkusSupporté
MicronautSupporté
Jakarta EE 10Supporté