getlago / lago-go-client

Lago Go Client
MIT License
21 stars 19 forks source link

[BUG]: func (*Customer.Request) Create Failing With 422 Error Code #106

Closed c-nv-s closed 1 year ago

c-nv-s commented 1 year ago

Describe the bug The Create Customer function is constantly failing with 422 error code I have also noticed that it doesn't seem to be executing the same sql as when the equivalent curl command is used to create a customer. Also not that if fails regardless of whether setting sync or sync_with_provider to true.

To Reproduce

example go code (you will note that the go code shown at https://docs.getlago.com/api-reference/customers/create does not work because the Metadata value is incorrectly formatted, and also the VatRate field is missing a comma at the end of the line)

    lagoClient := lago.New().                                                                                                     
    SetBaseURL("http://my.lago.local:3000").
    SetApiKey("xxxxx-xxxx-xxxxx-xxxx-xxxxxxxx")                                                                                

var theCustomerMetadataArray = []lago.CustomerMetadataInput{lago.CustomerMetadataInput{ Key: "coolkey", Value: "coolvalue", DisplayInInvoice: true,},} 

customerInput := &lago.CustomerInput{
      ExternalID:              "7ce34fa7-c6ef-48ca-968c-a367bcdc4477",                                                                             
      Name:                    "otra cool customer",                                                                                      
      Email:                   "mayor@quimby.com",
      AddressLine1:            "Address Line 1",                   
      AddressLine2:            "Address Line 2",                   
      City:                    "Paris",   
      Country:                 "France",                                                                                               
      Currency:                "EUR",                                                                                                  
      State:                   "Paris",
      Zipcode:                 "75001",
      LegalName:               "Get dis",
      LegalNumber:             "654321",
      TaxIdentificationNumber: "EU987654321",
      Phone:                   "+330100000000",
      Timezone:                "Europe/Paris",
      URL:                     "https://getlago.com",
      Metadata: theCustomerMetadataArray,
      BillingConfiguration: lago.CustomerBillingConfigurationInput{ 
        InvoiceGracePeriod: 3,
        PaymentProvider: lago.PaymentProviderStripe,
        Sync: true,
        SyncWithProvider: true,
        DocumentLocale: "en",
      },
    }

var theError *lago.Error                   
        theContext := context.Background()
    customer, theError := lagoClient.Customer().Create(theContext, customerInput)                                                                                                                             

produces example sql log (notice that no stripe customer creation task/job is first created as seen in the subsequent curl example)

INFO -- : [3bd84174-4a93-4230-b684-0d3b5ee104de] {"method":"POST","path":"/api/v1/customers","format":"json","controller":"Api::V1::CustomersController","action":"create","status":422,"duration":752.39,"view":2.06,"db":268.5,"ddsource":"ruby","params":{"customer":{"external_id":"7ce34fa7-c6ef-48ca-968c-a367bcdc4477","name":"otra cool customer","email":"mayor@quimby.com","address_line1":"Address Line 1","address_line2":"Address Line 2","city":"Paris","zipcode":"75001","state":"Paris","country":"France","legal_name":"Get dis","legal_number":"654321","tax_identification_number":"EU987654321","phone":"+330100000000","url":"https://getlago.com","currency":"EUR","timezone":"Europe/Paris","metadata":[{"key":"coolkey","value":"coolvalue","display_in_invoice":true}],"billing_configuration":{"invoice_grace_period":3,"payment_provider":"stripe","document_locale":"en"}}},"sql_queries":"'Organization Load (3.1) SELECT \"organizations\".* FROM \"organizations\" WHERE \"organizations\".\"api_key\" = $1 LIMIT $2\nMembership Load (4.74) SELECT \"memberships\".* FROM \"memberships\" WHERE \"memberships\".\"organization_id\" = $1 ORDER BY \"memberships\".\"created_at\" ASC LIMIT $2\nCustomer Load (6.95) SELECT \"customers\".* FROM \"customers\" WHERE \"customers\".\"deleted_at\" IS NULL AND \"customers\".\"organization_id\" = $1 AND \"customers\".\"external_id\" = $2 LIMIT $3\nTRANSACTION (0.84) BEGIN\nCustomer Exists? (2.25) SELECT 1 AS one FROM \"customers\" WHERE \"customers\".\"external_id\" = $1 AND \"customers\".\"organization_id\" = $2 AND \"customers\".\"deleted_at\" IS NULL LIMIT $3\nTRANSACTION (1.28) COMMIT'","sql_queries_count":6}

example curl command

 curl --location --request POST "$LAGO_URL/api/v1/customers" \
  --header "Authorization: Bearer $API_KEY" \
  --header 'Content-Type: application/json' \
  --data-raw '{                                                                                                                            "customer": {                                                                                                                            "external_id": "7eb02857-a71e-4ea2-bcf9-57d3a41bc6ba",                                                                                 "address_line1": "5230 Penfield Ave",                                                                                                  "address_line2": "",                                                                                                                   "city": "Woodland Hills",                                                                                                              "country": "US",                                                                                                                       "currency": "USD",                                                                                                                     "email": "dinesh@piedpiper.test",
      "legal_name": "Coleman-Blair",
      "legal_number": "49-008-2965",
      "tax_identification_number": "EU123456789",
      "logo_url": "http://hooli.com/logo.png",
      "name": "Gavin Belson",
      "phone": "1-171-883-3711 x245",
      "state": "CA",
      "timezone": "Europe/Paris", 
      "url": "http://hooli.com",
      "zipcode": "91364",
      "billing_configuration": {
        "invoice_grace_period": 3,
        "payment_provider": "stripe",
        "sync": true,
        "sync_with_provider": true,
        "document_locale": "en",  
        "vat_rate": 12.5
      },
      "metadata": [
        {
          "key": "Purchase Order",
          "value": "123456789",   
          "display_in_invoice": true
        }                         

produces the following sql in the logs (notice the stripe customer creation job is also created first as well as the difference in sql)

I, [2023-07-11T00:05:39.220454 #11]  INFO -- : [67ae8abf-d8b3-4247-8acc-dee92ef1650c] [ActiveJob] [PaymentProviderCustomers::StripeCrea
teJob] [009a9b28-77b7-4eb1-935d-3e258f63e0cc] [membership/d5a1e22a-e234-4b8b-a95b-3a2cb66001d4] Performing PaymentProviderCustomers::St
ripeCreateJob (Job ID: 009a9b28-77b7-4eb1-935d-3e258f63e0cc) from Sidekiq(providers) enqueued at  with arguments: #<GlobalID:0x00007fb2
e5960d80 @uri=#<URI::GID gid://lago-api/PaymentProviderCustomers::StripeCustomer/68ef831e-1f1f-4935-a2fe-da6961cba3fe>>

I, [2023-07-11T00:05:39.979603 #11]  INFO -- : [67ae8abf-d8b3-4247-8acc-dee92ef1650c] [ActiveJob] [PaymentProviderCustomers::StripeCrea
teJob] [009a9b28-77b7-4eb1-935d-3e258f63e0cc] [membership/d5a1e22a-e234-4b8b-a95b-3a2cb66001d4] [membership/d5a1e22a-e234-4b8b-a95b-3a2
cb66001d4] Sidekiq 7.0.8 connecting to Redis with options {:size=>5, :pool_name=>"internal", :url=>"redis://redis:6379", :pool_timeout=
>5}

I, [2023-07-11T00:05:39.998027 #11]  INFO -- : [67ae8abf-d8b3-4247-8acc-dee92ef1650c] [ActiveJob] [PaymentProviderCustomers::StripeCrea
teJob] [009a9b28-77b7-4eb1-935d-3e258f63e0cc] [membership/d5a1e22a-e234-4b8b-a95b-3a2cb66001d4] [membership/d5a1e22a-e234-4b8b-a95b-3a2
cb66001d4] Enqueued PaymentProviderCustomers::StripeCheckoutUrlJob (Job ID: ffea9083-5913-41bb-b27e-b1c84b08f52c) to Sidekiq(providers)
 with arguments: #<GlobalID:0x00007fb2e580ae68 @uri=#<URI::GID gid://lago-api/PaymentProviderCustomers::StripeCustomer/68ef831e-1f1f-49
35-a2fe-da6961cba3fe>>

I, [2023-07-11T00:05:39.998613 #11]  INFO -- : [67ae8abf-d8b3-4247-8acc-dee92ef1650c] [ActiveJob] [PaymentProviderCustomers::StripeCrea
teJob] [009a9b28-77b7-4eb1-935d-3e258f63e0cc] [membership/d5a1e22a-e234-4b8b-a95b-3a2cb66001d4] Performed PaymentProviderCustomers::Str
ipeCreateJob (Job ID: 009a9b28-77b7-4eb1-935d-3e258f63e0cc) from Sidekiq(providers) in 778.82ms
I, [2023-07-11T00:05:40.009565 #11]  INFO -- : [67ae8abf-d8b3-4247-8acc-dee92ef1650c] [ActiveJob] [membership/d5a1e22a-e234-4b8b-a95b-3
a2cb66001d4] Enqueued SegmentTrackJob (Job ID: 660dea74-7afd-43e0-8a22-f1b3907f86e3) to Sidekiq(default) with arguments: {:membership_i
d=>"membership/d5a1e22a-e234-4b8b-a95b-3a2cb66001d4", :event=>"customer_created", :properties=>{:customer_id=>"5734238d-c086-4678-aaca-
c92f71d58636", :created_at=>Tue, 11 Jul 2023 00:05:38.551691000 UTC +00:00, :payment_provider=>"stripe", :organization_id=>"91886c78-fe
0b-4961-84a3-08a784e0ba94"}}

I, [2023-07-11T00:05:40.018760 #11]  INFO -- : [67ae8abf-d8b3-4247-8acc-dee92ef1650c] {"method":"POST","path":"/api/v1/customers","form
at":"*/*","controller":"Api::V1::CustomersController","action":"create","status":200,"duration":1679.74,"view":6.92,"db":511.35,"ddsour
ce":"ruby","params":{"customer":{"external_id":"7eb02857-a71e-4ea2-bcf9-57d3a41bc6ba","address_line1":"5230 Penfield Ave","address_line
2":"","city":"Woodland Hills","country":"US","currency":"USD","email":"dinesh@piedpiper.test","legal_name":"Coleman-Blair","legal_numbe
r":"49-008-2965","tax_identification_number":"EU123456789","logo_url":"http://hooli.com/logo.png","name":"Gavin Belson","phone":"1-171-
883-3711 x245","state":"CA","timezone":"Europe/Paris","url":"http://hooli.com","zipcode":"91364","billing_configuration":{"invoice_grac
e_period":3,"payment_provider":"stripe","sync":true,"sync_with_provider":true,"document_locale":"en","vat_rate":12.5},"metadata":[{"key
":"Purchase Order","value":"123456789","display_in_invoice":true}]}},"sql_queries":"'Organization Load (2.03) SELECT \"organizations\".
* FROM \"organizations\" WHERE \"organizations\".\"api_key\" = $1 LIMIT $2\nMembership Load (8.93) SELECT \"memberships\".* FROM \"memb
erships\" WHERE \"memberships\".\"organization_id\" = $1 ORDER BY \"memberships\".\"created_at\" ASC LIMIT $2\nCustomer Load (1.39) SEL
ECT \"customers\".* FROM \"customers\" WHERE \"customers\".\"deleted_at\" IS NULL AND \"customers\".\"organization_id\" = $1 AND \"cust
omers\".\"external_id\" = $2 LIMIT $3\nTRANSACTION (0.48) BEGIN\nCustomer Exists? (1.44) SELECT 1 AS one FROM \"customers\" WHERE \"cus
tomers\".\"external_id\" = $1 AND \"customers\".\"organization_id\" = $2 AND \"customers\".\"deleted_at\" IS NULL LIMIT $3\n (1.13) SEL
ECT pg_try_advisory_xact_lock(1394491257,0) AS t54a442a1d8bb8a76ce2788d06b48ee5b /* customer_lock */\nCustomer Pluck (1.25) SELECT \"cu
stomers\".\"sequential_id\" FROM \"customers\" WHERE \"customers\".\"organization_id\" = $1 AND \"customers\".\"sequential_id\" IS NOT 
NULL ORDER BY \"customers\".\"sequential_id\" DESC LIMIT $2\nCustomer Exists? (1.33) SELECT 1 AS one FROM \"customers\" WHERE \"custome
rs\".\"organization_id\" = $1 AND \"customers\".\"sequential_id\" = $2 LIMIT $3\nCustomer Create (43.2) INSERT INTO \"customers\" (\"ex
ternal_id\", \"name\", \"organization_id\", \"created_at\", \"updated_at\", \"country\", \"address_line1\", \"address_line2\", \"state\
", \"zipcode\", \"email\", \"city\", \"url\", \"phone\", \"logo_url\", \"legal_name\", \"legal_number\", \"vat_rate\", \"payment_provid
er\", \"slug\", \"sequential_id\", \"currency\", \"invoice_grace_period\", \"timezone\", \"deleted_at\", \"document_locale\", \"tax_ide
ntification_number\") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23,
 $24, $25, $26, $27) RETURNING \"id\"\nPaperTrail::Version Create (48.44) INSERT INTO \"versions\" (\"item_type\", \"item_id\", \"event
\", \"whodunnit\", \"object\", \"object_changes\", \"created_at\") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING \"id\"\nPaperTrail::Ve
rsion Count (2.44) SELECT COUNT(*) FROM \"versions\" WHERE \"versions\".\"item_type\" = $1 AND \"versions\".\"item_id\" = $2 AND \"vers
ions\".\"event\" != $3\nCustomer Exists? (1.6) SELECT 1 AS one FROM \"customers\" WHERE \"customers\".\"external_id\" = $1 AND \"custom
ers\".\"id\" != $2 AND \"customers\".\"organization_id\" = $3 AND \"customers\".\"deleted_at\" IS NULL LIMIT $4\nMetadata::CustomerMeta
data Exists? (3.0) SELECT 1 AS one FROM \"customer_metadata\" WHERE \"customer_metadata\".\"key\" = $1 AND \"customer_metadata\".\"cust
omer_id\" = $2 LIMIT $3\nMetadata::CustomerMetadata Create (2.06) INSERT INTO \"customer_metadata\" (\"customer_id\", \"key\", \"value\
", \"display_in_invoice\", \"created_at\", \"updated_at\") VALUES ($1, $2, $3, $4, $5, $6) RETURNING \"id\"\nTRANSACTION (46.97) COMMIT
\nTRANSACTION (0.71) BEGIN\nCustomer Exists? (1.48) SELECT 1 AS one FROM \"customers\" WHERE \"customers\".\"external_id\" = $1 AND \"c
ustomers\".\"id\" != $2 AND \"customers\".\"organization_id\" = $3 AND \"customers\".\"deleted_at\" IS NULL LIMIT $4\nCustomer Update (
2.11) UPDATE \"customers\" SET \"updated_at\" = $1, \"vat_rate\" = $2, \"payment_provider\" = $3, \"document_locale\" = $4 WHERE \"cust
omers\".\"id\" = $5\nPaperTrail::Version Create (93.1) INSERT INTO \"versions\" (\"item_type\", \"item_id\", \"event\", \"whodunnit\", 
\"object\", \"object_changes\", \"created_at\") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING \"id\"\nPaperTrail::Version Count (2.09)
SELECT COUNT(*) FROM \"versions\" WHERE \"versions\".\"item_type\" = $1 AND \"versions\".\"item_id\" = $2 AND \"versions\".\"event\" !=
 $3\nTRANSACTION (23.92) COMMIT\nPaymentProviders::StripeProvider Load (1.83) SELECT \"payment_providers\".* FROM \"payment_providers\"
 WHERE \"payment_providers\".\"type\" = $1 AND \"payment_providers\".\"organization_id\" = $2 LIMIT $3\nPaymentProviderCustomers::Strip
eCustomer Load (2.56) SELECT \"payment_provider_customers\".* FROM \"payment_provider_customers\" WHERE \"payment_provider_customers\".
\"type\" = $1 AND \"payment_provider_customers\".\"customer_id\" = $2 AND \"payment_provider_customers\".\"payment_provider_id\" = $3 L
IMIT $4\nTRANSACTION (9.23) BEGIN\nCustomer Load (1.09) SELECT \"customers\".* FROM \"customers\" WHERE \"customers\".\"deleted_at\" IS
 NULL AND \"customers\".\"id\" = $1 LIMIT $2\nPaymentProviderCustomers::BaseCustomer Exists? (6.23) SELECT 1 AS one FROM \"payment_prov
ider_customers\" WHERE \"payment_provider_customers\".\"customer_id\" = $1 AND \"payment_provider_customers\".\"type\" = $2 LIMIT $3\nP
aymentProviderCustomers::StripeCustomer Create (2.23) INSERT INTO \"payment_provider_customers\" (\"customer_id\", \"payment_provider_i
d\", \"type\", \"provider_customer_id\", \"settings\", \"created_at\", \"updated_at\") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING \"
id\"\nTRANSACTION (60.5) COMMIT\nOrganization Load (1.31) SELECT \"organizations\".* FROM \"organizations\" WHERE \"organizations\".\"i
d\" = $1 LIMIT $2\nPaymentProviders::StripeProvider Load (1.33) SELECT \"payment_providers\".* FROM \"payment_providers\" WHERE \"payme
nt_providers\".\"type\" = $1 AND \"payment_providers\".\"organization_id\" = $2 LIMIT $3\nTRANSACTION (0.95) BEGIN\nPaymentProviderCust
omers::BaseCustomer Exists? (3.53) SELECT 1 AS one FROM \"payment_provider_customers\" WHERE \"payment_provider_customers\".\"customer_
id\" = $1 AND \"payment_provider_customers\".\"id\" != $2 AND \"payment_provider_customers\".\"type\" = $3 LIMIT $4\nPaymentProviderCus
tomers::StripeCustomer Update (1.69) UPDATE \"payment_provider_customers\" SET \"provider_customer_id\" = $1, \"updated_at\" = $2 WHERE
 \"payment_provider_customers\".\"id\" = $3\nTRANSACTION (41.21) COMMIT\nTRANSACTION (0.55) BEGIN\nCustomer Exists? (1.08) SELECT 1 AS 
one FROM \"customers\" WHERE \"customers\".\"external_id\" = $1 AND \"customers\".\"id\" != $2 AND \"customers\".\"organization_id\" = 
$3 AND \"customers\".\"deleted_at\" IS NULL LIMIT $4\nTRANSACTION (0.72) COMMIT\nPaymentProviderCustomers::StripeCustomer Load (0.77) S
ELECT \"payment_provider_customers\".* FROM \"payment_provider_customers\" WHERE \"payment_provider_customers\".\"type\" = $1 AND \"pay
ment_provider_customers\".\"customer_id\" = $2 LIMIT $3\nMetadata::CustomerMetadata Load (0.81) SELECT \"customer_metadata\".* FROM \"c
ustomer_metadata\" WHERE \"customer_metadata\".\"customer_id\" = $1'","sql_queries_count":39}

Expected behavior The customer should be created in both lago and stripe as is the case when using a curl request

Support

Additional context Not sure if it makes a difference but when checking the differences in the http requests between the go client and curl. the go client has headers Accept: application/json, Accept-Encoding: gzip whereas curl just uses Accept: */* with no specified Encoding preference either

vincent-pochet commented 1 year ago

Hello @c-nv-s

The 422 response from the backend means there is a validation error with the provided data.

By looking at the sample you provider, it seems to be because the API is expecting the Country to be an ISO 3166 (alpha-2) and not a country name. You can see it in our API documentation: https://doc.getlago.com/api-reference/customers/create

The error from the service should contain the reason for the 422. We will investigate on it to make ensure error response are clear.

c-nv-s commented 1 year ago

you are correct that the format of the country was wrong, however the actual lago.Error was empty/nil so I couldn't even determine what was causing the issue :-(

vincent-pochet commented 1 year ago

Indeed, the current implementation of the error handling does not match the format of the error sent by the API.

A fix for this problem has just been opened at https://github.com/getlago/lago-go-client/pull/108. It will probably be part of the next release

I'm closing this issue as it is "fixed"