Giter VIP home page Giter VIP logo

stripe.cr's Introduction

Stripe

Shard CI API Documentation Website GitHub release

Stripe API wrapper for Crystal.

This version (>1.0) is changed to follow Ruby's method and class structure. We will follow Stripe::Class.method but follow crystal parameters to take care of the types automatically.

Notice

This api wrapper was tested on api version 2020-03-02 but have been trying to make it flexible with String? and correspondent in the types.

Installation

Add this to your application's shard.yml:

dependencies:
  stripe:
    github: confact/stripe.cr

Usage

require "stripe"

Stripe.api_key = "YOUR_API_TOKEN"

token = Stripe::Token.create(card: {
  number: "4242424242424242",
  exp_month: 12,
  exp_year: 2019,
  cvc: 123,
})

customer = Stripe::Customer.create(source: token)
charge = Stripe::Charge.create(amount: 1000, currency: "usd", customer: customer)

custom API version

You can set custom api version if needed. Version need to be set before first api call is called. otherwise it won't be used.

require "stripe"

Stripe.api_key = "YOUR_API_TOKEN"
Stripe.version = "2019-03-29"

Example of setting up a subscription

Here is a simple way to setup a subscription by using payment_method.

Follow the instruction for setting up an subscription at stripe: https://stripe.com/docs/billing/subscriptions/set-up-subscription

When the step is at server code, check the code below that is corresponding towards the ruby code for same step.

Setting up an customer:

  token = params['StripeToken'] # or what the param for the token is called for you.
  customer = Stripe::Customer.create(email: email,
                         description: name, # just example
                         payment_method: token, # or token.payment_method.id
                         # depends what you do in the frontend to handle the token.
                         invoice_settings: { default_payment_method: token })

create a subscription with that customer:

Stripe::Subscription.create(customer: customer,
expand: ["latest_invoice.payment_intent"]) # yes - create_subscription support expand.

The rest is frontend to check SCA and more. You should not need to do more than this on the backend.

But follow https://stripe.com/docs/billing/subscriptions/set-up-subscription for the frontend part to make sure it works for SCA and other things.

Progress

API methods

Core

Balance
  • Retrieve balance

  • Retrieve a balance transaction

  • List all balance history

Charges
  • Create a charge

  • Retrieve a charge

  • Update a charge

  • Capture a charge

  • List all charges

Sources
  • Create a source

  • Retrieve a source

  • Attach a source to customer

  • Detach a source from customer

  • Update a source

Subscriptions
  • Create a Subscription

  • Retrieve a Subscription

  • Update a Subscription

  • Delete a Subscription

  • List all Subscriptions

Setup Intent
  • Create a Setup Intent

  • Retrieve a Setup Intent

  • Confirm a Setup Intent

  • Update a Setup Intent

  • Cancel a Setup Intent

  • Delete a Setup Intent

  • List all Setup Intents

Payment Intent
  • Create a Payment Intent

  • Retrieve a Payment Intent

  • Confirm a Payment Intent

  • Update a Payment Intent

  • Cancel a Payment Intent

  • Delete a Payment Intent

  • List all Payment Intents

Customers
  • Create a customer

  • Retrieve a customer

  • Update a customer

  • Delete a customer

  • List all customers

Customer Tax IDs
  • Create a customer tax ID

  • Retrieve a customer tax ID

  • Delete a customer tax ID

Refund
  • Create a refund

  • Retrieve a refund

  • Update a refund

  • List all refunds

Tax Rate
  • Create a tax rate

  • retrieve a tax rate

  • Update a tax rate

  • List all tax rates

Tokens
  • Create a card token

  • Create a bank account token

  • Create a PII token

  • Create an account token

  • Retrieve a token

Invoices
  • Create a invoice

  • Retrieve a invoice

  • Update a invoice

  • Delete a invoice

  • List all invoices

Files
  • Create a file

  • Retrieve a file

  • Update a file

  • List all files

File Links
  • Create a file link

  • Retrieve a file link

  • Update a file link

  • List all file links

Objects

Core

  • Balance

  • Balance transaction

  • Charge

  • Product

  • Customer

  • Customer Tax ID

  • Subscription

  • Invoice

  • Dispute

  • Dispute evidence

  • Event

  • File

  • File link

  • Payout

  • Refund

  • Tax Rate

  • Token

  • Payment Intent

  • Setup Intent

Payment methods

  • Payment Method

  • Bank account

  • Card

  • Source

  • Attach a payment method to a customer

Connect

  • Account

  • Login link

  • Application fee refund

  • Application fee

  • Country spec

  • Top-up

  • Transfer

  • Transfer reversal

Prices
  • Create a price

  • Retrieve a price

  • Update a price

  • List all prices

Products
  • Create a product

  • Retrieve a product

  • Update a product

  • List all products

Discounts
  • Create a discount

  • Retrieve a discount

  • Update a discount

  • List all discounts

Promotion codes
  • Create a promotion code

  • Retrieve a promotion code

  • Update a promotion codes

  • List all promotion codes

Coupons
  • Create a coupon

  • Retrieve a coupon

  • Update a coupon

  • List all coupons

Development

TODO: Write development instructions here

Contributing

  1. Fork it (https://github.com/confact/stripe.cr/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors

stripe.cr's People

Contributors

atlantis avatar confact avatar dependabot[bot] avatar gdotdesign avatar paula4230 avatar rmarronnier avatar stephendolan avatar vectorselector avatar vladfaust avatar xaviablaza avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

stripe.cr's Issues

Automatically generate and host API documentation

This library seems like a perfect example of a repository that would benefit from Crystal's documentation output.

I've set this up in a number of our Lucky repositories, and would be happy to submit a PR that accomplishes the following if the core team is amenable:

  • Use a GitHub action to generate the docs in a default gh-pages branch with each push to master (free, since this is a public repo)
  • Add a badge to the top of the README linking to the API documentation

The only activity that would be necessary from you, @confact, would be setting up the GH Pages in settings after my PR is merged (this only takes 2 clicks, and hosting is free).

JSON::SerializableError when Stripe is sending events from a Test Clock

Stripe Test Clocks allow asking Stripe to send events to a webhook which wouldn't happen for a month, year, etc.

When I'm running a customer through a Test Clock, webhooks that are fired have a subtly different JSON payload, which results in this:

Expected String but was Null at line 160, column 15
  parsing Stripe::Event::Request#id at line 160, column 5
  parsing Stripe::Event#request at line 159, column 3

Backtrace:

/Users/user/.asdf/installs/crystal/1.6.0/src/json/serialization.cr:181:7 in 'initialize:__pull_for_json_serializable'
lib/stripe/src/stripe/objects/core/event.cr:4:3 in 'new_from_json_pull_parser'
lib/stripe/src/stripe/objects/core/event.cr:4:3 in 'new'
/Users/user/.asdf/installs/crystal/1.6.0/src/json/from_json.cr:13:3 in 'from_json'
lib/stripe/src/stripe/methods/core/webhook/webhook.cr:17:5 in 'construct_event:payload:sig_header:secret'

I setup a socat to nab the JSON coming from the stripe cli, and indeed the Event Request id is null:

{
  "request": {
    "id": null,
    "idempotency_key": null
  },
  "type": "customer.subscription.updated"
}
I doubt it will be necessary, but here is the full json body


{
"id": "evt_3LuMlwJKARJsQAQH19xu3rKQ",
"object": "event",
"api_version": "2022-08-01",
"created": 1666126346,
"data": {
"object": {
"id": "pi_3LuMlwJKARJsQAQH1KQQ8lK9",
"object": "payment_intent",
"amount": 875,
"amount_capturable": 0,
"amount_details": {
"tip": {
}
},
"amount_received": 875,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"object": "list",
"data": [
{
"id": "ch_3LuMlwJKARJsQAQH1es3Nfz0",
"object": "charge",
"amount": 875,
"amount_captured": 875,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_3LuMlwJKARJsQAQH10HtpZFp",
"billing_details": {
"address": {
"city": null,
"country": "US",
"line1": null,
"line2": null,
"postal_code": "33333",
"state": null
},
"email": "[email protected]",
"name": ",",
"phone": null
},
"calculated_statement_descriptor": "example company",
"captured": true,
"created": 1666126345,
"currency": "usd",
"customer": "cus_MddrDiWTbHGXca",
"description": "Subscription update",
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {
},
"invoice": "in_1LuMlKJKARJsQAQH9IdHEddF",
"livemode": false,
"metadata": {
},
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 56,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_3LuMlwJKARJsQAQH1KQQ8lK9",
"payment_method": "pm_1LuMbwJKARJsQAQHqqaVZJbS",
"payment_method_details": {
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": "pass",
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2034,
"fingerprint": "rx7FZqAPdbxj3sCp",
"funding": "credit",
"installments": null,
"last4": "1111",
"mandate": null,
"network": "visa",
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"receipt_email": null,
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/invoices/CAcaFwoVYWNjdF8xQUNXSVpKS0FSSnNRQVFIKIqkvJoGMgboQ1UuLbo6LBYGDGGDDSVa86VspiHe_TZzw9tH_owfRjar?s=ap",
"refunded": false,
"refunds": {
"object": "list",
"data": [

          ],
          "has_more": false,
          "total_count": 0,
          "url": "/v1/charges/ch_3LuMlwJKsQAQH1es3Nfz0/refunds"
        },
        "review": null,
        "shipping": null,
        "source": null,
        "source_transfer": null,
        "statement_descriptor": null,
        "statement_descriptor_suffix": null,
        "status": "succeeded",
        "transfer_data": null,
        "transfer_group": null
      }
    ],
    "has_more": false,
    "total_count": 1,
    "url": "/v1/charges?payment_intent=pi_3LuMlwJKARJsQAQH1KQQ8lK9"
  },
  "client_secret": "pi_3LuMlwJKARJsQAQH1KQQ8lK9_secret_D5YyTs2FyWs6RBJEK2FcyeAb0",
  "confirmation_method": "automatic",
  "created": 1666126344,
  "currency": "usd",
  "customer": "cus_MddrDiWTbHGXca",
  "description": "Subscription update",
  "invoice": "in_1LuMlKJKARJsQAQH9IdHEddF",
  "last_payment_error": null,
  "livemode": false,
  "metadata": {
  },
  "next_action": null,
  "on_behalf_of": null,
  "payment_method": "pm_1LuMbwJKARJsQAQHqqaVZJbS",
  "payment_method_options": {
    "card": {
      "installments": null,
      "mandate_options": null,
      "network": null,
      "request_three_d_secure": "automatic"
    }
  },
  "payment_method_types": [
    "card"
  ],
  "processing": null,
  "receipt_email": null,
  "review": null,
  "setup_future_usage": null,
  "shipping": null,
  "source": null,
  "statement_descriptor": null,
  "statement_descriptor_suffix": null,
  "status": "succeeded",
  "transfer_data": null,
  "transfer_group": null
}

},
"livemode": false,
"pending_webhooks": 3,
"request": {
"id": null,
"idempotency_key": "in_1LuMlKJKARJsQAQH9IdHEddF-initial_attempt-87dfa0d7d8a015a1f"
},
"type": "payment_intent.succeeded"
}

No specs?

Hi, I love to use this shard in one of my projects, but I am a bit on the fence because there is not a single spec. How do I know you code works as intended?

verifying webhook problem

  stripe_webhook_secret = "whatever"

  body = env.request.body.as(IO).gets_to_end
  File.write("payload.json", body)

  timestamp = Time.utc

  payload = File.read("event.json")
  payload2 = File.read("payload.json")

  json = JSON.parse(payload2)
  pp json


  signature = Stripe::Webhook::Signature.compute_signature(
    timestamp,
    payload, #payload2 fails here
    stripe_webhook_secret
  )
  stripe_sig_header = Stripe::Webhook::Signature.generate_header(
    timestamp,
    signature,
    scheme: "v1"
  )


  event = Stripe::Webhook.construct_event(payload, stripe_sig_header, stripe_webhook_secret, 99999) #payload2 fails here
  pp event
  

Using Kemal master branch https://github.com/kemalcr/kemal
You can access "raw" request body by env.request.body.as(IO).gets_to_end

event.json is from https://github.com/confact/stripe.cr/blob/master/spec/support/event.json
The skeleton code is modified from this repo's spec file https://github.com/confact/stripe.cr/blob/master/spec/stripe/methods/webhook_spec.cr

payload1 is the result of loading the spec files default event.json file, and this signature-matches OK and constructs an event object fine via Stripe::Webhook.construct_event

`payload2' is the result of loading MY file. my file is a real Stripe request.body obtained via Kemal's context, which is basic Crystal HTTP::Request.body as IO as String from this aforementioned Kemal method env.request.body.as(IO).gets_to_end

payload2 fails to parse:

Exception: Unexpected token: , at line 55, column 4
parsing Stripe::Event::Data#object at line 7, column 5
parsing Stripe::Event#data at line 6, column 3 (JSON::SerializableError)
from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/serialization.cr:159:7 in 'initialize:__pull_for_json_serializable'
from lib/stripe/src/stripe/objects/core/event.cr:4:3 in 'new_from_json_pull_parser'
from lib/stripe/src/stripe/objects/core/event.cr:4:3 in 'new'
from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/from_json.cr:13:3 in 'from_json'
from lib/stripe/src/stripe/methods/core/webhook/webhook.cr:17:5 in 'construct_event'

the "raw" request body also fails.

both DO parse as JSON fine below

  json = JSON.parse(payload2)
  pp json

both files appear similar, structurally, with respect to whitespace. charset on both = UTF-8
both are JSON files. (below in comment)

I do not see what is wrong with payload.json that causes Stripe::Webhook::Signature to have problems.
the problem appears inside Stripe::Webhook.construct_event particularly when making a new Stripe::Event.from_json(payload)

Making an event from_json https://github.com/confact/stripe.cr/blob/master/src/stripe/objects/core/event.cr
appears to have a problem, but JSON.parse(payload) works fine and makes a valid Hash

Type confusion when updating a subscription

Hey I'm not exactly sure what's the cleanest way to solve this so I'm just going to explain it for now:

I'm trying to update_subscription with BOTH items: [...] and add_invoice_items: [...]however because the type is applying to both of them I get:

Error: expected argument 'add_invoice_items' to 'Stripe::Subscription.update' to be (Array(NamedTuple(id: String, price: String, quantity: Int32)) | Nil), not Array(NamedTuple(price: String, quantity: Int32))

Since the add_invoice_items named tuple doesn't take the id field. I can update the subscription in two separate calls and it works fine, but it won't compile if I try to update both fields in the same call.

Any ideas for how to solve that cleanly? If I change update_subscription.cr to add_invoice_items : T? | Unset = Unset.new it works, but not sure what that T was there for previously and perhaps I'm messing something up?

Cut new release for Stripe Checkout functionality

I'm happy to help with the Changelog if necessary, but basically it's just getting this PR into an official release:
#7

I didn't want to use master for my production app, so I'm currently installing a specific commit of this library. It'd be great to move to a more standard version bump :)

List methods with parameters are broken

I think #40 broke list methods. The code generated prior to this change looked like this (for Price in this case):

builder.add("active", active) unless active.nil?
builder.add("product", product) unless product.nil?
⋮
builder.add("ending_before", ending_before) unless ending_before.nil?

after the changes in #40 the generated code looks like this:

def self.list(active : Bool | ::Nil = nil, product : String | ::Nil = nil, currency : String | ::Nil = nil, type : String | ::Nil = nil, lookup_keys : Array(String) | ::Nil = nil, recurring : Hash(String, String | Int32 | Nil) | ::Nil = nil, limit : Int32 | ::Nil = nil, created : Hash(String, Int32) | ::Nil = nil, starting_after : String | ::Nil = nil, ending_before : String | ::Nil = nil, expand : Array(String) | ::Nil = nil) : List(Stripe::Price)
  io = IO::Memory.new
  builder = ParamsBuilder.new(io)

  builder.add(active, active) unless active.nil?
  builder.add(product, product) unless product.nil?
  ⋮
  builder.add(ending_before, ending_before) unless ending_before.nil?

  response = Stripe.client.get("/v1/#{"price"}s", form: io.to_s)

  if response.status_code == 200
    List(Stripe::Price).from_json(response.body)
  else
    raise Error.from_json(response.body, "error")
  end
end

Note that the keys are no longer quoted. This results in parameters like this being generated:

"%5B%22price_1MJ8JLGRhOlPd2IWrLbjKJgs%22%5D%5B0%5D=price_1MJ8JLGRhOlPd2IWrLbjKJgs&%5B%22data.product%22%5D%5B0%5D=data.product"

which URL decoded is:

["price_1MJ8JLGRhOlPd2IWrLbjKJgs"][0]=price_1MJ8JLGRhOlPd2IWrLbjKJgs&["data.product"][0]=data.product

note how the value is being used as the key and value.

The following test added to price_spec.cr illustrates the problem:

  it "listing prices with params" do
    WebMock.stub(:get, "https://api.stripe.com/v1/prices")
      .with(body: "currency=AUD")
      .to_return(status: 200, body: File.read("spec/support/list_prices.json"), headers: {"Content-Type" => "application/json"})

    prices = Stripe::Price.list(currency: "AUD")
    prices.first.id.should eq("price_1IqjDxJN5FrkuvKhKExUK1B2")
  end

With resulting error:

       Real HTTP connections are disabled. Unregistered request: GET https://api.stripe.com/v1/prices with body "AUD=AUD" with headers {"Content-Length" => "7", "Content-Type" => "application/x-www-form-urlencoded", "Host" => "api.stripe.com", "Authorization" => "Bearer test"}

Note that the body has the value twice.

The solution appears to be a call to stringify in the add_list_method macro, which makes the above test pass:

  {% for x in arguments.map &.var.id %}
    builder.add({{x.stringify}}, {{x.id}}) unless {{x.id}}.nil?
  {% end %}

Stripe `invoice.payment_succeeded` event is NOT idempotent and is missing `event.request.id` when concluding an "invoice.payment_action_required" workflow.

Stripe "invoice.payment_succeeded" event is NOT idempotent and is missing event.request.id when concluding an invoice.payment_action_required workflow.

When processing invoice.payment_succeeded events, in the case that this event comes in the end of a "invoice.payment_action_required" workflow (3D auth, for example, any additional auth/action needed by customer.)
the invoice.payment_succeeded event has a request.id of nil
This breaks JSON parsing on the Stripe::Webhook.construct_event call like this:

Exception: Expected String but was Null at line 189, column 15
  parsing Stripe::Event::Request#id at line 189, column 5
  parsing Stripe::Event#request at line 188, column 3 (JSON::SerializableError)
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/serialization.cr:159:7 in 'initialize:__pull_for_json_serializable'
  from lib/stripe/src/stripe/objects/core/event.cr:4:3 in 'new_from_json_pull_parser'
  from lib/stripe/src/stripe/objects/core/event.cr:4:3 in 'new'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/from_json.cr:13:3 in 'from_json'
  from lib/stripe/src/stripe/methods/core/webhook/webhook.cr:17:5 in 'construct_event'
  from src/webhooks/stripe_webhooks.cr:122:3 in '->'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/primitives.cr:255:3 in '->'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/primitives.cr:255:3 in 'process_request'
  from lib/kemal/src/kemal/route_handler.cr:17:7 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/websocket_handler.cr:13:14 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/filter_handler.cr:21:7 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from src/user/user_auth_for_public_site.cr:23:12 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from src/user/admin_auth.cr:9:12 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/static_file_handler.cr:11:11 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/exception_handler.cr:8:7 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/log_handler.cr:8:37 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/init_handler.cr:12:7 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/request_processor.cr:51:11 in 'process'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server.cr:513:5 in 'handle_client'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server.cr:468:13 in '->'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/primitives.cr:255:3 in 'run'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/fiber.cr:92:34 in '->'
  from ???
Caused by: Expected String but was Null at line 189, column 15
  parsing Stripe::Event::Request#id at line 189, column 5 (JSON::SerializableError)
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/serialization.cr:159:7 in 'initialize:__pull_for_json_serializable'
  from lib/stripe/src/stripe/objects/core/event.cr:7:5 in 'new_from_json_pull_parser'
  from lib/stripe/src/stripe/objects/core/event.cr:7:5 in 'new'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/from_json.cr:352:3 in 'new'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/serialization.cr:159:7 in 'initialize:__pull_for_json_serializable'
  from lib/stripe/src/stripe/objects/core/event.cr:4:3 in 'new_from_json_pull_parser'
  from lib/stripe/src/stripe/objects/core/event.cr:4:3 in 'new'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/from_json.cr:13:3 in 'from_json'
  from lib/stripe/src/stripe/methods/core/webhook/webhook.cr:17:5 in 'construct_event'
  from src/webhooks/stripe_webhooks.cr:122:3 in '->'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/primitives.cr:255:3 in '->'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/primitives.cr:255:3 in 'process_request'
  from lib/kemal/src/kemal/route_handler.cr:17:7 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/websocket_handler.cr:13:14 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/filter_handler.cr:21:7 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from src/user/user_auth_for_public_site.cr:23:12 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from src/user/admin_auth.cr:9:12 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/static_file_handler.cr:11:11 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/exception_handler.cr:8:7 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/log_handler.cr:8:37 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/init_handler.cr:12:7 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/request_processor.cr:51:11 in 'process'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server.cr:513:5 in 'handle_client'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server.cr:468:13 in '->'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/primitives.cr:255:3 in 'run'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/fiber.cr:92:34 in '->'
  from ???
Caused by: Expected String but was Null at line 189, column 15 (JSON::ParseException)
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/pull_parser.cr:691:7 in 'raise'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/pull_parser.cr:682:5 in 'expect_kind'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/pull_parser.cr:305:5 in 'read_string'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/from_json.cr:132:3 in 'new'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/serialization.cr:159:7 in 'initialize:__pull_for_json_serializable'
  from lib/stripe/src/stripe/objects/core/event.cr:7:5 in 'new_from_json_pull_parser'
  from lib/stripe/src/stripe/objects/core/event.cr:7:5 in 'new'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/from_json.cr:352:3 in 'new'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/serialization.cr:159:7 in 'initialize:__pull_for_json_serializable'
  from lib/stripe/src/stripe/objects/core/event.cr:4:3 in 'new_from_json_pull_parser'
  from lib/stripe/src/stripe/objects/core/event.cr:4:3 in 'new'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/json/from_json.cr:13:3 in 'from_json'
  from lib/stripe/src/stripe/methods/core/webhook/webhook.cr:17:5 in 'construct_event'
  from src/webhooks/stripe_webhooks.cr:122:3 in '->'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/primitives.cr:255:3 in '->'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/primitives.cr:255:3 in 'process_request'
  from lib/kemal/src/kemal/route_handler.cr:17:7 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/websocket_handler.cr:13:14 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/filter_handler.cr:21:7 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from src/user/user_auth_for_public_site.cr:23:12 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from src/user/admin_auth.cr:9:12 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/static_file_handler.cr:11:11 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/exception_handler.cr:8:7 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/log_handler.cr:8:37 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/init_handler.cr:12:7 in 'call'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server/request_processor.cr:51:11 in 'process'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server.cr:513:5 in 'handle_client'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/http/server.cr:468:13 in '->'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/primitives.cr:255:3 in 'run'
  from /home/aaron/.crenv/versions/1.0.0/share/crystal/src/fiber.cr:92:34 in '->'
  from ???

In this case from src/webhooks/stripe_webhooks.cr:122:3 in '->'
here is line 122
event = Stripe::Webhook.construct_event(payload, stripe_signature, stripe_webhook_secret, 99999)

(SIDE_NOTE: this comment here is for informing people, because it's a stumbling block and time-waster.
Stripe has a number of evil API decisions, IMO, including sending out invoice.payment_failed events when payment_action_required is sent, so that your payment_failed webhook endpoint has to filter a NON-EMBEDDED PaymentIntent that you must do a lookup on in every webhook hit to see if you care or not, if it's really a failed payment or a failed payment with a PaymentIntent.status of RequiresAction which is not a string, btw...This simply emphasizes that there are many tricky Stripe things that we either have to hide in here, or make users aware of in docs.)

Unable to create card token

I'm trying to implement the basic example

  stripe = Stripe.new("#{YOUR_API_TOKEN}")

  token = stripe.create_card_token(card: {
    number: "4242424242424242",
    exp_month: 12,
    exp_year: 2019,
    cvc: 123,
  })

And getting this error

There was a problem expanding macro 'macro_4463988000'

Called macro defined in lib/stripe/src/stripe/methods/core/tokens/create_card_token.cr:6:20

 6 | validate card, {{T}} do

Which expanded to:

 > 1 | NamedTuple(number: String, exp_month: Int32, exp_year: Int32, cvc: Int32)
Error: NamedTuple(T).class is not a generic type, it's a class

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.