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.
Relevant Cobot concepts
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.
Limitations
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.
Collect payment details
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:
require 'sinatra'
require 'rest-client'
require 'json'
LocalPayAccessToken = 'asdklfb98gpibu'
get '/' do
erb :payment_method
end
post '/payment_method' do
card_number = params['card_number']
name = params['name']
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(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/'
}
)
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.
Process Payments
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.
Subscribe to the webhook on Cobot
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'
}
)
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.
Implement the webhook callback in your app
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'
post '/webhook' do
url = JSON.parse(request.body.read.to_s)['url']
invoice = RestClient.get(url, {Authorization: "Bearer #{COBOT_ACCESS_TOKEN}"})
currency = invoice['currency']
amount = invoice['payable_amount']
membership_id = invoice['membership_id']
card_token = load_card_token(membership_id)
response = RestClient.post(
'http://api.localpay.com/payments',
{amount: amount, currency: currency, card_token: card_token},
{Authorization: LocalPayAccessToken}
)
ok = JSON.parse(response.body)['ok']
if ok
RestClient.post(
"https://demospace.cobot.me/api/invoices#{invoice['id']}/payment_records",
{
amount: amount,
paid_at: Date.today,
note: 'LocalPay'
},
{Authorization: "Bearer #{COBOT_ACCESS_TOKEN}"}
)
end
head 200
end
Recommended error handling
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
Cobot Authentication
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.
Authenticate member / Cobot user
For applications written in Ruby, you could use our omniauth_cobot gem.
To see the process, follow the example implementation of the flow below:
COBOT_CLIENT_ID = '123'
COBOT_CLIENT_SECRET = 'xyz'
enable :sessions
get '/' do
if logged_in?
erb :payment_method
else
redirect '/auth'
end
end
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
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']
}
)
access_token = JSON.parse(response)['access_token']
session[:access_token] = access_token
redirect '/'
end
helpers do
def logged_in?
session[:access_token]
end
end
Get Cobot membership_id
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
)
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).