SlideShare a Scribd company logo
1 of 81
Download to read offline
ELIXIR + STRIPE
HH ELIXIR MEETUP
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Visited Leipzig back in 2005

for a conference
Took the high speed train

to Paris
Hello Hamburg!
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
B.A.Sc in software engineering
M.Sc. and PhD in computer science
Worked with Deloitte, IBM, Canadian Federal Government
PHP, C#, Java, Ruby, Clojure
Elixir part-time only, 

but since the beginning

(3 tiny PRs in the elixir core)
Part-time professor at the University of Ottawa
Two kids, August (4) and Hayden (6)
Married 13 years to Ayana
Currently working at CrossFit 

(remotely, HQ located in California, USA)
Andrew Forward

aforward@gmail.com

@a4word

a4word.com

github.com/aforward

SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Let’s Get Started!
STRIPE
AWESOME PAYMENT GATEWAY
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://stripe.com/docs/api
WORKFLOW
SO HOW DO WE ACCEPT PAYMENTS?
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Enter user information
Share directly with Stripe
Returns unguessable token
Send payment 

token to your server
Apply charge

to Stripe 

account
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://stripe.com/docs/security
Keep CC information off your computer*
EXISTING CLIENTS
NO GREAT ELIXIR LIBRARY (YET/STILL?)
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://hex.pm/packages?search=stripe&sort=downloads
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://github.com/code-corps/stripity_stripe
PAYMENT GUI
WE NEED A UI TO ACCEPT PAYMENTS
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://github.com/aforward/stripe-ui
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
handler = StripeCheckout.configure({
key: $("#stripePkey").val(),
image: 'https://stripe.com/img/documentation/checkout/marketplace.png',
locale: 'auto',
token: function(stripeToken) {
// TBD, send information to server
}
});
handler.open({
name: $("#invoiceName").val(),
description: $("#invoiceDescription").val(),
amount: parseInt($("#invoiceAmount").val()) * 100,
currency: $("#invoiceCurrency").val()
};);
https://checkout.stripe.com/checkout.js
Share directly with Stripe
JS module provided by Stipe
Receive and react to payment token
Open popup to collect payment
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Returns unguessable token
{
"id": "tok_1A3QMREz7Te5wka71D40vfk1",
"card": {
"id": "card_1A3QMREz7Te5wka7XSzH1osR",
"name": "aforward@gmail.com",
"brand": "Visa",
"last4": "1111",
"object": "card",
"country": "US",
"funding": "unknown",
"exp_year": "2019",
"cvc_check": "pass",
"exp_month": "12",
"address_zip": "",
"address_city": "",
"address_line1": "",
"address_line2": "",
"address_state": "",
"dynamic_last4": "",
"address_country": "",
"address_zip_check": "",
"address_line1_check": "",
"tokenization_method": ""
},
"type": "card",
"used": "false",
"email": "aforward@gmail.com",
"object": "token",
"created": "1490969059",
"livemode": "false",
"client_ip": "69.159.206.244"
}
No sensitive CC information
The unguessable token
Amount is not provided, as

we have only requested authority

to make the charge
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Send payment 

token to your server
token: function(stripeToken) {
$.ajax({
type: "POST",
url: "api/tokens",
data: {token: {"stripe": stripeToken,
"invoice": entity.invoiceData}},
});
}
Forward on the

token to your server

for processing
That unguessable token
For this example, we are also passing

along an invoice full of details about

the payment
{
"stripe": {
"id": "tok_1A3QMREz7Te5wka71D40vfk1",
},
"invoice": {
"name": “Payment Gateway
"currency": "cad",
"description": "3 woggles"
}
}
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Append invoice information as well

to more easily apply the payment
Send payment 

token to your server
Pass along the stripe token (other

fields omitted here for brevity)
Anything client side is manipulable

from, well, the client. So beware!
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Apply charge to
Stripe account
iex(1)> StripePost.post(
...(1)> "https://api.stripe.com/v1/charges",
...(1)> "amount=10000&currency=cad&description=3+wozzle&source=pk_abc_123",
...(1)> [{"Authorization", "Bearer abc123"},
...(1)> {"Content-Type", "application/x-www-form-urlencoded"}])
Another REST call to Stripe APIs, but

now done in Elixir on the server
Endpoint for making charges
URL encoded data
Your stripe “sensitive” key

(only for server side transactions)
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
{
"id": "ch_1A3QMUEz7Te5wka7iSOjxEsY",
"paid": true,
"order": null,
"amount": 1900,
"object": "charge",
"review": null,
"source": {
"id": "card_1A3QMREz7Te5wka7XSzH1osR",
"name": "aforward@gmail.com",
"brand": "Visa",
"last4": "1111",
"object": "card",
"country": "US",
"funding": "unknown",
"customer": null,
"exp_year": 2019,
"metadata": {},
"cvc_check": "pass",
"exp_month": 12,
"address_zip": null,
"fingerprint": "FDyIAV2xCOcXsxWn",
"address_city": null,
"address_line1": null,
"address_line2": null,
"address_state": null,
"dynamic_last4": null,
"address_country": null,
"address_zip_check": null,
"address_line1_check": null,
"tokenization_method": null
},
"status": "succeeded",
"created": 1490969062,
"dispute": null,
"invoice": null,
"outcome": {
"type": "authorized",
"reason": null,
"risk_level": "normal",
"network_status": "approved_by_network",
"seller_message": "Payment complete."
},
"refunds": {
"url": "/v1/charges/ch_1A3QMUEz7Te5wka7iSOjxEsY/refunds",
"data": [],
"object": "list",
"has_more": false,
"total_count": 0
},
"captured": true,
"currency": "cad",
"customer": null,
"livemode": false,
"metadata": {},
"refunded": false,
"shipping": null,
"application": null,
"description": "3 woggles",
"destination": null,
"failure_code": null,
"on_behalf_of": null,
"fraud_details": {},
"receipt_email": null,
"receipt_number": null,
"transfer_group": null,
"amount_refunded": 0,
"application_fee": null,
"failure_message": null,
"source_transfer": null,
"balance_transaction": "txn_1A3QMVEz7Te5wka7eSD3L70J",
"statement_descriptor": null
}
And the sample response
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
ELIXIR CLIENT
TALKING TO STRIPE API
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://github.com/aforward/stripe-post
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
@doc"""
Post a message to the Stripe API by providing all the necessary
information. The answer will be
If successful
{status_code, body}
Under error
{:error, reason}
"""
def post(url, body, headers) do
case HTTPoison.post(url, body, headers) do
{:ok, %HTTPoison.Response{status_code: status_code, body: body}} ->
{status_code, Poison.decode!(body)}
{:error, %HTTPoison.Error{reason: reason}} ->
{:error, reason}
end
end
Stripe endpoint
URL encoded data
Authentication + other headers
Delegate to HTTPoison (I prefer its API to 

alternatives like HTTPotion)
Similar to {:ok, object} tuple response, except here it’s the

HTTP status code response (e.g. 200 for OK)
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
iex(1)> StripePost.post(
...(1)> "https://api.stripe.com/v1/charges",
...(1)> "amount=10000&currency=cad&description=3+wozzle&source=pk_abc_123",
...(1)> [{"Authorization", "Bearer abc123"},
...(1)> {"Content-Type", "application/x-www-form-urlencoded"}])
16:41:29.215 [error] SSL: :certify: tls_connection.erl:704:Fatal error: handshake
failure - malformed_handshake_data
{:error, {:tls_alert, 'handshake failure'}}
Underlying error with HTTPosion 

(mix deps.update --all)
Not a legit authentication token
Not a legit payment token
And too much “raw” code

here…
{401,
%{"error" => %{"message" => "Invalid API Key provided: **c123",
"type" => "invalid_request_error"}}}
Contrived example, but showing

that indeed we are hitting the API
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
@doc"""
Charge an account with the following body configurations
body = %{amount: 10000, currency: "cad", description: "3 wozzle", source: "pk_abc_123"}
configs = %{secret_key: "sk_test_abc123"}
"""
def charge(body, configs) do
post(Api.url <> "/charges", encode_body(body), headers(configs))
end
Constant (which 

could/should be configurable)
Helper to convert maps to

URL encoded bodies
Helper to convert maps

to valid HTTPosion headers
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
iex(1)> StripePost.charge(
...(1)> %{amount: 10000,
...(1)> currency: "cad",
...(1)> description: "3 wozzle",
...(1)> source: "pk_abc_123"},
...(1)> %{secret_key: "sk_test_abc123"})
{401,
%{"error" => %{"message" => "Invalid API Key provided: **c123",
"type" => "invalid_request_error"}}}
Still contrived
But at least now more elixir like
Passing in the secret key directly

is OK, but not practical
As you will have one for testing

and one for production, so

it’s really more a config
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
use Mix.Config
config :stripe_post,
secret_key: "sk_test_abc123",
public_key: "pk_test_def456"
iex(1)> StripePost.charge(
...(1)> %{amount: 10000,
...(1)> currency: "cad",
...(1)> description: "3 wozzle",
...(1)> source: "pk_abc_123"})
So, let’s leverage Elixir’s config instead
No we can charge without

providing the configs

directly
Note, that is the payment token

not the sensitive secret key from above

so it’s still part of the API
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Not real keys
Sensitive keys

are for the server

(“s” for secret)
Public keys are

for the client

(i.e. JavaScript)
Production keys

separate from

testing
STORING RESPONSES
TRUST BUT VERIFY
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://github.com/aforward/stripe-callbacks
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
iex(1)> StripeCallbacks.process(%{
...(1)> "stripe" => %{"id" => "pk_abc_123"},
...(1)> "invoice" => %{
...(1)> "amount" => 2000,
...(1)> "currency" => "cad",
...(1)> "description" => "3 wozzle"}})
Still contrived
But we log to a database
Split between expected 

response from stripe

and the “charge” invoice info
10:22:01.870 [debug] QUERY OK db=3.1ms
UPDATE "tokens" SET "token_status" = $1, "updated_at" = $2 WHERE "id" = $3 ["invalid", {{2017, 4, 25}, {14, 22, 1, 867165}}, 1]
{:ok,
%StripeCallbacks.Token{__meta__: #Ecto.Schema.Metadata<:loaded, "tokens">,
data: %{"invoice" => %{"amount" => 2000, "currency" => "cad",
"description" => "3 wozzle"}, "stripe" => %{"id" => "pk_abc_123"}}, id: 1,
inserted_at: ~N[2017-04-25 14:22:01.075526], token_status: "invalid",
updated_at: ~N[2017-04-25 14:22:01.867165]}}
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Store tokens from the client in the database
Also store the response from Stripe trying to make the charge
Contrived example, 

so we are still in “error”

but not for long
stripe_callbacks_dev=# select data, token_status from tokens;
data | token_status
-----------------------------------------------------------------------------+--------------
{ "stripe": {"id": "pk_abc_123"}, | invalid
"invoice": {"amount": 2000, "currency": "cad", "description": "3 wozzle"}} |
stripe_callbacks_dev=# select data, response_status, status_code, token_id from responses;
data | response_status | status_code | token_id
-----------------------------------------------------------------+-----------------+-------------+----------
{“error”. : {"type": "invalid_request_error", | failure | 400 | 1

"message": "Invalid token id: pk_abc_123"}} | | |
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
ELIXIR PROJECTS
HOUSE KEEPING
NEW PROJECTS
ALMOST ALWAYS --SUP
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
mix new <my-project> --sup --module MyProject --app my_project
11:32 /tmp $ mix new stripe-callbacks --sup --module StripeCallbacks --app stripe_callbacks
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/stripe_callbacks.ex
* creating lib/stripe_callbacks/application.ex
* creating test
* creating test/test_helper.exs
* creating test/stripe_callbacks_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd stripe-callbacks
mix test
Run "mix help" for more commands.
DOCUMENTATION
YOUR DOCS ARE YOUR TESTS
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
@doc"""
Encode the provided hash map for the URL.
## Examples
iex> StripePost.Api.encode_body(%{a: "one", b: "two"})
"a=one&b=two"
iex> StripePost.Api.encode_body(%{a: "o ne"})
"a=o+ne"
"""
def encode_body(map), do: URI.encode_query(map)
Simply delegates to available

core Elixir function
But documentation is a 

first class property of Elixir
And testing too!
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://hexdocs.pm/stripe_post/StripePost.Api.html
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
@doc"""
Build the headers for your API
## Examples
iex> StripePost.Api.headers(%{content_type: "application/json", secret_key: "abc123"})
[{"Authorization", "Bearer abc123"}, {"Content-Type", "application/json"}]
iex> StripePost.Api.headers(%{secret_key: "abc123"})
[{"Authorization", "Bearer abc123"}, {"Content-Type", "application/x-www-form-urlencoded"}]
iex> StripePost.Api.headers(%{})
[{"Authorization", "Bearer sk_test_abc123"}, {"Content-Type", "application/x-www-form-urlencoded"}]
iex> StripePost.Api.headers()
[{"Authorization", "Bearer sk_test_abc123"}, {"Content-Type", "application/x-www-form-urlencoded"}]
"""
def headers(), do: headers(%{})
def headers(nil), do: headers(%{})
def headers(data) do
h = %{content_type: "application/x-www-form-urlencoded"}
|> Map.merge(app_headers())
|> Map.merge(reject_nil(data))
[{"Authorization", "Bearer #{h[:secret_key]}"},
{"Content-Type", h[:content_type]}]
end
Pattern matching to avoid “if”ing out

bad, missing, defaulted data
Generating headers in a structure

like HTTPosion wants
Examples, examples, examples
CONFIGS
EVERYTHING IN ELIXIR IS IN ELIXIR
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
use Mix.Config
config :stripe_post,
secret_key: "sk_test_abc123",
public_key: "pk_test_def456"
iex(1)> StripePost.charge(
...(1)> %{amount: 10000,
...(1)> currency: "cad",
...(1)> description: "3 wozzle",
...(1)> source: "pk_abc_123"})
So, let’s leverage Elixir’s config instead
No we can charge without

providing the configs

directly
Note, that is the payment token

not the sensitive secret key from above

so it’s still part of the API
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
use Mix.Config
# You will need to configure your public and private keys in Stripe.
# optionally you can also set the default content type
# https://dashboard.stripe.com/account/apikeys
# config :stripe_post,
# secret_key: "sk_test_abc123"
# public_key: "pk_test_abc123"
# content_type: "application/x-www-form-urlencoded"
#
# Within the application we will reference these using
# Application.get_env(:stripe_post, :secret_key)
# Application.get_env(:stripe_post, :public_key)
# Application.get_env(:stripe_post, :content_type)
#
import_config "#{Mix.env}.exs"
Elixir convention for 

loading MIX_ENV specific

configs
And then each env, typically

dev, test and prod can be

separately configured
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
DO NOT commit sensitive

information to your repo
If some prod configs are not sensitive

then split them between prod.exs and

prod.secret.exs
Make sure you .gitignore the file
But, DO add a .example file, to help others (and your future self)

remember what production configs need to be set
CONTINUOUS TESTING
STREAMLINE YOUR TDD EFFORTS
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
mix test.watch
https://hex.pm/packages/mix_test_watch
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
autotesting rocks!
11:23 ~/sin/projects/current/stripe-callbacks (master)$ mix test.watch
===> Fetching pc ({pkg,<<"pc">>,<<"1.4.0">>})
===> Downloaded package, caching at /Users/aforward/.cache/rebar3/hex/default/packages/pc-1.4.0.tar
===> Compiling pc
===> Fetching rebar3_hex ({pkg,<<"rebar3_hex">>,<<"3.0.0">>})
===> Downloaded package, caching at /Users/aforward/.cache/rebar3/hex/default/packages/
rebar3_hex-3.0.0.tar
===> Compiling rebar3_hex
===> Compiling fs
===> Compiling /Users/aforward/sin/projects/current/stripe-callbacks/deps/fs/c_src/mac/cli.c
===> Compiling /Users/aforward/sin/projects/current/stripe-callbacks/deps/fs/c_src/mac/compat.c
===> Compiling /Users/aforward/sin/projects/current/stripe-callbacks/deps/fs/c_src/mac/main.c
===> Linking /Users/aforward/sin/projects/current/stripe-callbacks/deps/fs/priv/mac_listener
==> mix_test_watch
Compiling 12 files (.ex)
Generated mix_test_watch app
Running tests...
Compiling 2 files (.ex)
Generated stripe_callbacks app
..
Finished in 0.03 seconds
2 tests, 0 failures
Randomized with seed 587946
Each time you save a file in your project

the tests are re-run.
Unlike some other languages, your tests will be so fast

mix_test_watch does not need to be smart about

which tests to run
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://medium.com/@a4word/continuous-testing-with-elixir-ddc1107c5cc0
Learn more about continuous testing
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Learn more A LOT MORE 

about continuous testing
ECTO
MANAGE YOUR DB SCHEMA LIKE A PRO
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Grab ecto as well as the appropriate

underlying DB manager (e.g. postgrex

for PostgreSQL)
Create your Repo (thank you 

macros… just 3 LOC
Make sure your application 

supervises your database
Tell you application which

ecto_repos you care about
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Support different DBs 

for different environments
Uncomment this line in your config.exs
Sample configuration

for your dev database
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
So add this if you want your

`mix ecto.migrate` to work
I really like small commits, so I usually setup

and commit an empty schema
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
/stripe-callbacks (master)$ mix ecto.gen.migration add_tokens
* creating priv/repo/migrations
* creating priv/repo/migrations/20170325160151_add_tokens.exs
Your database is nothing more

than a bunch of schema migrations


Ecto provides a create 

language to easily create 

tables, indexes, etc.
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Ecto is NOT an ORM
Describe your DB

schema
Configure your entity

as a JSON response

for RESTful APIs
Changesets are confusing,

until they aren’t
TESTING WITH DBS
ADDITIONAL SETUP FOR ECTO TESTS
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
This allows for asynchronous

testing
Ecto is chatty, so this will 

disable much of the logging
But now each test needs it’s own

DB connection
All set, go forth and test
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
1) test create (default status) (StripeCallbacks.TokenTest)
test/token_test.exs:22
** (MatchError) no match of right hand side value: {:error, i
%DBConnection.ConnectionError{message:
"connection not available because of disconnection"}}
stacktrace:
test/token_test.exs:8: StripeCallbacks.TokenTest.__ex_unit_setup_0/1
test/token_test.exs:1: StripeCallbacks.TokenTest.__ex_unit__/2
Oopses, no database
Override test* to ensure your database is properly up and running
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
$ mix test.watch
Running tests...
Compiling 6 files (.ex)
Generated stripe_callbacks app
** (Mix) The database for StripeCallbacks.Repo couldn't be dropped:
ERROR 55006 (object_in_use): database "stripe_callbacks_test"
is being accessed by other users
There are 10 other sessions using the database.
But now we broke `mix test.watch`
Safe way to support a “one off” testing versus test.watch

but you need to call `MIX_ENV=test mix test.once`
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://github.com/lpil/mix-test.watch/pull/70
https://github.com/lpil/mix-test.watch/pull/71
Support for just running something “once”
Circumventing (unnecessary?) internal

call that break testing that includes

database setup
But, we can do better
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Fix feature-bug for creating / dropping

databases as part of your testing
To avoid re-creating the database

on each test run (renamed from

just “test”)
Enable creating your

database just once when 

start `mix test.run` 

(instead of each run)
But, you have to live on the edge
CHANGESETS
JUST ANOTHER STRUCT
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Schema to describe

your data
Key/Value inputs from

a user (very raw)
Data transformations

(cleaned, defaulted, 

derived, validated)
+
Insert into database
Or, present

errors for correction

to the user
This IS a changeset

(just a data structure)
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
iex(1)> schema = %{first_name: :string, last_name: :string, email: :string}
%{email: :string, first_name: :string, last_name: :string}
iex(2)> params = %{"first_name" => "James", "last_name" => "Url"}
%{"first_name" => "James", "last_name" => "Url"}
iex(3)> changeset = Ecto.Changeset.cast({%{}, schema}, params, Map.keys(schema))
iex(4)> changeset.changes
%{first_name: "James", last_name: "Url"}
iex(5)> changeset.valid?
true
A changeset is just a tuple

with starting data, and the schema
#Ecto.Changeset<action: nil, changes: %{first_name: "James", last_name: "Url"},
errors: [], data: %{}, valid?: true>
The params are the user input

so usually strings as keys
What are the 

valid fields
And it’s really just data structure coming back
That you can access like any struct
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
defmodule StripeCallbacks.Token do
use Ecto.Schema
import Ecto.Changeset
schema "tokens" do
field :data, :map
field :token_status, :string
timestamps()
end
def changeset(model, params  %{}) do
model
|> cast(params, [:data, :token_status])
end
end
The ecto struct (e.g. %Token{})
Based on the schema
Params and valid fields still apply
%Token{}
|> Token.changeset( 

%{"data" => %{"apples" => "red"},
"token_status" => “processed"})
|> Repo.insert
Using changes 

to insert data
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
@doc """
Creates a changeset based on the `model` and `params`.
If no params are provided, an invalid changeset is returned
with no validation performed.
"""
def changeset(model, params  %{}) do
model
|> cast(params, [:pubid, :name, :slug, :owner, :status])
|> ChangesetMerger.defaulted(:name, "LiveCode")
|> ChangesetMerger.defaulted(:owner, "Teacher")
|> ChangesetMerger.Slug.derive_if_missing
|> ChangesetMerger.Token.defaulted(:pubid, 4)
|> ChangesetMerger.defaulted(:status, "created")
end
A much more involved changes

that includes default values, 

slug (aka URL safe) generation

and tokens
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://hex.pm/packages/changeset_merger
SERVICES
BOOTSTRAP FOR YOUR (MICRO)SERVICE
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://medium.com/@a4word/building-small-elixir-services-using-ecto-without-phoenix-1aba00b53e54
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
https://pragdave.me/blog/2017/04/18/elixir-project-generator.html
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
mix archive.install mix_templates
mix archive.install mix_generator
mix template.install hex gen_template_ecto_service
Install projects from Dave

Thomas
Install custom template
19:25 /tmp $ mix gen ecto_service foo_bar
* creating ./foo_bar
* creating ./foo_bar/.gitignore
* creating ./foo_bar/.iex.exs
* creating ./foo_bar/config
* creating ./foo_bar/config/config.exs
* creating ./foo_bar/config/dev.exs
* creating ./foo_bar/config/test.exs
* creating ./foo_bar/lib
* creating ./foo_bar/lib/foo_bar
* creating ./foo_bar/lib/foo_bar/action.ex
* creating ./foo_bar/lib/foo_bar/application.ex
* creating ./foo_bar/lib/foo_bar/repo.ex
* creating ./foo_bar/lib/foo_bar.ex
* creating ./foo_bar/mix.exs
* creating ./foo_bar/priv
* creating ./foo_bar/priv/repo
* creating ./foo_bar/priv/repo/migrations
* creating ./foo_bar/priv/repo/migrations/20170418164826_create_actions.exs
* creating ./foo_bar/README.md
* creating ./foo_bar/test
* creating ./foo_bar/test/foo_bar_test.exs
* creating ./foo_bar/test/action_test.exs
* creating ./foo_bar/test/test_helper.exs
Successfully generated foo_bar in .
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
Sets up Ecto, an actions schema

(to log actions in your service)

and mix test.watch
An alternative to `mix new` that allows you

to easily generate projects with various

styles of project structures.
19:25 /tmp $ mix gen ecto_service foo_bar
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE

More Related Content

Similar to Accepting payments using Stripe and Elixir

SDKs, the good the bad the ugly - Japan
SDKs, the good the bad the ugly - JapanSDKs, the good the bad the ugly - Japan
SDKs, the good the bad the ugly - Japantristansokol
 
Context-aware application development with FIWARE #CPBR8
Context-aware application development with FIWARE #CPBR8Context-aware application development with FIWARE #CPBR8
Context-aware application development with FIWARE #CPBR8Fermin Galan
 
SH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptx
SH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptxSH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptx
SH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptxMongoDB
 
SH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptx
SH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptxSH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptx
SH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptxMongoDB
 
FIWARE Developers Week_ Introduction to Managing Context Information at Large...
FIWARE Developers Week_ Introduction to Managing Context Information at Large...FIWARE Developers Week_ Introduction to Managing Context Information at Large...
FIWARE Developers Week_ Introduction to Managing Context Information at Large...FIWARE
 
201410 2 fiware-orion-contextbroker
201410 2 fiware-orion-contextbroker201410 2 fiware-orion-contextbroker
201410 2 fiware-orion-contextbrokerFIWARE
 
Data Mining Open Ap Is
Data Mining Open Ap IsData Mining Open Ap Is
Data Mining Open Ap Isoscon2007
 
The Future of Progressive Web Apps - View Source conference, Berlin 2016
The Future of Progressive Web Apps - View Source conference, Berlin 2016The Future of Progressive Web Apps - View Source conference, Berlin 2016
The Future of Progressive Web Apps - View Source conference, Berlin 2016Robert Nyman
 
Hypermedia API’s
Hypermedia API’s Hypermedia API’s
Hypermedia API’s 3camp
 
GraphQL - when REST API is to less - lessons learned
GraphQL - when REST API is to less - lessons learnedGraphQL - when REST API is to less - lessons learned
GraphQL - when REST API is to less - lessons learnedMarcinStachniuk
 
Web Forms People Don't Hate
Web Forms People Don't HateWeb Forms People Don't Hate
Web Forms People Don't Hatecliener
 
0 to 60 with AWS AppSync: Rapid Development Techniques for Mobile APIs (MOB32...
0 to 60 with AWS AppSync: Rapid Development Techniques for Mobile APIs (MOB32...0 to 60 with AWS AppSync: Rapid Development Techniques for Mobile APIs (MOB32...
0 to 60 with AWS AppSync: Rapid Development Techniques for Mobile APIs (MOB32...Amazon Web Services
 
The Future of the Web - Cold Front conference 2016
The Future of the Web - Cold Front conference 2016The Future of the Web - Cold Front conference 2016
The Future of the Web - Cold Front conference 2016Robert Nyman
 
Bare-knuckle web development
Bare-knuckle web developmentBare-knuckle web development
Bare-knuckle web developmentJohannes Brodwall
 
How to Leverage APIs for SEO #TTTLive2019
How to Leverage APIs for SEO #TTTLive2019How to Leverage APIs for SEO #TTTLive2019
How to Leverage APIs for SEO #TTTLive2019Paul Shapiro
 
MongoDB Analytics: Learn Aggregation by Example - Exploratory Analytics and V...
MongoDB Analytics: Learn Aggregation by Example - Exploratory Analytics and V...MongoDB Analytics: Learn Aggregation by Example - Exploratory Analytics and V...
MongoDB Analytics: Learn Aggregation by Example - Exploratory Analytics and V...MongoDB
 
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代Shengyou Fan
 
Serverless Microservices Communication with Amazon EventBridge
Serverless Microservices Communication with Amazon EventBridgeServerless Microservices Communication with Amazon EventBridge
Serverless Microservices Communication with Amazon EventBridgeSheenBrisals
 

Similar to Accepting payments using Stripe and Elixir (20)

SDKs, the good the bad the ugly - Japan
SDKs, the good the bad the ugly - JapanSDKs, the good the bad the ugly - Japan
SDKs, the good the bad the ugly - Japan
 
Drupal Mobile
Drupal MobileDrupal Mobile
Drupal Mobile
 
Context-aware application development with FIWARE #CPBR8
Context-aware application development with FIWARE #CPBR8Context-aware application development with FIWARE #CPBR8
Context-aware application development with FIWARE #CPBR8
 
SH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptx
SH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptxSH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptx
SH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptx
 
SH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptx
SH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptxSH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptx
SH 1 - SES 2 part 2 - Tel Aviv MDBlocal - Eliot Keynote.pptx
 
FIWARE Developers Week_ Introduction to Managing Context Information at Large...
FIWARE Developers Week_ Introduction to Managing Context Information at Large...FIWARE Developers Week_ Introduction to Managing Context Information at Large...
FIWARE Developers Week_ Introduction to Managing Context Information at Large...
 
201410 2 fiware-orion-contextbroker
201410 2 fiware-orion-contextbroker201410 2 fiware-orion-contextbroker
201410 2 fiware-orion-contextbroker
 
Data Mining Open Ap Is
Data Mining Open Ap IsData Mining Open Ap Is
Data Mining Open Ap Is
 
The Future of Progressive Web Apps - View Source conference, Berlin 2016
The Future of Progressive Web Apps - View Source conference, Berlin 2016The Future of Progressive Web Apps - View Source conference, Berlin 2016
The Future of Progressive Web Apps - View Source conference, Berlin 2016
 
Ams adapters
Ams adaptersAms adapters
Ams adapters
 
Hypermedia API’s
Hypermedia API’s Hypermedia API’s
Hypermedia API’s
 
GraphQL - when REST API is to less - lessons learned
GraphQL - when REST API is to less - lessons learnedGraphQL - when REST API is to less - lessons learned
GraphQL - when REST API is to less - lessons learned
 
Web Forms People Don't Hate
Web Forms People Don't HateWeb Forms People Don't Hate
Web Forms People Don't Hate
 
0 to 60 with AWS AppSync: Rapid Development Techniques for Mobile APIs (MOB32...
0 to 60 with AWS AppSync: Rapid Development Techniques for Mobile APIs (MOB32...0 to 60 with AWS AppSync: Rapid Development Techniques for Mobile APIs (MOB32...
0 to 60 with AWS AppSync: Rapid Development Techniques for Mobile APIs (MOB32...
 
The Future of the Web - Cold Front conference 2016
The Future of the Web - Cold Front conference 2016The Future of the Web - Cold Front conference 2016
The Future of the Web - Cold Front conference 2016
 
Bare-knuckle web development
Bare-knuckle web developmentBare-knuckle web development
Bare-knuckle web development
 
How to Leverage APIs for SEO #TTTLive2019
How to Leverage APIs for SEO #TTTLive2019How to Leverage APIs for SEO #TTTLive2019
How to Leverage APIs for SEO #TTTLive2019
 
MongoDB Analytics: Learn Aggregation by Example - Exploratory Analytics and V...
MongoDB Analytics: Learn Aggregation by Example - Exploratory Analytics and V...MongoDB Analytics: Learn Aggregation by Example - Exploratory Analytics and V...
MongoDB Analytics: Learn Aggregation by Example - Exploratory Analytics and V...
 
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
 
Serverless Microservices Communication with Amazon EventBridge
Serverless Microservices Communication with Amazon EventBridgeServerless Microservices Communication with Amazon EventBridge
Serverless Microservices Communication with Amazon EventBridge
 

Recently uploaded

Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobeapidays
 
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​Bhuvaneswari Subramani
 
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Zilliz
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesrafiqahmad00786416
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businesspanagenda
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FMESafe Software
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAndrey Devyatkin
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...Zilliz
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistandanishmna97
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingEdi Saputra
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoffsammart93
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...apidays
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusZilliz
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodJuan lago vázquez
 
Six Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal OntologySix Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal Ontologyjohnbeverley2021
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdfSandro Moreira
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDropbox
 

Recently uploaded (20)

+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
 
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with Milvus
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Six Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal OntologySix Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal Ontology
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 

Accepting payments using Stripe and Elixir

  • 1. ELIXIR + STRIPE HH ELIXIR MEETUP
  • 2. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
  • 3. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Visited Leipzig back in 2005
 for a conference Took the high speed train
 to Paris Hello Hamburg!
  • 4. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE B.A.Sc in software engineering M.Sc. and PhD in computer science Worked with Deloitte, IBM, Canadian Federal Government PHP, C#, Java, Ruby, Clojure Elixir part-time only, 
 but since the beginning
 (3 tiny PRs in the elixir core) Part-time professor at the University of Ottawa Two kids, August (4) and Hayden (6) Married 13 years to Ayana Currently working at CrossFit 
 (remotely, HQ located in California, USA) Andrew Forward
 aforward@gmail.com
 @a4word
 a4word.com
 github.com/aforward

  • 5. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Let’s Get Started!
  • 7. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
  • 8. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
  • 9. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
  • 10. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
  • 11. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://stripe.com/docs/api
  • 12. WORKFLOW SO HOW DO WE ACCEPT PAYMENTS?
  • 13. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Enter user information Share directly with Stripe Returns unguessable token Send payment 
 token to your server Apply charge
 to Stripe 
 account
  • 14. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://stripe.com/docs/security Keep CC information off your computer*
  • 15. EXISTING CLIENTS NO GREAT ELIXIR LIBRARY (YET/STILL?)
  • 16. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://hex.pm/packages?search=stripe&sort=downloads
  • 17. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://github.com/code-corps/stripity_stripe
  • 18. PAYMENT GUI WE NEED A UI TO ACCEPT PAYMENTS
  • 19. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://github.com/aforward/stripe-ui
  • 20. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE handler = StripeCheckout.configure({ key: $("#stripePkey").val(), image: 'https://stripe.com/img/documentation/checkout/marketplace.png', locale: 'auto', token: function(stripeToken) { // TBD, send information to server } }); handler.open({ name: $("#invoiceName").val(), description: $("#invoiceDescription").val(), amount: parseInt($("#invoiceAmount").val()) * 100, currency: $("#invoiceCurrency").val() };); https://checkout.stripe.com/checkout.js Share directly with Stripe JS module provided by Stipe Receive and react to payment token Open popup to collect payment
  • 21. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
  • 22. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Returns unguessable token { "id": "tok_1A3QMREz7Te5wka71D40vfk1", "card": { "id": "card_1A3QMREz7Te5wka7XSzH1osR", "name": "aforward@gmail.com", "brand": "Visa", "last4": "1111", "object": "card", "country": "US", "funding": "unknown", "exp_year": "2019", "cvc_check": "pass", "exp_month": "12", "address_zip": "", "address_city": "", "address_line1": "", "address_line2": "", "address_state": "", "dynamic_last4": "", "address_country": "", "address_zip_check": "", "address_line1_check": "", "tokenization_method": "" }, "type": "card", "used": "false", "email": "aforward@gmail.com", "object": "token", "created": "1490969059", "livemode": "false", "client_ip": "69.159.206.244" } No sensitive CC information The unguessable token Amount is not provided, as
 we have only requested authority
 to make the charge
  • 23. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Send payment 
 token to your server token: function(stripeToken) { $.ajax({ type: "POST", url: "api/tokens", data: {token: {"stripe": stripeToken, "invoice": entity.invoiceData}}, }); } Forward on the
 token to your server
 for processing That unguessable token For this example, we are also passing
 along an invoice full of details about
 the payment
  • 24. { "stripe": { "id": "tok_1A3QMREz7Te5wka71D40vfk1", }, "invoice": { "name": “Payment Gateway "currency": "cad", "description": "3 woggles" } } SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Append invoice information as well
 to more easily apply the payment Send payment 
 token to your server Pass along the stripe token (other
 fields omitted here for brevity) Anything client side is manipulable
 from, well, the client. So beware!
  • 25. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Apply charge to Stripe account iex(1)> StripePost.post( ...(1)> "https://api.stripe.com/v1/charges", ...(1)> "amount=10000&currency=cad&description=3+wozzle&source=pk_abc_123", ...(1)> [{"Authorization", "Bearer abc123"}, ...(1)> {"Content-Type", "application/x-www-form-urlencoded"}]) Another REST call to Stripe APIs, but
 now done in Elixir on the server Endpoint for making charges URL encoded data Your stripe “sensitive” key
 (only for server side transactions)
  • 26. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE { "id": "ch_1A3QMUEz7Te5wka7iSOjxEsY", "paid": true, "order": null, "amount": 1900, "object": "charge", "review": null, "source": { "id": "card_1A3QMREz7Te5wka7XSzH1osR", "name": "aforward@gmail.com", "brand": "Visa", "last4": "1111", "object": "card", "country": "US", "funding": "unknown", "customer": null, "exp_year": 2019, "metadata": {}, "cvc_check": "pass", "exp_month": 12, "address_zip": null, "fingerprint": "FDyIAV2xCOcXsxWn", "address_city": null, "address_line1": null, "address_line2": null, "address_state": null, "dynamic_last4": null, "address_country": null, "address_zip_check": null, "address_line1_check": null, "tokenization_method": null }, "status": "succeeded", "created": 1490969062, "dispute": null, "invoice": null, "outcome": { "type": "authorized", "reason": null, "risk_level": "normal", "network_status": "approved_by_network", "seller_message": "Payment complete." }, "refunds": { "url": "/v1/charges/ch_1A3QMUEz7Te5wka7iSOjxEsY/refunds", "data": [], "object": "list", "has_more": false, "total_count": 0 }, "captured": true, "currency": "cad", "customer": null, "livemode": false, "metadata": {}, "refunded": false, "shipping": null, "application": null, "description": "3 woggles", "destination": null, "failure_code": null, "on_behalf_of": null, "fraud_details": {}, "receipt_email": null, "receipt_number": null, "transfer_group": null, "amount_refunded": 0, "application_fee": null, "failure_message": null, "source_transfer": null, "balance_transaction": "txn_1A3QMVEz7Te5wka7eSD3L70J", "statement_descriptor": null } And the sample response
  • 27. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
  • 29. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://github.com/aforward/stripe-post
  • 30. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE @doc""" Post a message to the Stripe API by providing all the necessary information. The answer will be If successful {status_code, body} Under error {:error, reason} """ def post(url, body, headers) do case HTTPoison.post(url, body, headers) do {:ok, %HTTPoison.Response{status_code: status_code, body: body}} -> {status_code, Poison.decode!(body)} {:error, %HTTPoison.Error{reason: reason}} -> {:error, reason} end end Stripe endpoint URL encoded data Authentication + other headers Delegate to HTTPoison (I prefer its API to 
 alternatives like HTTPotion) Similar to {:ok, object} tuple response, except here it’s the
 HTTP status code response (e.g. 200 for OK)
  • 31. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE iex(1)> StripePost.post( ...(1)> "https://api.stripe.com/v1/charges", ...(1)> "amount=10000&currency=cad&description=3+wozzle&source=pk_abc_123", ...(1)> [{"Authorization", "Bearer abc123"}, ...(1)> {"Content-Type", "application/x-www-form-urlencoded"}]) 16:41:29.215 [error] SSL: :certify: tls_connection.erl:704:Fatal error: handshake failure - malformed_handshake_data {:error, {:tls_alert, 'handshake failure'}} Underlying error with HTTPosion 
 (mix deps.update --all) Not a legit authentication token Not a legit payment token And too much “raw” code
 here… {401, %{"error" => %{"message" => "Invalid API Key provided: **c123", "type" => "invalid_request_error"}}} Contrived example, but showing
 that indeed we are hitting the API
  • 32. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE @doc""" Charge an account with the following body configurations body = %{amount: 10000, currency: "cad", description: "3 wozzle", source: "pk_abc_123"} configs = %{secret_key: "sk_test_abc123"} """ def charge(body, configs) do post(Api.url <> "/charges", encode_body(body), headers(configs)) end Constant (which 
 could/should be configurable) Helper to convert maps to
 URL encoded bodies Helper to convert maps
 to valid HTTPosion headers
  • 33. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE iex(1)> StripePost.charge( ...(1)> %{amount: 10000, ...(1)> currency: "cad", ...(1)> description: "3 wozzle", ...(1)> source: "pk_abc_123"}, ...(1)> %{secret_key: "sk_test_abc123"}) {401, %{"error" => %{"message" => "Invalid API Key provided: **c123", "type" => "invalid_request_error"}}} Still contrived But at least now more elixir like Passing in the secret key directly
 is OK, but not practical As you will have one for testing
 and one for production, so
 it’s really more a config
  • 34. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE use Mix.Config config :stripe_post, secret_key: "sk_test_abc123", public_key: "pk_test_def456" iex(1)> StripePost.charge( ...(1)> %{amount: 10000, ...(1)> currency: "cad", ...(1)> description: "3 wozzle", ...(1)> source: "pk_abc_123"}) So, let’s leverage Elixir’s config instead No we can charge without
 providing the configs
 directly Note, that is the payment token
 not the sensitive secret key from above
 so it’s still part of the API
  • 35. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Not real keys Sensitive keys
 are for the server
 (“s” for secret) Public keys are
 for the client
 (i.e. JavaScript) Production keys
 separate from
 testing
  • 37. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://github.com/aforward/stripe-callbacks
  • 38. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE iex(1)> StripeCallbacks.process(%{ ...(1)> "stripe" => %{"id" => "pk_abc_123"}, ...(1)> "invoice" => %{ ...(1)> "amount" => 2000, ...(1)> "currency" => "cad", ...(1)> "description" => "3 wozzle"}}) Still contrived But we log to a database Split between expected 
 response from stripe
 and the “charge” invoice info 10:22:01.870 [debug] QUERY OK db=3.1ms UPDATE "tokens" SET "token_status" = $1, "updated_at" = $2 WHERE "id" = $3 ["invalid", {{2017, 4, 25}, {14, 22, 1, 867165}}, 1] {:ok, %StripeCallbacks.Token{__meta__: #Ecto.Schema.Metadata<:loaded, "tokens">, data: %{"invoice" => %{"amount" => 2000, "currency" => "cad", "description" => "3 wozzle"}, "stripe" => %{"id" => "pk_abc_123"}}, id: 1, inserted_at: ~N[2017-04-25 14:22:01.075526], token_status: "invalid", updated_at: ~N[2017-04-25 14:22:01.867165]}}
  • 39. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Store tokens from the client in the database Also store the response from Stripe trying to make the charge Contrived example, 
 so we are still in “error”
 but not for long stripe_callbacks_dev=# select data, token_status from tokens; data | token_status -----------------------------------------------------------------------------+-------------- { "stripe": {"id": "pk_abc_123"}, | invalid "invoice": {"amount": 2000, "currency": "cad", "description": "3 wozzle"}} | stripe_callbacks_dev=# select data, response_status, status_code, token_id from responses; data | response_status | status_code | token_id -----------------------------------------------------------------+-----------------+-------------+---------- {“error”. : {"type": "invalid_request_error", | failure | 400 | 1
 "message": "Invalid token id: pk_abc_123"}} | | |
  • 40. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
  • 43. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE mix new <my-project> --sup --module MyProject --app my_project 11:32 /tmp $ mix new stripe-callbacks --sup --module StripeCallbacks --app stripe_callbacks * creating README.md * creating .gitignore * creating mix.exs * creating config * creating config/config.exs * creating lib * creating lib/stripe_callbacks.ex * creating lib/stripe_callbacks/application.ex * creating test * creating test/test_helper.exs * creating test/stripe_callbacks_test.exs Your Mix project was created successfully. You can use "mix" to compile it, test it, and more: cd stripe-callbacks mix test Run "mix help" for more commands.
  • 45. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE @doc""" Encode the provided hash map for the URL. ## Examples iex> StripePost.Api.encode_body(%{a: "one", b: "two"}) "a=one&b=two" iex> StripePost.Api.encode_body(%{a: "o ne"}) "a=o+ne" """ def encode_body(map), do: URI.encode_query(map) Simply delegates to available
 core Elixir function But documentation is a 
 first class property of Elixir And testing too!
  • 46. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://hexdocs.pm/stripe_post/StripePost.Api.html
  • 47. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE @doc""" Build the headers for your API ## Examples iex> StripePost.Api.headers(%{content_type: "application/json", secret_key: "abc123"}) [{"Authorization", "Bearer abc123"}, {"Content-Type", "application/json"}] iex> StripePost.Api.headers(%{secret_key: "abc123"}) [{"Authorization", "Bearer abc123"}, {"Content-Type", "application/x-www-form-urlencoded"}] iex> StripePost.Api.headers(%{}) [{"Authorization", "Bearer sk_test_abc123"}, {"Content-Type", "application/x-www-form-urlencoded"}] iex> StripePost.Api.headers() [{"Authorization", "Bearer sk_test_abc123"}, {"Content-Type", "application/x-www-form-urlencoded"}] """ def headers(), do: headers(%{}) def headers(nil), do: headers(%{}) def headers(data) do h = %{content_type: "application/x-www-form-urlencoded"} |> Map.merge(app_headers()) |> Map.merge(reject_nil(data)) [{"Authorization", "Bearer #{h[:secret_key]}"}, {"Content-Type", h[:content_type]}] end Pattern matching to avoid “if”ing out
 bad, missing, defaulted data Generating headers in a structure
 like HTTPosion wants Examples, examples, examples
  • 49. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE use Mix.Config config :stripe_post, secret_key: "sk_test_abc123", public_key: "pk_test_def456" iex(1)> StripePost.charge( ...(1)> %{amount: 10000, ...(1)> currency: "cad", ...(1)> description: "3 wozzle", ...(1)> source: "pk_abc_123"}) So, let’s leverage Elixir’s config instead No we can charge without
 providing the configs
 directly Note, that is the payment token
 not the sensitive secret key from above
 so it’s still part of the API
  • 50. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE use Mix.Config # You will need to configure your public and private keys in Stripe. # optionally you can also set the default content type # https://dashboard.stripe.com/account/apikeys # config :stripe_post, # secret_key: "sk_test_abc123" # public_key: "pk_test_abc123" # content_type: "application/x-www-form-urlencoded" # # Within the application we will reference these using # Application.get_env(:stripe_post, :secret_key) # Application.get_env(:stripe_post, :public_key) # Application.get_env(:stripe_post, :content_type) # import_config "#{Mix.env}.exs" Elixir convention for 
 loading MIX_ENV specific
 configs And then each env, typically
 dev, test and prod can be
 separately configured
  • 51. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE DO NOT commit sensitive
 information to your repo If some prod configs are not sensitive
 then split them between prod.exs and
 prod.secret.exs Make sure you .gitignore the file But, DO add a .example file, to help others (and your future self)
 remember what production configs need to be set
  • 53. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE mix test.watch https://hex.pm/packages/mix_test_watch
  • 54. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE autotesting rocks! 11:23 ~/sin/projects/current/stripe-callbacks (master)$ mix test.watch ===> Fetching pc ({pkg,<<"pc">>,<<"1.4.0">>}) ===> Downloaded package, caching at /Users/aforward/.cache/rebar3/hex/default/packages/pc-1.4.0.tar ===> Compiling pc ===> Fetching rebar3_hex ({pkg,<<"rebar3_hex">>,<<"3.0.0">>}) ===> Downloaded package, caching at /Users/aforward/.cache/rebar3/hex/default/packages/ rebar3_hex-3.0.0.tar ===> Compiling rebar3_hex ===> Compiling fs ===> Compiling /Users/aforward/sin/projects/current/stripe-callbacks/deps/fs/c_src/mac/cli.c ===> Compiling /Users/aforward/sin/projects/current/stripe-callbacks/deps/fs/c_src/mac/compat.c ===> Compiling /Users/aforward/sin/projects/current/stripe-callbacks/deps/fs/c_src/mac/main.c ===> Linking /Users/aforward/sin/projects/current/stripe-callbacks/deps/fs/priv/mac_listener ==> mix_test_watch Compiling 12 files (.ex) Generated mix_test_watch app Running tests... Compiling 2 files (.ex) Generated stripe_callbacks app .. Finished in 0.03 seconds 2 tests, 0 failures Randomized with seed 587946 Each time you save a file in your project
 the tests are re-run. Unlike some other languages, your tests will be so fast
 mix_test_watch does not need to be smart about
 which tests to run
  • 55. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://medium.com/@a4word/continuous-testing-with-elixir-ddc1107c5cc0 Learn more about continuous testing
  • 56. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Learn more A LOT MORE 
 about continuous testing
  • 57. ECTO MANAGE YOUR DB SCHEMA LIKE A PRO
  • 58. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Grab ecto as well as the appropriate
 underlying DB manager (e.g. postgrex
 for PostgreSQL) Create your Repo (thank you 
 macros… just 3 LOC Make sure your application 
 supervises your database Tell you application which
 ecto_repos you care about
  • 59. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Support different DBs 
 for different environments Uncomment this line in your config.exs Sample configuration
 for your dev database
  • 60. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE So add this if you want your
 `mix ecto.migrate` to work I really like small commits, so I usually setup
 and commit an empty schema
  • 61. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE /stripe-callbacks (master)$ mix ecto.gen.migration add_tokens * creating priv/repo/migrations * creating priv/repo/migrations/20170325160151_add_tokens.exs Your database is nothing more
 than a bunch of schema migrations 
 Ecto provides a create 
 language to easily create 
 tables, indexes, etc.
  • 62. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Ecto is NOT an ORM Describe your DB
 schema Configure your entity
 as a JSON response
 for RESTful APIs Changesets are confusing,
 until they aren’t
  • 63. TESTING WITH DBS ADDITIONAL SETUP FOR ECTO TESTS
  • 64. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE This allows for asynchronous
 testing Ecto is chatty, so this will 
 disable much of the logging But now each test needs it’s own
 DB connection All set, go forth and test
  • 65. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE 1) test create (default status) (StripeCallbacks.TokenTest) test/token_test.exs:22 ** (MatchError) no match of right hand side value: {:error, i %DBConnection.ConnectionError{message: "connection not available because of disconnection"}} stacktrace: test/token_test.exs:8: StripeCallbacks.TokenTest.__ex_unit_setup_0/1 test/token_test.exs:1: StripeCallbacks.TokenTest.__ex_unit__/2 Oopses, no database Override test* to ensure your database is properly up and running
  • 66. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE $ mix test.watch Running tests... Compiling 6 files (.ex) Generated stripe_callbacks app ** (Mix) The database for StripeCallbacks.Repo couldn't be dropped: ERROR 55006 (object_in_use): database "stripe_callbacks_test" is being accessed by other users There are 10 other sessions using the database. But now we broke `mix test.watch` Safe way to support a “one off” testing versus test.watch
 but you need to call `MIX_ENV=test mix test.once`
  • 67. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://github.com/lpil/mix-test.watch/pull/70 https://github.com/lpil/mix-test.watch/pull/71 Support for just running something “once” Circumventing (unnecessary?) internal
 call that break testing that includes
 database setup But, we can do better
  • 68. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Fix feature-bug for creating / dropping
 databases as part of your testing To avoid re-creating the database
 on each test run (renamed from
 just “test”) Enable creating your
 database just once when 
 start `mix test.run` 
 (instead of each run) But, you have to live on the edge
  • 70. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Schema to describe
 your data Key/Value inputs from
 a user (very raw) Data transformations
 (cleaned, defaulted, 
 derived, validated) + Insert into database Or, present
 errors for correction
 to the user This IS a changeset
 (just a data structure)
  • 71. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE iex(1)> schema = %{first_name: :string, last_name: :string, email: :string} %{email: :string, first_name: :string, last_name: :string} iex(2)> params = %{"first_name" => "James", "last_name" => "Url"} %{"first_name" => "James", "last_name" => "Url"} iex(3)> changeset = Ecto.Changeset.cast({%{}, schema}, params, Map.keys(schema)) iex(4)> changeset.changes %{first_name: "James", last_name: "Url"} iex(5)> changeset.valid? true A changeset is just a tuple
 with starting data, and the schema #Ecto.Changeset<action: nil, changes: %{first_name: "James", last_name: "Url"}, errors: [], data: %{}, valid?: true> The params are the user input
 so usually strings as keys What are the 
 valid fields And it’s really just data structure coming back That you can access like any struct
  • 72. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE defmodule StripeCallbacks.Token do use Ecto.Schema import Ecto.Changeset schema "tokens" do field :data, :map field :token_status, :string timestamps() end def changeset(model, params %{}) do model |> cast(params, [:data, :token_status]) end end The ecto struct (e.g. %Token{}) Based on the schema Params and valid fields still apply %Token{} |> Token.changeset( 
 %{"data" => %{"apples" => "red"}, "token_status" => “processed"}) |> Repo.insert Using changes 
 to insert data
  • 73. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE @doc """ Creates a changeset based on the `model` and `params`. If no params are provided, an invalid changeset is returned with no validation performed. """ def changeset(model, params %{}) do model |> cast(params, [:pubid, :name, :slug, :owner, :status]) |> ChangesetMerger.defaulted(:name, "LiveCode") |> ChangesetMerger.defaulted(:owner, "Teacher") |> ChangesetMerger.Slug.derive_if_missing |> ChangesetMerger.Token.defaulted(:pubid, 4) |> ChangesetMerger.defaulted(:status, "created") end A much more involved changes
 that includes default values, 
 slug (aka URL safe) generation
 and tokens
  • 74. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://hex.pm/packages/changeset_merger
  • 75. SERVICES BOOTSTRAP FOR YOUR (MICRO)SERVICE
  • 76. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://medium.com/@a4word/building-small-elixir-services-using-ecto-without-phoenix-1aba00b53e54
  • 77. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE https://pragdave.me/blog/2017/04/18/elixir-project-generator.html
  • 78. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE mix archive.install mix_templates mix archive.install mix_generator mix template.install hex gen_template_ecto_service Install projects from Dave
 Thomas Install custom template
  • 79. 19:25 /tmp $ mix gen ecto_service foo_bar * creating ./foo_bar * creating ./foo_bar/.gitignore * creating ./foo_bar/.iex.exs * creating ./foo_bar/config * creating ./foo_bar/config/config.exs * creating ./foo_bar/config/dev.exs * creating ./foo_bar/config/test.exs * creating ./foo_bar/lib * creating ./foo_bar/lib/foo_bar * creating ./foo_bar/lib/foo_bar/action.ex * creating ./foo_bar/lib/foo_bar/application.ex * creating ./foo_bar/lib/foo_bar/repo.ex * creating ./foo_bar/lib/foo_bar.ex * creating ./foo_bar/mix.exs * creating ./foo_bar/priv * creating ./foo_bar/priv/repo * creating ./foo_bar/priv/repo/migrations * creating ./foo_bar/priv/repo/migrations/20170418164826_create_actions.exs * creating ./foo_bar/README.md * creating ./foo_bar/test * creating ./foo_bar/test/foo_bar_test.exs * creating ./foo_bar/test/action_test.exs * creating ./foo_bar/test/test_helper.exs Successfully generated foo_bar in . SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE Sets up Ecto, an actions schema
 (to log actions in your service)
 and mix test.watch An alternative to `mix new` that allows you
 to easily generate projects with various
 styles of project structures. 19:25 /tmp $ mix gen ecto_service foo_bar
  • 80. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE
  • 81. SIMPLE PAYMENT PAGE USING ELIXIR AND STRIPE