Java
Pine Labs Server SDK for Java.
pinelabs-java is the official Java SDK for the Pine Labs Online Payment Gateway (Plural). It is generated from the same OpenAPI spec that powers this documentation, so every operation, request, and response model is fully typed.
- Coordinates:
com.pinelabsonline:pinelabs-javaon Maven Central - Runtime: Java 11+
- Transport: OkHttp 4 + Jackson
Install
<dependency>
<groupId>com.pinelabsonline</groupId>
<artifactId>pinelabs-java</artifactId>
<version>0.1.0</version>
</dependency>
Quickstart
The Pine Labs API uses OAuth2 with the client_credentials grant. Exchange your <client-id> / <client-secret> for an access token, then pass it to PinelabsApiClient.
import com.pinelabsonline.api.PinelabsApiClient;
import com.pinelabsonline.api.resources.authentication.requests.GenerateTokenRequest;
import com.pinelabsonline.api.resources.orders.requests.CreateOrderRequest;
import com.pinelabsonline.api.types.Amount;
public class Main {
public static void main(String[] args) {
String baseUrl = "https://pluraluat.v2.pinepg.in"; // UAT
// String baseUrl = "https://api.pluralpay.in"; // Production
// 1. Bootstrap client used only to fetch a token. The /token endpoint
// does not require authentication, so we pass an empty token here.
PinelabsApiClient auth = PinelabsApiClient.builder()
.token("")
.url(baseUrl)
.build();
var tokenResponse = auth.authentication().generateToken(
GenerateTokenRequest.builder()
.grantType("client_credentials")
.clientId(System.getenv("PINELABS_CLIENT_ID"))
.clientSecret(System.getenv("PINELABS_CLIENT_SECRET"))
.build()
);
// 2. Build an authenticated client
PinelabsApiClient client = PinelabsApiClient.builder()
.token(tokenResponse.getAccessToken())
.url(baseUrl)
.build();
// 3. Call any operation
var order = client.orders().createOrder(
CreateOrderRequest.builder()
.merchantOrderReference("order-001")
.orderAmount(Amount.builder()
.value(50000) // ₹500.00
.currency("INR")
.build())
.build()
);
System.out.println(order);
}
}
Environments
| Environment | Base URL |
|---|---|
| UAT | https://pluraluat.v2.pinepg.in |
| Production | https://api.pluralpay.in |
Pass the URL via .url(baseUrl) on the builder. The exported Environment enum currently only contains PRODUCTION; for UAT, set the URL explicitly.
import com.pinelabsonline.api.core.Environment;
PinelabsApiClient client = PinelabsApiClient.builder()
.token("<access-token>")
.environment(Environment.PRODUCTION)
.build();
Auto-refreshing token
For long-running services, cache the token and refresh it before it expires. The builder accepts a Supplier<String> for token — it is invoked on every request. Use a separate bootstrap auth client (without a supplier) to fetch the token, so the supplier never recurses into itself.
import com.pinelabsonline.api.PinelabsApiClient;
import com.pinelabsonline.api.resources.authentication.requests.GenerateTokenRequest;
import java.time.Instant;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
public class PinelabsClientFactory {
private static final String BASE_URL = "https://pluraluat.v2.pinepg.in";
// Bootstrap client used only to fetch tokens. It has no supplier, so calling
// generateToken() from inside the supplier cannot recurse.
private static final PinelabsApiClient AUTH = PinelabsApiClient.builder()
.token("")
.url(BASE_URL)
.build();
private static final ReentrantLock LOCK = new ReentrantLock();
private static volatile String cachedToken;
private static volatile Instant expiresAt = Instant.EPOCH;
private static final Supplier<String> tokenSupplier = () -> {
// Refresh ~30s before expiry
if (cachedToken != null && Instant.now().isBefore(expiresAt.minusSeconds(30))) {
return cachedToken;
}
LOCK.lock();
try {
if (cachedToken != null && Instant.now().isBefore(expiresAt.minusSeconds(30))) {
return cachedToken;
}
var r = AUTH.authentication().generateToken(
GenerateTokenRequest.builder()
.grantType("client_credentials")
.clientId(System.getenv("PINELABS_CLIENT_ID"))
.clientSecret(System.getenv("PINELABS_CLIENT_SECRET"))
.build()
);
cachedToken = r.getAccessToken();
expiresAt = Instant.now().plusSeconds(r.getExpiresIn());
return cachedToken;
} finally {
LOCK.unlock();
}
};
public static PinelabsApiClient client() {
return PinelabsApiClient.builder()
.token(tokenSupplier)
.url(BASE_URL)
.build();
}
}
Always create a dedicated auth client (with token("")) to fetch tokens. Calling generateToken() on a client that itself uses the supplier callable will recurse and hang.
Sub-clients
PinelabsApiClient exposes one sub-client per API tag via accessor methods:
| Sub-client | Purpose |
|---|---|
client.authentication() | OAuth token generation |
client.orders() | Create, capture, cancel, and fetch orders |
client.refunds() | Create and look up refunds |
client.settlements() | Settlement reports + UTR lookup |
client.checkout() | Hosted-checkout related operations |
client.paymentLinks() | Single + bulk payment links |
client.cardPayments() | Direct card payment + OTP flow |
client.bnpl() | Buy-Now-Pay-Later eligibility and flows |
client.convenienceFee() | Convenience-fee config and computation |
client.eChallans() | Government e-challan integration |
client.applePay() | Apple Pay session + decryption |
client.internationalPayments() | Cross-border (DCC / MCC) payments |
client.customers() | Customer profile management |
client.tokenization() | Card / network tokenization |
client.payouts() | Payouts: balance, create, cancel, list |
client.subscriptionsPlans() | Recurring-billing plans |
client.subscriptionsSubscriptions() | Subscription lifecycle |
client.subscriptionsPresentations() | Subscription debit presentations |
client.payByPoints() | Loyalty / points-based payments |
client.affordabilitySuite() | EMI / offer eligibility |
client.splitSettlements() | Split settlements between sub-merchants |
For the full operation list and request/response schemas, see the API reference.
Recipes
Create a payment link
import com.pinelabsonline.api.resources.paymentlinks.requests.CreatePaymentLinkRequest;
import com.pinelabsonline.api.types.Amount;
var link = client.paymentLinks().createPaymentLink(
CreatePaymentLinkRequest.builder()
.merchantPaymentLinkReference("link-001")
.amount(Amount.builder().value(50000).currency("INR").build())
.description("Order #001")
.build()
);
System.out.println(link.getShortUrl());
Fetch an order
import com.pinelabsonline.api.resources.orders.requests.GetOrderByIdRequest;
var order = client.orders().getOrderById(
GetOrderByIdRequest.builder()
.orderId("v1-241010055924-aa-AHbN0s")
.build()
);
System.out.println(order);
Refund an order
import com.pinelabsonline.api.resources.refunds.requests.CreateRefundRequest;
import com.pinelabsonline.api.resources.refunds.types.CreateRefundRequestOrderAmount;
var refund = client.refunds().createRefund(
CreateRefundRequest.builder()
.orderId("v1-241010055924-aa-AHbN0s")
.merchantOrderReference("refund-001")
.orderAmount(CreateRefundRequestOrderAmount.builder()
.value(400)
.currency("INR")
.build())
.build()
);
System.out.println(refund);
Error handling
All HTTP errors derive from PinelabsApiApiException. The SDK also exposes typed subclasses per status code so you can handle specific failures cleanly.
import com.pinelabsonline.api.core.PinelabsApiApiException;
import com.pinelabsonline.api.core.PinelabsApiException;
import com.pinelabsonline.api.errors.NotFoundError;
import com.pinelabsonline.api.errors.UnauthorizedError;
try {
client.orders().getOrderById(
GetOrderByIdRequest.builder().orderId("missing").build()
);
} catch (NotFoundError e) {
System.err.println("order does not exist: " + e.body());
} catch (UnauthorizedError e) {
System.err.println("token expired or invalid");
} catch (PinelabsApiApiException e) {
System.err.println("HTTP " + e.statusCode() + ": " + e.body());
} catch (PinelabsApiException e) {
System.err.println("Error: " + e.getMessage());
}
PinelabsApiApiException exposes statusCode(), body() (parsed when the server returns JSON), and headers().
Per-request options
Every operation accepts a RequestOptions argument for ad-hoc overrides — useful for per-call timeouts, propagating trace IDs, or disabling retries on idempotency-sensitive flows.
import com.pinelabsonline.api.core.RequestOptions;
import java.time.Duration;
var options = RequestOptions.builder()
.timeout(Duration.ofSeconds(10))
.addHeader("x-correlation-id", "req-abc")
.build();
client.orders().createOrder(
CreateOrderRequest.builder()
.merchantOrderReference("order-002")
.orderAmount(Amount.builder().value(1000).currency("INR").build())
.build(),
options
);
Timeouts
The default timeout is 60 seconds. Override at the client or per-request level:
PinelabsApiClient client = PinelabsApiClient.builder()
.token("<access-token>")
.timeout(20) // seconds
.build();
Custom HTTP client
import com.pinelabsonline.api.PinelabsApiClient;
import okhttp3.OkHttpClient;
import java.util.concurrent.TimeUnit;
OkHttpClient http = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
PinelabsApiClient client = PinelabsApiClient.builder()
.token("<access-token>")
.httpClient(http)
.build();
Source & support
- Maven Central: com.pinelabsonline:pinelabs-java
- GitHub: github.com/plural-pinelabs/pinelabs-java
- API reference: /api
