Skip to content

Commit

Permalink
feat: Send receipt email to users (#3)
Browse files Browse the repository at this point in the history
* Updated to ruby 2.7
* Added Stripe integration
  * Update Stripe charge to make them send a receipt to the purchaser. More info at https://medium.com/typeforms-engineering-blog/send-a-receipt-after-accepting-payment-on-a-typeform-175e5261404d
  • Loading branch information
pablorc authored Feb 16, 2022
1 parent 1122b65 commit 7e94d25
Show file tree
Hide file tree
Showing 1,047 changed files with 173,831 additions and 35 deletions.
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ SLACK_API_KEY=
SLACK_TEAM=
SLACK_NOTIFICATIONS_WEBHOOK=
TYPEFORM_SECRET=
STRIPE_SECRET=
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
source 'https://rubygems.org'
ruby '2.5.3'
ruby '2.7.2'

gem 'sinatra'
gem 'dotenv'
gem 'httparty'
gem 'rack'
gem 'rack-contrib'
gem 'json'
gem 'stripe'
6 changes: 4 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ GEM
rack (~> 2.0)
rack-protection (= 2.0.5)
tilt (~> 2.0)
stripe (5.43.0)
tilt (2.0.9)

PLATFORMS
Expand All @@ -33,9 +34,10 @@ DEPENDENCIES
rack
rack-contrib
sinatra
stripe

RUBY VERSION
ruby 2.5.3p105
ruby 2.7.2p137

BUNDLED WITH
1.16.2
1.17.3
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ The Typeform form needs to be configured to add a Webhook pointing to the URL wh

### Dependencies

* Ruby 2.5.3
* Git (needed to clone the repo in order to install the gem)
- Ruby 2.5.3
- Git (needed to clone the repo in order to install the gem)

### How to install

Follow these steps to install:

1. Clone this repository: ```git clone https://github.com/MarsBased/slackform```
1. Clone this repository: `git clone https://github.com/MarsBased/slackform`
2. Install gems: `bundle install`
3. Configure environment variables. Use `.env.sample` to see the variables that need to be configured.

Expand All @@ -30,21 +30,23 @@ Just run `ruby app.rb`. It will start listening on port 4567. If you want to tes
Slackform is configured by using environment variables. In development, it includes `dotenv` to make it easier to configure, just create a `.env` file with all the variables. These are the variables you need to configure:

- **SLACK_API_KEY:** You can find it in the Slack API page while you are logged in your Slack team. Go to the [Slack Web API page](https://api.slack.com/web) and in the "Authentication" section you will be able to see or create the token for the team. Note that in this page you will see the API tokens for all the Slack teams where you are registered. Just copy the token of the team where you want to invite the new members.
- **SLACK_TEAM:** This is the name of the Slack team where you want to invite new members. This is just your Slack subdomain. For example, if you access Slack through ```https://coolteam.slack.com```, then the Slack team is ```coolteam```
- **SLACK_TEAM:** This is the name of the Slack team where you want to invite new members. This is just your Slack subdomain. For example, if you access Slack through `https://coolteam.slack.com`, then the Slack team is `coolteam`
- **EMAIL_FIELD_ID, FIRST_NAME_FIELD_ID and LAST_NAME_FIELD_ID:** A Slack invitation consists of 3 parameters: an email (required), a first name (optional) and a last name (optional). This variables are used to specify the Typeform fields that are used to extract each of the parameters. For each invitation parameter you need to specify the Typeform field id that will be used. See [how to check a typeform field id](#how-to-check-a-typeform-field-id) for details.
- **STRIPE_SECRET:** In order to automatically send receipt emails aftter the purchase, a key to Stripe is needed. Retrieve yours from the [Stripe Dashboard](https://dashboard.stripe.com/apikeys). We recommend using restricted keys, whose permissions can be cherry-picked to not allow full access to all Stripe data. In this case, this library only needs read and write access to _charges_.
- **TYPEFORM_SECRET: (Optional)** The secret configured in the Typeform Webhook.

### How to check a Typeform field ID

Follow these steps:

1. Open the Form edition page
![image](https://cloud.githubusercontent.com/assets/3403704/11236413/bb45c554-8dd9-11e5-8f03-9f3dbb611d30.png)
![image](https://cloud.githubusercontent.com/assets/3403704/11236413/bb45c554-8dd9-11e5-8f03-9f3dbb611d30.png)

2. Check the HTML of the field. In Chrome and other browsers you can just "Inspect Element" on the field.
![image](https://cloud.githubusercontent.com/assets/3403704/11236582/f57f2340-8dda-11e5-8d56-65b039952910.png)
![image](https://cloud.githubusercontent.com/assets/3403704/11236582/f57f2340-8dda-11e5-8d56-65b039952910.png)

3. Find the parent ```<li>``` element for the field and check its ```id``` attribute, it should be something like: ```field-12345678```
![image](https://cloud.githubusercontent.com/assets/3403704/11236716/b2af6c68-8ddb-11e5-9e50-5782336e8cce.png)
3. Find the parent `<li>` element for the field and check its `id` attribute, it should be something like: `field-12345678`
![image](https://cloud.githubusercontent.com/assets/3403704/11236716/b2af6c68-8ddb-11e5-9e50-5782336e8cce.png)

4. Finally remove the "field-" part and that's your id. For example, for "field-12345678" it would be "12345678".

Expand All @@ -62,7 +64,7 @@ To configure notifications, add the following variable:

Follow these steps to configure a new Webhook:

1. Open the Slack administration panel for the team where you want notifications to be posted. For example: ```https://coolteam.slack.com/admin```
1. Open the Slack administration panel for the team where you want notifications to be posted. For example: `https://coolteam.slack.com/admin`
2. In the side menu go to "Integrations"
3. Click "View" on "Incoming WebHooks"

Expand Down
5 changes: 4 additions & 1 deletion app.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
require 'dotenv/load'
require 'sinatra'
require 'httparty'
require 'dotenv/load'
require 'stripe'

require './app/api_gateway/base'
require './app/api_gateway/slack'

require './app/notifier/default'
require './app/notifier/slack'

require './app/receipt_email_sender'
require './app/slack_inviter'
require './app/typeform_response'

Expand All @@ -32,5 +34,6 @@
email = event.answer_for_field(ENV['EMAIL_FIELD_ID'])

SlackInviter.new.invite(first_name, last_name, email)
ReceiptEmailSender.new(event.form_id, event.response_token, email).send_email
end
end
50 changes: 30 additions & 20 deletions app/notifier/slack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,43 @@ def initialize(webhook)

def notify_successful_invitation(name, email)
message = "#{name} (#{email}) has been successfully invited."
data = {
attachments: [{
fallback: message,
text: message,
color: 'good'
}]
}

make_post('', body: data.to_json)
send_message(success_message_payload(message))
end

def notify_failed_invitation(name, email, error_message)
message = "Error while trying to invite #{name} (#{email})."
data = {
attachments: [{
fallback: message,
text: message,
color: 'danger',
fields: [{
title: 'Error',
value: error_message
}]
}]
}

make_post('', body: data.to_json)
send_message(error_message_payload(message, error_message))
end

def notify_successfully_sent_receipt_email(email)
message = "Receipt email sent to #{email}."

send_message(success_message_payload(message))
end

def notify_failed_sending_receipt_email(email, error_message)
message = "Error while trying to send receipt email to #{email}."

send_message(error_message_payload(message, error_message))
end

private

def send_message(payload)
make_post('', body: payload.to_json)
end

def success_message_payload(message)
{ attachments: [{ fallback: message, text: message, color: 'good' }] }
end

def error_message_payload(message, error_message)
{ attachments: [{ fallback: message,
text: message,
color: 'danger',
fields: [{ title: 'Error', value: error_message }] }] }
end
end
end
52 changes: 52 additions & 0 deletions app/receipt_email_sender.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
class ReceiptEmailSender
def initialize(form_id, response_token, email)
@form_id = form_id
@response_token = response_token
@email = email
end

def send_email
update_charge(user_charge.id)

handle_success
rescue Exception => error
handle_error(error)
end

private

def user_charge
return @charge if @charge
charge = stripe_api.list(limit: 10).find do |charge|
charge.metadata.typeform_form_id == @form_id &&
charge.metadata.typeform_response_id == @response_token
end

raise RuntimeError, "Charge not found in Stripe for email #{@email}" unless charge

@charge = charge
end

def update_charge(charge_id)
stripe_api.update(charge_id, receipt_email: @email)
end

def handle_success
notifier.notify_successfully_sent_receipt_email(@email)
end

def handle_error(error)
notifier.notify_failed_sending_receipt_email(@email, error.message)
end

def stripe_api
return @stripe_api if @stripe_api

::Stripe.api_key = ENV['STRIPE_SECRET']
@stripe_api = ::Stripe::Charge
end

def notifier
@notifier ||= Notifier::Slack.new(ENV['SLACK_NOTIFICATIONS_WEBHOOK'])
end
end
10 changes: 8 additions & 2 deletions app/typeform_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,18 @@ def response?
def answer_for_field(id)
answer = answers.find { |answer| answer['field']['id'] == id }
return unless answer

return answer.fetch('text') if answer['type'] == 'text'
return answer.fetch('email') if answer['type'] == 'email'
end

def form_id
@data['form_response']['form_id']
end

def response_token
@data['form_response']['token']
end

private

def event_type
Expand All @@ -25,5 +32,4 @@ def event_type
def answers
@data['form_response']['answers']
end

end
23 changes: 23 additions & 0 deletions vendor/bundle/ruby/2.5.0/bin/ruby_executable_hooks
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env ruby

title = "ruby #{ARGV*" "}"
$0 = ARGV.shift
Process.setproctitle(title) if Process.methods.include?(:setproctitle)

require 'rubygems'
begin
require 'executable-hooks/hooks'
Gem::ExecutableHooks.run($0)
rescue LoadError
warn "unable to load executable-hooks/hooks" if ENV.key?('ExecutableHooks_DEBUG')
end unless $0.end_with?('/executable-hooks-uninstaller')

content = File.read($0)

if (index = content.index("\n#!ruby\n")) && index > 0
skipped_content = content.slice!(0..index)
start_line = skipped_content.count("\n") + 1
eval content, binding, $0, start_line
else
eval content, binding, $0
end
27 changes: 27 additions & 0 deletions vendor/bundle/ruby/2.5.0/bin/stripe-console
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env ruby_executable_hooks
#
# This file was generated by RubyGems.
#
# The application 'stripe' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'

version = ">= 0.a"

str = ARGV.first
if str
str = str.b[/\A_(.*)_\z/, 1]
if str and Gem::Version.correct?(str)
version = str
ARGV.shift
end
end

if Gem.respond_to?(:activate_bin_path)
load Gem.activate_bin_path('stripe', 'stripe-console', version)
else
gem "stripe", version
load Gem.bin_path("stripe", "stripe-console", version)
end
Binary file added vendor/bundle/ruby/2.5.0/cache/stripe-5.43.0.gem
Binary file not shown.
Loading

0 comments on commit 7e94d25

Please sign in to comment.