stripe / stripe-java

Java library for the Stripe API.
https://stripe.com
MIT License
824 stars 361 forks source link

Subscription Json generated using ApiResource.GSON.toJson is not able to get converted in model again using ApiResource.GSON.fromJson #1925

Open sashubelt opened 1 week ago

sashubelt commented 1 week ago

Describe the bug

Generate JSON from to jeson method

String json = ApiResource.GSON.toJson(Subscription.retrieve("sub_1QL3OEFMxpe1bhsdExkQGaSA"));

Now create the model from generated json :

Subscription subs = ApiResource.GSON.fromJson(json, Subscription.class);

If the Json generated has following data the fromJson method breaks at this com.stripe.model.PaymentSourceTypeAdapterFactory line 50:

"default_source": {
    "id": "card_xxxxxAFMxpe1bhsdxxxxxxx"
 }

Generated Example Json :

{
  "automatic_tax": {
    "enabled": false
  },
  "billing_cycle_anchor": 1731626859,
  "cancel_at_period_end": false,
  "cancellation_details": {},
  "collection_method": "charge_automatically",
  "created": 1731592038,
  "currency": "usd",
  "current_period_end": 1763162859,
  "current_period_start": 1731626859,
  "customer": {
    "id": "cus_RDUL2neF83DgTI"
  },
  "default_source": {
    "id": "card_1QL3OAFMxpe1bhsdof3pn97j"
  },
  "default_tax_rates": [],
  "discounts": [],
  "id": "sub_1QL3OEFMxpe1bhsdExkQGaSA",
  "invoice_settings": {
    "issuer": {
      "type": "self"
    }
  },
  "items": {
    "object": "list",
    "data": [
      {
        "created": 1731592762,
        "discounts": [],
        "id": "si_RDUZp0IBRQgWqO",
        "metadata": {},
        "object": "subscription_item",
        "plan": {
          "active": true,
          "amount": 15000,
          "amount_decimal": 15000,
          "billing_scheme": "per_unit",
          "created": 1695754337,
          "currency": "usd",
          "id": "price_1NugMnFMxpe1bhsddxbfMvf2",
          "interval": "year",
          "interval_count": 1,
          "livemode": false,
          "metadata": {
            "active": "true"
          },
          "nickname": "ACTIVE Yearly",
          "object": "plan",
          "product": {
            "id": "prod_OdsYZw3Bn5ZCP1"
          },
          "trial_period_days": 14,
          "usage_type": "licensed"
        },
        "price": {
          "active": true,
          "billing_scheme": "per_unit",
          "created": 1695754337,
          "currency": "usd",
          "id": "price_1NugMnFMxpe1bhsddxbfMvf2",
          "livemode": false,
          "metadata": {
            "active": "true"
          },
          "nickname": "ACTIVE Yearly",
          "object": "price",
          "product": {
            "id": "prod_OdsYZw3Bn5ZCP1"
          },
          "recurring": {
            "interval": "year",
            "interval_count": 1,
            "trial_period_days": 14,
            "usage_type": "licensed"
          },
          "tax_behavior": "unspecified",
          "type": "recurring",
          "unit_amount": 15000,
          "unit_amount_decimal": 15000
        },
        "quantity": 50,
        "subscription": "sub_1QL3OEFMxpe1bhsdExkQGaSA",
        "tax_rates": []
      }
    ],
    "has_more": false,
    "url": "/v1/subscription_items?subscription=sub_1QL3OEFMxpe1bhsdExkQGaSA"
  },
  "latest_invoice": {
    "id": "in_1QLCRrFMxpe1bhsdInM5NsM9"
  },
  "livemode": false,
  "metadata": {},
  "object": "subscription",
  "payment_settings": {
    "save_default_payment_method": "off"
  },
  "start_date": 1731592038,
  "status": "active",
  "trial_end": 1731626858,
  "trial_settings": {
    "end_behavior": {
      "missing_payment_method": "create_invoice"
    }
  },
  "trial_start": 1731592761
}

Exception Stack Trace:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.google.gson.JsonPrimitive.getAsString()" because the return value of "com.google.gson.JsonObject.getAsJsonPrimitive(String)" is null
    at com.stripe.model.PaymentSourceTypeAdapterFactory$1.read(PaymentSourceTypeAdapterFactory.java:50)
    at com.stripe.model.PaymentSourceTypeAdapterFactory$1.read(PaymentSourceTypeAdapterFactory.java:40)
    at com.google.gson.TypeAdapter$1.read(TypeAdapter.java:204)
    at com.google.gson.Gson.fromJson(Gson.java:1227)
    at com.google.gson.Gson.fromJson(Gson.java:1329)
    at com.google.gson.Gson.fromJson(Gson.java:1300)
    at com.google.gson.internal.bind.TreeTypeAdapter$GsonContextImpl.deserialize(TreeTypeAdapter.java:179)
    at com.stripe.model.ExpandableFieldDeserializer.deserialize(ExpandableFieldDeserializer.java:47)
    at com.stripe.model.ExpandableFieldDeserializer.deserialize(ExpandableFieldDeserializer.java:12)
    at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:76)
    at com.stripe.net.StripeCollectionItemTypeSettingFactory$1.read(StripeCollectionItemTypeSettingFactory.java:25)
    at com.stripe.net.StripeResponseGetterSettingTypeAdapterFactory$1.read(StripeResponseGetterSettingTypeAdapterFactory.java:26)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.readIntoField(ReflectiveTypeAdapterFactory.java:212)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$FieldReflectionAdapter.readField(ReflectiveTypeAdapterFactory.java:433)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:393)
    at com.stripe.net.StripeCollectionItemTypeSettingFactory$1.read(StripeCollectionItemTypeSettingFactory.java:25)
    at com.stripe.net.StripeResponseGetterSettingTypeAdapterFactory$1.read(StripeResponseGetterSettingTypeAdapterFactory.java:26)
    at com.google.gson.Gson.fromJson(Gson.java:1227)
    at com.google.gson.Gson.fromJson(Gson.java:1137)
    at com.google.gson.Gson.fromJson(Gson.java:1047)
    at com.google.gson.Gson.fromJson(Gson.java:982)
    at com.stripe.main.StripeClient.main(StripeClient.java:37)

To Reproduce

Generate JSON from to jeson method

String json = ApiResource.GSON.toJson(Subscription.retrieve("sub_1QL3OEFMxpe1bhsdExkQGaSA"));

Now create the model from generated json :

Subscription subs = ApiResource.GSON.fromJson(json, Subscription.class);

If the Json generated has following data the fromJson method breaks at this com.stripe.model.PaymentSourceTypeAdapterFactory line 50:

"default_source": {
    "id": "card_xxxxxAFMxpe1bhsdxxxxxxx"
 }

Generated Example Json :

{
  "automatic_tax": {
    "enabled": false
  },
  "billing_cycle_anchor": 1731626859,
  "cancel_at_period_end": false,
  "cancellation_details": {},
  "collection_method": "charge_automatically",
  "created": 1731592038,
  "currency": "usd",
  "current_period_end": 1763162859,
  "current_period_start": 1731626859,
  "customer": {
    "id": "cus_RDUL2neF83DgTI"
  },
  "default_source": {
    "id": "card_1QL3OAFMxpe1bhsdof3pn97j"
  },
  "default_tax_rates": [],
  "discounts": [],
  "id": "sub_1QL3OEFMxpe1bhsdExkQGaSA",
  "invoice_settings": {
    "issuer": {
      "type": "self"
    }
  },
  "items": {
    "object": "list",
    "data": [
      {
        "created": 1731592762,
        "discounts": [],
        "id": "si_RDUZp0IBRQgWqO",
        "metadata": {},
        "object": "subscription_item",
        "plan": {
          "active": true,
          "amount": 15000,
          "amount_decimal": 15000,
          "billing_scheme": "per_unit",
          "created": 1695754337,
          "currency": "usd",
          "id": "price_1NugMnFMxpe1bhsddxbfMvf2",
          "interval": "year",
          "interval_count": 1,
          "livemode": false,
          "metadata": {
            "active": "true"
          },
          "nickname": "ACTIVE Yearly",
          "object": "plan",
          "product": {
            "id": "prod_OdsYZw3Bn5ZCP1"
          },
          "trial_period_days": 14,
          "usage_type": "licensed"
        },
        "price": {
          "active": true,
          "billing_scheme": "per_unit",
          "created": 1695754337,
          "currency": "usd",
          "id": "price_1NugMnFMxpe1bhsddxbfMvf2",
          "livemode": false,
          "metadata": {
            "active": "true"
          },
          "nickname": "ACTIVE Yearly",
          "object": "price",
          "product": {
            "id": "prod_OdsYZw3Bn5ZCP1"
          },
          "recurring": {
            "interval": "year",
            "interval_count": 1,
            "trial_period_days": 14,
            "usage_type": "licensed"
          },
          "tax_behavior": "unspecified",
          "type": "recurring",
          "unit_amount": 15000,
          "unit_amount_decimal": 15000
        },
        "quantity": 50,
        "subscription": "sub_1QL3OEFMxpe1bhsdExkQGaSA",
        "tax_rates": []
      }
    ],
    "has_more": false,
    "url": "/v1/subscription_items?subscription=sub_1QL3OEFMxpe1bhsdExkQGaSA"
  },
  "latest_invoice": {
    "id": "in_1QLCRrFMxpe1bhsdInM5NsM9"
  },
  "livemode": false,
  "metadata": {},
  "object": "subscription",
  "payment_settings": {
    "save_default_payment_method": "off"
  },
  "start_date": 1731592038,
  "status": "active",
  "trial_end": 1731626858,
  "trial_settings": {
    "end_behavior": {
      "missing_payment_method": "create_invoice"
    }
  },
  "trial_start": 1731592761
}

Exception Stack Trace:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.google.gson.JsonPrimitive.getAsString()" because the return value of "com.google.gson.JsonObject.getAsJsonPrimitive(String)" is null
    at com.stripe.model.PaymentSourceTypeAdapterFactory$1.read(PaymentSourceTypeAdapterFactory.java:50)
    at com.stripe.model.PaymentSourceTypeAdapterFactory$1.read(PaymentSourceTypeAdapterFactory.java:40)
    at com.google.gson.TypeAdapter$1.read(TypeAdapter.java:204)
    at com.google.gson.Gson.fromJson(Gson.java:1227)
    at com.google.gson.Gson.fromJson(Gson.java:1329)
    at com.google.gson.Gson.fromJson(Gson.java:1300)
    at com.google.gson.internal.bind.TreeTypeAdapter$GsonContextImpl.deserialize(TreeTypeAdapter.java:179)
    at com.stripe.model.ExpandableFieldDeserializer.deserialize(ExpandableFieldDeserializer.java:47)
    at com.stripe.model.ExpandableFieldDeserializer.deserialize(ExpandableFieldDeserializer.java:12)
    at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:76)
    at com.stripe.net.StripeCollectionItemTypeSettingFactory$1.read(StripeCollectionItemTypeSettingFactory.java:25)
    at com.stripe.net.StripeResponseGetterSettingTypeAdapterFactory$1.read(StripeResponseGetterSettingTypeAdapterFactory.java:26)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.readIntoField(ReflectiveTypeAdapterFactory.java:212)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$FieldReflectionAdapter.readField(ReflectiveTypeAdapterFactory.java:433)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:393)
    at com.stripe.net.StripeCollectionItemTypeSettingFactory$1.read(StripeCollectionItemTypeSettingFactory.java:25)
    at com.stripe.net.StripeResponseGetterSettingTypeAdapterFactory$1.read(StripeResponseGetterSettingTypeAdapterFactory.java:26)
    at com.google.gson.Gson.fromJson(Gson.java:1227)
    at com.google.gson.Gson.fromJson(Gson.java:1137)
    at com.google.gson.Gson.fromJson(Gson.java:1047)
    at com.google.gson.Gson.fromJson(Gson.java:982)
    at com.stripe.main.StripeClient.main(StripeClient.java:37)

Expected behavior

the json should be able to generate subscription model class again

Code snippets

//Subscription Id used below has default_source not null and has value
String json = ApiResource.GSON.toJson(Subscription.retrieve("sub_1QL3OEFMxpe1bhsdExkQGaSA"));
System.out.println(json);

// This line breaks if the subscription has default_source: example value : "default_source": {
//    "id": "card_1QL3OAFMxpe1bhsdof3pn97j"
//  }
Subscription subs = ApiResource.GSON.fromJson(json, Subscription.class);
System.out.println(subs);

OS

macOS

Java version

Java21

stripe-java version

v28.0.1

API version

2024-10-28.acacia

Additional context

No response

jar-stripe commented 3 days ago

Hi @sashubelt , thanks for reporting! We've added this to our backlog and will take a look. Is this actively blocking functionality for you? Or have you been able to work around it?