Skip to main content

Java Library (omise-java)

The omise-java library provides a modern Java interface to the Omise API with Java 8+ features including Optional, Stream APIs, and excellent Spring Boot integration.

Installationโ€‹

Mavenโ€‹

Add to your pom.xml:

<dependency>
<groupId>co.omise</groupId>
<artifactId>omise-java</artifactId>
<version>4.4.0</version>
</dependency>

Gradleโ€‹

Add to your build.gradle:

dependencies {
implementation 'co.omise:omise-java:4.4.0'
}

Gradle (Kotlin DSL)โ€‹

dependencies {
implementation("co.omise:omise-java:4.4.0")
}

Requirementsโ€‹

  • Java 8 or higher (including Java 17+)
  • Maven 3+ or Gradle 6+ for dependency management
  • Spring Boot 2.x or 3.x (optional, for Spring applications)

Quick Startโ€‹

Basic Configurationโ€‹

import co.omise.Client;
import co.omise.models.Charge;

public class OmiseExample {
public static void main(String[] args) throws Exception {
Client client = new Client.Builder()
.publicKey("pkey_test_123456789")
.secretKey("skey_test_123456789")
.build();
}
}

Spring Boot Configurationโ€‹

// application.properties
omise.public-key=pkey_test_123456789
omise.secret-key=skey_test_123456789
omise.api-version=2019-05-29

// Configuration class
import co.omise.Client;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "omise")
public class OmiseConfig {
private String publicKey;
private String secretKey;
private String apiVersion;

@Bean
public Client omiseClient() {
return new Client.Builder()
.publicKey(publicKey)
.secretKey(secretKey)
.apiVersion(apiVersion)
.build();
}

// Getters and setters
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}

public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}

public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}
}

Environment Variablesโ€‹

# Development/Test
OMISE_SECRET_KEY=skey_test_123456789
OMISE_PUBLIC_KEY=pkey_test_123456789

# Production
# OMISE_SECRET_KEY=skey_live_123456789
# OMISE_PUBLIC_KEY=pkey_live_123456789

Common Operationsโ€‹

Creating a Chargeโ€‹

import co.omise.Client;
import co.omise.models.Charge;
import co.omise.requests.ChargeCreateRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class ChargeExample {
private final Client client;

public ChargeExample(Client client) {
this.client = client;
}

public Charge createCharge(String token, long amount) throws IOException {
Map<String, Object> metadata = new HashMap<>();
metadata.put("order_id", "1234");
metadata.put("customer_name", "John Doe");

ChargeCreateRequest request = new ChargeCreateRequest()
.amount(amount) // 1,000.00 THB = 100000 satang
.currency("THB")
.card(token)
.description("Order #1234")
.metadata(metadata);

Charge charge = client.charges().create(request);

if (charge.isPaid()) {
System.out.println("Charge successful: " + charge.getId());
} else {
System.out.println("Charge failed: " + charge.getFailureMessage());
}

return charge;
}
}

With Optional (Java 8+)โ€‹

import java.util.Optional;

public class ChargeService {
private final Client client;

public Optional<Charge> createChargeOptional(
String token,
long amount,
Map<String, Object> metadata
) {
try {
ChargeCreateRequest request = new ChargeCreateRequest()
.amount(amount)
.currency("THB")
.card(token)
.metadata(metadata);

Charge charge = client.charges().create(request);
return charge.isPaid() ? Optional.of(charge) : Optional.empty();
} catch (IOException e) {
return Optional.empty();
}
}
}

With 3D Secureโ€‹

public class SecureChargeService {
private final Client client;

public ChargeResult createSecureCharge(
String token,
long amount,
String returnUri
) throws IOException {
ChargeCreateRequest request = new ChargeCreateRequest()
.amount(amount)
.currency("THB")
.card(token)
.returnUri(returnUri);

Charge charge = client.charges().create(request);

if (charge.isAuthorized()) {
if (charge.getAuthorizeUri() != null) {
return new ChargeResult(charge.getAuthorizeUri());
} else {
return new ChargeResult(charge);
}
}

throw new RuntimeException(charge.getFailureMessage());
}
}

class ChargeResult {
private final Charge charge;
private final String redirectUri;

public ChargeResult(Charge charge) {
this.charge = charge;
this.redirectUri = null;
}

public ChargeResult(String redirectUri) {
this.charge = null;
this.redirectUri = redirectUri;
}

public boolean needsRedirect() {
return redirectUri != null;
}

public String getRedirectUri() {
return redirectUri;
}

public Charge getCharge() {
return charge;
}
}

Retrieving a Chargeโ€‹

import co.omise.models.Charge;

public Charge getCharge(String chargeId) throws IOException {
Charge charge = client.charges().get(chargeId);

System.out.println("Amount: " + charge.getAmount());
System.out.println("Currency: " + charge.getCurrency());
System.out.println("Status: " + charge.getStatus());
System.out.println("Paid: " + charge.isPaid());

return charge;
}

Listing Charges with Stream APIโ€‹

import co.omise.models.ScopedList;
import co.omise.models.Charge;
import co.omise.requests.ChargeListRequest;
import java.util.List;
import java.util.stream.Collectors;

public List<Charge> listPaidCharges() throws IOException {
ChargeListRequest request = new ChargeListRequest()
.limit(100)
.offset(0)
.order(ScopedList.Ordering.ReverseChronological);

ScopedList<Charge> charges = client.charges().list(request);

return charges.getData().stream()
.filter(Charge::isPaid)
.collect(Collectors.toList());
}

// With date filtering
public List<Charge> listRecentCharges() throws IOException {
LocalDate weekAgo = LocalDate.now().minusDays(7);
LocalDate today = LocalDate.now();

ChargeListRequest request = new ChargeListRequest()
.from(weekAgo)
.to(today);

ScopedList<Charge> charges = client.charges().list(request);
return charges.getData();
}

Creating a Customerโ€‹

import co.omise.models.Customer;
import co.omise.requests.CustomerCreateRequest;
import java.util.HashMap;
import java.util.Map;

public Customer createCustomer(String email, String description) throws IOException {
Map<String, Object> metadata = new HashMap<>();
metadata.put("user_id", "12345");
metadata.put("account_type", "premium");

CustomerCreateRequest request = new CustomerCreateRequest()
.email(email)
.description(description)
.metadata(metadata);

Customer customer = client.customers().create(request);
System.out.println("Customer created: " + customer.getId());

return customer;
}

Saving a Card to a Customerโ€‹

import co.omise.models.Customer;
import co.omise.requests.CustomerUpdateRequest;

public Customer addCardToCustomer(String customerId, String token) throws IOException {
CustomerUpdateRequest request = new CustomerUpdateRequest()
.card(token);

Customer customer = client.customers().update(customerId, request);
System.out.println("Card saved: " + customer.getDefaultCard());

return customer;
}

// Create customer with card
public Customer createCustomerWithCard(String email, String token) throws IOException {
CustomerCreateRequest request = new CustomerCreateRequest()
.email(email)
.card(token);

return client.customers().create(request);
}

Listing Customer Cardsโ€‹

import co.omise.models.Customer;
import co.omise.models.Card;

public void listCustomerCards(String customerId) throws IOException {
Customer customer = client.customers().get(customerId);

for (Card card : customer.getCards().getData()) {
System.out.println(
card.getBrand() + " ending in " + card.getLastDigits()
);
System.out.println(
"Expires: " + card.getExpirationMonth() + "/" + card.getExpirationYear()
);
}
}

Creating a Refundโ€‹

import co.omise.models.Refund;
import co.omise.requests.RefundCreateRequest;

// Full refund
public Refund refundCharge(String chargeId) throws IOException {
return client.charges().refund(chargeId);
}

// Partial refund
public Refund partialRefund(String chargeId, long amount) throws IOException {
Map<String, Object> metadata = new HashMap<>();
metadata.put("reason", "customer_request");
metadata.put("ticket_id", "TICKET-123");

RefundCreateRequest request = new RefundCreateRequest()
.amount(amount)
.metadata(metadata);

Refund refund = client.charges().refund(chargeId, request);
System.out.println("Refund " + refund.getId() + ": " + refund.getAmount());

return refund;
}

Creating a Transferโ€‹

import co.omise.models.Transfer;
import co.omise.requests.TransferCreateRequest;

public Transfer createTransfer(long amount, String recipientId) throws IOException {
Map<String, Object> metadata = new HashMap<>();
metadata.put("payout_id", "PAYOUT-456");

TransferCreateRequest request = new TransferCreateRequest()
.amount(amount)
.recipient(recipientId)
.metadata(metadata);

Transfer transfer = client.transfers().create(request);
System.out.println("Transfer " + transfer.getId() + ": " + transfer.getAmount());

return transfer;
}

Alternative Payment Methodsโ€‹

Creating a Sourceโ€‹

import co.omise.models.Source;
import co.omise.requests.SourceCreateRequest;

// Prompt Pay QR
public Source createPromptPaySource(long amount) throws IOException {
SourceCreateRequest request = new SourceCreateRequest()
.type(Source.Type.PromptPay)
.amount(amount)
.currency("THB");

Source source = client.sources().create(request);
System.out.println("QR Code URL: " +
source.getScannableCode().getImage().getDownloadUri()
);

// Create charge with source
ChargeCreateRequest chargeRequest = new ChargeCreateRequest()
.amount(amount)
.currency("THB")
.source(source.getId())
.returnUri("https://example.com/payment/callback");

Charge charge = client.charges().create(chargeRequest);
return source;
}

Internet Bankingโ€‹

public Charge createInternetBankingCharge(long amount, String bank) throws IOException {
SourceCreateRequest sourceRequest = new SourceCreateRequest()
.type(Source.Type.InternetBankingSCB)
.amount(amount)
.currency("THB");

Source source = client.sources().create(sourceRequest);

ChargeCreateRequest chargeRequest = new ChargeCreateRequest()
.amount(amount)
.currency("THB")
.source(source.getId())
.returnUri("https://example.com/payment/callback");

Charge charge = client.charges().create(chargeRequest);

// Redirect to charge.getAuthorizeUri()
return charge;
}

Installmentsโ€‹

public Charge createInstallmentCharge(long amount, String bank, int term) throws IOException {
SourceCreateRequest sourceRequest = new SourceCreateRequest()
.type(Source.Type.InstallmentKBank)
.amount(amount)
.currency("THB")
.installmentTerm(term);

Source source = client.sources().create(sourceRequest);

ChargeCreateRequest chargeRequest = new ChargeCreateRequest()
.amount(amount)
.currency("THB")
.source(source.getId())
.returnUri("https://example.com/payment/callback");

return client.charges().create(chargeRequest);
}

Error Handlingโ€‹

import co.omise.Client;
import co.omise.models.OmiseException;
import java.io.IOException;

public class ErrorHandlingExample {
private final Client client;

public Charge createChargeWithErrorHandling(ChargeCreateRequest request) {
try {
return client.charges().create(request);
} catch (OmiseException e) {
System.err.println("Omise Error: " + e.getMessage());
System.err.println("Status Code: " + e.getStatusCode());
System.err.println("Error Code: " + e.getCode());

switch (e.getCode()) {
case "authentication_failure":
throw new RuntimeException("Invalid API key");
case "invalid_card":
throw new RuntimeException("Card was declined");
case "insufficient_fund":
throw new RuntimeException("Insufficient funds");
default:
throw new RuntimeException(e);
}
} catch (IOException e) {
System.err.println("Network error: " + e.getMessage());
throw new RuntimeException("Network error", e);
}
}
}

Custom Exception Handlerโ€‹

public class PaymentErrorHandler {
private static final Map<String, String> ERROR_MESSAGES = Map.of(
"insufficient_fund", "Insufficient funds on card",
"stolen_or_lost_card", "Card reported as stolen or lost",
"invalid_security_code", "Invalid CVV code",
"payment_cancelled", "Payment was cancelled"
);

public static String getErrorMessage(OmiseException ex) {
return ERROR_MESSAGES.getOrDefault(ex.getCode(), ex.getMessage());
}
}

Spring Boot Integrationโ€‹

REST Controllerโ€‹

import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import co.omise.Client;
import co.omise.models.Charge;

@RestController
@RequestMapping("/api/payment")
public class PaymentController {
private final Client omiseClient;
private final PaymentService paymentService;

public PaymentController(Client omiseClient, PaymentService paymentService) {
this.omiseClient = omiseClient;
this.paymentService = paymentService;
}

@PostMapping("/{orderId}")
public ResponseEntity<?> createPayment(
@PathVariable Long orderId,
@RequestBody CreatePaymentRequest request
) {
try {
Order order = paymentService.findOrder(orderId)
.orElseThrow(() -> new RuntimeException("Order not found"));

Map<String, Object> metadata = new HashMap<>();
metadata.put("order_id", order.getId().toString());
metadata.put("customer_email", order.getEmail());

ChargeCreateRequest chargeRequest = new ChargeCreateRequest()
.amount((long) (order.getTotal() * 100))
.currency("THB")
.card(request.getOmiseToken())
.description("Order #" + order.getId())
.metadata(metadata)
.returnUri(request.getReturnUri());

Charge charge = omiseClient.charges().create(chargeRequest);

paymentService.savePayment(Payment.builder()
.orderId(order.getId())
.chargeId(charge.getId())
.amount(order.getTotal())
.status(charge.getStatus())
.paid(charge.isPaid())
.build()
);

if (charge.isPaid()) {
paymentService.markOrderPaid(order.getId());
return ResponseEntity.ok(new ChargeResponse(true, charge));
} else if (charge.getAuthorizeUri() != null) {
return ResponseEntity.ok(
new ChargeResponse(charge.getAuthorizeUri())
);
} else {
return ResponseEntity.badRequest()
.body(Map.of("error", charge.getFailureMessage()));
}
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Payment failed"));
}
}

@GetMapping("/callback")
public ResponseEntity<?> paymentCallback(@RequestParam String id) {
try {
Charge charge = omiseClient.charges().get(id);
paymentService.updatePaymentStatus(
charge.getId(),
charge.getStatus(),
charge.isPaid()
);

if (charge.isPaid()) {
return ResponseEntity.ok(
Map.of("success", true, "message", "Payment successful")
);
} else {
return ResponseEntity.badRequest()
.body(Map.of("error", charge.getFailureMessage()));
}
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Payment verification failed"));
}
}
}

Service Layerโ€‹

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import co.omise.Client;
import co.omise.models.Charge;
import java.util.Optional;

@Service
public class PaymentService {
private final Client omiseClient;
private final PaymentRepository paymentRepository;
private final OrderRepository orderRepository;

public PaymentService(
Client omiseClient,
PaymentRepository paymentRepository,
OrderRepository orderRepository
) {
this.omiseClient = omiseClient;
this.paymentRepository = paymentRepository;
this.orderRepository = orderRepository;
}

public Optional<Order> findOrder(Long orderId) {
return orderRepository.findById(orderId);
}

@Transactional
public Payment savePayment(Payment payment) {
return paymentRepository.save(payment);
}

@Transactional
public void updatePaymentStatus(String chargeId, String status, boolean paid) {
paymentRepository.findByChargeId(chargeId).ifPresent(payment -> {
payment.setStatus(status);
payment.setPaid(paid);
paymentRepository.save(payment);
});
}

@Transactional
public void markOrderPaid(Long orderId) {
orderRepository.findById(orderId).ifPresent(order -> {
order.setPaymentStatus("paid");
orderRepository.save(order);
});
}

public Charge chargeCustomer(String customerId, long amount) throws IOException {
ChargeCreateRequest request = new ChargeCreateRequest()
.amount(amount)
.currency("THB")
.customer(customerId);

return omiseClient.charges().create(request);
}

public Refund refundCharge(String chargeId, Long amount) throws IOException {
if (amount != null) {
RefundCreateRequest request = new RefundCreateRequest()
.amount(amount);
return omiseClient.charges().refund(chargeId, request);
}

return omiseClient.charges().refund(chargeId);
}
}

Entity Classesโ€‹

import javax.persistence.*;
import lombok.Data;
import lombok.Builder;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Entity
@Table(name = "payments")
@Data
@Builder
public class Payment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "order_id", nullable = false)
private Long orderId;

@Column(name = "charge_id", unique = true)
private String chargeId;

@Column(nullable = false)
private BigDecimal amount;

@Column(nullable = false)
private String currency = "THB";

@Column(nullable = false)
private String status;

@Column(nullable = false)
private Boolean paid = false;

@Column(name = "failure_code")
private String failureCode;

@Column(name = "failure_message")
private String failureMessage;

@Column(name = "refund_id")
private String refundId;

@Column(name = "refund_amount")
private BigDecimal refundAmount;

@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt = LocalDateTime.now();

@Column(name = "updated_at")
private LocalDateTime updatedAt = LocalDateTime.now();

@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}

Scheduled Task for Subscriptionsโ€‹

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import co.omise.Client;
import co.omise.models.Charge;
import java.time.LocalDateTime;
import java.util.List;

@Component
public class SubscriptionScheduler {
private final Client omiseClient;
private final SubscriptionRepository subscriptionRepository;

public SubscriptionScheduler(
Client omiseClient,
SubscriptionRepository subscriptionRepository
) {
this.omiseClient = omiseClient;
this.subscriptionRepository = subscriptionRepository;
}

@Scheduled(cron = "0 0 1 * * ?") // Run daily at 1 AM
public void chargeSubscriptions() {
List<Subscription> subscriptions = subscriptionRepository
.findByStatus("active");

for (Subscription subscription : subscriptions) {
try {
ChargeCreateRequest request = new ChargeCreateRequest()
.amount(subscription.getPlanAmount())
.currency("THB")
.customer(subscription.getCustomerId())
.description("Subscription " + LocalDateTime.now().getMonth());

Charge charge = omiseClient.charges().create(request);

if (charge.isPaid()) {
subscription.setLastChargeDate(LocalDateTime.now());
subscriptionRepository.save(subscription);
}
} catch (Exception e) {
// Handle failed payment
}
}
}
}

Best Practicesโ€‹

1. Use Builder Patternโ€‹

Client client = new Client.Builder()
.publicKey(System.getenv("OMISE_PUBLIC_KEY"))
.secretKey(System.getenv("OMISE_SECRET_KEY"))
.apiVersion("2019-05-29")
.timeout(60000)
.build();

2. Use Optional for Null Safetyโ€‹

public Optional<Charge> findCharge(String chargeId) {
try {
return Optional.of(client.charges().get(chargeId));
} catch (IOException e) {
return Optional.empty();
}
}

3. Implement Idempotencyโ€‹

public Charge createIdempotentCharge(
ChargeCreateRequest request,
String orderId
) throws IOException {
String idempotencyKey = "order-" + orderId + "-" + UUID.randomUUID();

return client.charges().create(request, new RequestOptions()
.idempotencyKey(idempotencyKey)
);
}

4. Use Value Objectsโ€‹

public class Money {
private final long amount;
private final String currency;

public Money(BigDecimal amount, String currency) {
this.amount = toSmallestUnit(amount, currency);
this.currency = currency;
}

private long toSmallestUnit(BigDecimal amount, String currency) {
return currency.equals("JPY")
? amount.longValue()
: amount.multiply(BigDecimal.valueOf(100)).longValue();
}

public long getAmount() {
return amount;
}

public String getCurrency() {
return currency;
}
}

5. Implement Loggingโ€‹

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PaymentService {
private static final Logger logger = LoggerFactory.getLogger(PaymentService.class);
private final Client client;

public Charge createCharge(ChargeCreateRequest request) throws IOException {
logger.info("Creating charge: amount={}, currency={}",
request.getAmount(), request.getCurrency());

try {
Charge charge = client.charges().create(request);
logger.info("Charge created: chargeId={}, paid={}",
charge.getId(), charge.isPaid());
return charge;
} catch (Exception e) {
logger.error("Charge failed: {}", e.getMessage(), e);
throw e;
}
}
}

6. Use CompletableFuture for Async Operationsโ€‹

import java.util.concurrent.CompletableFuture;

public CompletableFuture<Charge> createChargeAsync(ChargeCreateRequest request) {
return CompletableFuture.supplyAsync(() -> {
try {
return client.charges().create(request);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}

Testingโ€‹

JUnit 5 with Mockitoโ€‹

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class PaymentServiceTest {
@Mock
private Client mockClient;

@Mock
private Charges mockCharges;

private PaymentService paymentService;

@BeforeEach
void setUp() {
when(mockClient.charges()).thenReturn(mockCharges);
paymentService = new PaymentService(mockClient);
}

@Test
void testCreateCharge_Success() throws IOException {
// Arrange
Charge expectedCharge = new Charge();
expectedCharge.setId("chrg_test_123");
expectedCharge.setAmount(100000L);
expectedCharge.setCurrency("THB");
expectedCharge.setPaid(true);

ChargeCreateRequest request = new ChargeCreateRequest()
.amount(100000L)
.currency("THB")
.card("tokn_test_123");

when(mockCharges.create(any(ChargeCreateRequest.class)))
.thenReturn(expectedCharge);

// Act
Charge charge = paymentService.createCharge(request);

// Assert
assertNotNull(charge);
assertEquals("chrg_test_123", charge.getId());
assertTrue(charge.isPaid());
verify(mockCharges).create(any(ChargeCreateRequest.class));
}

@Test
void testCreateCharge_ThrowsException() throws IOException {
// Arrange
ChargeCreateRequest request = new ChargeCreateRequest()
.amount(100000L)
.currency("THB")
.card("tokn_test_123");

when(mockCharges.create(any(ChargeCreateRequest.class)))
.thenThrow(new OmiseException("insufficient_fund", "Insufficient funds"));

// Act & Assert
assertThrows(OmiseException.class, () -> {
paymentService.createCharge(request);
});
}
}

Spring Boot Integration Testโ€‹

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class PaymentControllerIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;

@MockBean
private Client omiseClient;

@MockBean
private Charges charges;

@Test
void testCreatePayment_Success() throws IOException {
// Arrange
Charge mockCharge = new Charge();
mockCharge.setId("chrg_test_123");
mockCharge.setPaid(true);

when(omiseClient.charges()).thenReturn(charges);
when(charges.create(any(ChargeCreateRequest.class)))
.thenReturn(mockCharge);

CreatePaymentRequest request = new CreatePaymentRequest();
request.setOmiseToken("tokn_test_123");

// Act
ResponseEntity<ChargeResponse> response = restTemplate.postForEntity(
"/api/payment/1",
request,
ChargeResponse.class
);

// Assert
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody().isSuccess()).isTrue();
}
}

Troubleshootingโ€‹

SSL Certificate Issuesโ€‹

import javax.net.ssl.*;
import java.security.cert.X509Certificate;

// Only for development - NOT for production!
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
}, new SecureRandom());

Client client = new Client.Builder()
.publicKey("pkey_test_123")
.secretKey("skey_test_123")
.sslSocketFactory(sslContext.getSocketFactory())
.build();

Timeout Configurationโ€‹

Client client = new Client.Builder()
.publicKey("pkey_test_123")
.secretKey("skey_test_123")
.timeout(60000) // 60 seconds in milliseconds
.build();

Debug Loggingโ€‹

// application.properties
logging.level.co.omise=DEBUG

// Or programmatically
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Logger logger = LoggerFactory.getLogger(Client.class);

Webhook Signature Verificationโ€‹

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.util.Arrays;

public class WebhookVerificationService {
private final String webhookSecret;

public WebhookVerificationService(String webhookSecret) {
this.webhookSecret = webhookSecret;
}

public boolean verifySignature(String payload, String signature) {
try {
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
webhookSecret.getBytes("UTF-8"),
"HmacSHA256"
);
hmac.init(secretKey);

byte[] hash = hmac.doFinal(payload.getBytes("UTF-8"));
String expectedSignature = bytesToHex(hash);

return MessageDigest.isEqual(
signature.getBytes(),
expectedSignature.getBytes()
);
} catch (Exception e) {
return false;
}
}

private String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
}

// Spring Controller
@PostMapping("/webhooks/omise")
public ResponseEntity<?> omiseWebhook(
@RequestBody String payload,
@RequestHeader("Omise-Signature") String signature
) {
if (!webhookService.verifySignature(payload, signature)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(Map.of("error", "Invalid signature"));
}

// Process webhook event
return ResponseEntity.ok(Map.of("status", "ok"));
}

FAQโ€‹

How do I handle webhooks in Spring Boot?โ€‹

@RestController
@RequestMapping("/api/webhooks")
public class WebhookController {
private final Client omiseClient;
private final PaymentService paymentService;

@PostMapping("/omise")
public ResponseEntity<?> omiseWebhook(@RequestBody WebhookEvent event) {
switch (event.getKey()) {
case "charge.complete":
handleChargeComplete(event.getData());
break;
case "refund.create":
handleRefundCreate(event.getData());
break;
}

return ResponseEntity.ok(Map.of("status", "ok"));
}

private void handleChargeComplete(Map<String, Object> chargeData) throws IOException {
String chargeId = (String) chargeData.get("id");
Charge charge = omiseClient.charges().get(chargeId);
paymentService.updatePaymentStatus(
charge.getId(),
charge.getStatus(),
charge.isPaid()
);
}
}

How do I test payments without real charges?โ€‹

// Use test API keys
Client client = new Client.Builder()
.publicKey("pkey_test_123456789")
.secretKey("skey_test_123456789")
.build();

// Use test tokens
ChargeCreateRequest request = new ChargeCreateRequest()
.amount(100000L)
.currency("THB")
.card("tokn_test_5086xl7ddjbases4sq3i");

Charge charge = client.charges().create(request);
System.out.println("Test mode: " + !charge.isLivemode());

How do I handle partial refunds?โ€‹

public Refund refundCharge(String chargeId, Long refundAmount) throws IOException {
Charge charge = client.charges().get(chargeId);
long refundable = charge.getAmount() - charge.getRefunded();

if (refundAmount != null && refundAmount > refundable) {
throw new IllegalArgumentException("Refund amount exceeds refundable amount");
}

if (refundAmount != null) {
RefundCreateRequest request = new RefundCreateRequest()
.amount(refundAmount);
return client.charges().refund(chargeId, request);
}

return client.charges().refund(chargeId);
}

How do I implement subscription billing?โ€‹

@Component
public class SubscriptionManager {
private final Client client;

public Charge chargeMonthly(String customerId, long planAmount) throws IOException {
ChargeCreateRequest request = new ChargeCreateRequest()
.amount(planAmount)
.currency("THB")
.customer(customerId)
.description("Subscription " + LocalDate.now().getMonth());

return client.charges().create(request);
}
}

How do I save multiple cards for a customer?โ€‹

public Customer addCardToCustomer(String customerId, String token) throws IOException {
CustomerUpdateRequest request = new CustomerUpdateRequest()
.card(token);
return client.customers().update(customerId, request);
}

public List<Card> listCustomerCards(String customerId) throws IOException {
Customer customer = client.customers().get(customerId);
return customer.getCards().getData();
}

public Charge chargeSpecificCard(String customerId, String cardId, long amount)
throws IOException {
ChargeCreateRequest request = new ChargeCreateRequest()
.amount(amount)
.currency("THB")
.customer(customerId)
.card(cardId);

return client.charges().create(request);
}

How do I implement retry logic?โ€‹

import java.util.concurrent.TimeUnit;

public Charge createChargeWithRetry(
ChargeCreateRequest request,
int maxRetries
) throws IOException {
int attempt = 0;

while (attempt < maxRetries) {
try {
return client.charges().create(request);
} catch (IOException e) {
attempt++;
if (attempt >= maxRetries) {
throw e;
}

try {
TimeUnit.SECONDS.sleep((long) Math.pow(2, attempt));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new IOException(ie);
}
}
}

throw new IOException("Max retries exceeded");
}

Next Stepsโ€‹

Supportโ€‹

If you encounter issues with the Java library:

  1. Check the GitHub Issues
  2. Review the API documentation
  3. Contact support@omise.co with:
    • Java version
    • omise-java library version
    • Error message and stack trace
    • Steps to reproduce