diff --git a/.gitignore b/.gitignore index adc49d2..95997fb 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,7 @@ pickle-email-*.html # tilde files are usually backup files from a text editor *~ + +# StripeMock log +/stripe-mock-server.log +/stripe-mock-server.pid diff --git a/.rspec b/.rspec index 89aa361..ebc6c92 100644 --- a/.rspec +++ b/.rspec @@ -1,4 +1,4 @@ --color --format documentation --require spec_helper ---require rails_helper +--require rails_helper \ No newline at end of file diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..0f8cab0 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,11 @@ +AlignParameters: + Enabled: false + +Rails: + Enabled: true + +Metrics/LineLength: + Max: 120 + +Style/Encoding: + Enabled: false diff --git a/.ruby-gemset b/.ruby-gemset index c943f6e..032ea53 100644 --- a/.ruby-gemset +++ b/.ruby-gemset @@ -1 +1 @@ -rails-stripe-membership-saas +rails-stripe-membership-saas \ No newline at end of file diff --git a/.ruby-version b/.ruby-version index 5859406..276cbf9 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.2.3 +2.3.0 diff --git a/Gemfile b/Gemfile index b080a7b..cfa04be 100644 --- a/Gemfile +++ b/Gemfile @@ -1,36 +1,36 @@ source 'https://rubygems.org' -ruby '2.2.3' -gem 'rails', '4.2.5' +ruby '2.3.0' +gem 'rails', '4.2.5.2' gem 'sqlite3' -gem 'sass-rails', '~> 5.0' -gem 'uglifier', '>= 1.3.0' -gem 'coffee-rails', '~> 4.1.0' -gem 'jquery-rails' -gem 'jbuilder', '~> 2.0' -group :development, :test do - gem 'byebug' -end -group :development do - gem 'web-console', '~> 2.0' - gem 'spring' -end gem 'bootstrap-sass' +gem 'coffee-rails', '~> 4.1.0' gem 'devise' -gem 'gibbon' +gem 'gibbon', '~> 2.2', '>= 2.2.1' gem 'high_voltage' +gem 'jquery-rails' +gem 'jbuilder', '~> 2.0' gem 'payola-payments' +gem 'sass-rails', '~> 5.0' gem 'sucker_punch' +gem 'uglifier', '>= 1.3.0' group :development do gem 'better_errors' gem 'quiet_assets' gem 'rails_layout' gem 'spring-commands-rspec' + gem 'web-console', '~> 3.0' end group :development, :test do + gem 'byebug' gem 'factory_girl_rails' gem 'faker' + gem 'pry' gem 'rspec-rails' - gem 'stripe-ruby-mock', '~> 2.1.1', :require => 'stripe_mock' + gem 'rubocop', require: false + gem 'spring' + # gem 'stripe-ruby-mock', '~> 2.2.0', require: 'stripe_mock' + gem 'stripe-ruby-mock', git: 'https://github.com/rebelidealist/stripe-ruby-mock.git', branch: 'master', require: 'stripe_mock' + gem 'thin', '~> 1.6.3' end group :test do gem 'capybara' diff --git a/Gemfile.lock b/Gemfile.lock index 086f52f..0a1e41b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,97 +1,91 @@ +GIT + remote: https://github.com/rebelidealist/stripe-ruby-mock.git + revision: a48f90b1120ce0b7adf1d6e6b1e4902308009441 + branch: master + specs: + stripe-ruby-mock (2.2.2) + dante (>= 0.2.0) + jimson-temp + stripe (= 1.31.0) + GEM remote: https://rubygems.org/ specs: - aasm (4.5.0) - actionmailer (4.2.5) - actionpack (= 4.2.5) - actionview (= 4.2.5) - activejob (= 4.2.5) + aasm (4.9.0) + actionmailer (4.2.5.2) + actionpack (= 4.2.5.2) + actionview (= 4.2.5.2) + activejob (= 4.2.5.2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.5) - actionview (= 4.2.5) - activesupport (= 4.2.5) + actionpack (4.2.5.2) + actionview (= 4.2.5.2) + activesupport (= 4.2.5.2) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.5) - activesupport (= 4.2.5) + actionview (4.2.5.2) + activesupport (= 4.2.5.2) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.5) - activesupport (= 4.2.5) + activejob (4.2.5.2) + activesupport (= 4.2.5.2) globalid (>= 0.3.0) - activemodel (4.2.5) - activesupport (= 4.2.5) + activemodel (4.2.5.2) + activesupport (= 4.2.5.2) builder (~> 3.1) - activerecord (4.2.5) - activemodel (= 4.2.5) - activesupport (= 4.2.5) + activerecord (4.2.5.2) + activemodel (= 4.2.5.2) + activesupport (= 4.2.5.2) arel (~> 6.0) - activesupport (4.2.5) + activesupport (4.2.5.2) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - addressable (2.3.8) + addressable (2.4.0) arel (6.0.3) - autoprefixer-rails (6.1.0.1) + ast (2.2.0) + autoprefixer-rails (6.3.3.1) execjs - json bcrypt (3.1.10) better_errors (2.1.1) coderay (>= 1.0.0) erubis (>= 2.6.6) rack (>= 0.9.0) - binding_of_caller (0.7.2) - debug_inspector (>= 0.0.1) blankslate (3.1.3) - bootstrap-sass (3.3.5.1) - autoprefixer-rails (>= 5.0.0.1) - sass (>= 3.3.0) + bootstrap-sass (3.3.6) + autoprefixer-rails (>= 5.2.1) + sass (>= 3.3.4) builder (3.2.2) - byebug (8.2.0) - capybara (2.5.0) + byebug (8.2.2) + capybara (2.6.2) + addressable mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) - celluloid (0.17.2) - celluloid-essentials - celluloid-extras - celluloid-fsm - celluloid-pool - celluloid-supervision - timers (>= 4.1.1) - celluloid-essentials (0.20.5) - timers (>= 4.1.1) - celluloid-extras (0.20.5) - timers (>= 4.1.1) - celluloid-fsm (0.20.5) - timers (>= 4.1.1) - celluloid-pool (0.20.5) - timers (>= 4.1.1) - celluloid-supervision (0.20.5) - timers (>= 4.1.1) - childprocess (0.5.8) + childprocess (0.5.9) ffi (~> 1.0, >= 1.0.11) - coderay (1.1.0) - coffee-rails (4.1.0) + coderay (1.1.1) + coffee-rails (4.1.1) coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.0) + railties (>= 4.0.0, < 5.1.x) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.10.0) + concurrent-ruby (1.0.1) + daemons (1.2.3) dante (0.2.0) database_cleaner (1.5.1) debug_inspector (0.0.2) - devise (3.5.2) + devise (3.5.6) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 3.2.6, < 5) @@ -99,39 +93,39 @@ GEM thread_safe (~> 0.1) warden (~> 1.2.3) diff-lcs (1.2.5) - domain_name (0.5.25) + domain_name (0.5.20160216) unf (>= 0.0.5, < 1.0.0) erubis (2.7.0) + eventmachine (1.0.9.1) execjs (2.6.0) factory_girl (4.5.0) activesupport (>= 3.0.0) - factory_girl_rails (4.5.0) + factory_girl_rails (4.6.0) factory_girl (~> 4.5.0) railties (>= 3.0.0) - faker (1.5.0) + faker (1.6.3) i18n (~> 0.5) faraday (0.9.2) multipart-post (>= 1.2, < 3) ffi (1.9.10) - gibbon (2.1.2) + gibbon (2.2.1) faraday (>= 0.9.1) multi_json (>= 1.11.0) globalid (0.3.6) activesupport (>= 4.1.0) high_voltage (2.4.0) - hitimes (1.2.3) http-cookie (1.0.2) domain_name (~> 0.5) i18n (0.7.0) - jbuilder (2.3.2) - activesupport (>= 3.0.0, < 5) + jbuilder (2.4.1) + activesupport (>= 3.0.0, < 5.1) multi_json (~> 1.2) jimson-temp (0.9.5) blankslate (>= 3.1.2) multi_json (~> 1.0) rack (~> 1.4) rest-client (~> 1.0) - jquery-rails (4.0.5) + jquery-rails (4.1.0) rails-dom-testing (~> 1.0) railties (>= 4.2.0) thor (>= 0.14, < 2.0) @@ -142,36 +136,44 @@ GEM nokogiri (>= 1.5.9) mail (2.6.3) mime-types (>= 1.16, < 3) - mime-types (2.6.2) - mini_portile (0.6.2) - minitest (5.8.2) + method_source (0.8.2) + mime-types (2.99.1) + mini_portile2 (2.0.0) + minitest (5.8.4) multi_json (1.11.2) multipart-post (2.0.0) netrc (0.11.0) - nokogiri (1.6.6.3) - mini_portile (~> 0.6.0) + nokogiri (1.6.7.2) + mini_portile2 (~> 2.0.0.rc2) orm_adapter (0.5.0) - payola-payments (1.3.2) + parser (2.3.0.6) + ast (~> 2.2) + payola-payments (1.3.0) aasm (>= 4.0.7) jquery-rails rails (>= 4.1) - stripe (= 1.20.1) + stripe (>= 1.20.1) stripe_event (>= 1.3.0) + powerpack (0.1.1) + pry (0.10.3) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) quiet_assets (1.1.0) railties (>= 3.1, < 5.0) rack (1.6.4) rack-test (0.6.3) rack (>= 1.0) - rails (4.2.5) - actionmailer (= 4.2.5) - actionpack (= 4.2.5) - actionview (= 4.2.5) - activejob (= 4.2.5) - activemodel (= 4.2.5) - activerecord (= 4.2.5) - activesupport (= 4.2.5) + rails (4.2.5.2) + actionmailer (= 4.2.5.2) + actionpack (= 4.2.5.2) + actionview (= 4.2.5.2) + activejob (= 4.2.5.2) + activemodel (= 4.2.5.2) + activerecord (= 4.2.5.2) + activesupport (= 4.2.5.2) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.5) + railties (= 4.2.5.2) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) @@ -179,30 +181,31 @@ GEM activesupport (>= 4.2.0.beta, < 5.0) nokogiri (~> 1.6.0) rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.2) + rails-html-sanitizer (1.0.3) loofah (~> 2.0) - rails_layout (1.0.28) - railties (4.2.5) - actionpack (= 4.2.5) - activesupport (= 4.2.5) + rails_layout (1.0.29) + railties (4.2.5.2) + actionpack (= 4.2.5.2) + activesupport (= 4.2.5.2) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rake (10.4.2) - responders (2.1.0) - railties (>= 4.2.0, < 5) + rainbow (2.1.0) + rake (10.5.0) + responders (2.1.1) + railties (>= 4.2.0, < 5.1) rest-client (1.8.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 3.0) netrc (~> 0.7) - rspec-core (3.4.0) + rspec-core (3.4.3) rspec-support (~> 3.4.0) rspec-expectations (3.4.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.4.0) - rspec-mocks (3.4.0) + rspec-mocks (3.4.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.4.0) - rspec-rails (3.4.0) + rspec-rails (3.4.2) actionpack (>= 3.0, < 4.3) activesupport (>= 3.0, < 4.3) railties (>= 3.0, < 4.3) @@ -210,48 +213,54 @@ GEM rspec-expectations (~> 3.4.0) rspec-mocks (~> 3.4.0) rspec-support (~> 3.4.0) - rspec-support (3.4.0) - rubyzip (1.1.7) - sass (3.4.19) + rspec-support (3.4.1) + rubocop (0.37.2) + parser (>= 2.3.0.4, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 0.3) + ruby-progressbar (1.7.5) + rubyzip (1.2.0) + sass (3.4.21) sass-rails (5.0.4) railties (>= 4.0.0, < 5.0) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - selenium-webdriver (2.48.1) + selenium-webdriver (2.52.0) childprocess (~> 0.5) multi_json (~> 1.0) rubyzip (~> 1.0) websocket (~> 1.0) - spring (1.4.3) + slop (3.6.0) + spring (1.6.4) spring-commands-rspec (1.0.4) spring (>= 0.9.1) - sprockets (3.4.0) + sprockets (3.5.2) + concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (2.3.3) - actionpack (>= 3.0) - activesupport (>= 3.0) - sprockets (>= 2.8, < 4.0) + sprockets-rails (3.0.4) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) sqlite3 (1.3.11) - stripe (1.20.1) + stripe (1.31.0) json (~> 1.8.1) - mime-types (>= 1.25, < 3.0) rest-client (~> 1.4) - stripe-ruby-mock (2.1.1) - dante (>= 0.2.0) - jimson-temp - stripe (= 1.20.1) stripe_event (1.5.0) activesupport (>= 3.1) stripe (~> 1.6) - sucker_punch (1.6.0) - celluloid (~> 0.17.2) + sucker_punch (2.0.1) + concurrent-ruby (~> 1.0.0) + thin (1.6.4) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0, >= 1.0.4) + rack (~> 1.0) thor (0.19.1) thread_safe (0.3.5) - tilt (2.0.1) - timers (4.1.1) - hitimes + tilt (2.0.2) tzinfo (1.2.2) thread_safe (~> 0.1) uglifier (2.7.2) @@ -259,14 +268,14 @@ GEM json (>= 1.8.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.1) - warden (1.2.3) + unf_ext (0.0.7.2) + unicode-display_width (0.3.1) + warden (1.2.6) rack (>= 1.0) - web-console (2.2.1) - activemodel (>= 4.0) - binding_of_caller (>= 0.7.2) - railties (>= 4.0) - sprockets-rails (>= 2.0, < 4.0) + web-console (3.1.1) + activemodel (>= 4.2) + debug_inspector + railties (>= 4.2) websocket (1.2.2) xpath (2.0.0) nokogiri (~> 1.3) @@ -284,25 +293,28 @@ DEPENDENCIES devise factory_girl_rails faker - gibbon + gibbon (~> 2.2, >= 2.2.1) high_voltage jbuilder (~> 2.0) jquery-rails launchy payola-payments + pry quiet_assets - rails (= 4.2.5) + rails (= 4.2.5.2) rails_layout rspec-rails + rubocop sass-rails (~> 5.0) selenium-webdriver spring spring-commands-rspec sqlite3 - stripe-ruby-mock (~> 2.1.1) + stripe-ruby-mock! sucker_punch + thin (~> 1.6.3) uglifier (>= 1.3.0) - web-console (~> 2.0) + web-console (~> 3.0) BUNDLED WITH - 1.10.6 + 1.11.2 diff --git a/README b/README index de2e1ad..790c790 100644 --- a/README +++ b/README @@ -1,7 +1,7 @@ Rails Stripe Membership Saas ======================== -You can use this project as a starting point for a Rails web application. It requires Rails 4.2 and uses Devise for user management and authentication, Bootstrap for CSS styling, with Payola and Stripe for recurring billing and product sales. +You can use this project as a starting point for a Rails web application. It requires Rails 4.3 and uses Devise for user management and authentication, Bootstrap for CSS styling, with Payola and Stripe for recurring billing and product sales. "Devise":http://github.com/plataformatec/devise @@ -11,7 +11,7 @@ You can use this project as a starting point for a Rails web application. It req "Payola":https://www.payola.io/ -________________________ +"Enum":http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html For more information, please see the updated README file on GitHub: @@ -20,3 +20,4 @@ For more information, please see the updated README file on GitHub: ________________________ "MIT License":http://www.opensource.org/licenses/mit-license + diff --git a/README.md b/README.md new file mode 100644 index 0000000..2760361 --- /dev/null +++ b/README.md @@ -0,0 +1,396 @@ +# stripe-ruby-mock [![Build Status](https://travis-ci.org/rebelidealist/stripe-ruby-mock.png?branch=master)](https://travis-ci.org/rebelidealist/stripe-ruby-mock) [![Gitter chat](https://badges.gitter.im/rebelidealist/stripe-ruby-mock.png)](https://gitter.im/rebelidealist/stripe-ruby-mock) + +* Homepage: https://github.com/rebelidealist/stripe-ruby-mock +* Issues: https://github.com/rebelidealist/stripe-ruby-mock/issues +* **CHAT**: https://gitter.im/rebelidealist/stripe-ruby-mock + +# REQUEST: Looking for More Core Contributors + +This gem has unexpectedly grown in popularity and I've gotten pretty busy, so I'm currently looking for more core contributors to help me out. If you're interested, there is only one requirement: submit a significant enough pull request and have it merged into master (many of you have already done this). Afterwards, ping me in [chat](https://gitter.im/rebelidealist/stripe-ruby-mock) and I will add you as a collaborator. + +## Install + +In your gemfile: + + gem 'stripe-ruby-mock', '~> 2.2.0', :require => 'stripe_mock' + +## Features + +* No stripe server access required +* Easily test against stripe errors +* Mock and customize stripe webhooks +* Flip a switch to run your tests against Stripe's **live test servers** + +### Specifications + +**STRIPE API TARGET VERSION:** 2015-02-18 (master) + +Older API version branches: + +- [api-2014-06-17](https://github.com/rebelidealist/stripe-ruby-mock/tree/api-2014-06-17) + +### Versioning System + +Since StripeMock tries to keep up with Stripe's API version, its version system is a little different: + +- The **major** number (1.x.x) is for breaking changes involving how you use StripeMock itself +- The **minor** number (x.1.x) is for breaking changes involving Stripe's API +- The **patch** number (x.x.0) is for non-breaking changes/fixes involving Stripe's API, or for non-breaking changes/fixes/features for StripeMock itself. + +## Description + +** *WARNING: This library does not cover all Stripe API endpoints. If you need one that's missing, please create an issue for it, or [see this wiki page](https://github.com/rebelidealist/stripe-ruby-mock/wiki/Implementing-a-New-Behavior) if you're interested in contributing* ** + +At its core, this library overrides [stripe-ruby's](https://github.com/stripe/stripe-ruby) +request method to skip all http calls and +instead directly return test data. This allows you to write and run tests +without the need to actually hit stripe's servers. + +You can use stripe-ruby-mock with any ruby testing library. Here's a quick dummy example with RSpec: + +```ruby +require 'stripe_mock' + +describe MyApp do + let(:stripe_helper) { StripeMock.create_test_helper } + before { StripeMock.start } + after { StripeMock.stop } + + it "creates a stripe customer" do + + # This doesn't touch stripe's servers nor the internet! + # Specify :source in place of :card (with same value) to return customer with source data + customer = Stripe::Customer.create({ + email: 'johnny@appleseed.com', + card: stripe_helper.generate_card_token + }) + expect(customer.email).to eq('johnny@appleseed.com') + end +end +``` + +## Test Helpers + +Some Stripe API calls require several parameters. StripeMock helps you keep your test brief with some helpers: + +```ruby +describe MyApp do + let(:stripe_helper) { StripeMock.create_test_helper } + + it "creates a stripe plan" do + plan = stripe_helper.create_plan(:id => 'my_plan', :amount => 1500) + + # The above line replaces the following: + # plan = Stripe::Plan.create( + # :id => 'my_plan', + # :name => 'StripeMock Default Plan ID', + # :amount => 1500, + # :currency => 'usd', + # :interval => 'month' + # ) + expect(plan.id).to eq('my_plan') + expect(plan.amount).to eq(1500) + end +end +``` + +The [available helpers](lib/stripe_mock/test_strategies/) are: + +```ruby +stripe_helper.create_plan(my_plan_params) +stripe_helper.delete_plan(my_plan_params) +stripe_helper.generate_card_token(my_card_params) +``` + +For everything else, use Stripe as you normally would (i.e. use Stripe as if you were not using StripeMock). + +## Live Testing + +Every once in a while you want to make sure your tests are actually valid. StripeMock has a switch that allows you to run your test suite (or a subset thereof) against Stripe's live test servers. + +Here is an example of setting up your RSpec (2.x) test suite to run live with a command line switch: + +```ruby +# RSpec 2.x +RSpec.configure do |c| + if c.filter_manager.inclusions.keys.include?(:live) + StripeMock.toggle_live(true) + puts "Running **live** tests against Stripe..." + end +end +``` + +With this you can run live tests by running `rspec -t live` + +Here is an example of setting up your RSpec (3.x) test suite to run live with the same command line switch: + +```ruby +# RSpec 3.x +RSpec.configure do |c| + if c.filter_manager.inclusions.rules.include?(:live) + StripeMock.toggle_live(true) + puts "Running **live** tests against Stripe..." + end +end +``` + +## Mocking Card Errors + +Tired of manually inputting fake credit card numbers to test against errors? Tire no more! + +```ruby +it "mocks a declined card error" do + # Prepares an error for the next create charge request + StripeMock.prepare_card_error(:card_declined) + + expect { Stripe::Charge.create(amount: 1, currency: 'usd') }.to raise_error {|e| + expect(e).to be_a Stripe::CardError + expect(e.http_status).to eq(402) + expect(e.code).to eq('card_declined') + } +end +``` + +### Built-In Card Errors + +```ruby +StripeMock.prepare_card_error(:incorrect_number) +StripeMock.prepare_card_error(:invalid_number) +StripeMock.prepare_card_error(:invalid_expiry_month) +StripeMock.prepare_card_error(:invalid_expiry_year) +StripeMock.prepare_card_error(:invalid_cvc) +StripeMock.prepare_card_error(:expired_card) +StripeMock.prepare_card_error(:incorrect_cvc) +StripeMock.prepare_card_error(:card_declined) +StripeMock.prepare_card_error(:missing) +StripeMock.prepare_card_error(:processing_error) +``` + +You can see the details of each error in [lib/stripe_mock/api/errors.rb](lib/stripe_mock/api/errors.rb) + +### Specifying Card Errors + +By default, `prepare_card_error` only triggers for `:new_charge`, the event that happens when you run `Charge.create`. More explicitly, this is what happens by default: + +```ruby +StripeMock.prepare_card_error(:card_declined, :new_charge) +``` + +If you want the error to trigger on a different event, you need to replace `:new_charge` with a different event. For example: + +```ruby +StripeMock.prepare_card_error(:card_declined, :create_card) +customer = Stripe::Customer.create +# This line throws the card error +customer.cards.create +``` + +`:new_charge` and `:create_card` are names of methods in the [StripeMock request handlers](lib/stripe_mock/request_handlers). You can also set `StripeMock.toggle_debug(true)` to see the event name for each Stripe request made in your tests. + +### Custom Errors + +To raise an error on a specific type of request, take a look at the [request handlers folder](lib/stripe_mock/request_handlers/) and pass a method name to `StripeMock.prepare_error`. + +If you wanted to raise an error for creating a new customer, for instance, you would do the following: + +```ruby +it "raises a custom error for specific actions" do + custom_error = StandardError.new("Please knock first.") + + StripeMock.prepare_error(custom_error, :new_customer) + + expect { Stripe::Charge.create(amount: 1, currency: 'usd') }.to_not raise_error + expect { Stripe::Customer.create }.to raise_error {|e| + expect(e).to be_a StandardError + expect(e.message).to eq("Please knock first.") + } +end +``` + +In the above example, `:new_customer` is the name of a method from [customers.rb](lib/stripe_mock/request_handlers/customers.rb). + +## Running the Mock Server + +Sometimes you want your test stripe data to persist for a bit, such as during integration tests +running on different processes. In such cases you'll want to start the stripe mock server: + + # spec_helper.rb + # + # The mock server will automatically be killed when your tests are done running. + # + require 'thin' + StripeMock.spawn_server + +Then, instead of `StripeMock.start`, you'll want to use `StripeMock.start_client`: + +```ruby +describe MyApp do + before do + @client = StripeMock.start_client + end + + after do + StripeMock.stop_client + # Alternatively: + # @client.close! + # -- Or -- + # StripeMock.stop_client(:clear_server_data => true) + end +end +``` + +This is all essentially the same as using `StripeMock.start`, except that the stripe test +data is held in its own server process. + +Here are some other neat things you can do with the client: + +```ruby +@client.state #=> 'ready' + +@client.get_server_data(:customers) # Also works for :charges, :plans, etc. +@client.clear_server_data + +@client.close! +@client.state #=> 'closed' +``` + +### Mock Server Options + +```ruby +# NOTE: Shown below are the default options +StripeMock.default_server_pid_path = './stripe-mock-server.pid' + +StripeMock.spawn_server( + :pid_path => StripeMock.default_server_pid_path, + :host => '0.0.0.0', + :port => 4999, + :server => :thin +) + +StripeMock.kill_server(StripeMock.default_server_pid_path) +``` + +### Mock Server Command + +If you need the mock server to continue running even after your tests are done, +you'll want to use the executable: + + $ stripe-mock-server -p 4000 + $ stripe-mock-server --help + +## Mocking Webhooks + +If your application handles stripe webhooks, you are most likely retrieving the event from +stripe and passing the result to a handler. StripeMock helps you by easily mocking that event: + +```ruby +it "mocks a stripe webhook" do + event = StripeMock.mock_webhook_event('customer.created') + + customer_object = event.data.object + expect(customer_object.id).to_not be_nil + expect(customer_object.default_card).to_not be_nil + # etc. +end +``` + +### Customizing Webhooks + +By default, StripeMock searches in your `spec/fixtures/stripe_webhooks/` folder for your own, custom webhooks. +If it finds nothing, it falls back to [test events generated through stripe's webhooktester](lib/stripe_mock/webhook_fixtures/). + +For example, you could create a file in `spec/fixtures/stripe_webhooks/invoice.created.with-sub.json`, copy/paste the default from [the default invoice.created.json](lib/stripe_mock/webhook_fixtures/invoice.created.json), and customize it to your needs. + +Then you can use that webook directly in your specs: + +```ruby +it "can use a custom webhook fixture" do + event = StripeMock.mock_webhook_event('invoice.created.with-sub') + # etc. +end +``` + +You can alse override values on the fly: + +```ruby +it "can override webhook values" do + # NOTE: given hash values get merged directly into event.data.object + event = StripeMock.mock_webhook_event('customer.created', { + :id => 'cus_my_custom_value', + :email => 'joe@example.com' + }) + # Alternatively: + # event.data.object.id = 'cus_my_custom_value' + # event.data.object.email = 'joe@example.com' + expect(event.data.object.id).to eq('cus_my_custom_value') + expect(event.data.object.email).to eq('joe@example.com') +end +``` + +You can name events whatever you like in your `spec/fixtures/stripe_webhooks/` folder. However, if you try to call a non-standard event that's doesn't exist in that folder, StripeMock will throw an error. + +If you wish to use a different fixture path, you can set it yourself: + + StripeMock.webhook_fixture_path = './spec/other/folder/' + +## Generating Card Tokens + +Sometimes you need to check if your code reads a stripe card correctly. If so, you can specifically +assign card data to a generated card token: + +```ruby + it 'generates a stripe card token' do + card_token = StripeMock.generate_card_token(last4: '9191', exp_month: 12, exp_year: 2025) + cus = Stripe::Customer.create(source: card_token) + user = Stripe::Customer.retrieve(cus.id) + card = user.sources.data.first + expect(card.last4).to eq '9191' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2025 + expect(user.sources.data.first.id).to match /^test_cc/ + end +``` + +## Debugging + +To enable debug messages: + + StripeMock.toggle_debug(true) + +This will **only last for the session**; Once you call `StripeMock.stop` or `StripeMock.stop_client`, +debug will be toggled off. + +If you always want debug to be on (it's quite verbose), you should put this in a `before` block. + +## Miscellaneous Features + +You may have noticed that all generated Stripe ids start with `test_`. If you want to remove this: + +```ruby +# Turns off test_ prefix +StripeMock.global_id_prefix = false + +# Or you can set your own +StripeMock.global_id_prefix = 'my_app_' +``` + +## TODO + +* Cover all stripe urls/methods +* Throw useful errors that emulate Stripe's requirements + * For example: "You must supply either a card or a customer id" for `Stripe::Charge` +* Fingerprinting for other resources besides Cards + +## Developing stripe-ruby-mock + +[Please see this wiki page](https://github.com/rebelidealist/stripe-ruby-mock/wiki/Implementing-a-New-Behavior) + +Patches are welcome and greatly appreciated! If you're contributing to fix a problem, +be sure to write tests that illustrate the problem being fixed. +This will help ensure that the problem remains fixed in future updates. + +## Copyright + +Copyright (c) 2013 Gilbert + +See LICENSE.txt for details. diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index f4ac760..73ed1fe 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -7,6 +7,7 @@ def show end private + def identify_product valid_characters = "^[0-9a-zA-Z]*$".freeze unless params[:id].blank? @@ -25,4 +26,4 @@ def identify_product @file = "#{@product_id}.#{@format}" end -end +end \ No newline at end of file diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index c450f5e..73386ad 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -54,6 +54,10 @@ def change_plan private + def payola_can_modify_subscription?(subscription) + subscription.owner == current_user + end + def sign_up_params params.require(:user).permit(:email, :password, :password_confirmation, :plan_id) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 6eb1fde..1235981 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,5 @@ class UsersController < ApplicationController before_action :authenticate_user! - before_action :admin_only, :except => :show def index @users = User.all @@ -8,38 +7,36 @@ def index def show @user = User.find(params[:id]) + begin unless current_user.admin? unless @user == current_user - redirect_to :back, :alert => "Access denied." + redirect_to root_path alert: "Access denied." end end + rescue ActionController::RedirectBackError + redirect_to root_path + end end def update @user = User.find(params[:id]) if @user.update_attributes(secure_params) - redirect_to users_path, :notice => "User updated." + redirect_to users_path, notice: "User updated." else - redirect_to users_path, :alert => "Unable to update user." + redirect_to users_path, alert: "Unable to update user." end end def destroy user = User.find(params[:id]) user.destroy - redirect_to users_path, :notice => "User deleted." + redirect_to users_path, notice: "User deleted." end private - def admin_only - unless current_user.admin? - redirect_to :back, :alert => "Access denied." - end - end - def secure_params params.require(:user).permit(:role) end -end +end \ No newline at end of file diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be79..8bdbe98 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,28 @@ module ApplicationHelper -end + # reference for next three methods + # http://stackoverflow.com/questions/14866353/devise-sign-in-not-completing + + def resource_name + :user + end + + def resource + @resource ||= User.new + end + + def devise_mapping + @devise_mapping ||= Devise.mappings[:user] + end + + def display_base_errors resource + return '' if (resource.errors.empty?) or (resource.errors[:base].empty?) + messages = resource.errors[:base].map { |msg| content_tag(:p, msg) }.join + html = <<-HTML +
+ + #{messages} +
+ HTML + html.html_safe + end +end \ No newline at end of file diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index fd2d275..6f80060 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -1,7 +1,7 @@ class UserMailer < ActionMailer::Base - default :from => "do-not-reply@example.com" + default from: "do-not-reply@example.com" def expire_email(user) - mail(:to => user.email, :subject => "Subscription Cancelled") + mail(to: user.email, subject: "Subscription Cancelled") end end diff --git a/app/models/plan.rb b/app/models/plan.rb index 54d4458..5a0f412 100644 --- a/app/models/plan.rb +++ b/app/models/plan.rb @@ -2,8 +2,9 @@ class Plan < ActiveRecord::Base include Payola::Plan has_many :users - validates :stripe_id, inclusion: { in: Plan.pluck('DISTINCT stripe_id'), - message: "not a valid subscription plan" } + +# validates :stripe_id, inclusion: { in: Plan.pluck('DISTINCT stripe_id'), +# message: "not a valid subscription plan" } def redirect_path(subscription) '/' diff --git a/app/models/user.rb b/app/models/user.rb index 5da4d21..dd49f0a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,8 +1,8 @@ class User < ActiveRecord::Base enum role: [:user, :admin, :silver, :gold, :platinum] - after_initialize :set_default_role, :if => :new_record? - after_initialize :set_default_plan, :if => :new_record? - # after_create :sign_up_for_mailing_list + after_initialize :set_default_role, if: :new_record? + after_initialize :set_default_plan, if: :new_record? + after_create :sign_up_for_mailing_list belongs_to :plan validates_associated :plan @@ -34,5 +34,4 @@ def subscribe }) Rails.logger.info("Subscribed #{self.email} to MailChimp") if result end - end diff --git a/app/models/visitor.rb b/app/models/visitor.rb new file mode 100644 index 0000000..d2ce6fd --- /dev/null +++ b/app/models/visitor.rb @@ -0,0 +1,24 @@ +class Visitor < ActiveRecord::Base + validates_presence_of :email + validates :email, format: /.+@.+\..+/i + # ^^^^ rubocop format preference + # validates_format_of :email, with: /.+@.+\..+/i + # ^^^^ https://davidcel.is/posts/stop-validating-email-addresses-with-regex/ + # validates_format_of :email, :with => /\A[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}\z/i + after_create :subscribe + + def visitor_subscribe + begin + mailchimp = Gibbon::Request.new(api_key: Rails.application.secrets.mailchimp_api_key) + list_id = Rails.application.secrets.mailchimp_list_id + result = mailchimp.lists(list_id).members.create( + body: { + email_address: self.email, + status: 'subscribed' + }) + Rails.logger.info("Subscribed #{self.email} to MailChimp") if result + rescue Gibbon::MailChimpError => e + return ('/visitors/new'), flash: { error: e.message } + end + end +end diff --git a/app/services/create_plan_service.rb b/app/services/create_plan_service.rb index a49890e..92b66ed 100644 --- a/app/services/create_plan_service.rb +++ b/app/services/create_plan_service.rb @@ -1,22 +1,25 @@ class CreatePlanService def call - p1 = Plan.where(name: 'Platinum').first_or_initialize do |p| + platinum = Plan.where(name: 'Platinum').first_or_initialize do |p| p.amount = 2900 + # p.currency = 'usd' p.interval = 'month' p.stripe_id = 'platinum' end - p1.save!(:validate => false) - p2 = Plan.where(name: 'Gold').first_or_initialize do |p| + platinum.save!(validate: false) + gold = Plan.where(name: 'Gold').first_or_initialize do |p| p.amount = 1900 + # p.currency = 'usd' p.interval = 'month' p.stripe_id = 'gold' end - p2.save!(:validate => false) - p3 = Plan.where(name: 'Silver').first_or_initialize do |p| + gold.save!(validate: false) + silver = Plan.where(name: 'Silver').first_or_initialize do |p| p.amount = 900 + # p.currency = 'usd' p.interval = 'month' p.stripe_id = 'silver' end - p3.save!(:validate => false) + silver.save!(validate: false) end end diff --git a/app/services/create_user_service.rb b/app/services/create_user_service.rb new file mode 100644 index 0000000..de60e54 --- /dev/null +++ b/app/services/create_user_service.rb @@ -0,0 +1,25 @@ +class CreateUserService + def call + user2 = User.find_or_create_by(email: 'user2@example.com') do |user| + user.password = 'please123' + user.password_confirmation = 'please123' + user.role = 'silver' + user.plan_id = 1 + end + user2.save!(validate: false) + user3 = User.find_or_create_by(email: 'user3@example.com') do |user| + user.password = 'please123' + user.password_confirmation = 'please123' + user.role = 'gold' + user.plan_id = 2 + end + user3.save!(validate: false) + user4 = User.find_or_create_by(email: 'user4@example.com') do |user| + user.password = 'please123' + user.password_confirmation = 'please123' + user.role = 'platinum' + user.plan_id = 3 + end + user4.save!(validate: false) + end +end \ No newline at end of file diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 8d0767f..b11f33b 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,3 +1,2 @@

User

-

Name: <%= @user.name if @user.name %>

Email: <%= @user.email if @user.email %>

diff --git a/config/environments/development.rb b/config/environments/development.rb index 4930252..84fa438 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -10,8 +10,9 @@ config.eager_load = false # Show full error reports and disable caching. - config.consider_all_requests_local = true + config.consider_all_requests_local = true config.action_controller.perform_caching = false + config.action_controller.action_on_unpermitted_parameters = :raise # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false diff --git a/config/environments/production.rb b/config/environments/production.rb index 95df014..6166cec 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -81,15 +81,15 @@ password: Rails.application.secrets.email_provider_password } # ActionMailer Config - config.action_mailer.default_url_options = { :host => Rails.application.secrets.domain_name } + config.action_mailer.default_url_options = { host: Rails.application.secrets.domain_name } config.action_mailer.delivery_method = :smtp config.action_mailer.perform_deliveries = true - config.action_mailer.raise_delivery_errors = false - + #config.action_mailer.raise_delivery_errors = false # Production + config.action_mailer.raise_delivery_errors = true # Development # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false -end +end \ No newline at end of file diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index b185380..95fcf4c 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -6,7 +6,8 @@ # confirmation, reset password and unlock tokens in the database. # Devise will use the `secret_key_base` on Rails 4+ applications as its `secret_key` # by default. You can change it below and use your own secret key. - # config.secret_key = '25b4b29a3d7aea8b8ef92812b845014721cff8dbaaf9a6b1c7c9d166c41c32a5723276494992b858dab99ad347d0f142680d54f9ac2544c0c474cac9743181e5' + # Change this secret_key when you go into production + config.secret_key = 'd0acf5229989d2def7090e91f2d7a584b6eee8d2545048675ed17784aaeda8e3b3131b0679797a863177e83592b234847c931f67a4f5b7c7177a96f2e445acf0' # ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, diff --git a/config/initializers/devise_permitted_parameters.rb b/config/initializers/devise_permitted_parameters.rb index cbbee2a..43dad22 100644 --- a/config/initializers/devise_permitted_parameters.rb +++ b/config/initializers/devise_permitted_parameters.rb @@ -8,10 +8,10 @@ module DevisePermittedParameters protected def configure_permitted_parameters - devise_parameter_sanitizer.for(:sign_up) << :name - devise_parameter_sanitizer.for(:account_update) << :name + devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:email, :password, :remember_me) } + devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:email, :password, :password_confirmation, :current_password) } + devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:name, :coupon, :stripe_token, :email, :password, :password_confirmation) } end - end -DeviseController.send :include, DevisePermittedParameters +DeviseController.send :include, DevisePermittedParameters \ No newline at end of file diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index 2ab23e6..4a994e1 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -1,4 +1,4 @@ # Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. -Rails.application.config.filter_parameters += [:password, :password_confirmation] +Rails.application.config.filter_parameters += [:password] diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 8584b55..7d4d608 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -Rails.application.config.session_store :cookie_store, key: '_rails-stripe-membership-saas_session' +Rails.application.config.session_store :cookie_store, key: '_rails_stripe_membership_saas_session' diff --git a/config/initializers/stripe.rb b/config/initializers/stripe.rb new file mode 100644 index 0000000..de24a24 --- /dev/null +++ b/config/initializers/stripe.rb @@ -0,0 +1,48 @@ +# api_key : Note Stripe.api_key must be kept secret to prevent use of by other developers Stripe account. +Stripe.api_key = ENV["STRIPE_API_KEY"] +STRIPE_PUBLISHABLE_KEY = ENV["STRIPE_PUBLISHABLE_KEY"] + +# Note: do not listen for customer.subscription.updated +# Reference : http://imzank.com/2012/11/how-to-use-stripe-com-to-email-your-customers/ + +# changed from setup to configure : 20140214 +# Reference : https://github.com/RailsApps/rails-stripe-membership-saas/issues/96#issuecomment-35108722 +StripeEvent.configure do |events| + events.subscribe 'customer.subscription.deleted' do |event| + StripeEvent.event_retriever = lambda do |params| + verified_event = Stripe::Event.retrieve(params[event.id]) + + user = User.where(customer_id: verified_event.data.object.customer) + user.expire + end + end + + events.subscribe 'customer.charge.succeeded' do |event| + StripeEvent.event_retriever = lambda do |params| + verified_event = Stripe::Event.retrieve(params[event.id]) + + user = User.where(customer_id: verified_event.data.object.customer) + user.thanks + end + end + + events.subscribe 'transfer.created' do |event| + StripeEvent.event_retriever = lambda do |params| + verified_event = Stripe::Event.retrieve(params[event.id]) + + user = User.where(customer_id: verified_event.data.object.customer) + owner = User.first + owner.transfer_created + end + end + + events.subscribe 'customer.subscription.updated' do |event| + StripeEvent.event_retriever = lambda do |params| + verified_event = Stripe::Event.retrieve(params[event.id]) + + user = User.where(customer_id: verified_event.data.object.customer) + user.plan_changed + end + end + +end \ No newline at end of file diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index 26a10f2..0aff3d8 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -34,6 +34,7 @@ en: updated_not_active: "Your password has been changed successfully." registrations: destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." + invalid: 'Invalid email or password.' signed_up: "Welcome! You have signed up successfully." signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." diff --git a/config/routes.rb b/config/routes.rb index 40d771d..d78e204 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,10 +4,10 @@ get "content/platinum" mount Payola::Engine => '/payola', as: :payola root to: 'visitors#index' - get 'products/:id', to: 'products#show', :as => :products - devise_for :users, :controllers => { :registrations => 'registrations' } + get 'products/:id', to: 'products#show', as: :products + devise_for :users, controllers: { registrations: 'registrations' } devise_scope :user do - put 'change_plan', :to => 'registrations#change_plan' + put 'change_plan', to: 'registrations#change_plan' end resources :users end diff --git a/config/secrets.yml b/config/secrets.yml index e6f8f34..1fbe56b 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -21,11 +21,21 @@ development: mailchimp_list_id: <%= ENV["MAILCHIMP_LIST_ID"] %> stripe_api_key: <%= ENV["STRIPE_API_KEY"] %> stripe_publishable_key: <%= ENV["STRIPE_PUBLISHABLE_KEY"] %> - secret_key_base: 748f770f93f79bf365716d56810ac88893006c9acb49f1114b8997ff64b78620f0fbdd6ddbf6cc8b2762a8fe02b9b091f7555be9b31daca30a36305fde4633e6 + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> + test: + admin_name: First User + admin_email: user@example.com + admin_password: changeme + email_provider_username: <%= ENV["GMAIL_USERNAME"] %> + email_provider_password: <%= ENV["GMAIL_PASSWORD"] %> domain_name: example.com - secret_key_base: very_long_random_string + mailchimp_api_key: <%= ENV["MAILCHIMP_API_KEY"] %> + mailchimp_list_id: <%= ENV["MAILCHIMP_LIST_ID"] %> + stripe_api_key: <%= ENV["STRIPE_API_KEY"] %> + stripe_publishable_key: <%= ENV["STRIPE_PUBLISHABLE_KEY"] %> + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> # Do not keep production secrets in the repository, # instead read values from the environment. diff --git a/db/schema.rb b/db/schema.rb index b5a065e..2724ae8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -141,4 +141,4 @@ add_index "users", ["plan_id"], name: "index_users_on_plan_id" add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true -end +end \ No newline at end of file diff --git a/db/seeds.rb b/db/seeds.rb index b3c8672..2774255 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -9,3 +9,16 @@ puts 'CREATED ADMIN USER: ' << user.email CreatePlanService.new.call puts 'CREATED PLANS' +silver = Plan.find_by_name("Silver") +gold = Plan.find_by_name("Gold") +platinum = Plan.find_by_name("Platinum") +puts 'CREATED SILVER PLAN : stripe_id = ' + silver.stripe_id +puts 'CREATED GOLD PLAN : stripe_id = ' + gold.stripe_id +puts 'CREATED PLATINUM PLAN : stripe_id = ' + platinum.stripe_id +CreateUserService.new.call +silver_user = User.find_by_email("user2@example.com") +gold_user = User.find_by_email("user3@example.com") +platinum_user = User.find_by_email("user4@example.com") +puts 'CREATED SILVER USER :' << silver_user.email +puts 'CREATED GOLD USER :' << gold_user.email +puts 'CREATED PLATINUM USER :' << platinum_user.email diff --git a/spec/controllers/products_controller_spec.rb b/spec/controllers/products_controller_spec.rb new file mode 100644 index 0000000..b197968 --- /dev/null +++ b/spec/controllers/products_controller_spec.rb @@ -0,0 +1,10 @@ +describe ProductsController do + describe 'GET #show' do + + it "returns a PDF file" do + get :show, id: 'product', format: 'pdf' + expect(response.headers['Content-Type']).to have_content 'application/pdf' + end + + end +end \ No newline at end of file diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb new file mode 100644 index 0000000..8a77979 --- /dev/null +++ b/spec/controllers/users_controller_spec.rb @@ -0,0 +1,69 @@ +require 'stripe' + +include Warden::Test::Helpers +Warden.test_mode! + +RSpec.configure do + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # UsersController. Be sure to keep this updated too. + def valid_session + valid_session = { user_id: 1 } + end + + def valid_session2 + valid_session2 = { user_id: 2 } + end +end + +RSpec.describe UsersController, type: :controller do + + before(:each) do + @user = FactoryGirl.build(:user) + @user.role = "admin" + end + + after(:each) do + Warden.test_reset! + end + + context "GET #index" do + it 'success' do + @user.save! + expect(response).to be_success + end + + it "assigns @users" do + @user.save! + sign_in @user + expect(@user.id).to eq 1 + expect(@user.email).to eq 'test@example.com' + expect(@user.persisted?).to eq true + get :index + expect(assigns[:users]).to eq User.all + end + end + + context "GET #show" do + it "is successful" do + expect(@user._validators?).to eq true + @user.save! + sign_in @user + get :show, { id: @user.id }, valid_session + expect(Rails.logger.info response.body).to eq true + expect(Rails.logger.warn response.body).to eq true + expect(Rails.logger.debug response.body).to eq true + expect(response).to be_success + end + + it "finds the right user" do + @user = FactoryGirl.build(:user, email: 'newuser@example.com') + @user.role = 'admin' + @user.save! + sign_in @user + get :show, { id: @user.id }, valid_session2 + expect(response).to be_success + expect(@user.id).to eq 1 + end + end +end \ No newline at end of file diff --git a/spec/examples/example_spec.rb b/spec/examples/example_spec.rb new file mode 100644 index 0000000..4b2e2df --- /dev/null +++ b/spec/examples/example_spec.rb @@ -0,0 +1,5 @@ +describe 5 do + it "is greater than 4" do + expect(5).to be > 4 + end +end \ No newline at end of file diff --git a/spec/features/users/sign_in_spec.rb b/spec/features/users/sign_in_spec.rb new file mode 100644 index 0000000..1154a5f --- /dev/null +++ b/spec/features/users/sign_in_spec.rb @@ -0,0 +1,79 @@ +include Features::SessionHelpers +include Warden::Test::Helpers +Warden.test_mode! + +# Feature: Sign in +# As a user +# I want to sign in +# So I can visit protected areas of the site +feature 'User', :devise, js: true do + + before(:each) do + FactoryGirl.reload + end + + after(:each) do + Warden.test_reset! + end + + # Scenario: User cannot sign in if not registered + # Given I do not exist as a user + # When I sign in with valid credentials + # Then I see an invalid credentials message + scenario 'cannot sign in if not registered' do + user = FactoryGirl.create(:user) + visit new_user_session_path + expect(current_path).to eq '/users/sign_in' + sign_in('test@example.com', 'notmypassword') + expect(page).to have_content 'Invalid email or password.' + expect(page).to have_content I18n.t 'devise.failure.invalid', authentication_keys: 'email' + expect(page).to have_content I18n.t 'devise.failure.not_found_in_database', authentication_keys: 'email' + end + + # Scenario: User can sign in with valid credentials + # Given I exist as a user + # And I am not signed in + # When I sign in with valid credentials + # Then I see a success message + scenario 'can sign in with valid credentials' do + user = FactoryGirl.build(:user) + user.role = 'admin' + user.save! + visit new_user_session_path + sign_in(user.email, user.password) + expect(page).to have_content 'Signed in successfully.' + expect(page).to have_content I18n.t 'devise.sessions.signed_in' + visit '/users' + expect(current_path).to eq '/users' + end + + # Scenario: User cannot sign in with wrong email + # Given I exist as a user + # And I am not signed in + # When I sign in with a wrong email + # Then I see an invalid email message + scenario 'cannot sign in with wrong email' do + user = FactoryGirl.create(:user) + visit new_user_session_path + sign_in('invalid@example.com', 'user.password') + expect(page).to have_content 'Invalid email or password.' + expect(page).to have_content I18n.t 'devise.failure.invalid', authentication_keys: 'email' + expect(page).to have_content I18n.t 'devise.failure.not_found_in_database', authentication_keys: 'email' + end + + # Scenario: User cannot sign in with wrong password + # Given I exist as a user + # And I am not signed in + # When I sign in with a wrong password + # Then I see an invalid password message + scenario 'cannot sign in with wrong password' do + user = FactoryGirl.build(:user) + user.role = 'admin' + user.save! + visit new_user_session_path + sign_in(user.email, 'invalidpass') + expect(page).to have_content 'Invalid email or password.' + expect(page).to have_content I18n.t 'devise.failure.invalid', authentication_keys: 'email' + expect(page).to have_content I18n.t 'devise.failure.not_found_in_database', authentication_keys: 'email' + end +end \ No newline at end of file diff --git a/spec/features/users/sign_out_spec.rb b/spec/features/users/sign_out_spec.rb new file mode 100644 index 0000000..6fb1040 --- /dev/null +++ b/spec/features/users/sign_out_spec.rb @@ -0,0 +1,32 @@ +include Features::SessionHelpers +include Warden::Test::Helpers +Warden.test_mode! + +# Feature: Sign out +# As a user +# I want to sign out +# So I can protect my account from unauthorized access +feature 'Sign out', :devise do + + before(:each) do + FactoryGirl.reload + end + + after(:each) do + Warden.test_reset! + end + + # Scenario: User signs out successfully + # Given I am signed in + # When I sign out + # Then I see a signed out message + scenario 'user signs out successfully' do + user = FactoryGirl.create(:user) + sign_in(user.email, user.password) + expect(page).to have_content 'Signed in successfully.' + expect(page).to have_content I18n.t 'devise.sessions.signed_in' + click_link 'Sign out' + expect(page).to have_content 'Signed out successfully.' + expect(page).to have_content I18n.t 'devise.sessions.signed_out' + end +end \ No newline at end of file diff --git a/spec/features/users/user_edit_spec.rb b/spec/features/users/user_edit_spec.rb new file mode 100644 index 0000000..e69bdf3 --- /dev/null +++ b/spec/features/users/user_edit_spec.rb @@ -0,0 +1,55 @@ +include Warden::Test::Helpers +Warden.test_mode! + +# Feature: User edit +# As a user +# I want to edit my user profile +# So I can change my email address +feature 'User edit', :devise do + + after(:each) do + Warden.test_reset! + end + + # Scenario: User changes email address + # Given I am signed in + # When I change my email address + # Then I see an account updated message + scenario 'user changes email address' do + user = FactoryGirl.build(:user) + user.role = 'admin' + user.save! + login_as(user, scope: :user) + visit edit_user_registration_path(user) + fill_in 'Email', with: 'newemail@example.com' + fill_in 'Current password', with: user.password + click_button 'Update' + txt1 = I18n.t('devise.registrations.updated') + txt2 = I18n.t('devise.registrations.update_needs_confirmation') + expect(txt1).to eq "Your account has been updated successfully." + expect(txt2).to eq "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." + expect(page).to have_content(/.*#{txt1}.*|.*#{txt2}.*/) # this line is same as above two expect's + end + + # Scenario: User cannot edit another user's profile + # Given I am signed in + # When I try to edit another user's profile + # Then I see my own 'edit profile' page + scenario "user cannot cannot edit another user's profile", :user do + user = FactoryGirl.build(:user) + user.role = 'admin' + user.save! + other = FactoryGirl.build(:user, email: 'other@example.com') + other.role = 'admin' + user.save! + login_as(user, scope: :user) + visit edit_user_registration_path(other) + expect(page).to have_content 'Account' + expect(page).to have_field('Email', with: user.email) + fill_in 'Email', with: 'anotherprofile@example.com' + fill_in 'Current password', with: other.password + click_button 'Update' + txt = I18n.t('devise.registrations.invalid') + expect(txt).to eq "Invalid email or password." + end +end \ No newline at end of file diff --git a/spec/features/users/user_index_spec.rb b/spec/features/users/user_index_spec.rb new file mode 100644 index 0000000..e32035b --- /dev/null +++ b/spec/features/users/user_index_spec.rb @@ -0,0 +1,28 @@ +include Warden::Test::Helpers +Warden.test_mode! + +# Feature: User index page +# As an admin user +# I want to see a list of all users +# So I can see all users, their email and plan +feature 'User index page', :devise, js: true do + + after(:each) do + Warden.test_reset! + end + + # Scenario: Admin user sees all users on index page + # Given I am signed in + # When I visit the user index page + # Then I see all users, their email and plan + scenario 'admin user sees all users on index page' do + @user = FactoryGirl.build(:user) + @user.role = 'admin' + @user.save! + login_as(@user, scope: :user) + visit users_path + expect(current_path).to eq '/users' + expect(page).to have_content @user.email + expect(page).to have_content 'Admin' + end +end \ No newline at end of file diff --git a/spec/features/users/user_show_spec.rb b/spec/features/users/user_show_spec.rb new file mode 100644 index 0000000..8cff4a6 --- /dev/null +++ b/spec/features/users/user_show_spec.rb @@ -0,0 +1,175 @@ +include Warden::Test::Helpers +Warden.test_mode! + +# Feature: User profile page +# As a user +# I want to visit my user profile page +# So I can see my personal account data +feature 'User profile page', :devise, js: true do + + after(:each) do + Warden.test_reset! + end + + # Scenario: User sees own profile + # Given I am signed in + # When I visit the user profile page + # Then I see my own email address + scenario 'user sees own profile' do + user = FactoryGirl.build(:user) + user.role = 'admin' + user.save! + login_as(user, scope: :user) + visit user_path(user) + expect(page).to have_content 'User' + expect(page).to have_content user.email + end + + # Scenario: User cannot see another user's profile + # Given I am signed in + # When I visit another user's profile + # Then I see that access is denied to me + scenario "user cannot see another user's profile" do + @user = FactoryGirl.build(:user, email: 'johnny@appleseed.com') + @user.role = 'admin' + @user.save! + login_as(@user, scope: :user) + visit '/users' + expect(current_path).to eq '/users' + expect(@user.id).to eq 1 + click_link 'Sign out' + expect(current_path).to eq "/" + + plans = CreatePlanService.new.call + @other = FactoryGirl.build(:user, + email: 'frankie@appleseed.com', + password: 'changemenow', + password_confirmation: 'changemenow', + role: 3, + plan_id: 1 + ) + @other.save! + expect(@other.role).to eq 'gold' + expect(@other.plan_id).to eq 1 + expect(@other.email).to eq 'frankie@appleseed.com' + visit new_user_session_path + expect(current_path).to eq '/users/sign_in' + sign_in(@other.email, @other.password) + expect(page).to have_content 'Signed in successfully.' + expect(current_path).to eq '/content/gold' + visit user_path(@other) + expect(page).to have_content 'Edit account' + expect(page).to have_content 'frankie@appleseed.com' + expect(current_path).to eq '/users/2' + click_link 'Sign out' + expect(current_path).to eq '/' + expect(current_path).to eq root_path + expect(page).to have_content 'Signed out successfully.' + + expect(@user.active_for_authentication?).to be true + expect(@other.active_for_authentication?).to be true + expect(@user.persisted?).to be true + expect(@other.persisted?).to be true + expect(@user.admin?).to be true + expect(@other.admin?).to be false + expect(@user.role).to eq 'admin' + expect(@other.role).to eq 'gold' + expect(@user.sign_in_count).to eq 1 + + # we have proven both users can come and go and all is well above + # now we have one user try to visit another user's profile below + sign_in(@other.email, @other.password) + expect(current_path).to eq '/content/gold' + expect(page).to have_content 'Signed in successfully.' + expect(@other.plan_id).to eq 1 + expect(@other.role).to eq 'gold' + current_user = User.find(2) + expect(current_user.sign_in_count?).to be true + expect(current_user.sign_in_count).to eq 2 + + visit '/users/2' + expect(current_path).to eq '/users/2' + visit user_path(@other) + expect(current_path).to eq '/users/2' + visit user_path(@other, scope: :other) + expect(current_path).to eq '/users/2' + expect(@other.email).to eq 'frankie@appleseed.com' + + visit user_path(2) + expect(current_path).to eq '/users/2' + visit '/users/2' + expect(current_path).to eq '/users/2' + + visit user_path(1) + expect(current_url).to match /denied\.$/ + + visit '/users/1' + expect(current_url).to match /denied\.$/ + + visit '/users/2' + click_link 'Sign out' + expect(page).to have_content "Signed out successfully." + + ## user #2 has been prevented from seeing user #1 profile + ## all is well up to this point, and everyone is signed out + ## with user #2 signed out, access to profile is not available + visit '/users/2' + expect(page).to have_content "You need to sign in or sign up before continuing." + + # we sign in as 'other', and attempt to visit user's profile + visit new_user_session_path + sign_in(@other.email, @other.password) + expect(current_path).to eq '/content/gold' + visit '/users/1' # access is denied, as user #2 cannot see user #1's profile + expect(current_url).to match /denied\.$/ + expect(current_path).not_to eq '/user/1' + + visit users_path(2) # this will pass, as other can see their own profile + expect(current_path).to eq '/users.2' + click_link 'Sign out' + expect(current_path).to eq '/' + expect(page).to have_content "Signed out successfully." + + # both users are now signed out, we are on the root_path + # we now 'destroy the users' + user = {} + other = {} + + # here we attempt to view another's profile in several ways + visit users_path(1) # all of these should fail, as no signed in user exists + expect(page).to have_content 'You need to sign in or sign up before continuing.' + visit users_path(user, scope: :other) + expect(current_path).to eq '/users/sign_in' + expect(page).to have_content 'You need to sign in or sign up before continuing.' + visit user_path(2) + expect(page).to have_content 'You need to sign in or sign up before continuing.' + visit user_path(1) + expect(page).to have_content 'You need to sign in or sign up before continuing.' + visit users_path(other) + expect(page).to have_content 'You need to sign in or sign up before continuing.' + + # tight as a drum + # now, we sign in again as Admin user + + @user = User.first + @user.role = 'admin' + @user.save! + login_as(@user, scope: :user) + visit '/users' + expect(current_path).to eq '/users' + expect(@user.id).to eq 1 + expect(page).to have_content 'johnny@appleseed.com' + expect(page).to have_content 'frankie@appleseed.com' + expect(page).to have_select :user_role, 'Admin' + expect(page).to have_content 'Gold' + expect(page).to have_content 'frankie@appleseed.com' + expect(page).to have_select :user_role, 'Gold' + expect(page).to have_content 'Delete user' + visit users_path(2) + expect(current_path).to eq '/users.2' + expect(@user.sign_in_count?).to be true + expect(@user.sign_in_count).to eq 2 + click_link 'Sign out' + expect(current_path).to eq root_path + end +end \ No newline at end of file diff --git a/spec/features/visitors/home_page_spec.rb b/spec/features/visitors/home_page_spec.rb new file mode 100644 index 0000000..87c065b --- /dev/null +++ b/spec/features/visitors/home_page_spec.rb @@ -0,0 +1,18 @@ +# Feature: Home page +# As a visitor +# I want to visit a home page +# So I can learn more about the website +feature 'Home page', type: :feature do + +# Scenario: Visit the home page +# Given I am a visitor +# When I visit the home page +# Then I see "Welcome" +scenario 'visit the home page' do + visit root_path + save_and_open_page + expect(current_path).to eq '/' + expect(page).to have_content "Learn to build a successful subscription site." + end + +end \ No newline at end of file diff --git a/spec/features/visitors/navigation_spec.rb b/spec/features/visitors/navigation_spec.rb new file mode 100644 index 0000000..b920ef2 --- /dev/null +++ b/spec/features/visitors/navigation_spec.rb @@ -0,0 +1,18 @@ +# Feature: Navigation links +# As a visitor +# I want to see navigation links +# So I can find home, sign in, or sign up +feature 'Navigation links', :devise do + + # Scenario: View navigation links + # Given I am a visitor + # When I visit the home page + # Then I see "home," "sign in," and "sign up" + scenario 'view navigation links' do + visit root_path + expect(page).to have_content 'Home' + expect(page).to have_content 'Sign in' + expect(page).to have_content 'Sign up' + end + +end \ No newline at end of file diff --git a/spec/features/visitors/sign_up_spec.rb b/spec/features/visitors/sign_up_spec.rb new file mode 100644 index 0000000..a4ba6dc --- /dev/null +++ b/spec/features/visitors/sign_up_spec.rb @@ -0,0 +1,52 @@ +require 'stripe_mock' +include Features::SessionHelpers +include Warden::Test::Helpers +Warden.test_mode! + +RSpec.configure do |config| + config.before(:each) do + StripeMock.start + FactoryGirl.reload + end + + config.after(:each) do + StripeMock.stop + Warden.test_reset! + end +end + +# Feature: Sign up +# As a visitor +# I want to sign up +# So I can visit protected areas of the site +feature 'Sign Up', :devise, type: :controller, js: true do + before do + CreatePlanService.new.call + end + + # Scenario: Visitor can sign up with valid email address and password + # Given I am not signed in + # When I sign up with a valid email address and password + # Then I see a successful sign up message + scenario 'visitor can sign up as a silver subscriber' do + pending 'signups need more work' + visit '/users/sign_up?plan=silver' + expect(current_path).to eq '/users/sign_up' + sign_up_silver + expect(page).to have_content 'Welcome! You have signed up successfully.' + end + + scenario 'visitor can sign up as a gold subscriber' do + pending 'signups need more work' + visit '/users/sign_up?plan=gold' + expect(current_path).to eq '/users/sign_up' + sign_up_gold + expect(page).to have_content 'Welcome! You have signed up successfully.' + end + + scenario 'visitor can sign up as a platinum subscriber' do + pending 'signups need more work' + sign_up_platinum + expect(page).to have_content 'Welcome! You have signed up successfully.' + end +end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb new file mode 100644 index 0000000..d499660 --- /dev/null +++ b/spec/helpers/application_helper_spec.rb @@ -0,0 +1,8 @@ +RSpec.describe ApplicationHelper, type: :helper do + describe "#page_title" do + it "returns the default title" do + visit '/' + expect(page.title).to eq "Rails Stripe Membership" + end + end +end \ No newline at end of file diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb new file mode 100644 index 0000000..8740bc7 --- /dev/null +++ b/spec/mailers/user_mailer_spec.rb @@ -0,0 +1,18 @@ +describe UserMailer do + describe '#expire_mail' do + let(:user) { FactoryGirl.create(:user, email: 'johnny@appleseed.com') } + let(:mail) { UserMailer.expire_email(user) } + + it "has the correct user email" do + expect(mail.to).to eq([user.email]) + end + + it "has the correct senders email" do + expect(mail.from).to eq(["do-not-reply@example.com"]) + end + + it "has the correct subject" do + expect(mail.subject).to eq "Subscription Cancelled" + end + end +end \ No newline at end of file diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 48d0a4f..e9ae740 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,13 +1,225 @@ +require 'stripe_mock' +include Warden::Test::Helpers +Warden.test_mode! + +RSpec.configure do + @attr = { + name: 'Test User', + email: 'testuser@example.com', + password: 'changeme', + password_confirmation: 'changeme' + } +end + describe User do + before(:each) do + StripeMock.start + @user = FactoryGirl.build(:user) + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + it 'responds to email' do + expect(@user).to respond_to(:email) + end + + it '#email returns a string' do + expect(@user.email).to eq 'test@example.com' + end + + it 'should create a new instance given a valid attribute' do + @user.email = 'valid@example.com' + expect(@user.save!).to be true + end + + it 'should require an email address' do + expect(@user.email = '').not_to be_falsey + end + + it 'should accept valid email addresses' do + addresses = %w(user@foo.com THE_USER@foo.bar.org first.last@foo.jp) + addresses.each do |address| + @user.email = address.to_s + expect(@user.save!).not_to be_falsey + end + end + + it 'should reject invalid email addresses' do + addresses = %w(user@foo,com user_at_foo.org example.user@foo.) + addresses.each do |address| + expect(@user.email = address).to_not be true + end + end + + it 'should reject duplicate email addresses' do + @user.save + @duplicate_user = FactoryGirl.build(:user) + expect(@duplicate_user.save).not_to be true + end + + it 'should reject email addresses identical up to case' do + @user.save + @new_user = FactoryGirl.build(:user, email: 'test@example.com'.upcase) + expect(@new_user.save).not_to be true + end +end + +describe 'Passwords' do + before(:each) do + @user = FactoryGirl.build(:user) + end + + after(:each) do + Warden.test_reset! + end + + it 'should have a password attribute' do + expect(@user).to respond_to(:password) + end + + it 'should have a password confirmation attribute' do + expect(@user).to respond_to(:password_confirmation) + end +end + +describe 'password validations' do + before(:each) do + @user = FactoryGirl.build(:user) + end + + after(:each) do + Warden.test_reset! + end + + it 'should require a password' do + expect(@user.update_attributes(password: '', password_confirmation: '')).to eq false + end + + it 'should require a matching password confirmation' do + expect(@user.update_attributes(password_confirmation: 'invalid')).to be_falsey + end + + it 'should reject short passwords' do + @user.save! + short = 'a' * 5 + hash = { password: short, password_confirmation: short } + expect(@user.update_attributes(password: short, password_confirmation: short)).to be_falsey + expect(User.new(hash)).not_to be_valid + end +end + +describe 'password encryption' do + before(:each) do + @user = FactoryGirl.build(:user) + end + + after(:each) do + Warden.test_reset! + end + + it 'should have an encrypted password attribute' do + expect(@user).to respond_to(:password) + expect(@user).to respond_to(:encrypted_password) + end - before(:each) { @user = FactoryGirl.build(:user, email: 'test@example.com') } + it 'should set the encrypted password attribute' do + expect(@user.password).not_to be_blank + end +end + +describe 'expire' do + before(:each) do + @user = FactoryGirl.build(:user) + end + + after(:each) do + Warden.test_reset! + end - subject { @user } + it 'sends an email to user', live: true do + pending 'needs work' + @user.save + UserMailer.expire_email(@user) + expect(ActionMailer::Base.deliveries.last.to).to eq @user.email + end +end - it { should respond_to(:email) } +describe '#update_plan' do + before(:each) do + @user = FactoryGirl.build(:user) + @user1 = FactoryGirl.build(:user, email: 'user1@example.com', role: 2) + end - it "#email returns a string" do - expect(@user.email).to match 'test@example.com' + after(:each) do + Warden.test_reset! end -end \ No newline at end of file + it 'updates a users role' do + @user.save! + expect(@user.role).to eq 'user' + @user1.save! + expect(@user1.role).to eq 'silver' + @user1.update_attributes(role: 3) + expect(@user1.role).to eq 'gold' + end +end + +describe '.update_stripe' do + context 'with a non-existing user' do + let(:stripe_helper) { StripeMock.create_test_helper } + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + @user = FactoryGirl.build(:user) + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + it 'creates a new stripe customer and card token' do + customer = Stripe::Customer.create( + email: 'someone@example.com', + source: stripe_helper.generate_card_token + ) + expect(customer.email).to eq 'someone@example.com' + expect(customer.sources.first.id).to match(/^test_cc/) + end + + it 'creates a Silver stripe customer' do + plan = stripe_helper.create_plan( + id: 'silver', + name: 'Silver', + interval: 'month' + ) + customer = Stripe::Customer.create( + email: 'silver@example.com', + description: 'Silver Plan Creation', + source: stripe_helper.generate_card_token( + id: 'silver', + amount: 900, + currency: 'usd' + ) + ) + expect(customer.email).to eq('silver@example.com') + expect(plan.id).to eq 'silver' + subscription = customer.subscriptions.create(plan: 'silver') + expect(subscription.plan.id).to eq 'silver' + + user = Stripe::Customer.retrieve(customer.id) + expect(user.id).to match(/^test_cus/) + expect(user.subscriptions.data[0].id).to match(/^test_su/) + expect(user.subscriptions.total_count).to eq 1 + expect(user.subscriptions.data[0].plan.id).to eq 'silver' + expect(user.subscriptions.data[0].plan.name).to eq 'Silver' + expect(user.subscriptions.data[0].plan.interval_count).to eq 1 + expect(user.subscriptions.data[0].customer).to match(/^test_cus/) + expect(user.subscriptions.data[0].status).to eq 'active' + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 443c602..b6660ed 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -7,6 +7,23 @@ require 'rspec/rails' # Add additional requires below this line. Rails is not loaded until this point! +require 'rspec/mocks' +require 'capybara/rails' +require 'capybara/rspec' +require 'factory_girl_rails' +require 'stripe_mock' +require 'stripe_mock/server' +# require 'celluloid' # 20150111 see controllers/users_controller.rb +# require 'celluloid/autostart' # ditto above line +# require 'email_spec' +# require 'sucker_punch' +require 'sucker_punch/async_syntax' +# require 'sucker_punch/testing/inline' +require 'thin' +# ARGV = [] # Reset ARGV so Dante will quit using rspec params +# StripeMock.spawn_server # Live tests, run $ rspec -t live +# ActiveRecord::Base.connection.reconnect! + # Requires supporting ruby files with custom matchers and macros, etc, in # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are # run as spec files by default. This means that files in spec/support that end @@ -26,6 +43,14 @@ # If you are not using ActiveRecord, you can remove this line. ActiveRecord::Migration.maintain_test_schema! +# Reference : http://stackoverflow.com/questions/8862967/visit-method-not-found-in-my-rspec +module ::RSpec::Core + class ExampleGroup + include Capybara::DSL + include Capybara::RSpecMatchers + end +end + RSpec.configure do |config| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = "#{::Rails.root}/spec/fixtures" @@ -54,4 +79,6 @@ config.filter_rails_from_backtrace! # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") + + config.include Warden::Test::Helpers end diff --git a/spec/stripe/shared_stripe_examples/card_examples_spec.rb b/spec/stripe/shared_stripe_examples/card_examples_spec.rb new file mode 100644 index 0000000..ed4437a --- /dev/null +++ b/spec/stripe/shared_stripe_examples/card_examples_spec.rb @@ -0,0 +1,306 @@ +# https://github.com/rebelidealist/stripe-ruby-mock/blob/master/spec/shared_stripe_examples/card_examples.rb +require 'stripe_mock' +describe 'Card API', live: true do + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + end + + after(:each) do + StripeMock.stop + end + + it 'creates/returns a card when using customer.sources.create given a card token' do + customer = Stripe::Customer.create(id: 'test_customer_sub') + card_token = stripe_helper.generate_card_token( + last4: '1123', + exp_month: 12, + exp_year: 2015 + ) + card = customer.sources.create(source: card_token) + expect(card.customer).to eq 'test_customer_sub' + expect(card.last4).to eq '1123' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2015 + + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.sources.count).to eq 1 + card = customer.sources.data.first + expect(card.customer).to eq 'test_customer_sub' + expect(card.last4).to eq '1123' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2015 + end + + it 'creates/returns a card when using recipient.cards.create given a card token' do + recipient = Stripe::Recipient.create(id: 'test_recipient_sub') + card_token = stripe_helper.generate_card_token( + last4: '1123', + exp_month: 12, + exp_year: 2016 + ) + card = recipient.cards.create(card: card_token) + expect(card.recipient).to eq 'test_recipient_sub' + expect(card.last4).to eq '1123' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2016 + + recipient = Stripe::Recipient.retrieve('test_recipient_sub') + expect(recipient.cards.count).to eq 1 + card = recipient.cards.data.first + expect(card.recipient).to eq 'test_recipient_sub' + expect(card.last4).to eq '1123' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2016 + end + + it 'creates/returns a card when using customer.sources.create given card params' do + customer = Stripe::Customer.create(id: 'test_customer_sub') + card = customer.sources.create(card: { + number: '4242424242424242', + exp_month: '12', + exp_year: '2017', + cvc: '123' + }) + expect(card.customer).to eq 'test_customer_sub' + expect(card.last4).to eq '4242' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2017 + + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.sources.count).to eq 1 + card = customer.sources.data.first + expect(card.customer).to eq 'test_customer_sub' + expect(card.last4).to eq '4242' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2017 + end + + it 'creates/returns a card when using recipient.cards.create given card params' do + recipient = Stripe::Recipient.create(id: 'test_recipient_sub') + card = recipient.cards.create(card: { + number: '4000056655665556', + exp_month: '12', + exp_year: '2018', + cvc: '123' + }) + expect(card.recipient).to eq 'test_recipient_sub' + expect(card.last4).to eq '5556' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2018 + + recipient = Stripe::Recipient.retrieve('test_recipient_sub') + expect(recipient.cards.count).to eq 1 + card = recipient.cards.data.first + expect(card.recipient).to eq 'test_recipient_sub' + expect(card.last4).to eq '5556' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2018 + end + + it 'creates a single card with a generated card token' do + customer = Stripe::Customer.create + expect(customer.sources.count).to eq 0 + + customer.sources.create source: stripe_helper.generate_card_token + # Yes, stripe-ruby-mock does not actually add the new card to the customer instance + expect(customer.sources.count).to eq 0 + + customer2 = Stripe::Customer.retrieve(customer.id) + expect(customer2.sources.count).to eq 1 + expect(customer2.default_source).to eq customer2.sources.first.id + end + + it 'create does not change the customers default card if already set' do + customer = Stripe::Customer.create( + id: 'test_customer_sub', + default_source: 'test_cc_original' + ) + card_token = stripe_helper.generate_card_token( + last4: '1123', + exp_month: 12, + exp_year: 2019 + ) + card = customer.sources.create(source: card_token) + + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.default_source).to eq 'test_cc_original' + end + + it 'create updates the customers default card if not set' do + customer = Stripe::Customer.create(id: 'test_customer_sub') + card_token = stripe_helper.generate_card_token( + last4: '1123', + exp_month: 12, + exp_year: 2020 + ) + card = customer.sources.create(source: card_token) + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.default_source).to_not be_nil + end + + describe 'retrieval and deletion with customers' do + let!(:customer) { Stripe::Customer.create(id: 'test_customer_sub') } + let!(:card_token) { stripe_helper.generate_card_token(last4: '1123', exp_month: 12, exp_year: 2021) } + let!(:card) { customer.sources.create(source: card_token) } + + it 'can retrieve all cards of a customer' do + retrieved = customer.sources.all + expect(retrieved.count).to eq 1 + end + + it 'retrieves a customer card' do + retrieved = customer.sources.retrieve(card.id) + expect(retrieved.to_s).to eq card.to_s + end + + it 'retrieves a customer card after re-fetching the customer' do + retrieved = Stripe::Customer.retrieve(customer.id).sources.retrieve(card.id) + expect(retrieved.id).to eq card.id + end + + it 'deletes a customers card' do + card.delete + retrieved_cus = Stripe::Customer.retrieve(customer.id) + expect(retrieved_cus.sources.data).to be_empty + end + + it 'deletes a customers card then set the default_card to nil' do + card.delete + retrieved_cus = Stripe::Customer.retrieve(customer.id) + expect(retrieved_cus.default_source).to be_nil + end + + it 'updates the default card if deleted' do + card.delete + retrieved_cus = Stripe::Customer.retrieve(customer.id) + expect(retrieved_cus.default_source).to be_nil + end + + context 'deletion when the user has two cards' do + let!(:card_token_2) { stripe_helper.generate_card_token(last4: '1123', exp_month: 12, exp_year: 2022) } + let!(:card_2) { customer.sources.create(source: card_token_2) } + + it 'has just one card anymore' do + card.delete + retrieved_cus = Stripe::Customer.retrieve(customer.id) + expect(retrieved_cus.sources.data.count).to eq 1 + expect(retrieved_cus.sources.data.first.id).to eq card_2.id + end + + it 'sets the default_card id to the last card remaining id' do + card.delete + retrieved_cus = Stripe::Customer.retrieve(customer.id) + expect(retrieved_cus.default_source).to eq card_2.id + end + end + end + + describe 'retrieval and deletion with recipients' do + let!(:recipient) { Stripe::Recipient.create(name: 'Test Recipient', type: 'individual') } + let!(:card_token) { stripe_helper.generate_card_token(number: '4000056655665556') } + let!(:card) { recipient.cards.create(card: card_token) } + + it 'can retrieve all cards of a recipient' do + retrieved = recipient.cards.all + expect(retrieved.count).to eq 1 + end + + it 'deletes a recipient card' do + card.delete + retrieved_cus = Stripe::Recipient.retrieve(recipient.id) + expect(retrieved_cus.cards.data).to be_empty + end + + it 'deletes a recipient card then set the default_card to nil' do + card.delete + retrieved_cus = Stripe::Recipient.retrieve(recipient.id) + expect(retrieved_cus.default_card).to be_nil + end + + context 'deletion when the recipient has two cards' do + let!(:card_token_2) { stripe_helper.generate_card_token(number: '5200828282828210') } + let!(:card_2) { recipient.cards.create(card: card_token_2) } + + it 'has just one card anymore' do + card.delete + retrieved_rec = Stripe::Recipient.retrieve(recipient.id) + expect(retrieved_rec.cards.data.count).to eq 1 + expect(retrieved_rec.cards.data.first.id).to eq card_2.id + end + + it 'sets the default_card id to the last card remaining id' do + card.delete + retrieved_rec = Stripe::Recipient.retrieve(recipient.id) + expect(retrieved_rec.default_card).to eq card_2.id + end + end + end + + describe 'Errors' do + it 'throws an error when the customer does not have the retrieving card id' do + customer = Stripe::Customer.create + card_id = 'card_123' + expect { customer.sources.retrieve(card_id) }.to raise_error {|e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.message).to include 'no source', card_id + expect(e.param).to eq 'id' + expect(e.http_status).to eq 404 + } + end + end + + context 'update card' do + let!(:customer) { Stripe::Customer.create(id: 'test_customer_sub') } + let!(:card_token) { stripe_helper.generate_card_token(last4: '1123', exp_month: 12, exp_year: 2028) } + let!(:card) { customer.sources.create(source: card_token) } + + it 'updates the card' do + exp_month = 12 + exp_year = 2028 + card.exp_month = exp_month + card.exp_year = exp_year + card.save + retrieved = customer.sources.retrieve(card.id) + expect(retrieved.exp_month).to eq exp_month + expect(retrieved.exp_year).to eq exp_year + end + end + + context 'retrieve multiple cards' do + it 'retrieves a list of multiple cards' do + customer = Stripe::Customer.create(id: 'test_customer_card') + card_token = stripe_helper.generate_card_token( + last4: '1123', + exp_month: 12, + exp_year: 2029 + ) + card1 = customer.sources.create(source: card_token) + card_token = stripe_helper.generate_card_token( + last4: '1124', + exp_month: 12, + exp_year: 2030 + ) + card2 = customer.sources.create(source: card_token) + customer = Stripe::Customer.retrieve('test_customer_card') + list = customer.sources.all + expect(list.object).to eq 'list' + expect(list.count).to eq 2 + expect(list.data.length).to eq 2 + expect(list.data.first.object).to eq 'card' + expect(list.data.first.to_hash).to eq card1.to_hash + expect(list.data.last.object).to eq 'card' + expect(list.data.last.to_hash).to eq card2.to_hash + end + + it 'retrieves an empty list if there are no subscriptions' do + Stripe::Customer.create(id: 'no_cards') + customer = Stripe::Customer.retrieve('no_cards') + list = customer.sources.all + expect(list.object).to eq 'list' + expect(list.count).to eq 0 + expect(list.data.length).to eq 0 + end + end +end diff --git a/spec/stripe/shared_stripe_examples/customer_card_examples_spec.rb b/spec/stripe/shared_stripe_examples/customer_card_examples_spec.rb new file mode 100644 index 0000000..2070188 --- /dev/null +++ b/spec/stripe/shared_stripe_examples/customer_card_examples_spec.rb @@ -0,0 +1,39 @@ +shared_examples "Multiple Customer Cards" do + + before { StripeMock.start } + after { StripeMock.stop } + + it "handles multiple cards", live: true do + tok1 = Stripe::Token.retrieve stripe_helper.generate_card_token number: "4242424242424242" + tok2 = Stripe::Token.retrieve stripe_helper.generate_card_token number: "4012888888881881" + customer = Stripe::Customer.create(email: 'alice@bob.com', card: tok1.id) + default_source = customer.sources.first + customer.sources.create(card: tok2.id) + customer = Stripe::Customer.retrieve(customer.id) + customer.sources { include[]=total_count } + expect(customer.sources.total_count).to eq(2) + expect(customer.default_source).to eq default_source.id + end + + it "gives the same two card numbers the same fingerprints", live: true do + tok1 = Stripe::Token.retrieve stripe_helper.generate_card_token number: "4242424242424242" + tok2 = Stripe::Token.retrieve stripe_helper.generate_card_token number: "4242424242424242" + customer = Stripe::Customer.create(email: 'alice@bob.com', card: tok1.id) + customer = Stripe::Customer.retrieve(customer.id) + card = customer.sources.find do |existing_card| + existing_card.fingerprint == tok2.card.fingerprint + end + expect(card).to_not be_nil + end + + it "gives different card numbers different fingerprints", live: true do + tok1 = Stripe::Token.retrieve stripe_helper.generate_card_token number: "4242424242424242" + tok2 = Stripe::Token.retrieve stripe_helper.generate_card_token number: "4012888888881881" + customer = Stripe::Customer.create(email: 'alice@bob.com', card: tok1.id) + customer = Stripe::Customer.retrieve(customer.id) + card = customer.sources.find do |existing_card| + existing_card.fingerprint == tok2.card.fingerprint + end + expect(card).to be_nil + end +end diff --git a/spec/stripe/shared_stripe_examples/subscription_examples_spec.rb b/spec/stripe/shared_stripe_examples/subscription_examples_spec.rb new file mode 100644 index 0000000..9d5065e --- /dev/null +++ b/spec/stripe/shared_stripe_examples/subscription_examples_spec.rb @@ -0,0 +1,754 @@ +require 'stripe_mock' +include Warden::Test::Helpers +Warden.test_mode! + +RSpec.configure do + def gen_card_tk + card = stripe_helper.generate_card_token( + last4: '4242', + exp_month: 12, + exp_year: 2021 + ) + end +end + +# shared_examples +describe 'Customer Subscriptions', live: true do + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + FactoryGirl.reload + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + context 'creating a new subscription' do + it 'adds a new subscription to customer with none' do + plan = stripe_helper.create_plan( + id: 'five', + name: 'Five', + amount: 500, + currency: 'usd', + interval: 'month', + source: gen_card_tk, + trial_period_days: nil, + metadata: { foo: 'bar', example: 'yes' }, + statement_descriptor: 'Shows on Invoice' + ) + customer = Stripe::Customer.create(source: gen_card_tk, plan: plan.id) + + customer = Stripe::Customer.retrieve(customer.id) + expect(customer.subscriptions.object).to eq 'list' + expect(customer.subscriptions.data).to_not be_empty + expect(customer.subscriptions.data[0].status).to eq 'active' + expect(customer.subscriptions.data[0].id).to match(/^test_su/) + expect(customer.subscriptions.data[0][:id]).to match(/^test_su/) + expect(customer.subscriptions.data.count).to eq 1 + expect(customer.subscriptions.data[0].plan.id).to eq 'five' + expect(customer.subscriptions.data[0].plan.object).to eq 'plan' + expect(customer.subscriptions.data[0].plan.to_hash).to eq plan.to_hash + expect(customer.subscriptions.data[0].plan.metadata.foo).to eq('bar') + expect(customer.subscriptions.data[0].plan.metadata.example).to eq 'yes' + expect(customer.subscriptions.data).to_not be_empty + expect(customer.subscriptions.count).to eq 1 + expect(customer.subscriptions.data.length).to eq 1 + expect(customer.subscriptions.data[0].plan.to_hash).to eq plan.to_hash + expect(customer.subscriptions.data[0].customer).to eq customer.id + end + + it 'adds additional subscription to customer with existing subscription' do + silver = stripe_helper.create_plan(id: 'silver') + gold = stripe_helper.create_plan(id: 'gold') + customer = Stripe::Customer.create( + id: 'test_customer_sub', + source: gen_card_tk, + plan: 'gold' + ) + sub = customer.subscriptions.create(plan: 'silver') + expect(sub.object).to eq 'subscription' + expect(sub.plan.to_hash).to eq(silver.to_hash) + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.subscriptions.data).to_not be_empty + expect(customer.subscriptions.count).to eq 2 + expect(customer.subscriptions.data.length).to eq 2 + expect(customer.subscriptions.data.last.plan.to_hash).to eq gold.to_hash + expect(customer.subscriptions.data.last.customer).to eq customer.id + expect(customer.subscriptions.data.first.id).to eq sub.id + expect(customer.subscriptions.data.first.plan.to_hash).to eq silver.to_hash + expect(customer.subscriptions.data.first.customer).to eq customer.id + end + + it 'subscribes a cardless customer when specifing a card token' do + plan = stripe_helper.create_plan(id: 'enterprise', amount: 499, source: gen_card_tk) + customer = Stripe::Customer.create(id: 'cardless') + sub = customer.subscriptions.create(plan: 'enterprise', source: gen_card_tk) + customer = Stripe::Customer.retrieve('cardless') + expect(customer.subscriptions.data.first.id).to eq sub.id + expect(customer.subscriptions.data.first.customer).to eq customer.id + expect(customer.sources.count).to eq 1 + expect(customer.sources.data.length).to eq 1 + expect(customer.default_source).to_not be_nil + expect(customer.default_source).to eq customer.sources.data.first.id + end + + it 'throws an error when plan does not exist' do + customer = Stripe::Customer.create(id: 'cardless') + expect { customer.subscriptions.create(plan: 'gazebo') }.to raise_error { |e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.http_status).to eq 404 + expect(e.message).to_not be_nil + } + expect(customer.subscriptions.data).to be_empty + expect(customer.subscriptions.count).to eq 0 + end + + it 'throws an error when subscribing a customer with no card' do + plan = stripe_helper.create_plan(id: 'enterprise', amount: 499) + customer = Stripe::Customer.create(id: 'cardless') + expect { customer.subscriptions.create(plan: 'enterprise') }.to raise_error { |e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.http_status).to eq 400 + expect(e.message).to_not be_nil + } + expect(customer.subscriptions.data).to be_empty + expect(customer.subscriptions.count).to eq 0 + end + end + + context 'cancelling a subscription' do + it 'cancels a stripe customers subscription', live: true do + truth = stripe_helper.create_plan(id: 'the truth') + customer = Stripe::Customer.create(source: gen_card_tk, plan: 'the truth') + sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id) + result = sub.delete + expect(result.status).to eq('canceled') + expect(result.cancel_at_period_end).to eq false + expect(result.canceled_at).to_not be_nil + expect(result.id).to eq sub.id + customer = Stripe::Customer.retrieve(customer.id) + expect(customer.subscriptions.data).to be_empty + expect(customer.subscriptions.count).to eq 0 + expect(customer.subscriptions.data.length).to eq 0 + end + + it 'retrieves an empty list if theres no subscriptions' do + Stripe::Customer.create(id: 'no_subs') + customer = Stripe::Customer.retrieve('no_subs') + list = customer.subscriptions.all + expect(list.object).to eq 'list' + expect(list.count).to eq 0 + expect(list.data.length).to eq 0 + end + end +end + +describe 'address line check, metadata usage, and more', live: true do + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + FactoryGirl.reload + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + it 'creates a stripe customer and subscribes them to a plan with metadata' do + plan = stripe_helper.create_plan( + amount: 500, + interval: 'month', + name: 'Sample Plan', + currency: 'usd', + id: 'sample5', + statement_descriptor: 'Plan Statement' + ) + + plan = Stripe::Plan.retrieve(plan.id) + expect(plan.id).to eq 'sample5' + expect(plan.interval).to eq 'month' + expect(plan.name).to eq 'Sample Plan' + # fails : stripe-ruby-mock error 20150906 : is it worth testing ? or accounting for ? + # expect(plan.created).not_to eq nil + expect(plan.amount).to eq 500 + expect(plan.currency).to eq 'usd' + expect(plan.object).to eq 'plan' + expect(plan.livemode).to eq false + expect(plan.interval_count).to eq 1 + expect(plan.trial_period_days).to be nil + expect(plan.statement_descriptor).to eq 'Plan Statement' + + customer = Stripe::Customer.create( + email: 'johnny@appleseed.com', + source: gen_card_tk + ) + expect(customer.id).to match(/^test_cus/) + expect(customer.email).to eq 'johnny@appleseed.com' + expect(customer.subscriptions.total_count).to eq 0 + + subscription = customer.subscriptions.create(plan: 'sample5') + expect(subscription.id).to match(/^test_su/) + # expect(subscription.current_period_start).to eq 1441403417 + # expect(subscription.current_period_end).to eq 1443995417 + expect(subscription.status).to eq 'active' + expect(subscription.plan.id).to eq 'sample5' + expect(subscription.plan.interval).to eq 'month' + expect(subscription.plan.name).to eq 'Sample Plan' + expect(subscription.plan.amount).to eq 500 + expect(subscription.plan.currency).to eq 'usd' + expect(subscription.plan.object).to eq 'plan' + expect(subscription.plan.livemode).to eq false + expect(subscription.plan.interval_count).to eq 1 + expect(subscription.plan.trial_period_days).to eq nil + expect(subscription.plan.statement_descriptor).to eq 'Plan Statement' + expect(subscription.cancel_at_period_end).to eq false + expect(subscription.canceled_at).to eq nil + expect(subscription.ended_at).to eq nil + expect(subscription.start).to eq 1308595038 + expect(subscription.object).to eq 'subscription' + expect(subscription.trial_start).to eq nil + expect(subscription.trial_end).to eq nil + expect(subscription.customer).to match(/^test_cus_3/) + expect(subscription.quantity).to eq 1 + expect(subscription.tax_percent).to eq nil + expect(subscription.discount).to eq nil + expect(subscription.metadata).to be_a Stripe::StripeObject + expect(subscription.metadata.to_json).to eq '{}' + + subscription.metadata['foo'] = 'bar' + expect(subscription).to be_a Stripe::Subscription + subscription = customer.subscriptions.retrieve(subscription.id) + expect(subscription.customer).to match(/^test_cus/) + + customer = Stripe::Customer.retrieve(customer.id) + expect(customer.subscriptions.object).to eq 'list' + expect(customer.subscriptions.total_count).to eq 1 + expect(customer.subscriptions.url).to match(/test_cus/) + expect(customer.default_source).to match(/^test_cc/) + expect(customer.subscriptions.data[0].id).to match(/^test_su/) + expect(customer.subscriptions.first.plan.id).to eq 'sample5' + end + + # data you do not provide is being sourced from the stripe-ruby-mock gem method + # look for this method : def self.mock_card(params={}) with the data parameters below it + # ref: https://github.com/rebelidealist/stripe-ruby-mock/blob/master/lib/stripe_mock/data.rb + it 'resumes an at period end cancelled subscription' do + truth = stripe_helper.create_plan(id: 'the_truth') + expect(truth.id).to eq 'the_truth' + expect(truth.interval).to eq 'month' + expect(truth.name).to eq 'StripeMock Default Plan ID' + expect(truth.amount).to eq 1337 + expect(truth.currency).to eq 'usd' + expect(truth.object).to eq 'plan' + expect(truth.livemode).to eq false + expect(truth.interval_count).to eq 1 + + customer = Stripe::Customer.create(id: 'test_customer_sub', source: gen_card_tk, plan: 'the_truth') + sub = customer.subscriptions.retrieve(customer.subscriptions.data[0].id) + result = sub.delete(at_period_end: true) + sub.plan = 'the_truth' + sub.save + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.subscriptions.data).to_not be_empty + expect(customer.subscriptions.count).to eq 1 + expect(customer.subscriptions.data.length).to eq 1 + expect(customer.subscriptions.data.first.status).to eq 'active' + expect(customer.subscriptions.data.first.cancel_at_period_end).to eq false + expect(customer.subscriptions.data.first.ended_at).to be_nil + expect(customer.subscriptions.data.first.canceled_at).to be_nil + end + + it 'cancels a stripe customers subscription at period end' do + truth = stripe_helper.create_plan(id: 'the_truth') + customer = Stripe::Customer.create(id: 'test_customer_sub', source: gen_card_tk, plan: 'the_truth') + sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id) + result = sub.delete(at_period_end: true) + expect(result.status).to eq 'active' + expect(result.cancel_at_period_end).to eq true + expect(result.id).to eq sub.id + + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.subscriptions.data).to_not be_empty + expect(customer.subscriptions.count).to eq 1 + expect(customer.subscriptions.data.length).to eq 1 + expect(customer.subscriptions.data.first.status).to eq 'active' + expect(customer.subscriptions.data.first.cancel_at_period_end).to eq true + expect(customer.subscriptions.data.first.ended_at).to be_nil + expect(customer.subscriptions.data.first.canceled_at).to_not be_nil + end + + it 'subscribes a customer with no card to a plan with a free trial' do + plan = stripe_helper.create_plan(id: 'trial', amount: 999, trial_period_days: 14) + customer = Stripe::Customer.create(id: 'cardless') + sub = customer.subscriptions.create(plan: 'trial') + expect(sub.object).to eq 'subscription' + expect(sub.plan.to_hash).to eq plan.to_hash + expect(sub.trial_end - sub.trial_start).to eq(14 * 86_400) + + customer = Stripe::Customer.retrieve('cardless') + expect(customer.subscriptions.data).to_not be_empty + expect(customer.subscriptions.count).to eq 1 + expect(customer.subscriptions.data.length).to eq 1 + expect(customer.subscriptions.data.first.id).to eq sub.id + expect(customer.subscriptions.data.first.plan.to_hash).to eq plan.to_hash + expect(customer.subscriptions.data.first.customer).to eq customer.id + end + + it 'subscribes a customer with no card to a free plan' do + plan = stripe_helper.create_plan(id: 'free_tier', amount: 0) + customer = Stripe::Customer.create(id: 'cardless') + sub = customer.subscriptions.create(plan: 'free_tier') + expect(sub.object).to eq 'subscription' + expect(sub.plan.to_hash).to eq plan.to_hash + + customer = Stripe::Customer.retrieve('cardless') + expect(customer.subscriptions.data).to_not be_empty + expect(customer.subscriptions.count).to eq 1 + expect(customer.subscriptions.data.length).to eq 1 + expect(customer.subscriptions.data.first.id).to eq sub.id + expect(customer.subscriptions.data.first.plan.to_hash).to eq plan.to_hash + expect(customer.subscriptions.data.first.customer).to eq customer.id + end + + # TODO: next two are near duplicates, merge them + it 'overrides trial length when trial end is set' do + plan = stripe_helper.create_plan(id: 'trial', amount: 999, trial_period_days: 14) + customer = Stripe::Customer.create(id: 'short_trial') + trial_end = Time.now.utc.to_i + 3600 + sub = customer.subscriptions.create(plan: 'trial', trial_end: trial_end) + expect(sub.object).to eq 'subscription' + expect(sub.plan.to_hash).to eq plan.to_hash + expect(sub.current_period_end).to eq trial_end + expect(sub.trial_end).to eq trial_end + end + + it 'overrides trial length when trial end is set' do + plan = stripe_helper.create_plan(id: 'trial', amount: 999, trial_period_days: 14) + customer = Stripe::Customer.create(id: 'short_trial') + trial_end = Time.now.utc.to_i + 3_600 + + sub = customer.subscriptions.create(plan: 'trial', trial_end: trial_end) + + expect(sub.object).to eq 'subscription' + expect(sub.plan.to_hash).to eq plan.to_hash + expect(sub.current_period_end).to eq trial_end + expect(sub.trial_end).to eq trial_end + end + + it 'returns without a trial when trial_end is set to now' do + plan = stripe_helper.create_plan(id: 'trial', amount: 999, trial_period_days: 14) + customer = Stripe::Customer.create(id: 'no_trial', source: gen_card_tk) + + sub = customer.subscriptions.create(plan: 'trial', trial_end: 'now') + + expect(sub.object).to eq 'subscription' + expect(sub.plan.to_hash).to eq plan.to_hash + expect(sub.status).to eq 'active' + expect(sub.trial_start).to be_nil + expect(sub.trial_end).to be_nil + end + + it 'raises error when trial_end is not an integer or now' do + plan = stripe_helper.create_plan(id: 'trial', amount: 999, trial_period_days: 14) + customer = Stripe::Customer.create(id: 'cus_trial') + expect { customer.subscriptions.create(plan: 'trial', trial_end: 'gazebo') }.to raise_error { |e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.http_status).to eq 400 + expect(e.message).to eq('Invalid timestamp: must be an integer') + } + end + + it 'does not change status of subscription when cancelling at period end' do + trial = stripe_helper.create_plan(id: 'trial', trial_period_days: 14) + customer = Stripe::Customer.create(id: 'test_customer_sub', source: gen_card_tk, plan: 'trial') + sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id) + result = sub.delete(at_period_end: true) + expect(result.status).to eq 'trialing' + + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.subscriptions.data.first.status).to eq 'trialing' + end + + it 'raises error when trial_end is set to a time in the past' do + plan = stripe_helper.create_plan(id: 'trial', amount: 999, trial_period_days: 14) + customer = Stripe::Customer.create(id: 'past_trial') + trial_end = Time.now.utc.to_i - 3600 + expect { customer.subscriptions.create(plan: 'trial', trial_end: trial_end) }.to raise_error { |e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.http_status).to eq 400 + expect(e.message).to eq('Invalid timestamp: must be an integer Unix timestamp in the future') + } + end + + it 'raises error when trial_end is set to a time more than five years in the future' do + plan = stripe_helper.create_plan(id: 'trial', amount: 999, trial_period_days: 14) + customer = Stripe::Customer.create(id: 'long_trial') + trial_end = Time.now.utc.to_i + 31_557_600 * 5 + 3_600 # 5 years + 1 hour + expect { customer.subscriptions.create(plan: 'trial', trial_end: trial_end) }.to raise_error { |e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.http_status).to eq 400 + expect(e.message).to eq('Invalid timestamp: can be no more than five years in the future') + } + end + + context 'updating a subscription' do + it 'updates a stripe customers existing subscription' do + silver = stripe_helper.create_plan(id: 'silver') + gold = stripe_helper.create_plan(id: 'gold') + customer = Stripe::Customer.create(id: 'test_customer_sub', source: gen_card_tk, plan: 'silver') + sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id) + sub.plan = 'gold' + sub.quantity = 5 + sub.metadata.foo = 'bar' + sub.metadata.example = 'yes' + expect(sub.save).to be_truthy + expect(sub.object).to eq 'subscription' + expect(sub.plan.to_hash).to eq(gold.to_hash) + expect(sub.quantity).to eq(5) + expect(sub.metadata.foo).to eq('bar') + expect(sub.metadata.example).to eq 'yes' + + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.subscriptions.data).to_not be_empty + expect(customer.subscriptions.count).to eq 1 + expect(customer.subscriptions.data.length).to eq 1 + expect(customer.subscriptions.data.first.id).to eq sub.id + expect(customer.subscriptions.data.first.plan.to_hash).to eq(gold.to_hash) + expect(customer.subscriptions.data.first.customer).to eq customer.id + end + + it 'throws an error when plan does not exist' do + free = stripe_helper.create_plan(id: 'free', amount: 0) + customer = Stripe::Customer.create(id: 'cardless', plan: 'free') + sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id) + sub.plan = 'gazebo' + expect { sub.save }.to raise_error { |e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.http_status).to eq 404 + expect(e.message).to_not be_nil + } + customer = Stripe::Customer.retrieve('cardless') + expect(customer.subscriptions.count).to eq 1 + expect(customer.subscriptions.data.length).to eq 1 + expect(customer.subscriptions.data.first.plan.to_hash).to eq free.to_hash + end + + it 'throws an error when subscription does not exist' do + free = stripe_helper.create_plan(id: 'free', amount: 0) + customer = Stripe::Customer.create(id: 'cardless', plan: 'free') + expect { customer.subscriptions.retrieve('gazebo') }.to raise_error { |e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.http_status).to eq 404 + expect(e.message).to_not be_nil + } + customer = Stripe::Customer.retrieve('cardless') + expect(customer.subscriptions.count).to eq 1 + expect(customer.subscriptions.data.length).to eq 1 + expect(customer.subscriptions.data.first.plan.to_hash).to eq free.to_hash + end + + it 'throws an error when updating a customer with no card' do + free = stripe_helper.create_plan(id: 'free', amount: 0) + paid = stripe_helper.create_plan(id: 'enterprise', amount: 499) + customer = Stripe::Customer.create(id: 'cardless', plan: 'free') + sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id) + sub.plan = 'enterprise' + expect { sub.save }.to raise_error { |e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.http_status).to eq 400 + expect(e.message).to_not be_nil + } + customer = Stripe::Customer.retrieve('cardless') + expect(customer.subscriptions.count).to eq 1 + expect(customer.subscriptions.data.length).to eq 1 + expect(customer.subscriptions.data.first.plan.to_hash).to eq free.to_hash + end + + it 'updates a customer with no card to a plan with a free trial' do + free = stripe_helper.create_plan(id: 'free', amount: 0) + trial = stripe_helper.create_plan(id: 'trial', amount: 999, trial_period_days: 14) + customer = Stripe::Customer.create(id: 'cardless', plan: 'free') + sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id) + sub.plan = 'trial' + sub.save + expect(sub.object).to eq 'subscription' + expect(sub.plan.to_hash).to eq trial.to_hash + + customer = Stripe::Customer.retrieve('cardless') + expect(customer.subscriptions.data).to_not be_empty + expect(customer.subscriptions.count).to eq 1 + expect(customer.subscriptions.data.length).to eq 1 + expect(customer.subscriptions.data.first.id).to eq sub.id + expect(customer.subscriptions.data.first.plan.to_hash).to eq trial.to_hash + expect(customer.subscriptions.data.first.customer).to eq customer.id + end + + it 'updates a customer with no card to a free plan' do + free = stripe_helper.create_plan(id: 'free', amount: 0) + gratis = stripe_helper.create_plan(id: 'gratis', amount: 0) + customer = Stripe::Customer.create(id: 'cardless', plan: 'free') + sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id) + sub.plan = 'gratis' + sub.save + expect(sub.object).to eq 'subscription' + expect(sub.plan.to_hash).to eq gratis.to_hash + customer = Stripe::Customer.retrieve('cardless') + expect(customer.subscriptions.data).to_not be_empty + expect(customer.subscriptions.count).to eq 1 + expect(customer.subscriptions.data.length).to eq 1 + expect(customer.subscriptions.data.first.id).to eq sub.id + expect(customer.subscriptions.data.first.plan.to_hash).to eq gratis.to_hash + expect(customer.subscriptions.data.first.customer).to eq customer.id + end + + it "sets a card when updating a customer's subscription" do + free = stripe_helper.create_plan(id: 'free', amount: 0) + paid = stripe_helper.create_plan(id: 'paid', amount: 499) + customer = Stripe::Customer.create(id: 'test_customer_sub', plan: 'free') + + sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id) + sub.plan = 'paid' + sub.source = gen_card_tk + sub.save + + customer = Stripe::Customer.retrieve('test_customer_sub') + + expect(customer.sources.count).to eq(1) + expect(customer.sources.data.length).to eq(1) + expect(customer.default_source).to_not be_nil + expect(customer.default_source).to eq customer.sources.data.first.id + end + + it 'sets a card when updating a customers subscription' do + # our plans are silver, gold, platinum + free = stripe_helper.create_plan(id: 'free', amount: 0) + paid = stripe_helper.create_plan(id: 'paid', amount: 499) + silver = stripe_helper.create_plan(id: 'silver', amount: 900) + customer = Stripe::Customer.create(id: 'test_customer_sub', plan: 'silver', source: gen_card_tk) + sub = customer.subscriptions.retrieve(customer.subscriptions.data[0].id) + sub.plan = 'silver' + sub.source = gen_card_tk + sub.save + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.sources.count).to eq 2 + expect(customer.sources.data.length).to eq 2 + expect(customer.default_source).to_not be_nil + expect(customer.default_source).not_to eq customer.sources.data[0].id + end + + it 'changes an active subscription to a trial when trial_end is set' do + plan = stripe_helper.create_plan(id: 'no_trial', amount: 999) + expect(plan.id).to eq 'no_trial' + expect(plan.interval).to eq 'month' + expect(plan.name).to eq 'StripeMock Default Plan ID' + expect(plan.amount).to eq 999 + expect(plan.currency).to eq 'usd' + expect(plan.object).to eq 'plan' + expect(plan.livemode).to eq false + expect(plan.interval_count).to eq 1 + expect(plan.trial_period_days).to eq nil + + customer = Stripe::Customer.create(id: 'test_trial_end', plan: 'no_trial', source: gen_card_tk) + expect(customer.id). to eq 'test_trial_end' + expect(customer.email).to eq 'stripe_mock@example.com' + expect(customer.description).to eq 'an auto-generated stripe customer data mock' + expect(customer.object).to eq 'customer' + expect(customer.created).to eq 1372126710 + expect(customer.livemode).to eq false + expect(customer.delinquent).to eq false + expect(customer.discount).to eq nil + expect(customer.account_balance).to eq 0 + expect(customer.sources.object).to eq 'list' + expect(customer.sources.total_count).to eq 1 + expect(customer.sources.url).to eq '/v1/customers/test_trial_end/sources' + expect(customer.sources.data[0].id).to match(/^test_cc/) + expect(customer.sources.data[0].object).to eq 'card' + expect(customer.sources.data[0].last4).to eq '4242' + expect(customer.sources.data[0].type).to eq 'Visa' + expect(customer.sources.data[0].brand).to eq 'Visa' + expect(customer.sources.data[0].funding).to eq 'credit' + expect(customer.sources.data[0].exp_month).to eq 12 + expect(customer.sources.data[0].exp_year).to eq 2021 + expect(customer.sources.data[0].fingerprint).to eq 'eXWMGVNbMZcworZC' + expect(customer.sources.data[0].customer).to eq 'test_trial_end' + expect(customer.sources.data[0].country).to eq 'US' + expect(customer.sources.data[0].name).to eq 'Johnny App' + expect(customer.sources.data[0].address_line1).to eq nil + expect(customer.sources.data[0].address_line2).to eq nil + expect(customer.sources.data[0].address_city).to eq nil + expect(customer.sources.data[0].address_state).to eq nil + expect(customer.sources.data[0].address_zip).to eq nil + expect(customer.sources.data[0].address_country).to eq nil + expect(customer.sources.data[0].cvc_check).to eq nil + expect(customer.sources.data[0].address_line1_check).to eq nil + # If address_line1 was provided, results of the check: pass, fail, unavailable, or unchecked + expect(customer.sources.data[0].address_zip_check).to eq nil + expect(customer.sources.data[0].last4).to eq '4242' + expect(customer.sources.data[0].cvc).to eq '999' + expect(customer.subscriptions.object).to eq 'list' + expect(customer.subscriptions.total_count).to eq 1 + expect(customer.subscriptions.url).to eq '/v1/customers/test_trial_end/subscriptions' + expect(customer.subscriptions.data[0].id).to match(/^test_su/) + # expect(customer.subscriptions.data[0].current_period_start).to eq '1441384417' + # expect(customer.subscriptions.data[0].current_period_end).to eq '1443976417' + expect(customer.subscriptions.data[0].status).to eq 'active' + expect(customer.subscriptions.data[0].plan[:id]).to eq 'no_trial' + expect(customer.subscriptions.data[0].plan[:interval]).to eq 'month' + expect(customer.subscriptions.data[0].plan[:name]).to eq 'StripeMock Default Plan ID' + expect(customer.subscriptions.data[0].plan[:amount]).to eq 999 + expect(customer.subscriptions.data[0].plan[:currency]).to eq 'usd' + expect(customer.subscriptions.data[0].plan[:object]).to eq 'plan' + expect(customer.subscriptions.data[0].plan[:livemode]).to eq false + expect(customer.subscriptions.data[0].plan[:interval_count]).to eq 1 + expect(customer.subscriptions.data[0].plan[:trial_period_days]).to eq nil + + expect(customer.subscriptions.data[0].plan.interval).to eq 'month' + expect(customer.subscriptions.data[0].plan.name).to eq 'StripeMock Default Plan ID' + expect(customer.subscriptions.data[0].plan.amount).to eq 999 + expect(customer.subscriptions.data[0].plan.currency).to eq 'usd' + expect(customer.subscriptions.data[0].plan.object).to eq 'plan' + expect(customer.subscriptions.data[0].plan.livemode).to eq false + expect(customer.subscriptions.data[0].plan.interval_count).to eq 1 + expect(customer.subscriptions.data[0].plan.trial_period_days).to eq nil + # expect(customer.subscriptions.data[0].plan.cancel_at_period_end).to eq false # does not exist + # expect(customer.subscriptions.data[0].plan.canceled_at).to eq nil # does not exist + # expect(customer.subscriptions.data[0].plan.ended_at).to eq nil + # expect(customer.subscriptions.data[0].plan.start).to eq 1308595038 + expect(customer.subscriptions.data[0].plan.object).to eq 'plan' + # expect(customer.subscriptions.data[0].plan.trial_start).to eq nil + # expect(customer.subscriptions.data[0].plan.trial_end).to eq nil + # expect(customer.subscriptions.data[0].plan.test_trial_end).to eq trial_end + # expect(customer.subscriptions.data[0].plan.quantity).to eq 1 + # expect(customer.subscriptions.data[0].plan.tax_percent).to eq nil + # expect(customer.subscriptions.data[0].plan.discount).to eq nil + # expect(customer.subscriptions.data[0].plan.metadata). to eq {} + expect(customer.default_source).to match(/^test_cc/) + expect(customer.plan).to eq 'no_trial' + + sub = customer.subscriptions.retrieve(customer.subscriptions.data[0].id) + expect(sub.status).to eq 'active' + trial_end = Time.now.utc.to_i + 3_600 + sub.trial_end = trial_end + # sub.save + expect(sub.object).to eq 'subscription' + expect(sub.plan.to_hash).to eq plan.to_hash + # expect(sub.status).to eq('trialing') # still active TODO: is this a stripe-ruby-mock error ? + expect(sub.status).to eq 'active' + expect(sub.trial_end).to eq trial_end + # expect(sub.current_period_end).to eq trial_end # fails TODO: is this a stripe-ruby-mock error ? + end + + it 'does not require a card when trial_end is present', live: true do + plan = stripe_helper.create_plan( + amount: 2000, + interval: 'month', + name: 'Amazing Gold Plan', + currency: 'usd', + id: 'gold' + ) + options = { plan: plan.id, trial_end: (Date.today + 30).to_time.to_i } + stripe_customer = Stripe::Customer.create + stripe_customer.subscriptions.create options + end + end + + context 'retrieve multiple subscriptions' do + it 'retrieves a list of multiple subscriptions' do + free = stripe_helper.create_plan(id: 'free', amount: 0) + paid = stripe_helper.create_plan(id: 'paid', amount: 499) + customer = Stripe::Customer.create(id: 'test_customer_sub', source: gen_card_tk, plan: 'free') + customer.subscriptions.create(plan: 'paid') + customer = Stripe::Customer.retrieve('test_customer_sub') + list = customer.subscriptions.all + expect(list.object).to eq 'list' + expect(list.count).to eq 2 + expect(list.data.length).to eq 2 + expect(list.data.last.object).to eq 'subscription' + expect(list.data.last.plan.to_hash).to eq free.to_hash + expect(list.data.first.object).to eq 'subscription' + expect(list.data.first.plan.to_hash).to eq(paid.to_hash) + end + end + + # If address_line1 was provided, there are four possible Stripe responses to their address check: + # pass, fail, unavailable, or unchecked + context 'testing the address_line1_check' do + let(:stripe_helper) { StripeMock.create_test_helper } + + it 'receives and acts on the response from stripe for the address_line1_check' do + # pending 'needs more work as currently fails receiving a response beyond nil' + card_token = stripe_helper.generate_card_token( + last4: '4242', + exp_month: 12, + exp_year: 2021, + name: 'Johnny App', + address_line1: 'actual account address', + address_line2: 'the real address', + address_city: 'the city', + address_state: 'AK', + address_zip: '12345', + address_country: 'US', + address_line1_check: 'pass' + ) + plan = stripe_helper.create_plan(id: 'no_trial', amount: 550) + expect(plan.id).to eq 'no_trial' + expect(plan.interval).to eq 'month' + expect(plan.name).to eq 'StripeMock Default Plan ID' + expect(plan.amount).to eq 550 + expect(plan.currency).to eq 'usd' + expect(plan.trial_period_days).to eq nil + + customer = Stripe::Customer.create( + id: 'test_trial_end', + plan: 'no_trial', + source: card_token + ) + + customer = Stripe::Customer.retrieve(customer.id) + expect(customer.id). to eq 'test_trial_end' + expect(customer.email).to eq 'stripe_mock@example.com' + expect(customer.description).to eq 'an auto-generated stripe customer data mock' + expect(customer.sources.url).to eq '/v1/customers/test_trial_end/sources' + expect(customer.sources.data[0].id).to match(/^test_cc/) + expect(customer.sources.data[0].object).to eq 'card' + expect(customer.sources.data[0].last4).to eq '4242' + expect(customer.sources.data[0].exp_month).to eq 12 + expect(customer.sources.data[0].exp_year).to eq 2021 + expect(customer.sources.data[0].name).to eq 'Johnny App' + expect(customer.sources.data[0].address_line1).to eq 'actual account address' + expect(customer.sources.data[0].address_line2).to eq 'the real address' + expect(customer.sources.data[0].address_city).to eq 'the city' + expect(customer.sources.data[0].address_state).to eq 'AK' + expect(customer.sources.data[0].address_zip).to eq '12345' + expect(customer.sources.data[0].address_country).to eq 'US' + expect(customer.sources.data[0].cvc_check).to eq nil + # uncomment below if you are not mocking and testing the address line1 check + # expect(customer.sources.data[0].address_line1_check).to eq nil + + # If address_line1 is provided, Stripe returns one of four results of the check: + # pass, fail, unavailable, unchecked + # + # Here we test the mock above of Stripe passing the address line1 check + # We actually mocked the pass in this line in the card token creation above: + # address_line1_check: 'pass' + expect(customer.sources.data[0].address_line1_check).to eq 'pass' + # the other tests can likewise be created as needed + # expect(customer.sources.data[0].address_line1_check).to_not eq unavailable + # expect(customer.sources.data[0].address_line1_check).to_not eq unchecked + + # adjust code above to cause an error, then uncomment the error test below + # TODO: make sure this error code matches error generated + # expect(customer.sources.data[0].address_line1_check).to raise_error { |e| + # expect(e).to be_a RuntimeError + # expect(e.http_status).to eq(400) + # } + # See: https://github.com/rebelidealist/stripe-ruby-mock/blob/master/lib/stripe_mock/data.rb + # Find on that page, this method: def self.mock_card(params={}) + end + end +end diff --git a/spec/stripe/stripe_card_spec.rb b/spec/stripe/stripe_card_spec.rb new file mode 100644 index 0000000..b90413a --- /dev/null +++ b/spec/stripe/stripe_card_spec.rb @@ -0,0 +1,335 @@ +require 'stripe_mock' +include Warden::Test::Helpers +Warden.test_mode! + +describe 'Card API', live: true do + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + it 'creates/returns a card when using customer.sources.create given a card token' do + customer = Stripe::Customer.create(id: 'test_customer_sub') + card_token = stripe_helper.generate_card_token( + last4: '1123', + exp_month: 12, + exp_year: 2018 + ) + card = customer.sources.create(source: card_token) + expect(card.customer).to eq('test_customer_sub') + expect(card.last4).to eq '1123' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2018 + expect(customer.id).to match(/^test_cus/) + expect(customer.email).to eq 'stripe_mock@example.com' + expect(customer.description).to eq 'an auto-generated stripe customer data mock' + + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.sources.count).to eq 1 + expect(customer.sources.data[0].id).to match(/^test_cc/) + expect(customer.sources.data[0].last4).to eq '1123' + expect(customer.sources.data[0].exp_month).to eq 12 + expect(customer.sources.data[0].exp_year).to eq 2018 + + card = customer.sources.data.first + expect(card.customer).to eq 'test_customer_sub' + expect(card.last4).to eq '1123' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2018 + end + + it 'creates/returns a card when using customer.sources.create given a card token' do + customer = Stripe::Customer.create(id: 'test_customer_sub') + card_token = stripe_helper.generate_card_token( + last4: '1123', + exp_month: 11, + exp_year: 2019 + ) + card = customer.sources.create(source: card_token) + expect(card.customer).to eq 'test_customer_sub' + expect(card.last4).to eq '1123' + expect(card.exp_month).to eq 11 + expect(card.exp_year).to eq 2019 + + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.sources.count).to eq 1 + card = customer.sources.data.first + expect(card.customer).to eq 'test_customer_sub' + expect(card.last4).to eq '1123' + expect(card.exp_month).to eq 11 + expect(card.exp_year).to eq 2019 + end + + it 'creates a single card with a generated card token' do + customer = Stripe::Customer.create + expect(customer.sources.count).to eq 0 + + customer.sources.create(source: stripe_helper.generate_card_token) + # Yes, stripe-ruby-mock does not actually add the new card to the customer instance + expect(customer.sources.count).to eq 0 + + customer2 = Stripe::Customer.retrieve(customer.id) + expect(customer2.sources.count).to eq 1 + expect(customer2.default_source).to eq customer2.sources.first.id + end + + it 'create does not change the customers default source if already set' do + customer = Stripe::Customer.create( + id: 'test_customer_sub', + default_source: 'test_cc_original' + ) + card_token = stripe_helper.generate_card_token( + last4: '1123', + exp_month: 7, + exp_year: 2017 + ) + expect(card_token).to match(/^test_tok/) + + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.sources.total_count).to eq 0 + expect(customer.subscriptions.total_count).to eq 0 + expect(customer.sources.data).to be_empty + expect(customer.default_source).to match(/^test_cc_original/) + + card_token2 = stripe_helper.generate_card_token( + last4: '4242', + exp_month: 8, + exp_year: 2028 + ) + expect(card_token2).to match(/^test_tok/) + + customer.sources.create(source: card_token2) + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.sources.data[0].id).to match(/^test_cc/) + expect(customer.default_source).to eq 'test_cc_original' + end + + it 'create updates the customers newest default source' do + customer = Stripe::Customer.create(email: 'defaultsource@example.com') + expect(customer.default_source).to eq nil + + card_token = stripe_helper.generate_card_token( + last4: '4242', + exp_month: 12, + exp_year: 2026 + ) + card = customer.sources.create(source: card_token) + customer = Stripe::Customer.retrieve(customer.id) + expect(customer.default_source).to match(/^test_cc/) + end + + it 'create updates the customers default source if not set' do + customer = Stripe::Customer.create( + id: 'test_customer_sub', + default_source: 'test_cc_original' + ) + expect(customer.default_source).to match(/^test_cc_original/) + card_token = stripe_helper.generate_card_token( + last4: '1123', + exp_month: 8, + exp_year: 2018 + ) + card = customer.sources.create(source: card_token) + expect(card.id == 'test_cc_original').to eq false + + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.sources.data[0].id).to match(/^test_cc/) + expect(customer.default_source).to eq 'test_cc_original' + end + + describe 'retrieval and deletion with customer' do + let!(:customer) { Stripe::Customer.create(id: 'test_customer_sub') } + let!(:card_token) { stripe_helper.generate_card_token(last4: '1123', exp_month: 11, exp_year: 2025) } + let!(:card) { customer.sources.create(source: card_token) } + + it 'can retrieve all customers cards' do + retrieved = customer.sources.all + expect(retrieved.count).to eq 1 + end + + it 'retrieves a customer card' do + retrieved = customer.sources.retrieve(card.id) + expect(retrieved.to_s).to eq card.to_s + end + + it 'retrieves a customer card after re-fetching the customer' do + retrieved = Stripe::Customer.retrieve(customer.id).sources.retrieve(card.id) + expect(retrieved.id).to eq card.id + end + + it 'deletes a customer card' do + card.delete + retrieved_cus = Stripe::Customer.retrieve(customer.id) + expect(retrieved_cus.sources.data).to be_empty + end + + it 'deletes a customer card then set the default_source to nil' do + card.delete + retrieved_cus = Stripe::Customer.retrieve(customer.id) + expect(retrieved_cus.default_source).to be_nil + end + + it 'updates the default card if deleted' do + card.delete + retrieved_cus = Stripe::Customer.retrieve(customer.id) + expect(retrieved_cus.default_source).to be_nil + end + end + + context 'deletion when the user has two sources' do + let!(:card_token) { stripe_helper.generate_card_token(last4: '1123', exp_month: 12, exp_year: 2024) } + let!(:card) { customer.sources.create(source: card_token) } + let!(:card_token_2) { stripe_helper.generate_card_token(last4: '1123', exp_month: 12, exp_year: 2025) } + let!(:card_2) { customer.sources.create(source: card_token_2) } + let!(:customer) { Stripe::Customer.create(id: 'test_customer_sub', default_source: 'test_cc_original') } + + it 'has just one card anymore' do + card.delete + retrieved_cus = Stripe::Customer.retrieve(customer.id) + expect(retrieved_cus.sources.data.count).to eq 1 + expect(retrieved_cus.sources.data.first.id).to eq card_2.id + end + + it 'has just one card after a card deletion' do + customer = Stripe::Customer.create + card_token = stripe_helper.generate_card_token( + last4: '1123', + exp_month: 12, + exp_year: 2026 + ) + card = customer.sources.create(source: card_token) + card_token_2 = stripe_helper.generate_card_token( + last4: '1124', + exp_month: 12, + exp_year: 2027 + ) + card_2 = customer.sources.create(source: card_token_2) + customer.sources.retrieve(card_2.id).delete + retrieved_cus = Stripe::Customer.retrieve(customer.id).sources.all(object: 'card') + expect(retrieved_cus.data.count).to eq 1 + expect(retrieved_cus.data.first.id).to eq card.id + end + + it 'sets the default_source id to the last card remaining id' do + customer = Stripe::Customer.create + card_token = stripe_helper.generate_card_token( + last4: '1123', + exp_month: 12, + exp_year: 2028 + ) + card = customer.sources.create(source: card_token) + customer = Stripe::Customer.retrieve(customer.id) + card_token_2 = stripe_helper.generate_card_token( + last4: '1124', + exp_month: 12, + exp_year: 2029 + ) + card_2 = customer.sources.create(source: card_token_2) + customer = Stripe::Customer.retrieve(customer.id).sources.all(object: 'card') + list = customer.data + expect(list.first.id).to eq card.id + end + end + + describe 'Errors' do + it 'throws an error when the customer does not have the retrieving card id' do + customer = Stripe::Customer.create + card_id = 'card_123' + expect { customer.sources.retrieve(card_id) }.to raise_error { |e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.message).to include 'no source', card_id + expect(e.param).to eq 'id' + expect(e.http_status).to eq 404 + } + end + end + + context 'update card' do + let!(:customer) { Stripe::Customer.create(id: 'test_customer_sub') } + let!(:card_token) { stripe_helper.generate_card_token(last4: '1123', exp_month: 11, exp_year: 2025) } + let!(:card) { customer.sources.create(source: card_token) } + + it 'updates the card' do + exp_month = 12 + exp_year = 2028 + + card.exp_month = exp_month + card.exp_year = exp_year + card.save + + retrieved = customer.sources.retrieve(card.id) + + expect(retrieved.exp_month).to eq(exp_month) + expect(retrieved.exp_year).to eq(exp_year) + end + + it 'updates the card differently' do + customer = Stripe::Customer.create + card_token = stripe_helper.generate_card_token( + last4: '4242', + exp_month: 12, + exp_year: 2030 + ) + card = customer.sources.create(source: card_token) + exp_month = 12 + exp_year = 2031 + card.exp_month = exp_month + card.exp_year = exp_year + card.save + retrieved = customer.sources.retrieve(card.id) + expect(retrieved.exp_month).to eq exp_month + expect(retrieved.exp_year).to eq exp_year + end + end + + context 'retrieve multiple cards' do + it 'retrieves a list of multiple cards' do + customer = Stripe::Customer.create(id: 'test_customer_card') + card_token = stripe_helper.generate_card_token( + last4: '1123', + exp_month: 12, + exp_year: 2032 + ) + card1 = customer.sources.create(source: card_token) + card_token = stripe_helper.generate_card_token( + last4: '4242', + exp_month: 12, + exp_year: 2033 + ) + card2 = customer.sources.create(source: card_token) + customer = Stripe::Customer.retrieve('test_customer_card') + list = customer.sources.all + expect(list.object).to eq 'list' + expect(list.count).to eq 2 + expect(list.data.length).to eq 2 + expect(list.data.first.object).to eq 'card' + expect(list.data.first.to_hash).to eq card1.to_hash + expect(list.data.last.object).to eq 'card' + expect(list.data.last.to_hash).to eq card2.to_hash + end + + it 'retrieves an empty list if there are no subscriptions' do + Stripe::Customer.create(id: 'no_cards') + customer = Stripe::Customer.retrieve('no_cards') + list = customer.sources.all + expect(list.object).to eq 'list' + expect(list.count).to eq 0 + expect(list.data.length).to eq 0 + end + end + + describe 'prevents customer from deleting their default_source' do + pending 'code is yet to be written' + # Stripe API [Note that for cards belonging to customers, you may want to prevent customers + # on paid subscriptions from deleting all cards on file so that there is at least one default + # card for the next invoice payment attempt.] + # I should rewrite this test accordingly. + # Referenece : https://stripe.com/docs/api/ruby#delete_card + end +end diff --git a/spec/stripe/stripe_card_token_spec.rb b/spec/stripe/stripe_card_token_spec.rb new file mode 100644 index 0000000..08dfd45 --- /dev/null +++ b/spec/stripe/stripe_card_token_spec.rb @@ -0,0 +1,117 @@ +require 'stripe_mock' +include Warden::Test::Helpers +Warden.test_mode! + +describe 'StripeToken' do + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + describe 'Direct Token Creation' do + it 'generates and reads a card token for create charge' do + card_token = stripe_helper.generate_card_token(last4: '4242', exp_month: 9, exp_year: 2019) + card_charge = Stripe::Charge.create(amount: 500, currency: 'usd', source: card_token) + charge = Stripe::Charge.retrieve(card_charge.id) + expect(charge.source.last4).to eq '4242' + expect(charge.source.exp_month).to eq 9 + expect(charge.source.exp_year).to eq 2019 + end + + it 'generates and reads a card token for create customer' do + card_token = stripe_helper.generate_card_token(last4: '4242', exp_month: 10, exp_year: 2020) + customer = Stripe::Customer.create(source: card_token) + card = customer.sources.data.first + expect(card.last4).to eq '4242' + expect(card.exp_month).to eq 10 + expect(card.exp_year).to eq 2020 + end + + it 'generates and reads a card token for create customer' do + card_token = stripe_helper.generate_card_token(card_number: '4242424242424242', exp_month: 11, exp_year: 2021) + customer = Stripe::Customer.create(source: card_token) + card = customer.sources.data.first + expect(card.last4).to eq '4242' + expect(card.exp_month).to eq 11 + expect(card.exp_year).to eq 2021 + end + + it 'generates and reads a card token for update customer' do + card_token = stripe_helper.generate_card_token(card_number: '4242424242424242', exp_month: 12, exp_year: 2022) + customer = Stripe::Customer.create(source: card_token) + customer.card = card_token + customer.save + card = customer.sources.data.first + expect(card.last4).to eq '4242' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2022 + end + + it 'generates and reads a card token for update customer' do + card_token = stripe_helper.generate_card_token(last4: '1133', exp_month: 1, exp_year: 2023) + customer = Stripe::Customer.create(source: card_token) + customer.card = card_token + customer.save + card = customer.sources.data.first + expect(card.last4).to eq '1133' + expect(card.exp_month).to eq 1 + expect(card.exp_year).to eq 2023 + end + + it 'retrieves a created token' do + card_token = stripe_helper.generate_card_token(last4: '2323', exp_month: 2, exp_year: 2024) + token = Stripe::Token.retrieve(card_token) + expect(token.id).to eq(card_token) + expect(token.card.last4).to eq '2323' + expect(token.card.exp_month).to eq 2 + expect(token.card.exp_year).to eq 2024 + end + end + + describe 'Stripe::Token' do + it 'generates a card token created from customer' do + card_token = stripe_helper.generate_card_token( + source: { + card_number: '4242424242424242', + exp_month: 11, + exp_year: 2019 + } + ) + customer = Stripe::Customer.create(source: card_token) + expect(customer.default_source).to match(/^test_cc/) + customer.description = 'a StripeMock card from customer' + customer.save + expect(card_token).to match(/^test_tok/) + expect(customer.email).to eq 'stripe_mock@example.com' + expect(customer.description).to eq 'a StripeMock card from customer' + expect(customer.id).to match(/^test_cus/) + expect(customer.object).to match(/customer/) + expect(customer.livemode).to eq false + expect(customer.discount).to eq nil + expect(customer.default_source).to match(/^test_cc/) + end + + it 'generates a stripe card token' do + card_token = StripeMock.generate_card_token(last4: '9191', exp_month: 12, exp_year: 2025) + cus = Stripe::Customer.create(source: card_token) + user = Stripe::Customer.retrieve(cus.id) + card = user.sources.data.first + expect(card.last4).to eq '9191' + expect(card.exp_month).to eq 12 + expect(card.exp_year).to eq 2025 + expect(user.sources.data.first.id).to match(/^test_cc/) + end + end + + describe 'error handling' do + it 'throws an error if neither card nor customer are provided', live: true do + expect { Stripe::Token.create }.to raise_error(Stripe::InvalidRequestError, /must supply either a card, customer/) + end + end +end diff --git a/spec/stripe/stripe_charge_spec.rb b/spec/stripe/stripe_charge_spec.rb new file mode 100644 index 0000000..7ab14dc --- /dev/null +++ b/spec/stripe/stripe_charge_spec.rb @@ -0,0 +1,115 @@ +require 'stripe_mock' + +include Warden::Test::Helpers +Warden.test_mode! + +describe 'Charge API' do + + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + it "creates a stripe charge item with a card token" do + charge = Stripe::Charge.create({ + amount: 995, + currency: 'usd', + source: stripe_helper.generate_card_token(last4: "4242", exp_month: 12, exp_year: 2018), + description: 'card charge' + }) + expect(charge.id).to match(/^test_ch/) + expect(charge.amount).to eq(995) + expect(charge.description).to eq('card charge') + expect(charge.captured).to eq(true) + end + + it "creates a stripe charge item with a customer and card id" do + customer = Stripe::Customer.create({ + email: 'chargeitem@example.com', + source: stripe_helper.generate_card_token(number: '4242424242424242'), + description: "customer creation with card token" + }) + card_token = customer.sources.data[0].id + expect(customer.sources.data.length).to eq(1) + expect(customer.sources.data[0].id).not_to be_nil + expect(customer.sources.data[0].last4).to eq('4242') + end + + it "creates a stripe charge with a specific customer card" do + begin + customer = Stripe::Customer.create({ + email: 'chargeitem@example.com', + source: stripe_helper.generate_card_token(number: '4242424242424242'), + description: "customer creation with card token" + }) + card = customer.sources.data[0] + charge = Stripe::Charge.create({ + currency: 'usd', + customer: customer.id, + source: stripe_helper.generate_card_token(number: '4242424242424242', amount: 995), + description: 'a charge with a specific card', + }) + rescue Stripe::CardError => e + body = e.json_body + err = body[:error] + puts "Status is: #{e.http_status}" + puts "Type is: #{err[:type]}" + puts "Code is: #{err[:code]}" + # param is '' in this case + puts "Param is: #{err[:param]}" + puts "Message is: #{err[:message]}" + rescue Stripe::InvalidRequestError => e + # Invalid parameters were supplied to Stripe's API + rescue Stripe::AuthenticationError => e + # Authentication with Stripe's API failed + # (maybe you changed API keys recently) + rescue Stripe::APIConnectionError => e + # Network communication with Stripe failed + rescue Stripe::StripeError => e + # Display a very generic error to the user, and maybe send + # yourself an email + rescue => e # Something else happened, completely unrelated to Stripe + expect(charge.amount).to eq(995) + expect(charge.description).to eq('a charge with a specific card') + expect(charge.captured).to eq(true) + expect(charge.card.last4).to eq('4242') + expect(charge.id).to match(/^test_ch/) + end + end + + it "requires a valid card token", :live => true do + expect { + charge = Stripe::Charge.create({ + amount: 995, + currency: 'usd', + source: 'bogus_card_token' + }) + }.to raise_error(Stripe::InvalidRequestError, /Invalid token id/) + end + + it "retrieves a stripe charge" do + original = Stripe::Charge.create({ + amount: 995, + currency: 'usd', + source: stripe_helper.generate_card_token + }) + charge = Stripe::Charge.retrieve(original.id) + expect(charge.id).to eq(original.id) + expect(charge.amount).to eq(original.amount) + end + + it "cannot retrieve a charge that doesn't exist" do + expect { Stripe::Charge.retrieve('nope') }.to raise_error {|e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.param).to eq('charge') + expect(e.http_status).to eq(404) + } + end + +end diff --git a/spec/stripe/stripe_config_spec.rb b/spec/stripe/stripe_config_spec.rb new file mode 100644 index 0000000..715cd94 --- /dev/null +++ b/spec/stripe/stripe_config_spec.rb @@ -0,0 +1,18 @@ +describe 'Config Variables' do + describe 'STRIPE_API_KEY' do + # this tests env variables against config/secrets.yml + # notice we can retrieve and test the keys in two ways + it 'a stripe api key is set' do + api_key = ENV.fetch('STRIPE_API_KEY') + expect(api_key).to eq(Rails.application.secrets.stripe_api_key), + 'Your STRIPE_API_KEY is not set, Please refer to the "Configure the Stripe Initializer" section of the README' + end + end + + describe 'STRIPE_PUBLISHABLE_KEY' do + it 'a stripe publishable key is set' do + expect(ENV['STRIPE_PUBLISHABLE_KEY']).to eq(Rails.application.secrets.stripe_publishable_key), + 'Your STRIPE_PUBLISHABLE_KEY is not set, Please refer to the "Configure the Stripe Initializer" section of the README' + end + end +end \ No newline at end of file diff --git a/spec/stripe/stripe_customer_spec.rb b/spec/stripe/stripe_customer_spec.rb new file mode 100644 index 0000000..f5acecb --- /dev/null +++ b/spec/stripe/stripe_customer_spec.rb @@ -0,0 +1,169 @@ +require 'stripe_mock' +include Warden::Test::Helpers +Warden.test_mode! + +describe 'Customer API' do + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + it 'creates a stripe customer with a default source' do + card_token = StripeMock.generate_card_token( + last4: '1123', + exp_month: 9, + exp_year: 2016 + ) + customer = Stripe::Customer.create( + email: 'user@example.com', + source: card_token, + description: 'a customer description' + ) + charge = Stripe::Charge.create({ + amount: 900, + currency: 'usd', + interval: 'month', + source: stripe_helper.generate_card_token( + last4: '1123', + exp_month: 10, + exp_year: 2017 + ), + description: 'Charge for user@example.com' + }, { + idempotency_key: '95ea4310438306ch' + }) + card = customer.sources.data.first + charge = Stripe::Charge.retrieve(charge.id) + customer = Stripe::Customer.retrieve(customer.id) + expect(customer.sources.total_count).to eq 1 + expect(card.id).to match(/^test_cc/) + expect(charge.id).to match(/^test_ch/) + expect(customer.id).to match(/^test_cus/) + expect(customer.email).to eq 'user@example.com' + expect(customer.description).to eq 'a customer description' + expect(customer.sources.count).to eq 1 + expect(customer.sources.count).to eq 1 + expect(customer.sources.total_count).to eq 1 + expect(customer.sources.data.length).to eq 1 + expect(customer.sources.data.first).not_to be_nil + expect(customer.sources.data.first.id).to match(/^test_cc/) + expect(customer.default_source).not_to be_nil + expect(customer.default_source).to eq customer.sources.data.first.id + expect { customer.card }.to raise_error(NoMethodError, /card/) + end + + it 'creates a stripe customer without a card' do + customer = Stripe::Customer.create( + email: 'cardless@example.com', + description: 'no card' + ) + expect(customer.id).to match(/^test_cus/) + expect(customer.email).to eq('cardless@example.com') + expect(customer.description).to eq 'no card' + expect(customer.sources.count).to eq 0 + expect(customer.sources.data.length).to eq 0 + expect(customer.default_source).to be_nil + end + + it 'stores a created stripe customer in memory' do + customer = Stripe::Customer.create( + email: 'storedinmemory@example.com', + source: stripe_helper.generate_card_token + ) + customer2 = Stripe::Customer.create( + email: 'bob@example.com', + source: stripe_helper.generate_card_token + ) + customers = Stripe::Customer.all + array = customers.to_a + data = array.pop + expect(customer.id).to match(/^test_cus/) + expect(data.email).to eq 'bob@example.com' + data2 = array.pop + expect(data2.id).not_to be_nil + expect(data2.email).to eq 'storedinmemory@example.com' + end + + it 'retrieves an identified stripe customer' do + original = Stripe::Customer.create( + email: 'retrievesidentifiedcustomer@example.com', + source: stripe_helper.generate_card_token + ) + customer = Stripe::Customer.retrieve(original.id) + expect(customer.id).to eq original.id + expect(customer.email).to eq original.email + expect(customer.default_source).to eq original.default_source + expect(customer.subscriptions.count).to eq 0 + expect(customer.subscriptions.data).to be_empty + end + + it 'cannot retrieve a customer that does not exist' do + expect { Stripe::Customer.retrieve('nope') }.to raise_error { |e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.param).to eq 'customer' + expect(e.http_status).to eq 404 + } + end + + it 'retrieves all customers' do + StripeMock.start + all = Stripe::Customer.all + Stripe::Customer.create(email: 'one@example.com') + Stripe::Customer.create(email: 'two@example.com') + allnow = Stripe::Customer.all + expect(allnow.count).to eq(all.count + 2) + end + + it 'updates a stripe customer' do + customer = Stripe::Customer.create(email: 'updatecustomer@example.com') + customer = Stripe::Customer.retrieve(customer.id) + customer.email = 'updatedcustomer@example.com' + customer.save + email = customer.email + customer.description = 'new desc' + customer.save + expect(customer.email).to eq 'updatedcustomer@example.com' + expect(customer.description).to eq 'new desc' + end + + it 'updates a stripe customer card' do + card_one = stripe_helper.generate_card_token( + last4: '4242', + exp_month: 11, + exp_year: 2018 + ) + customer = Stripe::Customer.create( + id: 'test_customer_update', + source: card_one + ) + card_one = customer.sources.data.first + expect(card_one.id).to match(/^test_cc/) + expect(customer.default_source).to match(/^test_cc/) + expect(customer.default_source).to eq card_one.id + expect(customer.sources.count).to eq 1 + customer.card = stripe_helper.generate_card_token( + last4: '5555', + exp_month: 12, + exp_year: 2019 + ) + new_card = customer.sources.data.first + expect(customer.sources.total_count).to eq 1 + expect(customer.default_source).to eq new_card.id + end + + it 'deletes a customer' do + customer = Stripe::Customer.create( + email: 'deleteme@example.com', + source: stripe_helper.generate_card_token + ) + customer = Stripe::Customer.retrieve(customer.id) + customer = customer.delete + expect(customer.deleted).to eq true + end +end diff --git a/spec/stripe/stripe_idempotency_spec.rb b/spec/stripe/stripe_idempotency_spec.rb new file mode 100644 index 0000000..ac14a87 --- /dev/null +++ b/spec/stripe/stripe_idempotency_spec.rb @@ -0,0 +1,116 @@ +# 20150713 : communicating with Stripe support and stripe-ruby-mock on how to best create these tests +# 20150710 : unblocked to work on it again +# 20150709 : i blocked this test set out because it seems the idempotency_key cannot be tested +# as being useful or ever able to be used by Stripe, +# due to the token key being limited to a one time use. +# I have communications back and forth with stripe support on this matter. +# Testing the idempotency_key can be ignored for now +=begin +# require 'pry' : to use pry, put gem 'pry' in the Gemfile and run bundle +require 'stripe_mock' + +include Warden::Test::Helpers +Warden.test_mode! + +RSpec.configure do + + def create_charge(card_token, uuid) + Stripe::Charge.create( + { amount: 1000, currency: 'usd', source: card_token, description: 'blah blah' }, + { idempotency_key: uuid }, + ) + end +end + +describe 'Stripe idempotency_key' do + + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + it 'creates a customer, generates payment source, charges customer' do + customer = Stripe::Customer.create({ + email: 'johnny@appleseed.com', + source: stripe_helper.generate_card_token(card_number: "4242424242424242", exp_year: 2022, exp_month: 2, cvc: '123'), + description: "a customer description" + }, { + idempotency_key: "95ea4310438306cus" # just changed from ch ending to cus ending to differentiate from charge key + }) + expect(customer.id).to match /^test_cus/ + charge = Stripe::Charge.create({ + amount: 1500, + currency: 'usd', + customer: customer.id, + description: 'a charge with a specific card', + }, { + idempotency_key: "95ea4310438306ch" + }) + expect(charge.id).to match /^test_ch/ + end + + # first we create a failing card_token with a two digit CVC with an idempotency_key + # then we resubmit the same charge with the correct CVC, will it pass or will it fail ? + # Note 1. if it passes, then what good is the idempotency_key ? + # Note 2. if it fails, then what good is the idempotency_key ? Now I cannot correct error ? + # Note 3. if it fails, which it should because we now have a changed order, with an unchanged key, + # then again, what good is it ? and where is the recovery code to recover from the key preventing + # a correction on a charge that has not yet been processed ? and also, we are using the same + # token, again, which is a no-no. the conundrum deepens. lets see if we can test our way out. + + # our source_token will have a 2 digit code, not the 3 digit that is required, causing failure + it 'makes an idempotent charge' do + uuid = 'abc123' + source_token = stripe_helper.generate_card_token(card_number: "4242424242424242", exp_year: 2022, exp_month: 2, cvc: '12') + expect(source_token).to match /^test_tok/ +#binding.pry + # as the test stands now, this charge step will never be reached nor processed because the source_token will fail + charge = create_charge(source_token, uuid) + # here, we completely ignore stripe's response, acting as if their response was interrupted by net failure + # do nothing because we cannot see the response, so we do the charge again again, and we expect no error because we use the idempotent key + # because we did make the charge, the stripe response has been sent to us. here, we simply ignore that fact, and + # try to make the same charge again : we are faking ourselves out on this first if statement, as we know it is true +#binding.pry # update, the thing to do is first ask stripe for the charge, using the Stripe::Charge.retrieve(source_token) method. + if (expect(charge.id).to match /^test_ch/) == true + charge = create_charge(source_token, uuid) # Failure/Error: Stripe::Charge.create( + # Stripe::InvalidRequestError: + # Invalid token id: test_tok_1 + + elsif (expect(charge.id).to match /^test_ch/) == true + expect { create_charge(source_token, uuid) }.to_not raise_error + expect(charge.id).to match /^test_ch/ + end + end + + ## so far, we have proven we can do a repeat of a charge with the same idempotency_key, + ## and receive a successful response + ## now we change the idempotency_key, on the second try + + it 'prevents an idempotency_key changed charge' do + uuid = 'abcde12345' + source_token = stripe_helper.generate_card_token(card_number: "4242424242424242", exp_year: 2020, exp_month: 2, cvc: '123') + expect(source_token).to match /^test_tok/ + #charge = create_charge(source_token, uuid) + ## we have a successful charge, and we ignore it 'because of network error.' + ## we want to test if a changed idempotency_key will succeed, or fail as it should + ## we presume this first charge is successful, yet we do not get the response + expect{create_charge(source_token, uuid)}.to_not raise_error { |e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.param).to eq('tok') + expect(e.http_status).to eq(404) + } + ## so we try again, this time with a different uuid to see what happens + expect{create_charge(source_token, uuid + '4')}.to raise_error { |e| + expect(e).to be_a Stripe::InvalidRequestError + expect(e.param).to eq('tok') + expect(e.http_status).to eq(404) + } + end +end +=end \ No newline at end of file diff --git a/spec/stripe/stripe_integration_examples/charge_token_spec.rb b/spec/stripe/stripe_integration_examples/charge_token_spec.rb new file mode 100644 index 0000000..f4e624c --- /dev/null +++ b/spec/stripe/stripe_integration_examples/charge_token_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +shared_examples 'Charging with Tokens' do + + describe "With OAuth" do + + before do + @cus = Stripe::Customer.create( + :source => stripe_helper.generate_card_token({ :number => '4242424242424242', :brand => 'Visa' }) + ) + + @card_token = Stripe::Token.create({ + :customer => @cus.id, + :source => @cus.sources.first.id + }, ENV['STRIPE_TEST_OAUTH_ACCESS_TOKEN']) + end + + it "creates with an oauth access token", :oauth => true do + charge = Stripe::Charge.create({ + :amount => 1099, + :currency => 'usd', + :source => @card_token.id + }, ENV['STRIPE_TEST_OAUTH_ACCESS_TOKEN']) + + expect(charge.source.id).to_not eq @cus.sources.first.id + expect(charge.source.fingerprint).to eq @cus.sources.first.fingerprint + expect(charge.source.last4).to eq '4242' + expect(charge.source.brand).to eq 'Visa' + + retrieved_charge = Stripe::Charge.retrieve(charge.id) + + expect(retrieved_charge.source.id).to_not eq @cus.sources.first.id + expect(retrieved_charge.source.fingerprint).to eq @cus.sources.first.fingerprint + expect(retrieved_charge.source.last4).to eq '4242' + expect(retrieved_charge.source.brand).to eq 'Visa' + end + + it "throws an error when the card is not an id", :oauth => true do + expect { + charge = Stripe::Charge.create({ + :amount => 1099, + :currency => 'usd', + :source => @card_token + }, ENV['STRIPE_TEST_OAUTH_ACCESS_TOKEN']) + }.to raise_error(Stripe::InvalidRequestError, /Invalid token id/) + end + end + +end diff --git a/spec/stripe/stripe_integration_examples/customer_card_spec.rb b/spec/stripe/stripe_integration_examples/customer_card_spec.rb new file mode 100644 index 0000000..501d1eb --- /dev/null +++ b/spec/stripe/stripe_integration_examples/customer_card_spec.rb @@ -0,0 +1,43 @@ +require 'stripe_mock' +describe 'Multiple Customer Cards', live: true do + let(:stripe_helper) { StripeMock.create_test_helper } + + it 'handles multiple cards' do + tok1 = Stripe::Token.retrieve stripe_helper.generate_card_token number: '4242424242424242' + tok2 = Stripe::Token.retrieve stripe_helper.generate_card_token number: '4012888888881881' + + cus = Stripe::Customer.create(email: 'alice@bob.com', source: tok1.id) + default_card = cus.sources.first + cus.sources.create(source: tok2.id) + + cus = Stripe::Customer.retrieve(cus.id) + expect(cus.sources.count).to eq(2) + expect(cus.default_source).to eq default_card.id + end + + it 'gives the same two card numbers the same fingerprints' do + tok1 = Stripe::Token.retrieve stripe_helper.generate_card_token number: '4242424242424242' + tok2 = Stripe::Token.retrieve stripe_helper.generate_card_token number: '4242424242424242' + + cus = Stripe::Customer.create(email: 'alice@bob.com', source: tok1.id) + + cus = Stripe::Customer.retrieve(cus.id) + card = cus.sources.find do |existing_card| + existing_card.fingerprint == tok2.card.fingerprint + end + expect(card).to_not be_nil + end + + it 'gives different card numbers different fingerprints' do + tok1 = Stripe::Token.retrieve stripe_helper.generate_card_token number: '4242424242424242' + tok2 = Stripe::Token.retrieve stripe_helper.generate_card_token number: '4012888888881881' + + cus = Stripe::Customer.create(email: 'alice@bob.com', source: tok1.id) + + cus = Stripe::Customer.retrieve(cus.id) + source = cus.sources.find do |existing_card| + existing_card.fingerprint == tok2.card.fingerprint + end + expect(source).to be_nil + end +end diff --git a/spec/stripe/stripe_integration_examples/prepare_error_spec.rb b/spec/stripe/stripe_integration_examples/prepare_error_spec.rb new file mode 100644 index 0000000..b7dd68e --- /dev/null +++ b/spec/stripe/stripe_integration_examples/prepare_error_spec.rb @@ -0,0 +1,17 @@ +require 'stripe_mock' +describe 'Card Error Prep' do + it 'prepares a card error', live: true do + StripeMock.start + StripeMock.prepare_card_error(:card_declined, :new_charge) + cus = Stripe::Customer.create(email: 'alice@example.com') + expect(cus.id).to match(/^test_cus/) + expect { Stripe::Charge.create( + amount: 900, + currency: 'usd', + source: StripeMock.generate_card_token(number: '4242424242424241', brand: 'Visa'), + description: 'hello' + ) + }.to raise_error(Stripe::CardError, 'The card was declined') + StripeMock.stop + end +end diff --git a/spec/stripe/stripe_mock_spec.rb b/spec/stripe/stripe_mock_spec.rb new file mode 100644 index 0000000..3cf1c27 --- /dev/null +++ b/spec/stripe/stripe_mock_spec.rb @@ -0,0 +1,117 @@ +# Note for rails-stripe-memberships-saas app : this test can be ignored, removed, etc. +# this is more of a test upon stripe_mock itself +require 'stripe_mock' + +describe StripeMock do + it 'overrides stripe request method' do + StripeMock.start + Stripe.request(:xtest, '/', 'abcde') # no error + StripeMock.stop + end + + it 'reverts overriding stripe request method' do + StripeMock.start + Stripe.request(:xtest, '/', 'abcde') # no error + expect { Stripe.request(:x, '/', 'abcde') }.not_to raise_error + StripeMock.stop + end + + it 'does not persist data between mock sessions' do + StripeMock.start + StripeMock.instance.customers[:x] = 9 + StripeMock.stop + StripeMock.start + expect(StripeMock.instance.customers[:x]).to be_nil + expect(StripeMock.instance.customers.keys.length).to eq 0 + StripeMock.stop + end + + it 'throws an error when trying to prepare an error before starting' do + StripeMock.stop + expect { StripeMock.prepare_error(StandardError.new) }.to raise_error { |e| + expect(e).to be_a(StripeMock::UnstartedStateError) + } + expect { StripeMock.prepare_card_error(:card_declined) }.to raise_error { |e| + expect(e).to be_a(StripeMock::UnstartedStateError) + } + end + + describe 'Live Testing' do + after { StripeMock.instance_variable_set(:@state, 'ready') } + + it 'sets the default test strategy' do + StripeMock.stop + expect(StripeMock.state).to eq 'ready' + StripeMock.toggle_live(true) + expect(StripeMock.create_test_helper).to be_a StripeMock::TestStrategies::Live + StripeMock.toggle_live(false) + expect(StripeMock.create_test_helper).to be_a StripeMock::TestStrategies::Mock + end + + it 'does not start when live' do + StripeMock.instance_variable_set(:@state, 'ready') + expect(StripeMock.state).to eq 'ready' + StripeMock.toggle_live(true) + expect(StripeMock.state).to eq 'live' + expect(StripeMock.start).to be false + expect(StripeMock.start_client).to eq false + end + + it 'can be undone' do + StripeMock.stop + StripeMock.toggle_live(true) + expect(StripeMock.state).to eq 'live' + StripeMock.toggle_live(false) + expect(StripeMock.state).to eq 'ready' + StripeMock.toggle_live(false) + expect(StripeMock.start).to_not eq false + StripeMock.stop + end + + it 'cannot be toggled when already started' do + StripeMock.start + expect { StripeMock.toggle_live(true) }.to raise_error { |e| + expect(e).to be_a(RuntimeError) + } + StripeMock.stop + StripeMock.instance_variable_set(:@state, 'remote') + expect { StripeMock.toggle_live(true) }.to raise_error { |e| + expect(e).to be_a(RuntimeError) + } + end + end + + describe 'Test Helper Strategies' do + before { StripeMock.instance_variable_set('@__test_strat', nil) } + + it 'uses mock by default' do + helper = StripeMock.create_test_helper + expect(helper).to be_a StripeMock::TestStrategies::Mock + end + + it 'can specify which strategy to use' do + helper = StripeMock.create_test_helper(:live) + expect(helper).to be_a StripeMock::TestStrategies::Live + helper = StripeMock.create_test_helper(:mock) + expect(helper).to be_a StripeMock::TestStrategies::Mock + end + + it 'throws an error on an unknown strategy' do + expect { StripeMock.create_test_helper(:lol) }.to raise_error { |e| + expect(e).to be_a(RuntimeError) + } + end + + it 'can configure the default strategy' do + StripeMock.set_default_test_helper_strategy(:live) + helper = StripeMock.create_test_helper + expect(helper).to be_a StripeMock::TestStrategies::Live + end + + it 'can override a set default strategy' do + StripeMock.set_default_test_helper_strategy(:live) + helper = StripeMock.create_test_helper(:mock) + expect(helper).to be_a StripeMock::TestStrategies::Mock + end + end +end diff --git a/spec/stripe/stripe_recipient_card_spec copy.rb b/spec/stripe/stripe_recipient_card_spec copy.rb new file mode 100644 index 0000000..46b60ab --- /dev/null +++ b/spec/stripe/stripe_recipient_card_spec copy.rb @@ -0,0 +1,101 @@ +require 'stripe_mock' +include Warden::Test::Helpers +Warden.test_mode! + +describe 'Card API for Recipients' do + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + context 'retrieval and deletion with recipients', live: true do + let!(:recipient) { Stripe::Recipient.create(name: 'Test Recipient', type: 'individual') } + let!(:card_token) { stripe_helper.generate_card_token(number: '4000056655665556') } + let!(:card) { recipient.cards.create(card: card_token) } + + it 'can retrieve all recipient cards' do + retrieved = recipient.cards.all + expect(retrieved.count).to eq 1 + end + + it 'deletes a recipient card' do + card.delete + retrieved_cus = Stripe::Recipient.retrieve(recipient.id) + expect(retrieved_cus.cards.data).to be_empty + end + + it 'deletes a recipient card then set the default_card to nil' do + card.delete + retrieved_cus = Stripe::Recipient.retrieve(recipient.id) + expect(retrieved_cus.default_card).to be_nil + end + + it 'creates/returns a card when using recipient.cards.create given a card token' do + recipient = Stripe::Recipient.create(id: 'test_recipient_sub') + card_token = stripe_helper.generate_card_token( + last4: '4242', + exp_month: 11, + exp_year: 2019 + ) + card = recipient.cards.create(card: card_token) + expect(card.recipient).to eq 'test_recipient_sub' + expect(card.last4).to eq '4242' + expect(card.exp_month).to eq 11 + expect(card.exp_year).to eq 2019 + + recipient = Stripe::Recipient.retrieve('test_recipient_sub') + expect(recipient.cards.count).to eq 1 + card = recipient.cards.data.first + expect(card.recipient).to eq 'test_recipient_sub' + expect(card.last4).to eq '4242' + expect(card.exp_month).to eq 11 + expect(card.exp_year).to eq 2019 + end + + it 'creates/returns a card when using recipient.cards.create given card params' do + recipient = Stripe::Recipient.create(id: 'test_recipient_sub') + card = recipient.cards.create(card: { + number: '4000056655665556', + exp_month: '6', + exp_year: '2026', + cvc: '123' + }) + expect(card.recipient).to eq('test_recipient_sub') + expect(card.last4).to eq '5556' + expect(card.exp_month).to eq 6 + expect(card.exp_year).to eq 2026 + + recipient = Stripe::Recipient.retrieve('test_recipient_sub') + expect(recipient.cards.count).to eq 1 + card = recipient.cards.data.first + expect(card.recipient).to eq 'test_recipient_sub' + expect(card.last4).to eq '5556' + expect(card.exp_month).to eq 6 + expect(card.exp_year).to eq 2026 + end + + context 'deletion when the recipient has two cards' do + let!(:card_token_2) { stripe_helper.generate_card_token(number: '5200828282828210') } + let!(:card_2) { recipient.cards.create(card: card_token_2) } + + it 'has just one card anymore' do + card.delete + retrieved_rec = Stripe::Recipient.retrieve(recipient.id) + expect(retrieved_rec.cards.data.count).to eq 1 + expect(retrieved_rec.cards.data.first.id).to eq card_2.id + end + + it 'sets the default_card id to the last card remaining id' do + card.delete + retrieved_rec = Stripe::Recipient.retrieve(recipient.id) + expect(retrieved_rec.default_card).to eq card_2.id + end + end + end +end diff --git a/spec/stripe/stripe_recipient_card_spec.rb b/spec/stripe/stripe_recipient_card_spec.rb new file mode 100644 index 0000000..46b60ab --- /dev/null +++ b/spec/stripe/stripe_recipient_card_spec.rb @@ -0,0 +1,101 @@ +require 'stripe_mock' +include Warden::Test::Helpers +Warden.test_mode! + +describe 'Card API for Recipients' do + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + context 'retrieval and deletion with recipients', live: true do + let!(:recipient) { Stripe::Recipient.create(name: 'Test Recipient', type: 'individual') } + let!(:card_token) { stripe_helper.generate_card_token(number: '4000056655665556') } + let!(:card) { recipient.cards.create(card: card_token) } + + it 'can retrieve all recipient cards' do + retrieved = recipient.cards.all + expect(retrieved.count).to eq 1 + end + + it 'deletes a recipient card' do + card.delete + retrieved_cus = Stripe::Recipient.retrieve(recipient.id) + expect(retrieved_cus.cards.data).to be_empty + end + + it 'deletes a recipient card then set the default_card to nil' do + card.delete + retrieved_cus = Stripe::Recipient.retrieve(recipient.id) + expect(retrieved_cus.default_card).to be_nil + end + + it 'creates/returns a card when using recipient.cards.create given a card token' do + recipient = Stripe::Recipient.create(id: 'test_recipient_sub') + card_token = stripe_helper.generate_card_token( + last4: '4242', + exp_month: 11, + exp_year: 2019 + ) + card = recipient.cards.create(card: card_token) + expect(card.recipient).to eq 'test_recipient_sub' + expect(card.last4).to eq '4242' + expect(card.exp_month).to eq 11 + expect(card.exp_year).to eq 2019 + + recipient = Stripe::Recipient.retrieve('test_recipient_sub') + expect(recipient.cards.count).to eq 1 + card = recipient.cards.data.first + expect(card.recipient).to eq 'test_recipient_sub' + expect(card.last4).to eq '4242' + expect(card.exp_month).to eq 11 + expect(card.exp_year).to eq 2019 + end + + it 'creates/returns a card when using recipient.cards.create given card params' do + recipient = Stripe::Recipient.create(id: 'test_recipient_sub') + card = recipient.cards.create(card: { + number: '4000056655665556', + exp_month: '6', + exp_year: '2026', + cvc: '123' + }) + expect(card.recipient).to eq('test_recipient_sub') + expect(card.last4).to eq '5556' + expect(card.exp_month).to eq 6 + expect(card.exp_year).to eq 2026 + + recipient = Stripe::Recipient.retrieve('test_recipient_sub') + expect(recipient.cards.count).to eq 1 + card = recipient.cards.data.first + expect(card.recipient).to eq 'test_recipient_sub' + expect(card.last4).to eq '5556' + expect(card.exp_month).to eq 6 + expect(card.exp_year).to eq 2026 + end + + context 'deletion when the recipient has two cards' do + let!(:card_token_2) { stripe_helper.generate_card_token(number: '5200828282828210') } + let!(:card_2) { recipient.cards.create(card: card_token_2) } + + it 'has just one card anymore' do + card.delete + retrieved_rec = Stripe::Recipient.retrieve(recipient.id) + expect(retrieved_rec.cards.data.count).to eq 1 + expect(retrieved_rec.cards.data.first.id).to eq card_2.id + end + + it 'sets the default_card id to the last card remaining id' do + card.delete + retrieved_rec = Stripe::Recipient.retrieve(recipient.id) + expect(retrieved_rec.default_card).to eq card_2.id + end + end + end +end diff --git a/spec/stripe/stripe_subscription_spec.rb b/spec/stripe/stripe_subscription_spec.rb new file mode 100644 index 0000000..8fa010f --- /dev/null +++ b/spec/stripe/stripe_subscription_spec.rb @@ -0,0 +1,307 @@ +require 'stripe_mock' +include Warden::Test::Helpers +Warden.test_mode! + +describe 'Subscription API' do + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + it 'creates a stripe plan' do + plan = stripe_helper.create_plan(id: 'my_plan', amount: 1500) + # The above line creates / mocks / replaces the following: + # plan = Stripe::Plan.create( + # id: 'my_plan', + # name: 'StripeMock Default Plan ID', + # amount: 1500, + # currency: 'usd', + # object: 'plan', + # livemode: false, + # interval: 'month', + # interval_count: 1, + # trial_period_days: null + # ) + # any variable of plan can now be tested + expect(plan.id).to eq 'my_plan' + expect(plan.amount).to eq 1500 + expect(plan.currency).to eq 'usd' + expect(plan.interval).to eq 'month' + expect(plan.interval_count).not_to eq 2 + expect(plan.trial_period_days).not_to eq 7 + end + + it 'creates a different stripe plan' do + planb = stripe_helper.create_plan( + id: 'your_plan', + amount: 500, + name: 'Your Plan Name', + trial_period_days: 14 + ) + expect(planb.id).to eq 'your_plan' + expect(planb.amount).to eq 500 + expect(planb.currency).to eq 'usd' + expect(planb.interval).to eq 'month' + expect(planb.interval_count).not_to eq 0 + expect(planb.trial_period_days).to eq 14 + end + + it 'allows customer to cancel subscription' do + card_token = StripeMock.generate_card_token(last4: '4242', exp_month: 11, exp_year: 2016) + customer = Stripe::Customer.create( + email: 'cancelsub@example.com', + source: card_token, + description: 'a customer cancellation' + ) + @user = FactoryGirl.create(:user, email: 'cancelsub@example.com') + customer = Stripe::Customer.retrieve(customer.id) + expect(@user.email).to eq customer.email + # @user.customer_id = customer.id # customer_id not yet used in this app + # expect(@user.customer_id).to eq customer.id + expect(customer.sources.data[0].last4).to eq '4242' + expect(customer.sources.data[0].exp_month).to eq 11 + expect(customer.sources.data[0].exp_year).to eq 2016 + + plan = stripe_helper.create_plan(id: 'my_plan', amount: 1500) + PLAN_ID = plan.id + expect(PLAN_ID).to eq('my_plan') + expect(plan.amount).to eq(1500) + + customer = Stripe::Customer.retrieve(customer.id) + expect(customer.subscriptions.data.count).to eq 0 + + customer.subscriptions.create(plan: PLAN_ID, prorate: true) + customer = Stripe::Customer.retrieve(customer.id) + expect(customer.subscriptions.data.count).to eq 1 + + charge = Stripe::Charge.create({ + amount: 1500, + currency: 'usd', + interval: 'month', + plan: PLAN_ID, + customer: customer.id, + description: 'a charge with a specific card' + }, { + idempotency_key: '95ea4310438306ch' + }) + card_token = customer.sources.data[0].id + expect(card_token).to match(/^test_cc/) + + charge = Stripe::Charge.retrieve(charge.id) + expect(charge.id).to match(/^test_ch/) + expect(customer.id).to match(/^test_cus/) + expect(customer.sources.data[0].id).to match(/^test_cc/) + expect(customer.sources.data.length).to eq 1 + expect(customer.sources.data[0].id).not_to be_nil + expect(customer.sources.data[0].last4).to eq '4242' + expect(customer.subscriptions[:url]).to match(/\/v1\/customers\/test_cus\_.+\/subscriptions/) + expect(customer.subscriptions.data[0].id).to match(/^test_su/) + expect(customer.subscriptions.data[0].status).to eq 'active' + + subscription = customer.subscriptions.data[0] + subscription.delete + customer = Stripe::Customer.retrieve(customer.id) + expect(customer.subscriptions.total_count).to eq 0 + end + + it 'allows customer to delete their account' do + card_token = stripe_helper.generate_card_token(last4: '4242', exp_month: 11, exp_year: 2017) + customer = Stripe::Customer.create( + email: 'cancelcus@example.com', + source: card_token, + description: 'a customer cancellation' + ) + @user = FactoryGirl.create(:user, email: 'cancelcus@example.com') + customer = Stripe::Customer.retrieve(customer.id) + expect(@user.email).to eq customer.email + # @user.customer_id = customer.id # customer_id not yet used in this app + # expect(@user.customer_id).to eq customer.id + + plan = stripe_helper.create_plan(id: 'my_plan', amount: 1500) + expect(plan.id).to eq('my_plan') + expect(plan.amount).to eq(1500) + charge = Stripe::Charge.create({ + amount: 1500, + currency: 'usd', + interval: 'month', + customer: customer.id, + description: 'a charge with a specific card' + }, { + idempotency_key: '95ea4310438306ch' + }) + card_token = customer.sources.data[0].id + charge = Stripe::Charge.retrieve(charge.id) + customer = Stripe::Customer.retrieve(customer.id) + expect(card_token).to match(/^test_cc/) + expect(charge.id).to match(/^test_ch/) + expect(customer.id).to match(/^test_cus/) + expect(customer.sources.data.length).to eq 1 + expect(customer.sources.data[0].id).to match(/^test_cc/) + expect(customer.sources.data[0].last4).to eq '4242' + + customer.delete + expect(customer.id).to match(/^test_cus/) + expect(customer.deleted).to be true + end + + it 'allows customer with two subscriptions to cancel one' do + card_token = StripeMock.generate_card_token(last4: '4242', exp_month: 11, exp_year: 2018) + customer = Stripe::Customer.create( + email: 'cancelone@example.com', + source: card_token, + description: 'a customer cancellation' + ) + @user = FactoryGirl.create(:user, email: 'cancelone@example.com') + customer = Stripe::Customer.retrieve(customer.id) + expect(@user.email).to eq customer.email + # @user.customer_id = customer.id # customer_id not yet used in this app + # expect(@user.customer_id).to eq customer.id + + card_token = stripe_helper.generate_card_token( + last4: '4242', + exp_month: 11, + exp_year: 2019 + ) + plan = stripe_helper.create_plan(id: 'my_plan', amount: 1500) + expect(plan.id).to eq 'my_plan' + expect(plan.amount).to eq 1500 + + charge = Stripe::Charge.create({ + amount: 1500, + currency: 'usd', + interval: 'month', + customer: customer.id, + description: 'a charge with a specific card' + }, { + idempotency_key: '95ea4310438306ch' + }) + card_token = customer.sources.data[0].id + charge = Stripe::Charge.retrieve(charge.id) + customer = Stripe::Customer.retrieve(customer.id) + expect(card_token).to match(/^test_cc/) + expect(charge.id).to match(/^test_ch/) + expect(customer.id).to match(/^test_cus/) + expect(customer.sources.data[0].id).to match(/^test_cc/) + expect(customer.sources.data.length).to eq 1 + expect(customer.sources.data[0].id).not_to be_nil + expect(customer.sources.data[0].last4).to eq '4242' + + customer.delete + expect(customer.id).to match(/^test_cus/) + expect(customer.deleted).to be true + + card_token = stripe_helper.generate_card_token( + last4: '4242', + exp_month: 11, + exp_year: 2020 + ) + customer = Stripe::Customer.create( + email: 'cancelcus@example.com', + source: card_token, + description: 'a customer cancellation' + ) + @user = FactoryGirl.create(:user, email: 'cancelcus@example.com') + customer = Stripe::Customer.retrieve(customer.id) + expect(@user.email).to eq customer.email + # @user.customer_id = customer.id # customer_id not yet used in this app + # expect(@user.customer_id).to eq customer.id + + plan = stripe_helper.create_plan(id: 'my_new_plan', amount: 2500) + expect(plan.id).to eq 'my_new_plan' + expect(plan.amount).to eq 2500 + + charge = Stripe::Charge.create({ + amount: 2500, + currency: 'usd', + interval: 'month', + customer: customer.id, + description: 'a charge with a specific card' + }, { + idempotency_key: '95ea4310438306ch' + }) + card_token = customer.sources.data[0].id + charge = Stripe::Charge.retrieve(charge.id) + customer = Stripe::Customer.retrieve(customer.id) + expect(card_token).to match(/^test_cc/) + expect(charge.id).to match(/^test_ch/) + expect(customer.id).to match(/^test_cus/) + expect(customer.sources.data[0].id).to match(/^test_cc/) + expect(customer.sources.data[0].id).not_to be_nil + expect(customer.sources.data[0].last4).to eq '4242' + expect(customer.sources.data.length).to eq 1 + + customer.delete + expect(customer.id).to match(/^test_cus/) + expect(customer.deleted).to be true + end + + it 'creates a plan, a customer, no charge, add a new card' do + Stripe::Plan.create( + id: 'platinum', + amount: 900, + currency: 'usd', + interval: 'month', + name: 'Platinum' + ) + customer_attributes = { + email: 'test@test.com', + source: stripe_helper.generate_card_token( + last4: '1881', + exp_month: 11, + exp_year: 2021, + description: 'entering my card number' + ) + } + customer = Stripe::Customer.create(customer_attributes) + subscription = customer.subscriptions.create(plan: 'platinum', prorate: true) + source = customer.sources.retrieve(customer.default_source) + expect(customer.id).to match(/^test_cus/) + expect(subscription.id).to match(/^test_su/) + expect(source.id).to match(/^test_cc/) + + customer = Stripe::Customer.retrieve(customer.id) + expect(customer.id).to match(/^test_cus/) + expect(customer.sources.first.id).to match(/^test_cc/) + expect(customer.sources.data.first.last4).to eq '1881' + expect(customer.subscriptions.first.id).to match(/^test_su/) + expect(customer.subscriptions.data.first.id).to match(/^test_su/) + expect(customer.subscriptions.data[0].plan.id).to eq 'platinum' + expect(customer.subscriptions.first.plan.name).to eq 'Platinum' + expect(customer.subscriptions.first.id).to match(/^test_su/) + expect(customer.subscriptions.first.customer).to match(/^test_cus/) + expect(customer.subscriptions.first.plan.id).to eq 'platinum' + expect(customer.sources.total_count).to eq 1 + + new_card_token = stripe_helper.generate_card_token( + last4: '4242', + exp_month: 11, + exp_year: 2022, + currency: 'usd', + description: 'new card' + ) + customer.sources.create(source: new_card_token) + twocardcustomer = Stripe::Customer.retrieve(customer.id) + expect(twocardcustomer.sources.data.count).to eq 2 + expect(twocardcustomer.id).to match(/^test_cus/) + expect(twocardcustomer.email).to eq 'test@test.com' + expect(twocardcustomer.description).to eq 'an auto-generated stripe customer data mock' + expect(twocardcustomer.sources.first.id).to match(/^test_cc/) + expect(twocardcustomer.sources.data.first.id).to match(/^test_cc/) + expect(twocardcustomer.sources.data.second.id).to match(/^test_cc/) + expect(twocardcustomer.default_source).to match(/^test_cc/) + expect(twocardcustomer.sources.data[0].last4).to eq '1881' + expect(twocardcustomer.sources.data[0].exp_month).to eq 11 + expect(twocardcustomer.sources.data[0].exp_year).to eq 2021 + expect(twocardcustomer.sources.data[1].last4).to eq '4242' + expect(twocardcustomer.sources.data[1].exp_month).to eq 11 + expect(twocardcustomer.sources.data[1].exp_year).to eq 2022 + expect(twocardcustomer.object).to eq 'customer' + expect(twocardcustomer.livemode).to eq false + end +end diff --git a/spec/stripe/webhook_customer_created_spec.rb b/spec/stripe/webhook_customer_created_spec.rb new file mode 100644 index 0000000..6ce9289 --- /dev/null +++ b/spec/stripe/webhook_customer_created_spec.rb @@ -0,0 +1,147 @@ +require 'stripe_mock' +include Warden::Test::Helpers +Warden.test_mode! + +describe 'Stripe Customer Webhooks' do + let(:stripe_helper) { StripeMock.create_test_helper } + + before(:each) do + StripeMock.start + end + + after(:each) do + StripeMock.stop + Warden.test_reset! + end + + it 'mocks a stripe webhook', live: true do + # source reference for mock_webhook_event method: + # https://github.com/rebelidealist/stripe-ruby-mock/blob/master/lib/stripe_mock/api/webhooks.rb + event = StripeMock.mock_webhook_event('customer.created') + expect(event.object).to eq 'event' + expect(event.data.object).to be_a Stripe::Customer + expect(event.data.object.object).to eq 'customer' + expect(event.data.object.id).to match(/^cus_/) + + # a customer.created event will have the same information as + # retrieving the relevant customer would have + # https://stripe.com/docs/api#retrieve_event + verified_event = Stripe::Event.retrieve(event.id) + + customer_object = verified_event.data.object + expect(customer_object.id).to match(/^cus_/) + expect(customer_object.default_card).to_not be_nil + expect(customer_object.default_card).to match(/^cc_/) + expect(verified_event.id).to match(/^test_evt_/) + expect(verified_event.type).to eq 'customer.created' + + customer = Stripe::Customer.create(email: 'customer@example.com') + customer_id = customer.id + expect(customer.subscriptions.total_count).to eq 0 + + customer = Stripe::Customer.retrieve(customer_id) + expect(customer.sources.object).to eq 'list' + expect(customer.default_source).to eq nil + expect(customer.sources.url).to match(/^\/v1\/customers\/test_cus\_.+\/sources/) + # card below is created in StripeMock.mock_webhook_event above + # see https://github.com/rebelidealist/stripe-ruby-mock/blob/master/lib/stripe_mock/api/webhooks.rb + expect(verified_event.data.object.default_card).to match(/^cc\_/) + expect(verified_event.data.object).to be_truthy + expect(verified_event.id).to match(/^test_evt/) + expect(verified_event.data.object.id).to match(/^cus\_00000000000000/) + expect(verified_event.data.object[:id]).to match(/^cus\_00000000000000/) + expect(verified_event.data.object.object).to eq 'customer' + expect(verified_event.data.object.livemode).to be false + expect(verified_event.data.object.description).to be nil + expect(verified_event.data.object.email).to eq 'bond@mailinator.com' + expect(verified_event.data.object.delinquent).to be true + expect(verified_event.data.object.sources.data).to be_truthy + expect(verified_event.data.object.sources.data.count).to eq 1 + # Note these next two are same test called in different manner. + expect(verified_event.data.object.sources.data[0][:id]).to match(/^cc\_/) + expect(verified_event.data.object.sources.data.first[:id]).to match(/^cc\_/) + # We will stay with .first as we have only one card created. + expect(verified_event.data.object.sources.data.first[:customer]).to match(/^cus\_/) + expect(verified_event.data.object.sources.data.first[:last4]).to eq '0341' + expect(verified_event.data.object.sources.data.first[:type]).to eq 'Visa' + expect(verified_event.data.object.sources.data.first[:funding]).to eq 'credit' + expect(verified_event.data.object.sources.data.first.exp_month).to eq 12 + # Note this next line passes if you do not give your card creation a current exp_year. + # The reason for this is the StipeMock.gen_card_tk method returns the year as 2013. + expect(verified_event.data.object.sources.data.first.exp_year).to eq 2013 + # TODO: fix this so the exp_year from stripe-ruby-mock is 2019 aka valid card. + # expect(verified_event.data.object.sources.data.first.exp_year).to eq 2019 + expect(verified_event.data.object.sources.data.first.fingerprint).to_not be nil + expect(verified_event.data.object.sources.data.first.customer).to match(/^cus\_/) + expect(verified_event.data.object.sources.data.first.country).to eq 'US' + expect(verified_event.data.object.sources.data.first[:name]).to eq 'Johnny Goodman' + expect(verified_event.data.object.sources.data.first.address_line1).to be nil + expect(verified_event.data.object.sources.data.first.address_line2).to be nil + expect(verified_event.data.object.sources.data.first.address_city).to be nil + expect(verified_event.data.object.sources.data.first.address_state).to be nil + expect(verified_event.data.object.sources.data.first.address_zip).to be nil + expect(verified_event.data.object.sources.data.first.address_country).to be nil + expect(verified_event.data.object.sources.data.first.cvc_check).to eq 'pass' + expect(verified_event.data.object.sources.data.first.address_line1_check).to be nil + # If address_line1 was provided, results of the check: + # pass, fail, unavailable, or unchecked. + expect(verified_event.data.object.sources.data.first.address_zip_check).to be nil + expect(verified_event.data.object.object).to eq 'customer' + expect(verified_event.data.object.livemode).to be false + expect(verified_event.data.object.description).to be nil + + # TODO: run test where we enter email for the customer, not the event creation. + # expect(verified_event.data.object.email).to eq 'event_webhook@example.com' + # where do we add the customer email prior to or with the event creation ? 20151111 + # the above todo is because stripe-ruby-mock quietly provides this email address: + expect(verified_event.data.object.email).to eq 'bond@mailinator.com' + + expect(verified_event.data.object.delinquent).to be true + expect(verified_event.data.object.metadata).to be_a Stripe::StripeObject + expect(verified_event.data.object.subscription).to be nil + expect(verified_event.data.object.discount).to be nil + expect(verified_event.data.object.discount).to be nil + expect(verified_event.data.object.account_balance).to eq 0 + expect(verified_event.data.object.sources.object).to eq 'list' + expect(verified_event.id).to match(/^test_evt\_/) + expect(verified_event.created).to_not be nil + expect(verified_event.livemode).to eq false + expect(verified_event.type).to eq 'customer.created' + expect(verified_event.object).to eq 'event' + expect(verified_event.data.object.account_balance).to eq 0 + expect(verified_event.data.count).to eq 1 + expect(verified_event.url).to match(/\/v1\/events\/test_evt_/) + expect(verified_event.data.object.sources .data).to_not be_nil + expect(verified_event.data.object.sources.data.first[:id]).to match(/^cc\_/) + expect(verified_event.data.object.sources.data.first[:last4]).to eq '0341' + expect(verified_event.data.object.sources.data.first[:type]).to eq 'Visa' + expect(verified_event.data.object.sources.data.first[:brand]).to eq 'Visa' + expect(verified_event.data.object.sources.data.first[:funding]).to eq 'credit' + expect(verified_event.data.object.sources.data.first[:exp_month]).to eq 12 + + # out of date stripe_mock : still this day ? 20151111 ? TODO: verify + expect(verified_event.data.object.sources.data.first[:exp_year]).to eq 2013 + # TODO: 20151115 : revisit this one another time + # expect(verified_event.data.object.sources.data.first[:exp_year]).to eq 2019 + + expect(verified_event.data.object.sources.data.first[:fingerprint]).to_not be nil + expect(verified_event.data.object.sources.data.first[:customer]).to match(/^cus\_/) + expect(verified_event.data.object.sources.data.first[:country]).to eq 'US' + expect(verified_event.data.object.sources.data.first[:name]).to eq 'Johnny Goodman' + expect(verified_event.data.object.sources.data.first[:address_line1]).to eq nil + expect(verified_event.data.object.sources.data.first[:address_line2]).to eq nil + expect(verified_event.data.object.sources.data.first[:address_city]).to eq nil + expect(verified_event.data.object.sources.data.first[:address_state]).to eq nil + expect(verified_event.data.object.sources.data.first[:address_zip]).to eq nil + expect(verified_event.data.object.sources.data.first[:address_country]).to eq nil + expect(verified_event.data.object.sources.data.first[:cvc_check]).to eq 'pass' + expect(verified_event.data.object.sources.data.first[:address_line1_check]).to eq nil + expect(verified_event.data.object.sources.data.first[:address_zip_check]).to eq nil + customer_object = verified_event.data.object + expect(customer_object.id).to_not be_nil + expect(customer_object.default_card).to_not be_nil # ? out of date stripe_mock + # expect(customer_object.default_source).to_not be_nil + expect(customer_object.default_card).to match(/^cc\_/) # ? out of date stripe_mock + # expect(customer_object.default_source).to match /^cc\_/ + end +end diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb new file mode 100644 index 0000000..62b318b --- /dev/null +++ b/spec/support/capybara.rb @@ -0,0 +1 @@ +Capybara.asset_host = 'http://localhost:3000' \ No newline at end of file diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb new file mode 100644 index 0000000..447e152 --- /dev/null +++ b/spec/support/database_cleaner.rb @@ -0,0 +1,21 @@ +RSpec.configure do |config| + config.before(:suite) do + DatabaseCleaner.clean_with(:truncation) + end + + config.before(:each) do + DatabaseCleaner.strategy = :transaction + end + + config.before(:each, :js => true) do + DatabaseCleaner.strategy = :truncation + end + + config.before(:each) do + DatabaseCleaner.start + end + + config.append_after(:each) do + DatabaseCleaner.clean + end +end \ No newline at end of file diff --git a/spec/support/devise.rb b/spec/support/devise.rb new file mode 100644 index 0000000..21dd1e8 --- /dev/null +++ b/spec/support/devise.rb @@ -0,0 +1,3 @@ +RSpec.configure do |config| + config.include Devise::TestHelpers, type: :controller +end \ No newline at end of file diff --git a/spec/support/factory_girl.rb b/spec/support/factory_girl.rb new file mode 100644 index 0000000..eec437f --- /dev/null +++ b/spec/support/factory_girl.rb @@ -0,0 +1,3 @@ +RSpec.configure do |config| + config.include FactoryGirl::Syntax::Methods +end diff --git a/spec/support/fixtures/success.json b/spec/support/fixtures/success.json new file mode 100644 index 0000000..ca60522 --- /dev/null +++ b/spec/support/fixtures/success.json @@ -0,0 +1 @@ +{"object":"customer","created":1373955688,"id":"youAreSuccessful","livemode":false,"description":"asdjfasdf","email":"bla@ajsdf.com","delinquent":false,"subscription":{"plan":{"interval":"month","name":"Silver","amount":900,"currency":"usd","id":"silver","object":"plan","livemode":false,"interval_count":1,"trial_period_days":15},"object":"subscription","start":1373955688,"status":"trialing","customer":"cus_2CrZQp2cVLDLjd","cancel_at_period_end":false,"current_period_start":1373955688,"current_period_end":1375251688,"ended_at":null,"trial_start":1373955688,"trial_end":1375251688,"canceled_at":null,"quantity":1},"discount":null,"account_balance":0,"cards":{"object":"list","count":1,"url":"/v1/customers/cus_2CrZQp2cVLDLjd/cards","data":[{"id":"cc_2CrZUbQKIg7Pyp","object":"card","last4":"4242","type":"Visa","exp_month":1,"exp_year":2016,"fingerprint":"h7OEHTVeEFYHFl1n","customer":"cus_2CrZQp2cVLDLjd","country":"US","name":"asdjfasdf","address_line1":null,"address_line2":null,"address_city":null,"address_state":null,"address_zip":null,"address_country":null,"cvc_check":"pass","address_line1_check":null,"address_zip_check":null}]},"default_card":"cc_2CrZUbQKIg7Pyp"} diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb new file mode 100644 index 0000000..995a7cd --- /dev/null +++ b/spec/support/helpers.rb @@ -0,0 +1,4 @@ +require 'support/helpers/session_helpers' +RSpec.configure do |config| + config.include Features::SessionHelpers, type: :feature +end \ No newline at end of file diff --git a/spec/support/helpers/session_helpers.rb b/spec/support/helpers/session_helpers.rb new file mode 100644 index 0000000..97bcfcd --- /dev/null +++ b/spec/support/helpers/session_helpers.rb @@ -0,0 +1,56 @@ +module Features + module SessionHelpers + def sign_up_silver + fill_in 'Email', with: 'silver@johnnyappleseed.com' + fill_in 'Password', with: 'please123' + fill_in 'Password confirmation', with: 'please123' + fill_in 'card_number', with: '4242424242424242' + fill_in 'card_code', with: '123' + select 10, from: 'date_month' + select 2020, from: 'date_year' + click_button 'Sign up' + end + + def sign_up_gold + visit '/users/sign_up?plan=gold' + fill_in 'Email', with: 'tester@example.com' + fill_in 'Password', with: 'please123' + fill_in 'Password confirmation', with: 'please123' + fill_in 'card_number', with: '4242424242424242' + fill_in 'card_code', with: '123' + select 11, from: 'date_month' + select 2021, from: 'date_year' + click_button 'Sign up' + end + + def sign_up_platinum + visit '/users/sign_up?plan=platinum' + fill_in 'Email', with: 'testers@example.com' + fill_in 'Password', with: 'please123' + fill_in 'Password confirmation', with: 'please123' + fill_in 'card_number', with: '4242424242424242' + fill_in 'card_code', with: '123' + select 12, from: 'date_month' + select 2022, from: 'date_year' + end + + def sign_up(email, password, confirmation) + visit new_user_registration_path + fill_in 'Email', with: email + fill_in 'Password', with: password + fill_in 'Password confirmation', with: confirmation + click_button 'Sign up' + end + + def sign_in(email, password) + visit new_user_session_path + fill_in 'Email', with: email + fill_in 'Password', with: password + click_button 'Sign in' + end + + def sign_out + visit '/users/sign_out' + end + end +end diff --git a/spec/support/helpers/stripe_helper.rb b/spec/support/helpers/stripe_helper.rb new file mode 100644 index 0000000..ed4d31f --- /dev/null +++ b/spec/support/helpers/stripe_helper.rb @@ -0,0 +1,65 @@ +module StripeHelper + + class Response + begin + def self.new(stripe_response) + stripe_response_file = JSON.parse(IO.read("spec/support/fixtures/#{stripe_response}.json").freeze) + StripeHelper::NestedOstruct.new(stripe_response_file) + end + rescue Stripe::CardError => e + # Will any Stripe error be caught in this method above ? + body = e.json_body + err = body[:error] + + puts "Status is: #{e.http_status}" + puts "Type is: #{err[:type]}" + puts "Code is: #{err[:code]}" + # param is '' in this case + puts "Param is: #{err[:param]}" + puts "Message is: #{err[:message]}" + rescue Stripe::InvalidRequestError => e + # Invalid parameters were supplied to Stripe's API + rescue Stripe::AuthenticationError => e + # Authentication with Stripe's API failed + # (maybe you changed API keys recently) + rescue Stripe::APIConnectionError => e + # Network communication with Stripe failed + rescue Stripe::StripeError => e + # Display a very generic error to the user, and maybe send # yourself an email + rescue => e + # Something else happened, completely unrelated to Stripe + end + end + + class NestedOstruct + begin + def self.new(hash) + OpenStruct.new(hash.inject({}){|r,p| r[p[0]] = p[1].kind_of?(Hash) ? NestedOstruct.new(p[1]) : p[1]; r }) + rescue Stripe::CardError => e + # Will any Stripe error will be caught? + body = e.json_body + err = body[:error] + + puts "Status is: #{e.http_status}" + puts "Type is: #{err[:type]}" + puts "Code is: #{err[:code]}" + # param is '' in this case + puts "Param is: #{err[:param]}" + puts "Message is: #{err[:message]}" + rescue Stripe::InvalidRequestError => e + # Invalid parameters were supplied to Stripe's API + rescue Stripe::AuthenticationError => e + # Authentication with Stripe's API failed + # (maybe you changed API keys recently) + rescue Stripe::APIConnectionError => e + # Network communication with Stripe failed + rescue Stripe::StripeError => e + # Display a very generic error to the user, and maybe send # yourself an email + rescue => e + # Something else happened, completely unrelated to Stripe + end + + end + end + +end \ No newline at end of file diff --git a/spec/support/helpers/stripe_helpers.rb b/spec/support/helpers/stripe_helpers.rb new file mode 100644 index 0000000..a7364d6 --- /dev/null +++ b/spec/support/helpers/stripe_helpers.rb @@ -0,0 +1,74 @@ +module StripeHelpers + + require 'rails_helper' + require 'stripe_mock' + require 'thin' + + StripeMock.spawn_server + + describe 'StripeToken' do + + let(:stripe_helper) { StripeMock.create_test_helper } + + + before(:each) do + StripeMock.start + end + + after(:each) do + StripeMock.stop + end + + # Alternatively: + # before do + # @client = StripeMock.start_client + # end + # + # after do + # @client = StripeMock.stop_client + # # -- Or -- + # @client.close! + # # -- Or -- + # StripeMock.stop_client(:clear_server_data => true) + # end + + describe 'create stripe customer' do + + let(:stripe_helper) { StripeMock.create_test_helper } + + it "creates a stripe customer" do + # The stripeToken generator does not touch stripe's servers nor the internet! + stripeToken = stripe_helper.generate_card_token({ number: '4242424242424242', + cvc: '123', + currency: 'usd', + }) + Rails.logger.info "Your stripeToken has been generated as #{stripeToken} and to be used one time. " + customer = Stripe::Customer.create({ + email: 'johnny@appleseed.com', + source: stripeToken, + }) + expect(customer.email).to eq('johnny@appleseed.com') + end + end + + describe 'create stripe token' do + it "creates a stripe token" do + stripeToken = stripe_helper.generate_card_token + Rails.logger.info "Your stripeToken has been generated as #{stripeToken} and is to be used once. " + expect(stripeToken).to match(/test_tok/) + end + end + + describe 'card declined error' do + it "mocks a declined card error" do + # Prepares an error for the next create charge request + StripeMock.prepare_card_error(:card_declined, :new_charge) + expect { Stripe::Charge.create }.to raise_error {|e| + expect(e).to be_a Stripe::CardError + expect(e.http_status).to eq(402) + expect(e.code).to eq('card_declined') + } + end + end + end +end \ No newline at end of file diff --git a/spec/support/stripe_examples.rb b/spec/support/stripe_examples.rb new file mode 100644 index 0000000..a2a99f3 --- /dev/null +++ b/spec/support/stripe_examples.rb @@ -0,0 +1,29 @@ + +def require_stripe_examples + Dir["./spec/shared_stripe_examples/**/*.rb"].each {|f| require f} + Dir["./spec/integration_examples/**/*.rb"].each {|f| require f} +end + +def it_behaves_like_stripe(&block) + it_behaves_like 'Account API', &block + it_behaves_like 'Bank Account Token Mocking', &block + it_behaves_like 'Card Token Mocking', &block + it_behaves_like 'Card API', &block + it_behaves_like 'Charge API', &block + it_behaves_like 'Coupon API', &block + it_behaves_like 'Customer API', &block + it_behaves_like 'Extra Features', &block + it_behaves_like 'Invoice API', &block + it_behaves_like 'Invoice Item API', &block + it_behaves_like 'Plan API', &block + it_behaves_like 'Recipient API', &block + it_behaves_like 'Refund API', &block + it_behaves_like 'Transfer API', &block + it_behaves_like 'Stripe Error Mocking', &block + it_behaves_like 'Customer Subscriptions', &block + it_behaves_like 'Webhook Events API', &block + + # Integration tests + it_behaves_like 'Multiple Customer Cards' + it_behaves_like 'Charging with Tokens' +end