Adding payment integrations

Cobot integrates with a number of payment processors for processing credit card, direct debit and other types of payments. You can find more details here: https://www.cobot.me/guides/credit-cards-&-direct-debit. If you need to support other payment methods, you can integrate them yourself, using our API and webhooks.

In order to do that, you will need to create your own small web application, responsible for:

  • Displaying various forms to the members of a Cobot space.
  • Handling Cobot webhooks and communication with the Cobot via API.
  • Handling communication with payment provider.

This can be part of your already existing application and can be implemented using any programming language/web framework.

Below you can find an example integration with an imaginary payment provider called LocalPay using Ruby and the Sinatra web framework. The code should be easy to follow even if you haven't used Ruby or Sinatra before and you will see how to integrate with LocalPay, who offers an API of their own for registering cards/accounts and for processing payments.

To understand the following flows, you need to understand the following Cobot concepts:

  • user: is just an email and a password so that people can log in.
  • space: represents a coworking space/office on Cobot.
  • membership: represents a coworking member of a space. All activity happens between a space and a membership, such as signing up, generating an invoice.
  • invoice: automatically generated by Cobot. It keeps information about the payable amount.
  • payment records: used to keep track of how much of an invoice has been paid. Once the total sum of all payment records equals the total payable amount of the invoice, the invoice's paid status changes to paid.

At the moment, payment integrations written according to this guide do not allow members to buy time/booking passes online. The integrations can only be used to pay for invoices.

This tutorial only works for payment providers that let you store a person'a payment details once and later let you process payments without user interaction. We'll be adding another tutorial for one-off payments, i.e. where each payment requires a user interaction.

When a new member signs up, you need to collect their payment details. For that you need to create a form in your own web app and display it in the member section of Cobot. When a member submits the form, it should forward the payment details to LocalPay and then store a returned card token in your database together with Cobot membership_id. This stored information will be used later to initialize the payment when a new invoice is created for the given membership.

First of all you will need a form. In a typical Sinatra app, this would be stored in /views/payment_method.erb:

<p>Please enter your payment details below:</p>

<form action="/payment_method" method="POST">
  <input type="text" name="card_number">
  <input type="text" name="name">
</form>

The names of the input fields depend on the payment provider you want to integrate with. For non credit card payments, you would have fields for bank account or other information instead of the card number.

The example Sinatra app looks like below. It only has one route to show the form, and one to handle the form submit:

# app.rb
require 'sinatra'
require 'rest-client' # to make API calls
require 'json'

# our API token to talk to the imaginary LocalPay API
LocalPayAccessToken = 'asdklfb98gpibu'

# renders the form from above
get '/' do
  erb :payment_method
end

# handle form submit
post '/payment_method' do
  card_number = params['card_number']
  name = params['name']

  # send card details to imaginary LocalPay API endpoint for creating new payment method
  response = RestClient.post(
    'http://api.localpay.com/payment_method',
    {card_number: card_number, name: name},
    {Authorization: LocalPayAccessToken}
  )

  card_token = JSON.parse(response.body)['card_token']

  # store card token and membership id to use later
  # to see more details about cobot_membership_id see the Authentication section of this tutorial
  store_card_token(card_token, cobot_membership_id)

  erb :success
end

The success page:

<p>Your payment details have been stored.</p>

For this form to actually work, you will have to add Cobot authentication. See the authentication section below to get more details.

For a good user experience, you want the payment method form to be embedded on Cobot. For that you will need to use the navigation links API.

This lets you create an iFrame on Cobot that points to your form:

RestClient.post(
  'https://demospace.cobot.me/api/navigation_links',
  {
    section: 'members',
    label: 'Payments',
    iframe_url: 'https://paymentapp.com/' # url of your application
  }
)

This call needs to be made for every space on Cobot that wants to use your app. If you only want to set up a single space, you can run this once from a console. Otherwise you might want to make this part of your web app's setup flow.

You can read more about displaying an iFrame in the member section here: embedding add-ons.

Every time Cobot generates an invoice, it can notify your app using a webhook.

"Webhooks are user-defined HTTP callbacks. They are usually triggered by some event [..] When that event occurs, the source site makes an HTTP request to the URL configured for the webhook." (wikipedia).

After receiving the webhook, you need to send a request to the LocalPay API to process the payment. After LocalPay confirms the transaction, you add the payment to the invoice on Cobot via API, so that the invoice on Cobot reflects its correct state.

To receive a webhook on invoice creation, you need to subscribe to the particular event by sending a request to the subscriptions endpoint on Cobot:

RestClient.post(
  'https://demospace.cobot.me/api/subscriptions',
  {
    event: "created_invoice",
    callback_url: 'https://paymentapp.com/webhook' # the webhook endpoint of you application, more details in the next section
  }
)

Just like with the navigation links, this needs to be done once per space on Cobot, so could either be a one-off script or part of a setup flow.

From now on, each time Cobot generates a new invoice, you will receive a POST request containing the invoice's url like this:

{"url": "https://co-up.cobot.me/api/invoices/12345"}

For an overview of Cobot webhooks, see the webhook API docs.

In order to receive the mentioned webhook POST request from Cobot and trigger the payment, you need to add another POST handler to your web app:

COBOT_ACCESS_TOKEN = '123xyz' # a Cobot access token can be copied from the website after creating a client application at https://www.cobot.me/oauth2_clients (see below)

post '/webhook' do
  # parse received request from Cobot
  url = JSON.parse(request.body.read.to_s)['url']

  # get invoice details from Cobot (more details: https://www.cobot.me/api-docs/invoices#get-invoice-details)
  invoice = RestClient.get(url, {Authorization: "Bearer #{COBOT_ACCESS_TOKEN}"})
  currency = invoice['currency']
  amount = invoice['payable_amount']
  membership_id = invoice['membership_id']

  # retrieve card token from your database
  card_token = load_card_token(membership_id)

  # initialize payment using LocalPay API endpoint for processing payments
  response = RestClient.post(
    'http://api.localpay.com/payments',
    {amount: amount, currency: currency, card_token: card_token},
    {Authorization: LocalPayAccessToken}
  )

  # imaginary successful response from LocalPay
  ok = JSON.parse(response.body)['ok']

  if ok
    # record payment on Cobot (see more: https://www.cobot.me/api-docs/payment-records#create-payment-record-mark-invoice-as-paid-)
    RestClient.post(
      "https://demospace.cobot.me/api/invoices#{invoice['id']}/payment_records",
      {
        amount: amount,
        paid_at: Date.today,
        note: 'LocalPay' # not required
      },
      {Authorization: "Bearer #{COBOT_ACCESS_TOKEN}"}
    )
  end

  # send HTTP OK back to Cobot
  head 200
end

For a production app, you need to add error handling:

  • the invoice could be deleted, in which case fetching the invoice would return HTTP 404
  • invoice['payable_amount'] could be zero, in which case no payment should be processed
  • invoice['membership_id'] could be null, in which case no payment should be processed
  • you might not have the card_token for that membership_id in your database
  • payment processing at LocalPay could fail, in which case you might want to create an activity on Cobot with the error message
  • any network errors should be retried, so this code should ideally run in a job queue

In order for the payment details form to work, you need to identify which user/member on Cobot is using it in that moment. The same goes for determining the Cobot membership_id identifier. For that you need to authenticate with Cobot using OAuth.

For applications written in Ruby, you could use our omniauth_cobot gem.

To see the process, follow the example implementation of the flow below:

# app.rb

# to obtain a client id/secet, create a client application at https://www.cobot.me/oauth2_clients
COBOT_CLIENT_ID = '123'
COBOT_CLIENT_SECRET = 'xyz'

# store session data in cookie
enable :sessions

get '/' do
  if logged_in?
    erb :payment_method
  else
    redirect '/auth'
  end
end

# redirects users to Cobot where they will log in, approve the use of your app, and then be redirected back to the /auth/callback path (see below)
get '/auth' do
  redirect "https://www.cobot.me/oauth/authorize?response_type=code&client_id=#{COBOT_CLIENT_ID}&redirect_uri=https://paymentapp.com/auth/callback&scope=read_user+read_invoices+write_subscriptions+write_payment_records"
end

# user comes back from Cobot after logging in
get '/auth/callback' do
  response = RestClient.post(
    'https://www.cobot.me/oauth/access_token',
    {
      client_id: COBOT_CLIENT_ID,
      client_secret: COBOT_CLIENT_SECRET,
      grant_type: 'authorization_code',
      code: params['code'] # Cobot added a code param that is needed to obtain an access token
    }
  )

  access_token = JSON.parse(response)['access_token']
  # store access token in the cookie for later
  session[:access_token] = access_token

  # back to payment method form
  redirect '/'
end

helpers do
  def logged_in?
    session[:access_token]
  end
end

Now that you have the access token in the session, you can implement the cobot_membership_id method used in the POST handler of the payment form. This allows you to store the card token together with an identifier, so that you can retrieve it later for payment processing.

COBOT_SUBOMAIN = 'demospace'

def cobot_membership_id
  user = JSON.parse(
    RestClient.get(
      'https://www.cobot.me/api/user',
      {Authorization: "Bearer #{session[:access_token]}"}
    ).response
  )

  # figure out the user's membership at the space you are dealing with, see https://www.cobot.me/api-docs/users#get-user-details
  membership = user['memberships'].find { |m| m['space_subdomain'] == COBOT_SUBOMAIN }
  membership['id']
end

Later on, you will use the membership_id to retrieve the card token again for payment processing (see the load_card_token call in the webhook handler further up).

back to index