From 0c8582eb51809ae48f24a7659745186e4cb4d6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sabri=20Onur=20T=C3=BCz=C3=BCn?= Date: Tue, 13 Oct 2026 00:50:57 +0300 Subject: [PATCH] Read client version from the build --- .github/scripts/resolve-version.sh | 5 ++ .github/workflows/craftgate-build.yml | 68 ++++++++++++++++-- .github/workflows/craftgate-release.yml | 16 +++-- .gitignore | 4 ++ README.md | 27 +++++-- bin/.DS_Store | Bin 6148 -> 0 bytes build.gradle | 23 ++++++ gradle.properties | 1 - .../io/craftgate/adapter/BaseAdapter.java | 2 +- .../request/common/ClientVersion.java | 35 +++++++++ .../io/craftgate/craftgate-version.properties | 1 + .../adapter/BaseRequestFoundationTest.java | 13 ++++ .../io/craftgate/unit/ClientVersionTest.java | 21 ++++++ 13 files changed, 195 insertions(+), 21 deletions(-) create mode 100755 .github/scripts/resolve-version.sh delete mode 100644 bin/.DS_Store create mode 100644 src/main/java/io/craftgate/request/common/ClientVersion.java create mode 100644 src/main/resources/io/craftgate/craftgate-version.properties create mode 100644 src/test/java/io/craftgate/unit/ClientVersionTest.java diff --git a/.github/scripts/resolve-version.sh b/.github/scripts/resolve-version.sh new file mode 100755 index 00000000..5fdde57c --- /dev/null +++ b/.github/scripts/resolve-version.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Resolves the library version from a release tag: "v1.0.83" and "1.0.83" both become "1.0.83". +set -euo pipefail +tag="${1:?usage: resolve-version.sh }" +echo "VERSION=${tag#v}" >> "$GITHUB_ENV" diff --git a/.github/workflows/craftgate-build.yml b/.github/workflows/craftgate-build.yml index 403d5900..561fbd02 100644 --- a/.github/workflows/craftgate-build.yml +++ b/.github/workflows/craftgate-build.yml @@ -3,23 +3,77 @@ name: Craftgate Java CI on: [ push, pull_request ] jobs: - build: + test: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + java-version: [ '8', '11', '17', '21', '25' ] + name: Build with Java ${{ matrix.java-version }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + # The library is compiled with Java 8 (the last listed version becomes JAVA_HOME); + # the tests run on the matrix version. + - name: Set up JDK ${{ matrix.java-version }} and JDK 8 + uses: actions/setup-java@v6 with: - java-version: 1.8 + distribution: 'zulu' + java-version: | + ${{ matrix.java-version }} + 8 - name: Cache Gradle packages - uses: actions/cache@v3 + uses: actions/cache@v6 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: ${{ runner.os }}-gradle - name: Build with Gradle - run: ./gradlew build test + run: | + TEST_JAVA_HOME=$(printenv "JAVA_HOME_${{ matrix.java-version }}_X64") + "$TEST_JAVA_HOME/bin/java" -version + ./gradlew build test -PtestJavaHome="$TEST_JAVA_HOME" + + release-dry-run: + runs-on: ubuntu-latest + name: Release dry run + + steps: + - uses: actions/checkout@v7 + + - name: Set up JDK 8 + uses: actions/setup-java@v6 + with: + distribution: 'zulu' + java-version: '8' + + # Same version resolution as the release workflow, with a fake "v" prefixed tag. + - name: Resolve version from tag + run: .github/scripts/resolve-version.sh v0.0.0-dryrun + + - name: Build release artifacts without signing or publishing + run: | + test "$VERSION" = "0.0.0-dryrun" + ./gradlew clean test publishToMavenLocal -Pversion="$VERSION" -x signMavenJavaPublication + + - name: Verify the version in the artifacts + run: | + dir=~/.m2/repository/io/craftgate/craftgate/$VERSION + ls "$dir" + grep -q "$VERSION" "$dir/craftgate-$VERSION.pom" + unzip -p "$dir/craftgate-$VERSION.jar" io/craftgate/craftgate-version.properties | grep -qx "version=$VERSION" + + # Single "build" status for branch protection: passes only if every job above passed. + build: + if: always() + needs: [ test, release-dry-run ] + runs-on: ubuntu-latest + + steps: + - name: Check results + run: | + test "${{ needs.test.result }}" = "success" + test "${{ needs.release-dry-run.result }}" = "success" diff --git a/.github/workflows/craftgate-release.yml b/.github/workflows/craftgate-release.yml index f1c03bf6..7f398795 100644 --- a/.github/workflows/craftgate-release.yml +++ b/.github/workflows/craftgate-release.yml @@ -10,24 +10,30 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + uses: actions/setup-java@v6 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '8' - name: Cache Gradle packages - uses: actions/cache@v3 + uses: actions/cache@v6 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: ${{ runner.os }}-gradle + - name: Resolve version from tag + env: + TAG_NAME: ${{ github.event.release.tag_name }} + run: .github/scripts/resolve-version.sh "$TAG_NAME" + - name: Build and Publish with Gradle run: openssl enc -aes-256-cbc -md sha256 -pass pass:${{secrets.ENC_KEY}} -d -A -in craftgate.gpg.enc -out craftgate.gpg && ./gradlew clean && - ./gradlew publish -Dorg.gradle.internal.http.socketTimeout=120000 -Dorg.gradle.internal.http.connectionTimeout=120000 -Pversion=${{github.event.release.tag_name}} -PrepoUser=${{secrets.CP_TOKEN_USERNAME}} -PrepoPassword=${{secrets.CP_TOKEN_PASSWORD}} -Psigning.keyId=${{secrets.SIGNING_KEY_ID}} -Psigning.password=${{secrets.SIGNING_PASSWORD}} + ./gradlew publish -Dorg.gradle.internal.http.socketTimeout=120000 -Dorg.gradle.internal.http.connectionTimeout=120000 -Pversion=${VERSION} -PrepoUser=${{secrets.CP_TOKEN_USERNAME}} -PrepoPassword=${{secrets.CP_TOKEN_PASSWORD}} -Psigning.keyId=${{secrets.SIGNING_KEY_ID}} -Psigning.password=${{secrets.SIGNING_PASSWORD}} - name: Trigger deployment visibility run: | diff --git a/.gitignore b/.gitignore index 4417d5e1..9abf83bc 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ build .idea out/ + +# OS and IDE files +.DS_Store +bin/ diff --git a/README.md b/README.md index 63910405..440911ed 100644 --- a/README.md +++ b/README.md @@ -12,21 +12,23 @@ This repo contains the Java client for Craftgate API. - Java 1.8 or newer ## Installation +Replace `VERSION` with the latest version shown on the Maven Central badge above. + Apache Maven: -```bash +```xml io.craftgate craftgate - 1.0.82 + VERSION ``` Gradle Groovy DSL ``` -implementation 'io.craftgate:craftgate:1.0.80' +implementation 'io.craftgate:craftgate:VERSION' ``` Gradle Kotlin DSL ``` -implementation("io.craftgate:craftgate:1.0.80") +implementation("io.craftgate:craftgate:VERSION") ``` ## Usage @@ -41,7 +43,7 @@ Craftgate craftgate = new Craftgate("", ""); ``` -By default the Craftgate client connects to the production API servers at `https://api.craftgate.io`. For testing purposes, please use the sandbox URL `https://sandbox-api.craftgate.io` using the . +By default the Craftgate client connects to the production API servers at `https://api.craftgate.io`. For testing purposes, please use the sandbox URL `https://sandbox-api.craftgate.io`. ```java @@ -51,10 +53,21 @@ Craftgate craftgate = new Craftgate("", "", "http ``` ## Examples -Included in the project are a number of examples that cover almost all use-cases. Refer to [the `sample/` folder](./src/test/java/io/craftgate/sample)] for more info. +Included in the project are a number of examples that cover almost all use-cases. Refer to [the `sample/` folder](./src/test/java/io/craftgate/sample) for more info. ### Running the Examples -If you've cloned this repo on your development machine and wish to run the examples you can run an example with the command `./gradlew test` or run single test with the command `./gradlew test --tests PaymentSample.create_payment_sample` +The examples call the sandbox API, so first replace `api-key` and `secret-key` in the sample class you want to run with your sandbox credentials. + +Run all examples: +``` +./gradlew sampleTest +``` +Run a single example: +``` +./gradlew sampleTest --tests io.craftgate.sample.PaymentSample.create_payment +``` + +`./gradlew test` runs only the unit tests and does not call the API. ### Credit Card Payment Use Case Let's quickly review an example where we implement a credit card payment scenario. diff --git a/bin/.DS_Store b/bin/.DS_Store deleted file mode 100644 index 173cb4fc45d8f3c105828ade1a1140f723c3b1c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKy-ve05I#c$6%ZnLVqYoO;hz}4-dd|@d&Iu0%B(1 zJKJp?5=6s*Ds(5=UvlU3-F&7$ePtuflALYr9Msi zy?V19XGV)c7!U?ljRF4cZc>N3bVs-3{C=aMlj@fdQ5=S?e%!){((6lk=j`$MJUdSm zca|76yyx8_6>X1t$Y+2yLQWH4hfZlsG4dNx8}DPY#}xH)^S-`(E8D!6i09T)%rmFH z{oMMv&M~U}xza-ZZq2oi_gT#a=U9Bn-_DW4gGUj*?*k1|U(-82#9Er$-26|D4rXi3 zu`hDVv!~*2@HvOME!U~Is@ipJZrrEvYS*rnxv#q-^6aU&>XqZJn&8Z4DQz2+UKkJt zgn>B&yg&G8jDf|(px!#r=_>%xgIWuo^&SiKSpf_zCI+E_DB}t=uF769lyQgNx8nkf zi9zE|N=L>#HnOre6s4m>@9W{D0)x^E1HwR>fpzm*;q(9E`}=>|N#2A3Vc=gepuDgd zHc*m1TQkM+S?i createHttpHeaders(BaseRequest request, String headers.put(API_KEY_HEADER_NAME, requestOptions.getApiKey()); headers.put(RANDOM_HEADER_NAME, randomString); headers.put(AUTH_VERSION_HEADER_NAME, API_VERSION_HEADER_VALUE); - headers.put(CLIENT_VERSION_HEADER_NAME, CLIENT_VERSION_HEADER_VALUE + ":1.0.82"); + headers.put(CLIENT_VERSION_HEADER_NAME, CLIENT_VERSION_HEADER_VALUE + ":" + ClientVersion.get()); headers.put(SIGNATURE_HEADER_NAME, prepareAuthorizationString(request, path, randomString, requestOptions)); if (Objects.nonNull(requestOptions.getLanguage())) { headers.put(LANGUAGE_HEADER_NAME, requestOptions.getLanguage()); diff --git a/src/main/java/io/craftgate/request/common/ClientVersion.java b/src/main/java/io/craftgate/request/common/ClientVersion.java new file mode 100644 index 00000000..c8c20bdc --- /dev/null +++ b/src/main/java/io/craftgate/request/common/ClientVersion.java @@ -0,0 +1,35 @@ +package io.craftgate.request.common; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public final class ClientVersion { + + private static final String UNKNOWN = "unknown"; + private static final String VERSION = readVersion(); + + private ClientVersion() { + } + + public static String get() { + return VERSION; + } + + private static String readVersion() { + try (InputStream inputStream = ClientVersion.class.getResourceAsStream("/io/craftgate/craftgate-version.properties")) { + if (inputStream == null) { + return UNKNOWN; + } + Properties properties = new Properties(); + properties.load(inputStream); + String version = properties.getProperty("version"); + if (version == null || version.isEmpty() || version.startsWith("${") || "unspecified".equals(version)) { + return UNKNOWN; + } + return version; + } catch (IOException e) { + return UNKNOWN; + } + } +} diff --git a/src/main/resources/io/craftgate/craftgate-version.properties b/src/main/resources/io/craftgate/craftgate-version.properties new file mode 100644 index 00000000..a50bf5c8 --- /dev/null +++ b/src/main/resources/io/craftgate/craftgate-version.properties @@ -0,0 +1 @@ +version=${version} diff --git a/src/test/java/io/craftgate/adapter/BaseRequestFoundationTest.java b/src/test/java/io/craftgate/adapter/BaseRequestFoundationTest.java index f91157b2..6a5dbf34 100644 --- a/src/test/java/io/craftgate/adapter/BaseRequestFoundationTest.java +++ b/src/test/java/io/craftgate/adapter/BaseRequestFoundationTest.java @@ -25,6 +25,7 @@ public class BaseRequestFoundationTest { private static final String IDEMPOTENCY_KEY_HEADER_NAME = "x-idempotency-key"; private static final String RANDOM_HEADER_NAME = "x-rnd-key"; private static final String SIGNATURE_HEADER_NAME = "x-signature"; + private static final String CLIENT_VERSION_HEADER_NAME = "x-client-version"; private final RequestOptions requestOptions = RequestOptions.builder() .apiKey("api-key") @@ -107,6 +108,18 @@ void header_options_do_not_leak_into_query_params_of_read_requests() { assertFalse(query.contains("idempotency-key-1")); } + @Test + void request_sends_client_version_from_build() { + CreatePaymentTokenRequest request = CreatePaymentTokenRequest.builder() + .value("card-value") + .build(); + + Map headers = bodyAdapter.createHeaders(request, "/payment/v1/payment-tokens", requestOptions); + + String expectedVersion = System.getProperty("craftgate.expectedVersion", "unknown"); + assertEquals("craftgate-java-client:" + expectedVersion, headers.get(CLIENT_VERSION_HEADER_NAME)); + } + @Test void body_request_sends_idempotency_key_header() { CreatePaymentTokenRequest request = CreatePaymentTokenRequest.builder() diff --git a/src/test/java/io/craftgate/unit/ClientVersionTest.java b/src/test/java/io/craftgate/unit/ClientVersionTest.java new file mode 100644 index 00000000..98bcd3dd --- /dev/null +++ b/src/test/java/io/craftgate/unit/ClientVersionTest.java @@ -0,0 +1,21 @@ +package io.craftgate.unit; + +import io.craftgate.request.common.ClientVersion; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ClientVersionTest { + + @Test + void should_read_client_version_from_build() { + //given + String expectedVersion = System.getProperty("craftgate.expectedVersion", "unknown"); + + //when + String version = ClientVersion.get(); + + //then + assertEquals(expectedVersion, version); + } +}