Code Monkey home page Code Monkey logo

justifi-ruby's Introduction

JustiFi Ruby Coverage

The JustiFi gem provides a simple way to access JustiFi API for apps written in Ruby language. It includes a pre-defined set of modules and classes that are essentially wrapped versions of our API resources.

Installation

Add these lines to your application's Gemfile:

gem "justifi"

And then execute:

$ bundle install

Setup

The gem needs to be configured with your client_id and client_secret in order to access JustiFi API resources.

Set Justifi.client_id and Justifi.client_secret:

require 'justifi'

Justifi.client_id = 'live_13...'
Justifi.client_secret = 'live_TDYj_wdd...'

OR just use the Justifi.setup method to set all at once:

require 'justifi'

# setup
Justifi.setup(client_id:     ENV["JUSTIFI_CLIENT_ID"],
              client_secret: ENV["JUSTIFI_CLIENT_SECRET"])

Create Payment

There are two ways to create a payment:

  1. Create with tokenized payment method:
payment_params = {
  amount: 1000,
  currency: "usd",
  capture_strategy: "automatic",
  email: "[email protected]",
  description: "Charging $10 on Example.com",
  payment_method: {
    token: "#{tokenized_payment_method_id}"
  }
}

Justifi::Payment.create(params: payment_params)
  1. Create with full payment params:
payment_params = {
  amount: 1000,
  currency: "usd",
  capture_strategy: "automatic",
  email: "[email protected]",
  description: "Charging $10 on Example.com",
  payment_method: {
    card: {
      name: "JustiFi Tester",
      number: "4242424242424242",
      verification: "123",
      month: "3",
      year: "2040",
      address_postal_code: "55555"
    }
  }
}

Justifi::Payment.create(params: payment_params)

Idempotency Key

You can use your own idempotency-key when creating payments.

payment_params = {
  amount: 1000,
  currency: "usd",
  capture_strategy: "automatic",
  email: "[email protected]",
  description: "Charging $10 on Example.com",
  payment_method: {
    card: {
      name: "JustiFi Tester",
      number: "4242424242424242",
      verification: "123",
      month: "3",
      year: "2040",
      address_postal_code: "55555"
    }
  }
}

Justifi::Payment.create(params: payment_params, idempotency_key: "my_idempotency_key")

IMPORTANT: The gem will generate an idempotency key in case you don't want to use your own.

Create Payment Refund

In order to create a refund, you will need an amount, a payment_id ( py_2aBBouk... ).

payment_params = {
  amount: 1000,
  currency: "usd",
  capture_strategy: "automatic",
  email: "[email protected]",
  description: "Charging $10 on Example.com",
  payment_method: {
    card: {
      name: "JustiFi Tester",
      number: "4242424242424242",
      verification: "123",
      month: "3",
      year: "2040",
      address_postal_code: "55555"
    }
  }
}

payment_id = Justifi::Payment.create(params: payment_params).data[:id] # get the payment id
reason     = ['duplicate', 'fraudulent', 'customer_request'] # optional: one of these
amount     = 1000

Justifi::Payment.create_refund( amount: 1000, reason: reason, payment_id: payment_id )

Listing Resources

All top-level API resources have support for bulk fetches via array API methods. JustiFi uses cursor based pagination which supports limit, before_cursor and after_cursor. Each response will have a page_info object that contains the has_next and has_previous fields, you can find more information about this on JustiFi's Developer Portal.

List Payments

payments = Justifi::Payment.list

# pagination with end_cursor

query_params = {
  limit: 15,
  after_cursor: payments.data[:page_info][:end_cursor],
}

payments = Justifi::Payment.list(params: query_params)

List Refunds

refunds = Justifi::Refund.list

# pagination
refunds = refunds.next_page
refunds = refunds.previous_page

Get resource by id

payment = Justifi::Payment.get(payment_id: 'py_xyz')
refund = Justifi::Refund.get(refund_id: 're_xyz')

Seller Account (deprecated)

Note: the term seller account has been deprecated and will be removed in future versions. Please use sub account instead

You can make requests using the Seller-Account header (deprecated, use Sub-Account header) in order to process resources as a seller-account.

seller_account_id = "acc_xyzs"
Justifi::PaymentIntent.create(params: payment_intent_params, seller_account_id: seller_account_id)

Any API resource using the seller_account_id (deprecated, use sub_account_id) variable will include the Seller-Account header and be processed as the seller account.

Sub Account

You can make requests using the Sub-Account header in order to process resources as a sub-account.

sub_account_id = "acc_xyzs"
Justifi::PaymentIntent.create(params: payment_intent_params, sub_account_id: sub_account_id)

Any API resource using the sub_account_id variable will include the Sub-Account header and be processed as the sub account.

Create a Sub Account

Justifi::SubAccount.create(params: { name: "Created from Ruby SDK" })

List Sub Accounts

sub_accounts = Justifi::SubAccount.list

# pagination
sub_accounts = Justifi::SubAccount.list(params: {limit: 5})
sub_accounts = sub_accounts.next_page if sub_accounts.has_next
sub_accounts = sub_accounts.previous_page  if sub_accounts.has_previous

To list archived sub accounts, use the optional status parameter set to archived

archived_accounts = Justifi::SubAccount.list(params: {status: "archived"})

Get Sub Accounts

sub_account = Justifi::SubAccount.get(sub_account_id: "acc_xyzs")

Create a Checkout Session

If you are using our hosted checkout solution, follow this to generate a session:

# create a payment intent (check params [here](https://developer.justifi.ai/#operation/CreateCheckoutSession))
py = Justifi::PaymentIntent.create(sub_account_id: "acc_xyz", params: {})

# create a session (check params [here](https://developer.justifi.ai/#operation/CreateCheckoutSession))
session = Justifi::CheckoutSession.create(params: {})

# use the session id to access your hosted checkout page at:
hosted_checkout_url = "https://hosted-checkout.justifi.ai/#{session.data.checkout_session_id}"

Webhook Signature Verification

Webhooks are secured by signature verification. An encrypted header is sent as a POST to your API endpoint (JUSTIFI-SIGNATURE), which will need to be decrypted and verified with the signature secret provided. You can use the JustiFi Ruby gem to validate the signature.

received_event = { id: "py_..." } # JustiFi webhook event
signature = "2463896d3cb..." # justifi-signature header
timestamp  = "1651076887..." # justifi-timestamp header
secret_key  = "sigk_2..." # secret key used for this webhook
Justifi::Webhook.verify_signature(received_event: received_event, timestamp: timestamp, secret_key: secret_key, signature: signature) # valid or not

Contributing

Release a new version of the gem

Follow the steps below to make a release:

  1. Increment the version number in lib/justifi/version.rb
  2. Run bundle
  3. Commit the following file changes to your branch
  • Gemfile.lock
  • lib/justifi/version.rb
  1. Create and Merge your PR to main

After this, the .github/workflows/build_and_release.yml will tag and release the gem to github packages.

Code of Conduct

Everyone interacting in the JustApi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

justifi-ruby's People

Contributors

jmatheus avatar wlads avatar pete2786 avatar nicklamuro avatar nciemniak avatar jnwaletzko avatar nhalm avatar

Watchers

Greg Blasko avatar Jon Erickson avatar  avatar  avatar

justifi-ruby's Issues

OpenStruct responses should contain nested OpenStructs

When you retrieve a payment method:

pm = Justifi::PaymentMethod.get(token: 'payment token')

the data itself comes back as an OpenStruct, but the data nested inside of it is a hash.
CleanShot 2023-07-12 at 15 14 24@2x

In other words, you can't write:

pm.data.card.brand

You have to access it like this:

pm.data.card[:brand]

This one is definitely a nitpick as it's easily programmed around. Long term, it feels like there should be consistency in how the data is being returned to make accessing it easier. E.g. I don't really care if it's all a Hash or an OpenStruct, but it probably shouldn't be both.

Support creation of Business entity

We need to upgrade our existing Hosted Onboarding flow, and avoid the upcoming deprecation:

Passing a sub_account_id to the iframe instead of a business_id is still supported but will be deprecated soon

As the docs say, we will need to create a Business entity, but that's not currently supported by this Ruby gem.

Passed in idempotency keys are ignored

There appear to be widespread issues with idempotency keys being ignored if they are passed in by an application.

The idempotently_request method takes an optional idempotency_key parameter:

def idempotently_request(path, method:, params:, headers:, idempotency_key: nil)
  idempotency_key ||= Justifi.get_idempotency_key
  headers[:idempotency_key] = idempotency_key

  retryable_request do
    send("execute_#{method}_request", path, params, headers)
  end
end

But within the actual CRUD operations it is often not passed through to that method. An example of that is when creating payments (notice that idempotenccy_key is part of the accepted parameters, but is not passed to idempotently_request:

def create(params: {}, headers: {}, idempotency_key: nil, seller_account_id: nil, sub_account_id: nil)
  Justifi.seller_account_deprecation_warning if seller_account_id
  headers[:sub_account] = sub_account_id || seller_account_id if sub_account_id || seller_account_id

  JustifiOperations.idempotently_request("/v1/payments",
    method: :post,
    params: params,
    headers: headers)
end

There are a lot of endpoints that do not respect passed in idempotency keys. I have not done an extensive look at the code, but I have noticed at least the following endpoints with this issue (create payment and capture payment being the important ones):

See @pete2786 comment below, these endpoints do not support idempotency, removing.

There are several other endpoints that do not appear to accept idempotency keys that I would expect to offer it:

I would love to see refund creation get idempotency keys if at all possible. We often use background jobs to handle payment operations, and not having an idempotency key on refunds makes me nervous. I definitely do not want a situation where our app accidentally issues a partial refund twice!

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.