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>