Kotlin / JVM SDK
Kotlin JVM 17+ Android
Coroutines-native SDK built on Ktor HTTP client. Works in Android apps, Spring Boot backends, and any JVM project.
Install
kotlin
// build.gradle.kts
dependencies {
implementation("ly.neptune.astro:astro-kotlin:1.0.0")
// Or from local path during development:
// implementation(project(":packages:astro-kotlin"))
}For the git dependency before publishing:
kotlin
// settings.gradle.kts
sourceControl {
gitRepository(URI("https://github.com/neptune-astro/neptune-astro.git")) {
producesModule("ly.neptune.astro:astro-kotlin")
}
}Quick Start
kotlin
import ly.neptune.astro.*
import ly.neptune.astro.models.*
import kotlinx.coroutines.runBlocking
val astro = astroClient {
baseUrl = "https://astro.neptune.ly/api/v1"
merchantKey = System.getenv("ASTRO_MERCHANT_KEY")
}
runBlocking {
val session = astro.payments.createSession(
CreateSessionRequest(
amount = 50_000L, // 50.000 LYD
currency = "LYD",
destination = Destination(type = "alias", value = "mtellesy"),
reference = "order_1042",
redirectUrl = "https://myapp.com/checkout/result"
)
)
println("Checkout: ${session.checkoutUrl}")
// Resolve alias
val resolved = astro.identity.resolve("mtellesy")
println("Bank: ${resolved.bankHandle}")
}
astro.close() // closes Ktor HttpClientSpring Boot Integration
kotlin
@Configuration
class AstroConfig {
@Bean
fun astroClient(): AstroClient = astroClient {
baseUrl = System.getenv("ASTRO_BASE_URL")
merchantKey = System.getenv("ASTRO_MERCHANT_KEY")
}
}
@RestController
@RequestMapping("/api/checkout")
class CheckoutController(private val astro: AstroClient) {
@PostMapping
suspend fun createSession(@RequestBody req: OrderRequest): ResponseEntity<Map<String, String>> {
val session = astro.payments.createSession(
CreateSessionRequest(
amount = req.amount,
currency = "LYD",
destination = Destination("alias", req.customerAlias),
reference = req.orderId
)
)
return ResponseEntity.ok(mapOf("checkout_url" to session.checkoutUrl, "session_id" to session.sessionId))
}
}Payments
kotlin
// Get session
val session = astro.payments.getSession("ops_01HZGV...")
println(session.status) // PENDING | PROCESSING | COMPLETED | FAILED
// List sessions
val result = astro.payments.listSessions(status = "COMPLETED")
// Cancel
astro.payments.cancelSession(session.sessionId)
// Mandate
val mandate = astro.payments.createMandate(
CreateMandateRequest(
customerAlias = "mtellesy",
amount = 5_000L,
currency = "LYD",
interval = "monthly",
description = "Monthly subscription",
startDate = "2026-05-01"
)
)Open Banking
kotlin
// Create consent
val consent = astro.openBanking.createConsent(
CreateConsentRequest(
bankHandle = "andalus",
scopes = listOf("accounts:read", "transactions:read"),
redirectUri = "https://myapp.com/ob/callback",
state = UUID.randomUUID().toString(),
codeChallenge = pkce.challenge
)
)
// Redirect user to consent.consentUrl
// Exchange code
val tokens = astro.openBanking.exchangeCode(
code = callbackParams["code"]!!,
redirectUri = "https://myapp.com/ob/callback",
consentId = consent.consentId,
codeVerifier = pkce.verifier
)
// Fetch data
val accounts = astro.openBanking.getAccounts(tokens.accessToken, consent.consentId)
val txns = astro.openBanking.getTransactions(tokens.accessToken, consent.consentId, accounts[0].accountId)Webhook Handling
kotlin
import ly.neptune.astro.webhooks.WebhookReceiver
import kotlinx.serialization.json.*
val receiver = WebhookReceiver(secret = System.getenv("ASTRO_WEBHOOK_SECRET"))
receiver
.on("payment.completed") { payload ->
val obj = payload as? JsonObject ?: return@on
val ref = obj["data"]?.jsonObject?.get("reference")?.jsonPrimitive?.content
db.markPaid(ref)
}
.on("payment.failed") { payload ->
// handle failure
}
// Ktor route
post("/webhooks/astro") {
val body = call.receiveText()
val sig = call.request.headers["X-OpenWave-Signature"]
?: return@post call.respond(HttpStatusCode.BadRequest)
try {
receiver.handle(body, sig)
call.respond(HttpStatusCode.OK)
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.Unauthorized)
}
}Error Handling
kotlin
try {
val session = astro.payments.createSession(...)
} catch (e: AstroRequestException) {
when (e.code) {
"INSUFFICIENT_FUNDS" -> handleInsufficientFunds()
"ALIAS_NOT_FOUND" -> handleAliasNotFound()
else -> logger.error("Astro error [${e.code}] ${e.message} (req: ${e.requestId})")
}
}Source
packages/astro-kotlin/ in the neptune-astro repo.