Skip to content

Add a Kotlin + Ktor example - #14

Merged
evsinev merged 4 commits into
mainfrom
feat/kotlin-ktor-js
Sep 11, 2026
Merged

evsinev merged 4 commits into
mainfrom
feat/kotlin-ktor-js

Conversation

@evsinev

@evsinev evsinev commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

A tenth example: the same Hosted Fields payment on Kotlin + Ktor, built with Gradle.

The server half is 1:1 with go-js — the same routes under BASE_PATH, the same OAuth 1.0a
RSA-SHA256 signing, the same doubly-checked 3DS return, the same log redaction. The browser half
is shared/, copied in by scripts/sync-shared.sh like every other plain-JS example.

Port 3009, prefix /hosted-fields-examples-kotlin, e2e port 4021.

What it demonstrates that the other JVM example does not

Two dependencies, and one of them is the subject: Ktor, and kotlinx.serialization because the JVM
has no JSON of its own. Everything the integration itself needs is in the JDK — java.net.http
calls the gateway, java.security signs. Four things Ktor makes easy to get wrong, each handled
and commented where it is enforced:

  • Ktor has no context path. The page is registered at the trailing-slash form and the bare
    prefix answers a 301 from a route of its own — the redirect Go's mux and Tomcat send for free,
    and what the pages' relative asset URLs resolve against. IgnoreTrailingSlash is deliberately
    not installed; AutoHeadResponse is.
  • staticResources is never installed. public/ goes out through a four-name allowlist, the
    counterpart of spring.web.resources.add-mappings=false and Sinatra's static.
  • Every gateway call runs inside withContext(Dispatchers.IO). java.net.http's send is
    blocking and a Ktor handler runs on a Netty event-loop thread.
  • The signature encoder is hand-written RFC 3986, never URLEncoder.encode or
    encodeURLParameter. Paynet.formEncode is URLEncoder, because the body really is form
    encoded, and the 3DS callback is parsed off the body by hand.

One thing worth a second look

gradle/wrapper/gradle-wrapper.jar is the first binary in this repository. Gradle has no
script-only wrapper the way Maven's distributionType=only-script is, and the alternative was
asking every reader to install Gradle first. It is mitigated rather than waved through: the
distribution is pinned by SHA-256 in gradle-wrapper.properties, and CI runs
gradle/actions/wrapper-validation over the jar. Both READMEs say so.

Checks run locally

  • ./scripts/sync-shared.sh && git diff --exit-code — clean
  • ./gradlew ktlintCheck build buildFatJar from cold — 11 tests, on the same cross-language
    vectors as the other examples
  • npm run test:kotlin — 10 e2e specs, including the full 3DS round trip
  • npm run test:docker:kotlin — the same 10 against the container, which is what exercises the
    shipped deploy/nginx.conf
  • npm run test:docker:go — to confirm the compose stack is still intact with a tenth service in it

@evsinev
evsinev merged commit b5e3823 into main Sep 11, 2026
22 checks passed
@evsinev
evsinev deleted the feat/kotlin-ktor-js branch September 11, 2026 19:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant