A ready-to-use Spring Boot library for integrating the CLICK payment system into your Java application.
You do not need to understand the raw Click API. Just add this library, set your credentials, and write your business logic. The library handles all the HTTP communication, signature verification, and request/response mapping for you.
Click is a popular payment system in Uzbekistan. When a user pays through Click, the following happens behind the scenes:
- User initiates a payment via Click app, website, USSD, or Telegram bot.
- Click sends a "Prepare" request to your server — asking "Does this order exist? Is the amount correct?"
- Your server validates and responds.
- Click sends a "Complete" request to your server — telling you "Payment succeeded" or "Payment was cancelled."
- Your server confirms the order or cancels it.
This library automatically creates the endpoints that Click calls (steps 2 and 4) and handles all the security checks. You only need to write what happens with your orders (e.g., save to database, send confirmation email, etc.).
Additionally, this library provides a service for Merchant API — meaning you can also call Click's API directly from your code to create invoices, manage card tokens, cancel payments, and more.
- Automatic webhook endpoints —
/api/click/prepareand/api/click/completeare created for you - Signature verification — every incoming request from Click is verified using MD5 hash (you don't need to write this yourself)
- Merchant API client — call Click's API to create invoices, check statuses, cancel payments, and manage card tokens
- All request/response models included — every DTO (Data Transfer Object) for Click is already created as a Java class
- Configure once in
application.yml— your merchant credentials go in one place - You control your business logic — implement one interface and the library does the rest
Clone this repository and install it to your local Maven repository:
mvn clean installIn your Spring Boot project's pom.xml, add:
<dependency>
<groupId>integration.payment</groupId>
<artifactId>click-integration-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>That's it! Spring Boot will auto-detect and configure the library.
When you register with Click as a merchant, they give you 4 values:
- merchant_id — your unique merchant identifier
- service_id — the ID of the service you are selling
- merchant_user_id — your user ID in Click's system
- secret_key — a secret string used to verify requests (keep this safe!)
Put these values in your project's application.yml:
click:
merchant-id: 12345
service-id: 67890
merchant-user-id: 11111
secret-key: YOUR_SECRET_KEY_FROM_CLICKOr if you prefer application.properties:
click.merchant-id=12345
click.service-id=67890
click.merchant-user-id=11111
click.secret-key=YOUR_SECRET_KEY_FROM_CLICK| Property | Default Value | Description |
|---|---|---|
click.endpoint |
https://api.click.uz/v2/merchant/ |
Click's API base URL (you almost never change this) |
click.shop-api.base-path |
/api/click |
The URL path where prepare/complete endpoints are registered |
There are two parts to this library:
- SHOP-API (incoming webhooks) — Click calls YOUR server. You must implement one interface.
- Merchant API (outgoing calls) — YOUR server calls Click. Just inject a service and call methods.
This is the required part. Click needs to call your server to prepare and complete payments.
This is the only thing you need to write. The library calls your methods when Click sends requests.
import integration.payment.clickintegration.model.ClickErrorCode;
import integration.payment.clickintegration.model.shop.ClickCompleteRequest;
import integration.payment.clickintegration.model.shop.ClickPrepareRequest;
import integration.payment.clickintegration.service.ClickOrderResult;
import integration.payment.clickintegration.service.ClickOrderService;
import org.springframework.stereotype.Service;
@Service
public class MyOrderService implements ClickOrderService {
// You can inject your own repositories, services, etc.
// private final OrderRepository orderRepository;
/**
* Called FIRST to check if the order exists and the amount is correct.
* This is called before both Prepare and Complete.
*
* @param merchantTransId - this is YOUR order ID (the one you gave to Click)
* @param amount - the payment amount that Click is trying to charge
* @return ClickOrderResult - return ok() if valid, or error() if not
*/
@Override
public ClickOrderResult validateOrder(String merchantTransId, Double amount) {
// Example: look up your order from the database
// Order order = orderRepository.findById(Long.parseLong(merchantTransId)).orElse(null);
// If order doesn't exist:
// if (order == null) {
// return ClickOrderResult.error(ClickErrorCode.ORDER_NOT_FOUND);
// }
// If amounts don't match:
// if (Math.abs(order.getAmount() - amount) > 0.01) {
// return ClickOrderResult.error(ClickErrorCode.INCORRECT_AMOUNT);
// }
// If order was already paid:
// if (order.isPaid()) {
// return ClickOrderResult.error(ClickErrorCode.ALREADY_PAID);
// }
// If everything is fine:
// return ClickOrderResult.ok(order.getId());
// --- PLACEHOLDER (replace with your real logic) ---
return ClickOrderResult.ok(Long.parseLong(merchantTransId));
}
/**
* Called when Click sends a PREPARE request (Action = 0).
* This means: "A user wants to pay for this order. Please reserve it."
*
* You should:
* - Mark the order as "waiting for payment" in your database
* - Save the click_trans_id for future reference
* - Return the merchant_prepare_id (usually your order ID or transaction ID)
*
* @param request - contains click_trans_id, merchant_trans_id, amount, etc.
* @return ClickOrderResult with merchant_prepare_id
*/
@Override
public ClickOrderResult prepareOrder(ClickPrepareRequest request) {
// Example:
// Order order = orderRepository.findById(Long.parseLong(request.getMerchantTransId())).get();
// order.setStatus("WAITING_PAYMENT");
// order.setClickTransId(request.getClickTransId());
// orderRepository.save(order);
// return ClickOrderResult.ok(order.getId());
// --- PLACEHOLDER (replace with your real logic) ---
return ClickOrderResult.ok(Long.parseLong(request.getMerchantTransId()));
}
/**
* Called when Click sends a COMPLETE request (Action = 1).
* This means either:
* - Payment was SUCCESSFUL (request.getError() == 0) -> deliver the product/service
* - Payment was CANCELLED (request.getError() < 0) -> cancel the reservation
*
* You should:
* - Check request.getError() to know if it succeeded or failed
* - Update your order status in the database
* - Return confirmed() on success, or error() on failure
*
* @param request - contains click_trans_id, merchant_trans_id, merchant_prepare_id, error, etc.
* @return ClickOrderResult with merchant_confirm_id
*/
@Override
public ClickOrderResult completeOrder(ClickCompleteRequest request) {
// Example:
// Order order = orderRepository.findById(Long.parseLong(request.getMerchantTransId())).get();
//
// if (request.getError() != null && request.getError() < 0) {
// // Payment was cancelled by Click
// order.setStatus("CANCELLED");
// orderRepository.save(order);
// return ClickOrderResult.error(ClickErrorCode.TRANSACTION_CANCELLED);
// }
//
// // Payment was successful!
// order.setStatus("PAID");
// orderRepository.save(order);
// return ClickOrderResult.confirmed(order.getId(), order.getId());
// --- PLACEHOLDER (replace with your real logic) ---
return ClickOrderResult.confirmed(
request.getMerchantPrepareId(),
request.getMerchantPrepareId()
);
}
}Once you create this @Service bean, the library automatically:
- Registers
POST /api/click/prepareendpoint - Registers
POST /api/click/completeendpoint - Validates the signature (MD5 hash) on every request from Click
- Calls your
validateOrder(),prepareOrder(), andcompleteOrder()methods - Returns the correct JSON response to Click
You give these URLs to Click when setting up your merchant account:
- Prepare URL:
https://your-domain.com/api/click/prepare - Complete URL:
https://your-domain.com/api/click/complete
If you need to create invoices, manage card tokens, or cancel payments, inject ClickMerchantApiService:
import integration.payment.clickintegration.model.merchant.*;
import integration.payment.clickintegration.service.ClickMerchantApiService;
import org.springframework.stereotype.Service;
@Service
public class MyPaymentService {
private final ClickMerchantApiService clickMerchantApi;
public MyPaymentService(ClickMerchantApiService clickMerchantApi) {
this.clickMerchantApi = clickMerchantApi;
}
// =====================================================
// EXAMPLE 1: Create an invoice (sends SMS to the user)
// =====================================================
public void sendPaymentRequest() {
CreateInvoiceResponse response = clickMerchantApi.createInvoice(
"998901234567", // user's phone number (with country code)
50000.0, // amount in UZS
"order-123" // your order ID
);
if (response.getErrorCode() == 0) {
System.out.println("Invoice created! ID: " + response.getInvoiceId());
// Save response.getInvoiceId() to your database
} else {
System.out.println("Error: " + response.getErrorNote());
}
}
// =====================================================
// EXAMPLE 2: Check if an invoice was paid
// =====================================================
public void checkPayment(Long invoiceId) {
CheckInvoiceResponse response = clickMerchantApi.checkInvoice(invoiceId);
System.out.println("Invoice status: " + response.getInvoiceStatus());
// Status > 0 means paid
// Status == -99 means rejected
// Status < 0 means error
}
// =====================================================
// EXAMPLE 3: Cancel (reverse) a payment
// =====================================================
public void cancelPayment(Long paymentId) {
CancelPaymentResponse response = clickMerchantApi.cancelPayment(paymentId);
if (response.getErrorCode() == 0) {
System.out.println("Payment cancelled successfully!");
} else {
System.out.println("Cancel failed: " + response.getErrorNote());
}
}
// =====================================================
// EXAMPLE 4: Full card token flow
// =====================================================
public void payWithCard() {
// Step 1: Create a card token (user will receive an SMS code)
CreateCardTokenResponse tokenResponse = clickMerchantApi.createCardToken(
"8600123456789012", // card number
"0625", // expiry date (MMYY)
0 // 0 = permanent token, 1 = one-time use
);
if (tokenResponse.getErrorCode() != 0) {
System.out.println("Error creating token: " + tokenResponse.getErrorNote());
return;
}
String cardToken = tokenResponse.getCardToken();
// Step 2: Verify the card token with SMS code from the user
VerifyCardTokenResponse verifyResponse = clickMerchantApi.verifyCardToken(
cardToken,
"123456" // SMS code the user received
);
if (verifyResponse.getErrorCode() != 0) {
System.out.println("Verification failed: " + verifyResponse.getErrorNote());
return;
}
// Step 3: Make a payment using the verified card token
PaymentWithTokenResponse payResponse = clickMerchantApi.paymentWithToken(
cardToken,
50000.0, // amount in UZS
"order-456", // your order ID
"user-789" // user/contract identifier in your system
);
if (payResponse.getErrorCode() == 0) {
System.out.println("Payment successful! ID: " + payResponse.getPaymentId());
} else {
System.out.println("Payment failed: " + payResponse.getErrorNote());
}
// Step 4 (optional): Delete the card token when no longer needed
DeleteCardTokenResponse deleteResponse = clickMerchantApi.deleteCardToken(cardToken);
System.out.println("Token deleted: " + (deleteResponse.getErrorCode() == 0));
}
}Here is the complete flow of how a Click payment works with this library:
┌──────────┐ ┌───────────┐ ┌──────────────────┐
│ User │ │ CLICK │ │ Your Server │
│ │ │ System │ │ (this library) │
└────┬─────┘ └─────┬─────┘ └────────┬─────────┘
│ │ │
│ 1. User pays │ │
│ via Click app │ │
│ ──────────────────> │ │
│ │ │
│ │ 2. POST /prepare │
│ │ ────────────────────> │
│ │ │ Library verifies signature
│ │ │ Library calls YOUR validateOrder()
│ │ │ Library calls YOUR prepareOrder()
│ │ 3. Response (ok/error) │
│ │ <──────────────────── │
│ │ │
│ │ 4. POST /complete │
│ │ ────────────────────> │
│ │ │ Library verifies signature
│ │ │ Library calls YOUR validateOrder()
│ │ │ Library calls YOUR completeOrder()
│ │ 5. Response (ok/error) │
│ │ <──────────────────── │
│ │ │
│ 6. "Payment done!" │ │
│ <────────────────── │ │
│ │ │
Every Click API field is mapped to a Java class. You can use these for your own logic, database mapping, logging, etc.
These are in the package integration.payment.clickintegration.model.shop:
| Class | Purpose | Key Fields |
|---|---|---|
ClickPrepareRequest |
Click sends this to prepare a payment | clickTransId, serviceId, merchantTransId, amount, action (0), signString, signTime |
ClickPrepareResponse |
Your server returns this | clickTransId, merchantTransId, merchantPrepareId, error, errorNote |
ClickCompleteRequest |
Click sends this to complete a payment | clickTransId, serviceId, merchantTransId, merchantPrepareId, amount, action (1), error, signString, signTime |
ClickCompleteResponse |
Your server returns this | clickTransId, merchantTransId, merchantPrepareId, merchantConfirmId, error, errorNote |
These are in the package integration.payment.clickintegration.model.merchant:
| Class | Purpose |
|---|---|
CreateInvoiceRequest |
Send an invoice to a user's phone |
CreateInvoiceResponse |
Response with invoice ID |
CheckInvoiceResponse |
Invoice status check result |
CancelPaymentResponse |
Payment cancellation result |
CreateCardTokenRequest |
Request to tokenize a card |
CreateCardTokenResponse |
Response with card token |
VerifyCardTokenRequest |
Verify card token with SMS code |
VerifyCardTokenResponse |
Verification result |
PaymentWithTokenRequest |
Pay using a saved card token |
PaymentWithTokenResponse |
Payment result with status |
DeleteCardTokenResponse |
Card token deletion result |
The ClickErrorCode enum (in integration.payment.clickintegration.model) contains all possible errors:
| Code | Name | What it means |
|---|---|---|
0 |
SUCCESS |
Everything is fine |
-1 |
SIGN_CHECK_FAILED |
The signature in the request is invalid (possible tampering) |
-2 |
INCORRECT_AMOUNT |
The amount doesn't match your order |
-3 |
ACTION_NOT_FOUND |
Invalid action value (must be 0 for prepare, 1 for complete) |
-4 |
ALREADY_PAID |
This order was already paid |
-5 |
ORDER_NOT_FOUND |
The order ID doesn't exist in your system |
-6 |
TRANSACTION_NOT_FOUND |
The transaction doesn't exist |
-7 |
BAD_REQUEST |
General update error |
-8 |
ERROR_IN_REQUEST_FROM_CLICK |
Missing or invalid fields in Click's request |
-9 |
TRANSACTION_CANCELLED |
The payment was cancelled |
Use these in your ClickOrderService implementation:
// Order not found
return ClickOrderResult.error(ClickErrorCode.ORDER_NOT_FOUND);
// Amount mismatch
return ClickOrderResult.error(ClickErrorCode.INCORRECT_AMOUNT);
// Custom error message
return ClickOrderResult.error(ClickErrorCode.ORDER_NOT_FOUND, "Order #123 was deleted");| Method | When to use |
|---|---|
ClickOrderResult.ok(merchantPrepareId) |
Prepare succeeded — pass your order/transaction ID |
ClickOrderResult.confirmed(merchantPrepareId, merchantConfirmId) |
Complete succeeded — pass both IDs |
ClickOrderResult.error(ClickErrorCode) |
Something went wrong — pass the error code |
ClickOrderResult.error(ClickErrorCode, "custom message") |
Something went wrong — with a custom error message |
click-integration-spring-boot-starter/
│
├── config/
│ ├── ClickProperties.java — Reads your click.* settings from application.yml
│ └── ClickAutoConfiguration.java — Auto-creates all beans when the library is on the classpath
│
├── controller/
│ └── ClickShopApiController.java — The /prepare and /complete REST endpoints
│
├── model/
│ ├── ClickErrorCode.java — All error codes as a Java enum
│ │
│ ├── shop/ — Models for SHOP-API (Click calls you)
│ │ ├── ClickPrepareRequest.java
│ │ ├── ClickPrepareResponse.java
│ │ ├── ClickCompleteRequest.java
│ │ └── ClickCompleteResponse.java
│ │
│ └── merchant/ — Models for Merchant-API (you call Click)
│ ├── CreateInvoiceRequest.java
│ ├── CreateInvoiceResponse.java
│ ├── CheckInvoiceResponse.java
│ ├── CancelPaymentResponse.java
│ ├── CreateCardTokenRequest.java
│ ├── CreateCardTokenResponse.java
│ ├── VerifyCardTokenRequest.java
│ ├── VerifyCardTokenResponse.java
│ ├── PaymentWithTokenRequest.java
│ ├── PaymentWithTokenResponse.java
│ └── DeleteCardTokenResponse.java
│
├── service/
│ ├── ClickOrderService.java — Interface YOU implement (your business logic)
│ ├── ClickOrderResult.java — Result object you return from your implementation
│ ├── ClickShopApiService.java — Handles incoming webhooks (signature check + your logic)
│ └── ClickMerchantApiService.java — Makes outgoing HTTP calls to Click's API
│
└── util/
└── ClickSignatureUtil.java — MD5 and SHA-1 hash generation for signatures
Q: Do I need to create the /prepare and /complete endpoints myself?
No. The library creates them automatically. Just implement ClickOrderService and mark it with @Service.
Q: Where do I get the merchant credentials?
When you sign a contract with Click as a merchant, they provide you with merchant_id, service_id, merchant_user_id, and secret_key.
Q: What if I only want to use the Merchant API (create invoices, etc.) and don't need webhooks?
That works too. ClickMerchantApiService is always available as long as you configure click.secret-key in your application.yml. The webhook endpoints only activate when you provide a ClickOrderService bean.
Q: Can I change the webhook URL path?
Yes. Set click.shop-api.base-path in your application.yml. For example, click.shop-api.base-path=/payments/click will make the endpoints /payments/click/prepare and /payments/click/complete.
Q: What if I already have an OkHttpClient bean?
The library creates its own OkHttpClient bean (clickOkHttpClient) only if you don't already have one (@ConditionalOnMissingBean). If you have a custom OkHttpClient bean, the library will use yours.
Q: How does signature verification work?
Click sends a sign_string field with every request. The library computes the expected hash using your secret_key and the request parameters, then compares it. If they don't match, the request is rejected with error code -1. You don't need to do anything — this happens automatically.