mvn -B verify # compiles both modules and runs the testsThe build multi-targets a paylink-core module and a paylink-spring-boot-starter
module, both on Java 17.
The SDK's core job is reproducing a byte-exact, order-sensitive HMAC that the server
rebuilds independently. The server signs
base64(hmac_sha256(implode('', Arr::except($request->validated(), [...])))), and
Laravel's validated() returns fields in rules() order, skipping absent ones.
So the order of fields in FieldOrders must match the order of keys in the endpoint's
FormRequest rules(), minus token/signature (and payment_mode, added with
FieldSpec.unsigned(...)).
When you touch an endpoint:
- Open the matching FormRequest under
app/Http/Requests/Application/ExternalPaymentIntegration/in the PayLink repo and readrules()top to bottom. - Mirror that order in the
EndpointSpec.SignedBody.buildsigns the coerced values in that order — the compiler cannot verify ordering for you. - Add a golden vector.
paylink-core/src/test/resources/golden-signatures.json is shared with the JS, Python, and
.NET SDKs and generated by PHP using the same primitive the server uses. GoldenSignatureTest
asserts both that Signature.build(values) produces expected and that SignedBody.build
derives the same signature from structured input.
Adding a field to an endpoint without adding a golden case leaves that field's position untested.
Requests sign by opt-in: FieldOrders lists exactly what gets signed. Webhooks are the
opposite. PaymentIntegrationWebhookJob copies the whole payload and unset()s a fixed
exclusion list before hashing, so a field is signed if added before that unset() and
unsigned if added after (as auth_code is). The OPTIONAL_SIGNED list in DefaultWebhooks
must mirror that decision.
The SDK is DI-first: PaylinkClient is an interface you depend on and inject. The
paylink-spring-boot-starter provides the container wiring. Prefer constructor injection;
no static singletons or new'd dependencies inside methods.
- Javadoc (
/** ... */) above members only. No comments inside method bodies. - Format numbers with locale-independent primitives (
Long.toString,BigDecimal.toPlainString); a machine's locale must never shift a signature.
- Bump the
<version>in the parent POM and modules, andVersion.VALUEinpaylink-core/.../internal/Version.java(the User-Agent would otherwise report a stale version). Keep them in sync. - Update
CHANGELOG.md. - Tag
v<version>and publish to Maven Central.