killbill / killbill-client-java

Java client library for Kill Bill
https://killbill.io
Apache License 2.0
33 stars 63 forks source link

Java client throws IOException for 201 responses due to missing body #136

Open steven-barnes opened 4 days ago

steven-barnes commented 4 days ago
        Account body = new Account();
        body.setName("test3");
        body.setCurrency(Currency.USD);
        body.setExternalKey("89f20977-f937-4bd7-82f9-ac4ef07b9999");
        body.setEmail("test3@comcast.com");
        accountApi.createAccount(body, requestOptions);

This results in an IOException, as there is no response body. Running the code again results in a 409 exception, Account already exists for key 89f20977-f937-4bd7-82f9-ac4ef07b9999, indicating the account was successful created.

When creating similar accounts in the swagger page, I see the response code is 201, but the server does not return a body. The swagger page suggests that an Account object will be returned. This conflicts with the information in the docs, which says that the endpoint will return a URL in the location header.

curl -X POST "https://killbill.dev.commerce.comcast.com/1.0/kb/accounts" -H "accept: application/json" -H "X-Killbill-CreatedBy: testing" -H "authorization: Basic REDACTED" -H "X-Killbill-ApiKey: REDACTED" -H "X-Killbill-ApiSecret: REDACTED" -H "Content-Type: application/json" -d "{ \"name\": \"test99\", \"externalKey\": \"89f20977-f937-4bd7-82f9-ac4ef0999999\", \"email\": \"test99@comcast.com\", \"currency\": \"USD\"}"
pierre commented 3 days ago

Indeed, Kill Bill doesn't return the body upon creation (https://docs.killbill.io/latest/quick_start_with_kb_api#_step_3_create_an_account), but you can ask the client to fetch it automatically using requestOptions.withFollowLocation(true): https://github.com/killbill/killbill-client-java/blob/910bcdc58bbf6be08f4b18c5e3bb826837bdcef9/src/main/java/org/killbill/billing/client/KillBillHttpClient.java#L336

IIRC we could never really tell Swagger about this behaviour.

steven-barnes commented 1 day ago

I have tried that, and still get an IOException:

RequestOptions.builder
      .withCreatedBy("CS2")
      .withFollowLocation(Boolean.TRUE)
      .build()

Looking at the code for createAccount, it seems to be setting followLocation to TRUE by default.

pierre commented 1 day ago

Are you following https://docs.killbill.io/latest/java_client ?

If so, could you share a Main class that reproduces the issue on a vanilla installation? The Java client is used pervasively throughout our test suite, and I don't know of any regression.

steven-barnes commented 1 day ago

I am using the latest version in Maven Central, 1.3.6. I have coded a test in Java, and still get an IOException:

package org.example;

import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.client.*;
import org.killbill.billing.client.api.gen.*;
import org.killbill.billing.client.model.gen.Account;

public class Main {

    static KillBillHttpClient client = new KillBillHttpClient(REDACTED);

    static AccountApi accountApi = new AccountApi(client);

    public static void main(String[] args) {
        var requestOptions = RequestOptions.builder()
                .withCreatedBy("CS2")
                .withFollowLocation(Boolean.TRUE)
                .build();

        var body = new Account();
        body.setName("test");
        body.setCurrency(Currency.USD);
        body.setExternalKey("xyzzy2");
        body.setEmail("test@comcast.com");

        try {
            var result = accountApi.createAccount(body, requestOptions);
            System.out.println(result);
        } catch (KillBillClientException e) {
            e.printStackTrace();
        }
    }
}
pierre commented 22 hours ago

Unable to reproduce.

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>my-app</name>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>11</maven.compiler.release>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.kill-bill.billing</groupId>
      <artifactId>killbill-client-java</artifactId>
      <version>1.3.6</version>
    </dependency>
  </dependencies>
  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to
      parent pom) -->
      <plugins>
        <!-- clean lifecycle, see
        https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.4.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see
        https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.3.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.13.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>3.3.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.4.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>3.1.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>3.1.2</version>
        </plugin>
        <!-- site lifecycle, see
        https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.12.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.6.1</version>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>com.mycompany.app.App</mainClass>
            </manifest>
          </archive>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>single</goal>
            </goals>
            <phase>package</phase>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

App.java:

package com.mycompany.app;

import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.*;
import org.killbill.billing.client.api.gen.*;
import org.killbill.billing.client.model.gen.Account;

public class App {
    public static void main(String[] args) {
        String username = "admin";
        String password = "password";
        String apiKey = "bob";
        String apiSecret = "lazar";
        String serverHost = "localhost";
        int serverPort = 8080;
        String kbServerUrl = String.format("http://%s:%d", serverHost, serverPort);
        KillBillHttpClient killBillHttpClient = new KillBillHttpClient(kbServerUrl, username, password, apiKey,
                apiSecret);
        AccountApi accountApi = new AccountApi(killBillHttpClient);

        var requestOptions = RequestOptions.builder()
                .withCreatedBy("CS2")
                .withFollowLocation(Boolean.TRUE)
                .build();

        var body = new Account();
        body.setName("test");
        body.setCurrency(Currency.USD);
        body.setExternalKey("xyzzy");
        body.setEmail("test@comcast.com");

        try {
            var result = accountApi.createAccount(body, requestOptions);
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Execution:

$ java -jar target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
class Account {
    org.killbill.billing.client.model.gen.Account@fc5e4c1c
    accountId: 5dbaf858-5447-4262-8418-ccae0e26ca31
    name: test
    firstNameLength: null
    externalKey: xyzzy
    email: test@comcast.com
    billCycleDayLocal: 0
    currency: USD
    parentAccountId: null
    isPaymentDelegatedToParent: false
    paymentMethodId: null
    referenceTime: 2024-11-19T08:14:40.000Z
    timeZone: UTC
    address1: null
    address2: null
    postalCode: null
    company: null
    city: null
    state: null
    country: null
    locale: null
    phone: null
    notes: null
    isMigrated: null
    accountBalance: null
    accountCBA: null
    auditLogs: []
}
$ java -jar target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
org.killbill.billing.client.KillBillClientException: Account already exists for key xyzzy
        at org.killbill.billing.client.KillBillHttpClient.throwExceptionOnResponseError(KillBillHttpClient.java:390)
        at org.killbill.billing.client.KillBillHttpClient.doPrepareRequestInternal(KillBillHttpClient.java:349)
        at org.killbill.billing.client.KillBillHttpClient.doPrepareRequest(KillBillHttpClient.java:296)
        at org.killbill.billing.client.KillBillHttpClient.doPost(KillBillHttpClient.java:210)
        at org.killbill.billing.client.KillBillHttpClient.doPost(KillBillHttpClient.java:205)
        at org.killbill.billing.client.api.gen.AccountApi.createAccount(AccountApi.java:176)
        at com.mycompany.app.App.main(App.java:34)

Java version:

$ java -version
openjdk version "11.0.14.1" 2022-02-08 LTS
OpenJDK Runtime Environment Microsoft-31205 (build 11.0.14.1+1-LTS)
OpenJDK 64-Bit Server VM Microsoft-31205 (build 11.0.14.1+1-LTS, mixed mode)
steven-barnes commented 12 hours ago

Using the debugger, I can see that the location URL is using http, the request to killbill is using https:

    static KillBillHttpClient client = new KillBillHttpClient(
            "https://killbill.dev.commerce.comcast.com",

http://killbill.dev.commerce.comcast.com:80/1.0/kb/accounts/16c15fcb-f461-4ae0-baff-ec114883496a

We are self-hosting KB in AWS, and I am testing from my laptop.