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");
}
Related Resourcesโ
- Omise API Documentation
- omise-java GitHub Repository
- Maven Central
- Omise Dashboard
- Webhooks Guide
- Testing Guide
Next Stepsโ
Supportโ
If you encounter issues with the Java library:
- Check the GitHub Issues
- Review the API documentation
- Contact support@omise.co with:
- Java version
- omise-java library version
- Error message and stack trace
- Steps to reproduce