Java SDK

Complete Java examples for integrating with Lorn AI.


Requirements

  • Java 11+
  • No external dependencies (uses java.net.http)

For JSON parsing, you can use:

  • Jackson
  • Gson
  • org.json

Setup

// LornClient.java
 
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Map;
import java.util.StringJoiner;
 
public class LornClient {
    private static final String DEFAULT_BASE_URL = "https://{{YOUR_STORE_URL}}";
    private static final String DEFAULT_API_KEY = "{{YOUR_API_KEY}}";
    
    private final String baseUrl;
    private final String apiKey;
    private final HttpClient httpClient;
    
    public LornClient() {
        this(
            System.getenv().getOrDefault("LORN_BASE_URL", DEFAULT_BASE_URL),
            System.getenv().getOrDefault("LORN_API_KEY", DEFAULT_API_KEY)
        );
    }
    
    public LornClient(String baseUrl, String apiKey) {
        this.baseUrl = baseUrl;
        this.apiKey = apiKey;
        this.httpClient = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .build();
    }
    
    // Health check
    public String health() throws Exception {
        return get("/health");
    }
    
    // Search products
    public String searchProducts(Map<String, String> params) throws Exception {
        String queryString = buildQueryString(params);
        return get("/acp/products" + queryString);
    }
    
    public String searchProducts(String query) throws Exception {
        return searchProducts(Map.of("q", query));
    }
    
    public String searchProducts(String query, double maxPrice) throws Exception {
        return searchProducts(Map.of(
            "q", query,
            "max_price", String.valueOf(maxPrice)
        ));
    }
    
    // Get product
    public String getProduct(String productId) throws Exception {
        return get("/acp/products/" + productId);
    }
    
    // Create checkout
    public String createCheckout(String requestBody) throws Exception {
        return post("/checkout_sessions", requestBody);
    }
    
    // Get checkout
    public String getCheckout(String sessionId) throws Exception {
        return get("/checkout_sessions/" + sessionId);
    }
    
    // Update checkout
    public String updateCheckout(String sessionId, String requestBody) throws Exception {
        return patch("/checkout_sessions/" + sessionId, requestBody);
    }
    
    // Complete checkout
    public String completeCheckout(String sessionId) throws Exception {
        return post("/checkout_sessions/" + sessionId + "/complete", "");
    }
    
    // Cancel checkout
    public String cancelCheckout(String sessionId) throws Exception {
        return post("/checkout_sessions/" + sessionId + "/cancel", "");
    }
    
    // Emit webhook
    public String emitWebhook(String requestBody) throws Exception {
        return post("/webhooks/emit", requestBody);
    }
    
    // HTTP methods
    private String get(String endpoint) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + endpoint))
            .header("Accept", "application/json")
            .header("X-ACP-API-Key", apiKey)
            .GET()
            .build();
        
        return sendRequest(request);
    }
    
    private String post(String endpoint, String body) throws Exception {
        HttpRequest.Builder builder = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + endpoint))
            .header("Accept", "application/json")
            .header("X-ACP-API-Key", apiKey);
        
        if (body != null && !body.isEmpty()) {
            builder.header("Content-Type", "application/json")
                   .POST(HttpRequest.BodyPublishers.ofString(body));
        } else {
            builder.POST(HttpRequest.BodyPublishers.noBody());
        }
        
        return sendRequest(builder.build());
    }
    
    private String patch(String endpoint, String body) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + endpoint))
            .header("Accept", "application/json")
            .header("Content-Type", "application/json")
            .header("X-ACP-API-Key", apiKey)
            .method("PATCH", HttpRequest.BodyPublishers.ofString(body))
            .build();
        
        return sendRequest(request);
    }
    
    private String sendRequest(HttpRequest request) throws Exception {
        HttpResponse<String> response = httpClient.send(
            request, 
            HttpResponse.BodyHandlers.ofString()
        );
        
        if (response.statusCode() >= 400) {
            throw new RuntimeException(
                "API error: " + response.statusCode() + " - " + response.body()
            );
        }
        
        return response.body();
    }
    
    private String buildQueryString(Map<String, String> params) {
        if (params.isEmpty()) return "";
        
        StringJoiner joiner = new StringJoiner("&", "?", "");
        for (Map.Entry<String, String> entry : params.entrySet()) {
            joiner.add(
                URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8) + 
                "=" + 
                URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8)
            );
        }
        return joiner.toString();
    }
}

Usage Examples

Search Products

import java.util.Map;
 
public class SearchExample {
    public static void main(String[] args) throws Exception {
        LornClient client = new LornClient();
        
        // Basic search
        String results = client.searchProducts("running shoes");
        System.out.println(results);
        
        // With filters
        String filtered = client.searchProducts(Map.of(
            "q", "wireless headphones",
            "min_price", "50",
            "max_price", "200",
            "page_size", "5"
        ));
        System.out.println(filtered);
        
        // Category browse
        String electronics = client.searchProducts(Map.of(
            "category", "Electronics",
            "page_size", "20"
        ));
        System.out.println(electronics);
    }
}

Get Product Details

public class ProductExample {
    public static void main(String[] args) throws Exception {
        LornClient client = new LornClient();
        
        String product = client.getProduct("prod_abc123");
        System.out.println(product);
        
        // Parse with your preferred JSON library
        // Example with Jackson:
        // ObjectMapper mapper = new ObjectMapper();
        // JsonNode node = mapper.readTree(product);
        // System.out.println("Title: " + node.get("title").asText());
    }
}

Create Checkout

public class CheckoutExample {
    public static void main(String[] args) throws Exception {
        LornClient client = new LornClient();
        
        // Simple checkout
        String simpleCheckout = client.createCheckout("""
            {
                "items": [
                    {"product_id": "prod_abc123", "quantity": 1}
                ]
            }
            """);
        System.out.println(simpleCheckout);
        
        // Full checkout with all details
        String fullCheckout = client.createCheckout("""
            {
                "items": [
                    {
                        "product_id": "prod_abc123",
                        "variant_sku": "sku_black_m",
                        "quantity": 2
                    }
                ],
                "shipping_address": {
                    "name": "Jane Doe",
                    "line1": "123 Main Street",
                    "city": "San Francisco",
                    "state": "CA",
                    "postal_code": "94102",
                    "country": "US"
                },
                "customer": {
                    "email": "jane@example.com",
                    "name": "Jane Doe"
                }
            }
            """);
        System.out.println(fullCheckout);
    }
}

Update Checkout

public class UpdateCheckoutExample {
    public static void main(String[] args) throws Exception {
        LornClient client = new LornClient();
        
        // Update shipping address
        String updated = client.updateCheckout("cs_demo_abc123", """
            {
                "shipping_address": {
                    "name": "Jane Doe",
                    "line1": "456 Oak Avenue",
                    "city": "Los Angeles",
                    "state": "CA",
                    "postal_code": "90001",
                    "country": "US"
                }
            }
            """);
        System.out.println(updated);
        
        // Update cart items
        String cartUpdated = client.updateCheckout("cs_demo_abc123", """
            {
                "items": [
                    {"product_id": "prod_abc123", "quantity": 3}
                ]
            }
            """);
        System.out.println(cartUpdated);
    }
}

Complete Checkout

public class CompleteCheckoutExample {
    public static void main(String[] args) throws Exception {
        LornClient client = new LornClient();
        
        String order = client.completeCheckout("cs_demo_abc123");
        System.out.println("Order completed: " + order);
    }
}

Complete Purchase Flow

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
 
public class CompletePurchaseFlow {
    public static void main(String[] args) throws Exception {
        LornClient client = new LornClient();
        ObjectMapper mapper = new ObjectMapper();
        
        // 1. Search for products
        System.out.println("Searching for running shoes...");
        String searchResults = client.searchProducts("running shoes", 150.0);
        JsonNode results = mapper.readTree(searchResults);
        
        JsonNode items = results.get("items");
        if (items.isEmpty()) {
            System.out.println("No products found");
            return;
        }
        
        JsonNode product = items.get(0);
        String productId = product.get("id").asText();
        String title = product.get("title").asText();
        double price = product.get("price").asDouble();
        
        System.out.println("Found: " + title + " - $" + price);
        
        // 2. Get product details
        String productDetails = client.getProduct(productId);
        JsonNode details = mapper.readTree(productDetails);
        JsonNode variants = details.get("variants");
        
        String variantSku = null;
        if (variants != null && !variants.isEmpty()) {
            variantSku = variants.get(0).get("sku").asText();
        }
        
        // 3. Create checkout
        System.out.println("\nCreating checkout...");
        String checkoutBody = String.format("""
            {
                "items": [
                    {"product_id": "%s", "variant_sku": "%s", "quantity": 1}
                ],
                "shipping_address": {
                    "name": "Jane Doe",
                    "line1": "123 Main St",
                    "city": "San Francisco",
                    "state": "CA",
                    "postal_code": "94102",
                    "country": "US"
                },
                "customer": {"email": "jane@example.com"}
            }
            """, productId, variantSku != null ? variantSku : "");
        
        String checkoutResponse = client.createCheckout(checkoutBody);
        JsonNode checkout = mapper.readTree(checkoutResponse);
        JsonNode session = checkout.get("checkout_session");
        
        String sessionId = session.get("id").asText();
        JsonNode amounts = session.get("amounts");
        
        System.out.println("Session: " + sessionId);
        System.out.println("Subtotal: $" + amounts.get("subtotal").asDouble());
        System.out.println("Tax: $" + amounts.get("tax").asDouble());
        System.out.println("Shipping: $" + amounts.get("shipping").asDouble());
        System.out.println("Total: $" + amounts.get("total").asDouble());
        
        // 4. Complete checkout
        System.out.println("\nCompleting purchase...");
        String orderResponse = client.completeCheckout(sessionId);
        JsonNode order = mapper.readTree(orderResponse);
        
        String status = order.get("checkout_session").get("status").asText();
        System.out.println("Order completed!");
        System.out.println("Status: " + status);
    }
}

Error Handling

public class ErrorHandlingExample {
    public static void main(String[] args) {
        LornClient client = new LornClient();
        
        try {
            String result = client.getProduct("invalid_id");
            System.out.println(result);
        } catch (RuntimeException e) {
            String message = e.getMessage();
            
            if (message.contains("401")) {
                System.err.println("Invalid API key");
            } else if (message.contains("404")) {
                System.err.println("Product not found");
            } else if (message.contains("429")) {
                System.err.println("Rate limited - retry later");
            } else if (message.contains("500") || message.contains("503")) {
                System.err.println("Server error - retry later");
            } else {
                System.err.println("Error: " + message);
            }
        } catch (Exception e) {
            System.err.println("Connection error: " + e.getMessage());
        }
    }
}

Webhook Signature Verification

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
 
public class WebhookVerifier {
    private static final int TOLERANCE_SECONDS = 300;
    
    public static boolean verifySignature(
        byte[] body, 
        String signatureHeader, 
        String secret
    ) {
        // Parse signature header: "t=<timestamp>,v1=<signature>"
        String[] parts = signatureHeader.split(",");
        long timestamp = 0;
        String receivedSig = "";
        
        for (String part : parts) {
            String[] kv = part.split("=", 2);
            if (kv[0].equals("t")) {
                timestamp = Long.parseLong(kv[1]);
            } else if (kv[0].equals("v1")) {
                receivedSig = kv[1];
            }
        }
        
        // Check timestamp freshness
        long now = System.currentTimeMillis() / 1000;
        if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) {
            return false;
        }
        
        // Compute expected signature
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec keySpec = new SecretKeySpec(
                secret.getBytes(StandardCharsets.UTF_8), 
                "HmacSHA256"
            );
            mac.init(keySpec);
            byte[] hash = mac.doFinal(body);
            String expectedSig = HexFormat.of().formatHex(hash);
            
            // Timing-safe comparison
            return MessageDigest.isEqual(
                expectedSig.getBytes(), 
                receivedSig.getBytes()
            );
        } catch (Exception e) {
            return false;
        }
    }
}
 
// Spring Boot webhook handler example
/*
@RestController
public class WebhookController {
    @Value("${lorn.webhook.secret}")
    private String webhookSecret;
    
    @PostMapping("/webhooks/lorn")
    public ResponseEntity<?> handleWebhook(
        @RequestBody byte[] body,
        @RequestHeader("X-Webhook-Signature") String signature
    ) {
        if (!WebhookVerifier.verifySignature(body, signature, webhookSecret)) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid signature"));
        }
        
        ObjectMapper mapper = new ObjectMapper();
        JsonNode event = mapper.readTree(body);
        String eventType = event.get("type").asText();
        
        switch (eventType) {
            case "order.created":
                handleOrderCreated(event.get("data").get("object"));
                break;
            case "order.fulfilled":
                handleOrderFulfilled(event.get("data").get("object"));
                break;
        }
        
        return ResponseEntity.ok(Map.of("received", true));
    }
}
*/

Maven Dependencies (Optional)

If you want to use Jackson for JSON parsing:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.16.1</version>
</dependency>

For Gson:

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>