Skip to content

deepanshu-iiitu/killbill-prism-plugin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

killbill-prism-plugin

A Kill Bill payment plugin that fans one integration out to many payment processors via the Hyperswitch Prism unified-connector library. Wire your Kill Bill instance to Prism once; route individual payments (or whole tenants) to Stripe, Adyen, or any other Prism-supported connector by setting a single plugin property — no extra Kill Bill plugin per processor, no per-gateway integration drift.

Supported connectors and flows (v1)

Connector Authorize Capture Purchase Void Refund Webhook
Stripe ✓ (Stripe-Signature)
Adyen ✓ (HMAC)

All ticks reflect plugin code that ships in v1. Actual gateway traffic also requires the Hyperswitch Prism SDK on the bundle classpath — see Installation and DESIGN.md §SDK-binding.

Deliberately out of scope for v1: Hosted Payment Page (buildFormDescriptor), 3-D Secure / SCA flows, in-plugin tokenisation, creditPayment, searchPayments, additional connectors. See DESIGN.md §v1 scope.

Requirements

Version
Kill Bill 0.24.x (tested against 0.24.18)
Java runtime 11+ (Kill Bill 0.24's official Docker image ships OpenJDK 11)
Database MariaDB 10.6+ or MySQL 8.0+
Hyperswitch Prism SDK locally installable from the hyperswitch-prism repo (make pack + mvn install:install-file); not yet on Maven Central

The plugin needs a database. The DDL lives at src/main/resources/ddl.sql and creates three tables (prism_payment_methods, prism_responses, prism_webhooks).

Build

JAVA_HOME=$(/usr/libexec/java_home -v 17) \
mvn clean install -Drat.numUnapprovedLicenses=100

JAVA_HOME=17 is for the build; the resulting JAR is compiled to Java 11 byte­code to match the Kill Bill runtime. -Drat.numUnapprovedLicenses=100 tolerates the in-house copyright header style — adjust if you align headers with the Apache RAT defaults.

Installation

Locally, using kpm:

kpm install_java_plugin prism \
    --from-source-file target/prism-plugin-0.0.1-SNAPSHOT.jar \
    --destination /var/tmp/bundles

Or volume-mount the JAR into the Kill Bill bundle dir at /var/lib/killbill/bundles/plugins/java/prism-plugin/0.0.1/prism-plugin-0.0.1-SNAPSHOT.jar (this is what ../infra/docker-compose.yml does for local development).

Restart Kill Bill and confirm the plugin is loaded:

curl -sS -u admin:password \
     -H 'X-Killbill-ApiKey: bob' -H 'X-Killbill-ApiSecret: lazar' \
     http://localhost:8080/1.0/kb/nodesInfo \
  | jq '.[].pluginsInfo[] | select(.pluginName=="prism-plugin")'

The plugin's state should be RUNNING and services should list org.killbill.billing.payment.plugin.api.PaymentPluginApi. If services is empty, the activator skipped registration because the Prism SDK isn't on the bundle classpath; check Kill Bill's log for the unblock instructions printed by PrismActivator.

Configuration

Credentials are per-tenant, uploaded as YAML to Kill Bill's plugin-config endpoint:

curl -sS -X POST \
     -u admin:password \
     -H 'X-Killbill-ApiKey: bob' \
     -H 'X-Killbill-ApiSecret: lazar' \
     -H 'X-Killbill-CreatedBy: setup' \
     -H 'Content-Type: text/plain' \
     --data-binary @prism-config.yaml \
     http://localhost:8080/1.0/kb/tenants/uploadPluginConfig/prism-plugin

prism-config.yaml:

# Connector used when neither the plugin-property override nor the
# stored payment method specifies one. Optional but recommended.
defaultConnector: stripe

connectors:
  stripe:
    apiKey: sk_test_...                # required
    webhookSecret: whsec_...           # required to verify Stripe webhooks

  adyen:
    apiKey: AQEqhmfxKx...              # required
    merchantAccount: MyMerchant_TEST   # required
    hmacKey: 11223344AABBCCDD...       # required to verify Adyen webhooks (hex)

Add a connectors: entry for every processor you want this tenant to use. The plugin instantiates a Prism PaymentClient lazily on first use and caches it per (tenantId, connector) pair.

Per-tenant config changes propagate live via Kill Bill's TENANT_CONFIG_CHANGE event — no Kill Bill restart needed when you upload a new YAML.

Adding a payment method

Payment methods are tokenized out-of-band (Stripe.js, Adyen drop-in, or equivalent). The plugin stores the connector slug + the resulting token, then routes subsequent calls through it. Raw card data in plugin properties is rejected.

curl -sS -X POST \
     -u admin:password \
     -H 'X-Killbill-ApiKey: bob' \
     -H 'X-Killbill-ApiSecret: lazar' \
     -H 'X-Killbill-CreatedBy: demo' \
     -H 'Content-Type: application/json' \
     -d '{
           "pluginName": "prism-plugin",
           "pluginInfo": {
             "properties": [
               {"key": "prismConnector",      "value": "stripe"},
               {"key": "prismConnectorToken", "value": "pm_card_visa"}
             ]
           }
         }' \
     "http://localhost:8080/1.0/kb/accounts/${ACCOUNT_ID}/paymentMethods?isDefault=true"

Recognised plugin properties on addPaymentMethod:

Property Required Notes
prismConnector yes (or defaultConnector in tenant config) stripe, adyen, …
prismConnectorToken yes for live processing The connector's PM identifier (pm_…, tok_…, Adyen recurringDetailReference, …)
prismConnectorCustomerId no Stripe cus_…, Adyen shopperReference, etc. — stored for connectors that key on the customer
anything else no Persisted as JSON in prism_payment_methods.additional_data

ccNumber, ccVerificationValue, and ccTrackData cause the call to be rejected with PaymentPluginApiException. The plugin is not a card vault.

Running a payment

Once a payment method is on file, payments go through standard Kill Bill endpoints — Kill Bill routes through the registered PaymentPluginApi:

curl -sS -X POST \
     -u admin:password \
     -H 'X-Killbill-ApiKey: bob' \
     -H 'X-Killbill-ApiSecret: lazar' \
     -H 'X-Killbill-CreatedBy: demo' \
     -H 'Content-Type: application/json' \
     -d '{
           "transactionType":         "PURCHASE",
           "amount":                  "10.00",
           "currency":                "USD",
           "paymentExternalKey":      "order-1234",
           "transactionExternalKey":  "txn-1234-1"
         }' \
     "http://localhost:8080/1.0/kb/accounts/${ACCOUNT_ID}/payments"

The transaction returns status: SUCCESS for PROCESSED, PAYMENT_FAILURE for gateway declines, PENDING for async-completion flows (resolved later via webhook), and PLUGIN_FAILURE / UNKNOWN for plugin-side issues. The mapping from Prism's response statuses to these Kill Bill states is the table in DESIGN.md §5.

For an authorize+capture or purchase+refund flow, use transactionType=AUTHORIZE/CAPTURE/REFUND against the same payment.

Webhook setup

The plugin exposes one webhook endpoint per connector, per tenant:

POST /plugins/prism-plugin/webhook/{connector}/{tenantId}

The tenant UUID is in the URL because inbound webhooks have no Kill Bill auth, and the plugin needs to know which tenant's HMAC secret to verify against.

Stripe

  1. In the Stripe Dashboard → Developers → Webhooks → Add endpoint.
  2. URL: https://<your-killbill-host>/plugins/prism-plugin/webhook/stripe/<tenantId>. For local testing, expose Kill Bill via ngrok (ngrok http 8080) and use the ngrok URL.
  3. Subscribe to (at minimum): payment_intent.succeeded, payment_intent.payment_failed, payment_intent.canceled, charge.succeeded, charge.captured, charge.failed, charge.refunded, charge.refund.failed.
  4. Copy the signing secret (whsec_…) and store it under connectors.stripe.webhookSecret in the tenant YAML.

Adyen

  1. In the Customer Area → Developers → Webhooks → Standard notification.
  2. URL: https://<your-killbill-host>/plugins/prism-plugin/webhook/adyen/<tenantId>.
  3. Under Security → HMAC key, generate a key and copy the hex value.
  4. Set connectors.adyen.hmacKey in the tenant YAML to that hex value.

The plugin verifies signatures locally, dedupes redelivered events on the (connector, event_id) unique index in prism_webhooks, then calls PaymentApi.notifyPendingTransactionOfStateChanged to advance any matching Kill Bill transaction from PENDING.

Setup & test (local, end-to-end)

The full local path, in the order it actually works. All paths are relative to this plugin directory unless noted. You need: Docker, JDK 17 (for the build only — output is JDK 11 bytecode), a Stripe test key (sk_test_…), and — for the Prism SDK native library — Docker or a Rust toolchain.

1. Build and publish the Prism SDK to your local Maven repo

The plugin depends on io.hyperswitch:prism:0.0.4, which is not on Maven Central. Build it from the bundled reference checkout, targeting JDK 11 (the Kill Bill 0.24 runtime — the SDK's Gradle build is already pinned to jvmTarget = 11):

cd ../reference/hyperswitch-prism/sdk/java
VERSION=0.0.4 ./gradlew clean jar publishToMavenLocal

The SDK loads a native FFI library via JNA, and ships only a macOS .dylib. The Kill Bill container is Linux, so you must also build the matching .so and drop it where JNA looks (src/main/resources/<os-arch>/). For an Apple-Silicon host (Linux arm64 container):

cd ../reference/hyperswitch-prism            # repo root (has the Cargo workspace)
docker run --rm --platform=linux/arm64 \
  -e CARGO_PROFILE_RELEASE_OPT_LEVEL=1 -e CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16 \
  -v "$PWD:/src" -w /src public.ecr.aws/docker/library/rust:slim-bookworm \
  bash -c "apt-get update -qq && apt-get install -y -qq pkg-config libssl-dev g++ make perl protobuf-compiler && \
           cargo build -p ffi --no-default-features --features ffi/uniffi --release --target aarch64-unknown-linux-gnu -j 2"

mkdir -p sdk/java/src/main/resources/linux-aarch64
cp target/aarch64-unknown-linux-gnu/release/libconnector_service_ffi.so \
   sdk/java/src/main/resources/linux-aarch64/

# republish so the .so is packaged inside the SDK jar
cd sdk/java && VERSION=0.0.4 ./gradlew clean jar publishToMavenLocal

For an x86_64 Linux container use --platform=linux/amd64, target x86_64-unknown-linux-gnu, and the JNA folder linux-x86-64/. (The reduced-optimization env vars avoid an OOM on the final crate; drop them if your Docker VM has plenty of RAM.)

2. Build the plugin

cd ../../killbill-prism-plugin
JAVA_HOME=$(/usr/libexec/java_home -v 17) mvn clean install -Drat.numUnapprovedLicenses=100

3. Bring up the stack and load the schema

A docker-compose stack — MariaDB + Kill Bill 0.24.18 + Kaui — lives at ../infra/. The plugin JAR is volume-mounted into the KB bundle dir, so after any rebuild a docker compose restart killbill reloads it.

cd ../infra && docker compose up -d
# once KB answers, load the plugin DDL into the killbill DB:
docker exec -i kb-mariadb mariadb -uroot -pkillbill killbill \
  < ../killbill-prism-plugin/src/main/resources/ddl.sql

The DDL is idempotent (create table if not exists, indexes inline) — safe to re-run.

4. Confirm the plugin registered

curl -sS -u admin:password -H 'X-Killbill-ApiKey: bob' -H 'X-Killbill-ApiSecret: lazar' \
  http://localhost:8080/1.0/kb/nodesInfo \
  | jq '.[].pluginsInfo[] | select(.pluginName=="prism-plugin") | {state, services:[.services[].serviceTypeName]}'

Expect state: "RUNNING" and PaymentPluginApi in services. Empty services means the SDK isn't on the bundle classpath — revisit step 1.

5. Run the tests

export STRIPE_TEST_API_KEY=sk_test_...   # Stripe test-mode key (rotate after use)

# quick smoke: tenant → config → account → payment method → $10 purchase
../scripts/e2e-test.sh

# full matrix: auth→capture, auth→void, purchase→refund, getPaymentInfo,
# payment-method add/get/list/delete, idempotency guard, credit (unsupported)
../scripts/flows-test.sh

Unit tests (no network, in-memory H2): JAVA_HOME=$(/usr/libexec/java_home -v 17) mvn test runs the suite covering status mapping, DAO roundtrips, and the webhook servlet with mocked Stripe/Adyen HMAC payloads. Real-Stripe integration tests: mvn -Pintegration test (SDK installed, STRIPE_TEST_API_KEY exported).

Troubleshooting

These are the failures you'll most likely hit standing this up, with the fix for each:

Symptom (KB log or payment error) Cause Fix
services: [] / "SDK class … not on the bundle classpath" Prism SDK not bundled Build + publish the SDK (step 1), rebuild the plugin
UnsupportedClassVersionError … class file version 61.0 SDK compiled to JDK 17; KB runs JDK 11 Build the SDK with the JDK 11 target (already pinned in its Gradle build)
UnsatisfiedLinkError: …libconnector_service_ffi.so … (linux-aarch64/…) native lib missing for the container arch Cross-build the .so into src/main/resources/<os-arch>/ (step 1)
ProtobufRuntimeVersionException: gencode 4.34.x, runtime 4.33.x SDK's protobuf-java older than its generated stubs Bump the SDK's protobuf-java to match protoc
NoSuchMethodError ParserImpl.<init>(StreamReader) snakeyaml 2.x vs Jackson 2.13 Pin snakeyaml 1.33 (done in this plugin's pom)
NoClassDefFoundError org/skife/jdbi/... JDBI not exported to the bundle killbill-jdbi at compile scope so it inlines (done)
Payment error "Address is required" connector requires a billing address handled by the plugin (sends an empty billing address)
"No such PaymentMethod: 'tok_visa'" legacy Token vs the PaymentIntents API use a PaymentMethod token, e.g. pm_card_visa

Teardown

cd ../infra && docker compose down -v   # -v also wipes the MariaDB volume

Local development

The same docker-compose stack at ../infra/ is the local dev loop: rebuild the plugin, docker compose restart killbill, re-run the scripts above. See Setup & test for the full procedure.

Architecture

See DESIGN.md: ASCII layering diagram, connector resolution rules, the Prism-status → Kill Bill PaymentPluginStatus mapping, idempotency design, webhook handling, the v1 scope cut, and the SDK-binding boundary that keeps reflective code contained to PrismClientFactory.

Contributing

Issues and PRs welcome. For substantial changes, please open an issue first to discuss the design — particularly anything that touches the Prism SDK boundary, the status mapping table, or the webhook signature verification path.

When adding tests for new code, prefer the existing patterns:

  • Status mapping: @DataProvider parameterized tests
  • DAO: in-memory H2 in MySQL compat mode, fresh database per test
  • Webhook: Mockito mocks for OSGIKillbillAPI / PrismDao, synthetic HMAC payloads with known secrets

License

Apache License 2.0 — see LICENSE.

About

A Kill Bill payment plugin that fans one integration out to many payment processors via the Hyperswitch Prism unified-connector library

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages