Repository: thefrontside/stripe-rails Branch: master Commit: 2e21a48c3aac Files: 109 Total size: 212.1 KB Directory structure: gitextract_b2ydg9ox/ ├── .codeclimate.yml ├── .editorconfig ├── .github/ │ ├── issue_template.md │ ├── pull_request_template.md │ └── workflows/ │ └── ruby.yml ├── .gitignore ├── .rubocop.yml ├── CODE_OF_CONDUCT.md ├── Changelog.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── app/ │ ├── assets/ │ │ └── stripe/ │ │ ├── stripe_elements.css │ │ └── stripe_elements.js │ ├── controllers/ │ │ └── stripe/ │ │ ├── application_controller.rb │ │ └── events_controller.rb │ ├── helpers/ │ │ └── stripe/ │ │ └── javascript_helper.rb │ ├── models/ │ │ └── stripe/ │ │ └── event_dispatch.rb │ └── views/ │ └── stripe/ │ ├── _elements.html.erb │ ├── _elements_js.html.erb │ └── _js.html.erb ├── config/ │ ├── locales/ │ │ └── en.yml │ └── routes.rb ├── gemfiles/ │ ├── gemfiles/ │ │ └── rails70.gemfile │ ├── rails60.gemfile │ ├── rails61.gemfile │ ├── rails70.gemfile │ └── rails71.gemfile ├── lib/ │ ├── generators/ │ │ ├── stripe/ │ │ │ └── install_generator.rb │ │ └── templates/ │ │ ├── coupons.rb │ │ ├── plans.rb │ │ ├── prices.rb │ │ └── products.rb │ ├── stripe/ │ │ ├── billing_tier.rb │ │ ├── callbacks/ │ │ │ └── builder.rb │ │ ├── callbacks.rb │ │ ├── configuration_builder.rb │ │ ├── coupons.rb │ │ ├── current_api_version.rb │ │ ├── engine.rb │ │ ├── plans.rb │ │ ├── prices.rb │ │ ├── products.rb │ │ ├── rails/ │ │ │ ├── tasks.rake │ │ │ ├── testing.rb │ │ │ └── version.rb │ │ └── rails.rb │ └── stripe-rails.rb ├── stripe-rails.gemspec └── test/ ├── callbacks_spec.rb ├── coupon_builder_spec.rb ├── dummy/ │ ├── README.rdoc │ ├── Rakefile │ ├── app/ │ │ ├── assets/ │ │ │ ├── javascripts/ │ │ │ │ └── application.js │ │ │ └── stylesheets/ │ │ │ └── application.css │ │ ├── controllers/ │ │ │ ├── apis_controller.rb │ │ │ ├── application_controller.rb │ │ │ └── stripes_controller.rb │ │ ├── helpers/ │ │ │ └── application_helper.rb │ │ ├── mailers/ │ │ │ └── .gitkeep │ │ ├── models/ │ │ │ ├── .gitkeep │ │ │ └── dummy/ │ │ │ └── model_with_callbacks.rb │ │ └── views/ │ │ ├── layouts/ │ │ │ └── application.html.erb │ │ └── stripes/ │ │ └── new.html.erb │ ├── config/ │ │ ├── application.rb │ │ ├── boot.rb │ │ ├── database.yml │ │ ├── environment.rb │ │ ├── environments/ │ │ │ ├── development.rb │ │ │ ├── production.rb │ │ │ └── test.rb │ │ ├── initializers/ │ │ │ ├── backtrace_silencers.rb │ │ │ ├── inflections.rb │ │ │ ├── mime_types.rb │ │ │ ├── secret_token.rb │ │ │ ├── session_store.rb │ │ │ └── wrap_parameters.rb │ │ ├── locales/ │ │ │ └── en.yml │ │ ├── routes.rb │ │ └── stripe/ │ │ ├── plans.rb │ │ └── prices.rb │ ├── config.ru │ ├── lib/ │ │ ├── assets/ │ │ │ └── .gitkeep │ │ └── dummy/ │ │ └── module_with_callbacks.rb │ ├── log/ │ │ └── .gitkeep │ ├── public/ │ │ ├── 404.html │ │ ├── 422.html │ │ └── 500.html │ └── script/ │ └── rails ├── dummy_apis_controller_spec.rb ├── dummy_stripes_controller_spec.rb ├── event.json ├── events_controller_spec.rb ├── fixtures/ │ ├── stripe_plans.json │ ├── stripe_plans_headers.json │ ├── stripe_plans_headers_2017.json │ └── stripe_prices.json ├── invoice.json ├── javascript_helper_spec.rb ├── plan_builder_spec.rb ├── price_builder_spec.rb ├── product_builder_spec.rb ├── spec_helper.rb ├── stripe_initializers_spec.rb ├── support/ │ ├── application_system_test_case.rb │ ├── callback_helpers.rb │ └── fixture_loader.rb └── testing_spec.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .codeclimate.yml ================================================ --- engines: brakeman: enabled: true duplication: enabled: true config: languages: - ruby fixme: enabled: true rubocop: enabled: true ratings: paths: - Gemfile.lock - "**.erb" - "**.haml" - "**.rb" - "**.rhtml" - "**.slim" - "**.css" - "**.inc" - "**.js" - "**.jsx" - "**.module" exclude_paths: - config/ - test/ ================================================ FILE: .editorconfig ================================================ # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 [*.rb] indent_style = space indent_size = 2 charset = utf-8 ================================================ FILE: .github/issue_template.md ================================================ ================================================ FILE: .github/pull_request_template.md ================================================ ================================================ FILE: .github/workflows/ruby.yml ================================================ name: Ruby on: push: branches: - master pull_request: branches: - master jobs: build: runs-on: ubuntu-latest continue-on-error: true strategy: matrix: ruby: [3.0.7, 3.1.6, 3.2.5, 3.3.4] gemfile: [gemfiles/rails71.gemfile, gemfiles/rails70.gemfile, gemfiles/rails61.gemfile, gemfiles/rails60.gemfile] # Gemfile fails on test/callbacks_spec.rb:52 steps: - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - name: Set up Code Climate run: | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter chmod +x ./cc-test-reporter ./cc-test-reporter before-build - name: Build and Test env: BUNDLE_GEMFILE: ${{ matrix.gemfile }} CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} RUBY_VERSION: ${{ matrix.ruby }} run: | bundle install --jobs 4 --retry 3 bundle exec rake if [ `basename $BUNDLE_GEMFILE` == "Gemfile" ] && [ $RUBY_VERSION == "3.3.4" ] && [ ! -z ${CC_TEST_REPORTER_ID} ] ; then ./cc-test-reporter after-build --exit-code $? ; fi ================================================ FILE: .gitignore ================================================ *.gem gemfiles/*.lock *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp test/dummy/**/*.log tmp .rvmrc .DS_Store .ruby-version ================================================ FILE: .rubocop.yml ================================================ AllCops: DisabledByDefault: true #################### Lint ################################ Lint/AmbiguousOperator: Description: >- Checks for ambiguous operators in the first argument of a method invocation without parentheses. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args' Enabled: true Lint/AmbiguousRegexpLiteral: Description: >- Checks for ambiguous regexp literals in the first argument of a method invocation without parenthesis. Enabled: true Lint/AssignmentInCondition: Description: "Don't use assignment in conditions." StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition' Enabled: true Layout/BlockAlignment: Description: 'Align block ends correctly.' Enabled: true Lint/CircularArgumentReference: Description: "Don't refer to the keyword argument in the default value." Enabled: true Layout/ConditionPosition: Description: >- Checks for condition placed in a confusing position relative to the keyword. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition' Enabled: true Lint/Debugger: Description: 'Check for debugger calls.' Enabled: true Layout/DefEndAlignment: Description: 'Align ends corresponding to defs correctly.' Enabled: true Lint/DeprecatedClassMethods: Description: 'Check for deprecated class method calls.' Enabled: true Lint/DuplicateMethods: Description: 'Check for duplicate methods calls.' Enabled: true Lint/EachWithObjectArgument: Description: 'Check for immutable argument given to each_with_object.' Enabled: true Lint/ElseLayout: Description: 'Check for odd code arrangement in an else block.' Enabled: true Lint/EmptyEnsure: Description: 'Checks for empty ensure block.' Enabled: true Lint/EmptyInterpolation: Description: 'Checks for empty string interpolation.' Enabled: true Layout/EndAlignment: Description: 'Align ends correctly.' Enabled: true Lint/EndInMethod: Description: 'END blocks should not be placed inside method definitions.' Enabled: true Lint/EnsureReturn: Description: 'Do not use return in an ensure block.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure' Enabled: true Security/Eval: Description: 'The use of eval represents a serious security risk.' Enabled: true Lint/FormatParameterMismatch: Description: 'The number of parameters to format/sprint must match the fields.' Enabled: true Lint/HandleExceptions: Description: "Don't suppress exception." StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions' Enabled: true Lint/LiteralAsCondition: Description: 'Checks of literals used in conditions.' Enabled: true Lint/LiteralInInterpolation: Description: 'Checks for literals used in interpolation.' Enabled: true Lint/Loop: Description: >- Use Kernel#loop with break rather than begin/end/until or begin/end/while for post-loop tests. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break' Enabled: true Lint/NestedMethodDefinition: Description: 'Do not use nested method definitions.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods' Enabled: true Lint/NonLocalExitFromIterator: Description: 'Do not use return in iterator to cause non-local exit.' Enabled: true Lint/ParenthesesAsGroupedExpression: Description: >- Checks for method calls with a space before the opening parenthesis. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' Enabled: true Lint/RequireParentheses: Description: >- Use parentheses in the method call to avoid confusion about precedence. Enabled: true Lint/RescueException: Description: 'Avoid rescuing the Exception class.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues' Enabled: true Lint/ShadowingOuterLocalVariable: Description: >- Do not use the same name as outer local variable for block arguments or block local variables. Enabled: true Lint/StringConversionInInterpolation: Description: 'Checks for Object#to_s usage in string interpolation.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s' Enabled: true Lint/UnderscorePrefixedVariableName: Description: 'Do not use prefix `_` for a variable that is used.' Enabled: true Lint/UnneededCopDisableDirective: Description: >- Checks for rubocop:disable comments that can be removed. Note: this cop is not disabled when disabling all cops. It must be explicitly disabled. Enabled: true Lint/UnusedBlockArgument: Description: 'Checks for unused block arguments.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' Enabled: true Lint/UnusedMethodArgument: Description: 'Checks for unused method arguments.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' Enabled: true Lint/UnreachableCode: Description: 'Unreachable code.' Enabled: true Lint/UselessAccessModifier: Description: 'Checks for useless access modifiers.' Enabled: true Lint/UselessAssignment: Description: 'Checks for useless assignment to a local variable.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' Enabled: true Lint/UselessComparison: Description: 'Checks for comparison of something with itself.' Enabled: true Lint/UselessElseWithoutRescue: Description: 'Checks for useless `else` in `begin..end` without `rescue`.' Enabled: true Lint/UselessSetterCall: Description: 'Checks for useless setter call to a local variable.' Enabled: true Lint/Void: Description: 'Possible use of operator/literal/variable in void context.' Enabled: true ###################### Metrics #################################### Metrics/AbcSize: Description: >- A calculated magnitude based on number of assignments, branches, and conditions. Reference: 'http://c2.com/cgi/wiki?AbcMetric' Enabled: false Max: 20 Metrics/BlockNesting: Description: 'Avoid excessive block nesting' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' Enabled: true Max: 4 Metrics/ClassLength: Description: 'Avoid classes longer than 250 lines of code.' Enabled: true Max: 250 Metrics/CyclomaticComplexity: Description: >- A complexity metric that is strongly correlated to the number of test cases needed to validate a method. Enabled: true Metrics/LineLength: Description: 'Limit lines to 80 characters.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' Enabled: false Metrics/MethodLength: Description: 'Avoid methods longer than 30 lines of code.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' Enabled: true Max: 30 Metrics/ModuleLength: Description: 'Avoid modules longer than 250 lines of code.' Enabled: true Max: 250 Metrics/ParameterLists: Description: 'Avoid parameter lists longer than three or four parameters.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' Enabled: true Metrics/PerceivedComplexity: Description: >- A complexity metric geared towards measuring complexity for a human reader. Enabled: false ##################### Performance ############################# Performance/Count: Description: >- Use `count` instead of `select...size`, `reject...size`, `select...count`, `reject...count`, `select...length`, and `reject...length`. Enabled: true Performance/Detect: Description: >- Use `detect` instead of `select.first`, `find_all.first`, `select.last`, and `find_all.last`. Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code' Enabled: true Performance/FlatMap: Description: >- Use `Enumerable#flat_map` instead of `Enumerable#map...Array#flatten(1)` or `Enumberable#collect..Array#flatten(1)` Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code' Enabled: true EnabledForFlattenWithoutParams: false # If enabled, this cop will warn about usages of # `flatten` being called without any parameters. # This can be dangerous since `flat_map` will only flatten 1 level, and # `flatten` without any parameters can flatten multiple levels. Performance/ReverseEach: Description: 'Use `reverse_each` instead of `reverse.each`.' Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code' Enabled: true Performance/Sample: Description: >- Use `sample` instead of `shuffle.first`, `shuffle.last`, and `shuffle[Fixnum]`. Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' Enabled: true Performance/Size: Description: >- Use `size` instead of `count` for counting the number of elements in `Array` and `Hash`. Reference: 'https://github.com/JuanitoFatas/fast-ruby#arraycount-vs-arraysize-code' Enabled: true Performance/StringReplacement: Description: >- Use `tr` instead of `gsub` when you are replacing the same number of characters. Use `delete` instead of `gsub` when you are deleting characters. Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringgsub-vs-stringtr-code' Enabled: true ##################### Rails ################################## Rails/ActionFilter: Description: 'Enforces consistent use of action filter methods.' Enabled: false Rails/Date: Description: >- Checks the correct usage of date aware methods, such as Date.today, Date.current etc. Enabled: false Rails/Delegate: Description: 'Prefer delegate method for delegations.' Enabled: false Rails/FindBy: Description: 'Prefer find_by over where.first.' Enabled: false Rails/FindEach: Description: 'Prefer all.find_each over all.find.' Enabled: false Rails/HasAndBelongsToMany: Description: 'Prefer has_many :through to has_and_belongs_to_many.' Enabled: false Rails/Output: Description: 'Checks for calls to puts, print, etc.' Enabled: false Rails/ReadWriteAttribute: Description: >- Checks for read_attribute(:attr) and write_attribute(:attr, val). Enabled: false Rails/ScopeArgs: Description: 'Checks the arguments of ActiveRecord scopes.' Enabled: false Rails/TimeZone: Description: 'Checks the correct usage of time zone aware methods.' StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time' Reference: 'http://danilenko.org/2012/7/6/rails_timezones' Enabled: false Rails/Validation: Description: 'Use validates :attribute, hash of validations.' Enabled: false ################## Style ################################# Layout/AccessModifierIndentation: Description: Check indentation of private/protected visibility modifiers. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected' Enabled: false Naming/AccessorMethodName: Description: Check the naming of accessor methods for get_/set_. Enabled: false Style/Alias: Description: 'Use alias_method instead of alias.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method' Enabled: false Layout/AlignArray: Description: >- Align the elements of an array literal if they span more than one line. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays' Enabled: false Layout/AlignHash: Description: >- Align the elements of a hash literal if they span more than one line. Enabled: false Layout/AlignParameters: Description: >- Align the parameters of a method call if they span more than one line. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' Enabled: false Style/AndOr: Description: 'Use &&/|| instead of and/or.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-and-or-or' Enabled: false Style/ArrayJoin: Description: 'Use Array#join instead of Array#*.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join' Enabled: false Style/AsciiComments: Description: 'Use only ascii symbols in comments.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' Enabled: false Naming/AsciiIdentifiers: Description: 'Use only ascii symbols in identifiers.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' Enabled: false Style/Attr: Description: 'Checks for uses of Module#attr.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr' Enabled: false Style/BeginBlock: Description: 'Avoid the use of BEGIN blocks.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks' Enabled: false Style/BarePercentLiterals: Description: 'Checks if usage of %() or %Q() matches configuration.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand' Enabled: false Style/BlockComments: Description: 'Do not use block comments.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments' Enabled: false Layout/BlockEndNewline: Description: 'Put end statement of multiline block on its own line.' Enabled: false Style/BlockDelimiters: Description: >- Avoid using {...} for multi-line blocks (multiline chaining is always ugly). Prefer {...} over do...end for single-line blocks. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' Enabled: false Style/BracesAroundHashParameters: Description: 'Enforce braces style around hash parameters.' Enabled: false Style/CaseEquality: Description: 'Avoid explicit use of the case equality operator(===).' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality' Enabled: false Layout/CaseIndentation: Description: 'Indentation of when in a case/when/[else/]end.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-when-to-case' Enabled: false Style/CharacterLiteral: Description: 'Checks for uses of character literals.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals' Enabled: false Naming/ClassAndModuleCamelCase: Description: 'Use CamelCase for classes and modules.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes' Enabled: false Style/ClassAndModuleChildren: Description: 'Checks style of children classes and modules.' Enabled: false Style/ClassCheck: Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.' Enabled: false Style/ClassMethods: Description: 'Use self when defining module/class methods.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#def-self-class-methods' Enabled: false Style/ClassVars: Description: 'Avoid the use of class variables.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars' Enabled: false Layout/ClosingParenthesisIndentation: Description: 'Checks the indentation of hanging closing parentheses.' Enabled: false Style/ColonMethodCall: Description: 'Do not use :: for method call.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' Enabled: false Style/CommandLiteral: Description: 'Use `` or %x around command literals.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-x' Enabled: false Style/CommentAnnotation: Description: 'Checks formatting of annotation comments.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' Enabled: false Layout/CommentIndentation: Description: 'Indentation of comments.' Enabled: false Naming/ConstantName: Description: 'Constants should use SCREAMING_SNAKE_CASE.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case' Enabled: false Style/DefWithParentheses: Description: 'Use def with parentheses when there are arguments.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' Enabled: false Style/PreferredHashMethods: Description: 'Checks for use of deprecated Hash methods.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-key' Enabled: false Style/Documentation: Description: 'Document classes and non-namespace modules.' Enabled: false Layout/DotPosition: Description: 'Checks the position of the dot in multi-line method calls.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' Enabled: false Style/DoubleNegation: Description: 'Checks for uses of double negation (!!).' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang' Enabled: false Style/EachWithObject: Description: 'Prefer `each_with_object` over `inject` or `reduce`.' Enabled: false Layout/ElseAlignment: Description: 'Align elses and elsifs correctly.' Enabled: false Style/EmptyElse: Description: 'Avoid empty else-clauses.' Enabled: false Layout/EmptyLineBetweenDefs: Description: 'Use empty lines between defs.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods' Enabled: false Layout/EmptyLines: Description: "Don't use several empty lines in a row." Enabled: false Layout/EmptyLinesAroundAccessModifier: Description: "Keep blank lines around access modifiers." Enabled: false Layout/EmptyLinesAroundBlockBody: Description: "Keeps track of empty lines around block bodies." Enabled: false Layout/EmptyLinesAroundClassBody: Description: "Keeps track of empty lines around class bodies." Enabled: false Layout/EmptyLinesAroundModuleBody: Description: "Keeps track of empty lines around module bodies." Enabled: false Layout/EmptyLinesAroundMethodBody: Description: "Keeps track of empty lines around method bodies." Enabled: false Style/EmptyLiteral: Description: 'Prefer literals to Array.new/Hash.new/String.new.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash' Enabled: false Style/EndBlock: Description: 'Avoid the use of END blocks.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks' Enabled: false Layout/EndOfLine: Description: 'Use Unix-style line endings.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf' Enabled: false Style/EvenOdd: Description: 'Favor the use of Fixnum#even? && Fixnum#odd?' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' Enabled: false Layout/ExtraSpacing: Description: 'Do not use unnecessary spacing.' Enabled: false Naming/FileName: Description: 'Use snake_case for source file names.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' Enabled: false Layout/InitialIndentation: Description: >- Checks the indentation of the first non-blank non-comment line in a file. Enabled: false Layout/FirstParameterIndentation: Description: 'Checks the indentation of the first parameter in a method call.' Enabled: false Lint/FlipFlop: Description: 'Checks for flip flops' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' Enabled: false Style/For: Description: 'Checks use of for or each in multiline loops.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-for-loops' Enabled: false Style/FormatString: Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' Enabled: false Style/GlobalVars: Description: 'Do not introduce global variables.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars' Reference: 'http://www.zenspider.com/Languages/Ruby/QuickRef.html' Enabled: false Style/GuardClause: Description: 'Check for conditionals that can be replaced with guard clauses' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' Enabled: false Style/HashSyntax: Description: >- Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax { :a => 1, :b => 2 }. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-literals' Enabled: false Style/IfUnlessModifier: Description: >- Favor modifier if/unless usage when you have a single-line body. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier' Enabled: false Style/IfWithSemicolon: Description: 'Do not use if x; .... Use the ternary operator instead.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs' Enabled: false Layout/IndentationConsistency: Description: 'Keep indentation straight.' Enabled: false Layout/IndentationWidth: Description: 'Use 2 spaces for indentation.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' Enabled: false Layout/IndentArray: Description: >- Checks the indentation of the first element in an array literal. Enabled: false Layout/IndentHash: Description: 'Checks the indentation of the first key in a hash literal.' Enabled: false Style/InfiniteLoop: Description: 'Use Kernel#loop for infinite loops.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#infinite-loop' Enabled: false Style/Lambda: Description: 'Use the new lambda literal syntax for single-line blocks.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line' Enabled: false Style/LambdaCall: Description: 'Use lambda.call(...) instead of lambda.(...).' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call' Enabled: false Layout/LeadingCommentSpace: Description: 'Comments should start with a space.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space' Enabled: false Style/LineEndConcatenation: Description: >- Use \ instead of + or << to concatenate two string literals at line end. Enabled: false Style/MethodCallWithoutArgsParentheses: Description: 'Do not use parentheses for method calls with no arguments.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens' Enabled: false Style/MethodDefParentheses: Description: >- Checks if the method definitions have or don't have parentheses. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' Enabled: false Naming/MethodName: Description: 'Use the configured style when naming methods.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' Enabled: false Style/ModuleFunction: Description: 'Checks for usage of `extend self` in modules.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function' Enabled: false Style/MultilineBlockChain: Description: 'Avoid multi-line chains of blocks.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' Enabled: false Layout/MultilineBlockLayout: Description: 'Ensures newlines after multiline block do statements.' Enabled: false Style/MultilineIfThen: Description: 'Do not use then for multi-line if/unless.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-then' Enabled: false Layout/MultilineOperationIndentation: Description: >- Checks indentation of binary operations that span more than one line. Enabled: false Style/MultilineTernaryOperator: Description: >- Avoid multi-line ?: (the ternary operator); use if/unless instead. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary' Enabled: false Style/NegatedIf: Description: >- Favor unless over if for negative conditions (or control flow or). StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives' Enabled: false Style/NegatedWhile: Description: 'Favor until over while for negative conditions.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives' Enabled: false Style/NestedTernaryOperator: Description: 'Use one expression per branch in a ternary operator.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-ternary' Enabled: false Style/Next: Description: 'Use `next` to skip iteration instead of a condition at the end.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' Enabled: false Style/NilComparison: Description: 'Prefer x.nil? to x == nil.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' Enabled: false Style/NonNilCheck: Description: 'Checks for redundant nil checks.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks' Enabled: false Style/Not: Description: 'Use ! instead of not.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not' Enabled: false Style/NumericLiterals: Description: >- Add underscores to large numeric literals to improve their readability. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics' Enabled: false Style/OneLineConditional: Description: >- Favor the ternary operator(?:) over if/then/else/end constructs. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' Enabled: false Naming/BinaryOperatorParameterName: Description: 'When defining binary operators, name the argument other.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' Enabled: false Style/OptionalArguments: Description: >- Checks for optional arguments that do not appear at the end of the argument list StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#optional-arguments' Enabled: false Style/ParallelAssignment: Description: >- Check for simple usages of parallel assignment. It will only warn when the number of variables matches on both sides of the assignment. This also provides performance benefits StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parallel-assignment' Enabled: false Style/ParenthesesAroundCondition: Description: >- Don't use parentheses around the condition of an if/unless/while. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-parens-if' Enabled: false Style/PercentLiteralDelimiters: Description: 'Use `%`-literal delimiters consistently' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces' Enabled: false Style/PercentQLiterals: Description: 'Checks if uses of %Q/%q match the configured preference.' Enabled: false Style/PerlBackrefs: Description: 'Avoid Perl-style regex back references.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' Enabled: false Naming/PredicateName: Description: 'Check the names of predicate methods.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' Enabled: false Style/Proc: Description: 'Use proc instead of Proc.new.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc' Enabled: false Style/RaiseArgs: Description: 'Checks the arguments passed to raise/fail.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages' Enabled: false Style/RedundantBegin: Description: "Don't use begin blocks when they are not needed." StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#begin-implicit' Enabled: false Style/RedundantException: Description: "Checks for an obsolete RuntimeException argument in raise/fail." StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror' Enabled: false Style/RedundantReturn: Description: "Don't use return where it's not required." StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return' Enabled: false Style/RedundantSelf: Description: "Don't use self where it's not needed." StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-self-unless-required' Enabled: false Style/RegexpLiteral: Description: 'Use / or %r around regular expressions.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' Enabled: false Layout/RescueEnsureAlignment: Description: 'Align rescues and ensures correctly.' Enabled: false Style/RescueModifier: Description: 'Avoid using rescue in its modifier form.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers' Enabled: false Style/SelfAssignment: Description: >- Checks for places where self-assignment shorthand should have been used. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment' Enabled: false Style/Semicolon: Description: "Don't use semicolons to terminate expressions." StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon' Enabled: false Style/SignalException: Description: 'Checks for proper usage of fail and raise.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method' Enabled: false Style/SingleLineBlockParams: Description: 'Enforces the names of some block params.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks' Enabled: false Style/SingleLineMethods: Description: 'Avoid single-line methods.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods' Enabled: false Layout/SpaceBeforeFirstArg: Description: >- Checks that exactly one space is used between a method name and the first argument for method calls without parentheses. Enabled: true Layout/SpaceAfterColon: Description: 'Use spaces after colons.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' Enabled: false Layout/SpaceAfterComma: Description: 'Use spaces after commas.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' Enabled: false Layout/SpaceAroundKeyword: Description: 'Use spaces around keywords.' Enabled: false Layout/SpaceAfterMethodName: Description: >- Do not put a space between a method name and the opening parenthesis in a method definition. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' Enabled: false Layout/SpaceAfterNot: Description: Tracks redundant space after the ! operator. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-bang' Enabled: false Layout/SpaceAfterSemicolon: Description: 'Use spaces after semicolons.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' Enabled: false Layout/SpaceBeforeBlockBraces: Description: >- Checks that the left block brace has or doesn't have space before it. Enabled: false Layout/SpaceBeforeComma: Description: 'No spaces before commas.' Enabled: false Layout/SpaceBeforeComment: Description: >- Checks for missing space between code and a comment on the same line. Enabled: false Layout/SpaceBeforeSemicolon: Description: 'No spaces before semicolons.' Enabled: false Layout/SpaceInsideBlockBraces: Description: >- Checks that block braces have or don't have surrounding space. For blocks taking parameters, checks that the left brace has or doesn't have trailing space. Enabled: false Layout/SpaceAroundBlockParameters: Description: 'Checks the spacing inside and after block parameters pipes.' Enabled: false Layout/SpaceAroundEqualsInParameterDefault: Description: >- Checks that the equals signs in parameter default assignments have or don't have surrounding space depending on configuration. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-around-equals' Enabled: false Layout/SpaceAroundOperators: Description: 'Use a single space around operators.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' Enabled: false Layout/SpaceInsideHashLiteralBraces: Description: "Use spaces inside hash literal braces - or don't." StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' Enabled: false Layout/SpaceInsideParens: Description: 'No spaces after ( or before ).' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' Enabled: false Layout/SpaceInsideRangeLiteral: Description: 'No spaces inside range literals.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals' Enabled: false Layout/SpaceInsideStringInterpolation: Description: 'Checks for padding/surrounding spaces inside string interpolation.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#string-interpolation' Enabled: false Style/SpecialGlobalVars: Description: 'Avoid Perl-style global variables.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms' Enabled: false Style/StringLiterals: Description: 'Checks if uses of quotes match the configured preference.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals' Enabled: false Style/StringLiteralsInInterpolation: Description: >- Checks if uses of quotes inside expressions in interpolated strings match the configured preference. Enabled: false Style/StructInheritance: Description: 'Checks for inheritance from Struct.new.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-extend-struct-new' Enabled: false Style/SymbolLiteral: Description: 'Use plain symbols instead of string symbols when possible.' Enabled: false Style/SymbolProc: Description: 'Use symbols as procs instead of blocks when possible.' Enabled: false Layout/Tab: Description: 'No hard tabs.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' Enabled: false Layout/TrailingBlankLines: Description: 'Checks trailing blank lines and final newline.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof' Enabled: false Style/TrailingCommaInArguments: Description: 'Checks for trailing comma in parameter lists.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-params-comma' Enabled: false Layout/TrailingWhitespace: Description: 'Avoid trailing whitespace.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace' Enabled: false Style/TrivialAccessors: Description: 'Prefer attr_* methods to trivial readers/writers.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family' Enabled: false Style/UnlessElse: Description: >- Do not use unless with else. Rewrite these with the positive case first. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless' Enabled: false Style/UnneededCapitalW: Description: 'Checks for %W when interpolation is not needed.' Enabled: false Style/UnneededPercentQ: Description: 'Checks for %q/%Q when single quotes or double quotes would do.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q' Enabled: false Style/TrailingUnderscoreVariable: Description: >- Checks for the usage of unneeded trailing underscores at the end of parallel variable assignment. Enabled: false Style/VariableInterpolation: Description: >- Don't interpolate global, instance and class variables directly in strings. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate' Enabled: false Naming/VariableName: Description: 'Use the configured style when naming variables.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' Enabled: false Style/WhenThen: Description: 'Use when x then ... for one-line cases.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases' Enabled: false Style/WhileUntilDo: Description: 'Checks for redundant do after while or until.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do' Enabled: false Style/WhileUntilModifier: Description: >- Favor modifier while/until usage when you have a single-line body. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier' Enabled: false Style/WordArray: Description: 'Use %w or %W for arrays of words.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' Enabled: false ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Code of Conduct As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery * Personal attacks * Trolling or insulting/derogatory comments * Public or private harassment * Publishing other's private information, such as physical or electronic addresses, without explicit permission * Other unethical or unprofessional conduct Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at code-of-conduct@frontside.io. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [http://contributor-covenant.org/version/1/3/0/][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/3/0/ ================================================ FILE: Changelog.md ================================================ ## Next Release ## 2.6.0 (2024-08-12) - Add the following callbacks (Thanks @4nd2in !): * `checkout.session.async_payment_failed` * `checkout.session.async_payment_succeeded` * `customer.subscription.paused` * `customer.subscription.pending_update_applied` * `customer.subscription.pending_update_expired` * `customer.subscription.resumed` * `invoice.deleted` * `invoice.finalization_failed` * `invoice.overdue` * `invoice.will_be_due` - Fix request handling in rack versions >= 3.1 where `Request#[]` is deprecated (#230). Thanks @4nd2in and @light-flight ! - adds testing for Ruby 3 and Rails 7 - stops testing on Ruby < 3 and Rails 5 ## 2.5.0 (2023-03-21) - Allow `stripe_elements_tag` to accept a block. Thanks @chip ! ## 2.4.0 (2023-02-04) - Add `tax_behavior` attribute to Price. Thanks @szechyjs ! ## 2.3.5 (2022-10-01) - Fix some deprecation warnings in tests. Thanks @smtlaissezfaire ! - Fix `NameError when loading JavascriptHelper`. Thanks @smtlaissezfaire ! ## 2.3.4 (2022-05-03) - Add setup_intent.canceled and setup_intent.requires_action callbacks. Thanks @jamesjason ! ## 2.3.3 (2021-12-17) - Add checkout.session.expired callback. Thanks @danielwellman ! ## 2.3.2 (2021-10-15) - Add Subscription Schedule and Tax Rate Callbacks #213 . Thanks @lesliepoolman ! ## 2.3.1 (2021-09-17) - Add price callbacks. Thanks @StevenElberger ! ## 2.3.0 (2021-03-08) - Adds testing for Rails 6.0 - Add `name` attribute to coupons. Thanks @ZilvinasKucinskas! ## 2.2.1 (2020-12-22) - Add payment_intent.requires_action callback, thanks @VadimLeader. ## 2.2.0 (2020-12-06) - Add Prices as a configuration object. Thanks @jamesprior! ## 2.1.0 (2020-10-18) - Added option to ignore missing API key and don't show any warning. Thanks @ndbroadbent! - Handle passing nil to signing_secret= and add tests. Thanks @martron! ## 2.0.0 (2020-09-18) - Everything from on the 2.0.0.pre release - includes changes from the 1.10.2 release ## 1.10.2 (2020-09-18) - adds missing callback `invoice.paid`. Thanks @SyborgStudios. ## 2.0.0.pre (2020-05-29) * [Breaking] Updated to work only with Rails >= 5.1 * [Breaking] It'll only be tested on Ruby 2.7, 2.6 and 2.5. * [Breaking] Supports the Stripe gem => 3.15.0 (from 2 years ago) * [Breaking] Removes Stripe::PingsController controller. ## 1.10.1 (2020-05-29) - adds missing callbacks for `payment_intent`. Thanks @klapperkopp . ## 1.10.0 (2020-03-31) - Adds support for using multiple tiers in a plan, thanks @cpsoinos ## 1.9.1 (2019-10-28) - Fixes issue with `rake stripe:verify` thanks @Millariel ! ## 1.9.0 (2019-09-01) - Adds support for multiple signing secrets. Thanks again @jacobcsmith ! ## 1.8.2 (2019-08-31) - adds missing callbacks for `payment_intent`, `payment_method` and `setup_intent`. Thanks @jacobcsmith ! ## 1.8.1 (2019-07-26) * adds callback for invoice.payment_action_required. Thanks @alexagranov . * fixes when clearing callbacks after unload doesn't play nice with eager_load. Thanks @alexagranov for reporting the problem and coming up with an initial fix for it. ## 1.8.0 (2019-07-25) * Configure publishable key from ENV. Thanks @cyu . ## 1.7.2 (2019-06-29) * fixes `require` error after update from Stripe gem. Thanks @dark-panda ! ## 1.7.1 (2019-05-24) * Don't assume sprockets are loaded thanks @manusajith ## 1.7.0 (2019-05-09) * [New Feature] add support for Plan to use a constant name different from plan id thanks @alexagranov ! * Add checkout.session.completed webhook thanks @Nitrino ! ## 1.6.1 (2019-03-04) * Add new invoice webhooks thanks @noahezekwugo ! ## 1.6.0 (2019-01-08) * New Year New Feature: Easily include Stripe Elements into your project thanks to @garrettqmartin8 ! * Travis is now testing the gem on Ruby 2.6.0 ## 1.5.5 (2018-12-16) * Fixed issue with Rails development and Spring: Clear callbacks before files are reloaded during development and test - thanks @ndbroadbent ## 1.5.4 (2018-11-14) * Removes test exception from event dispatch * Travis is now testing the gem on Ruby 2.5.3, 2.4.5, 2.3.8 ## 1.5.3 (2018-10-25) * Add usage_type, aggregate_usage, and billing_scheme - thanks @garrettqmartin8 ## 1.5.2 (2018-10-15) * fixes undefined method `expand_path' for Stripe::File:Class - Thanks to @BitesGit for reporting this. ## 1.5.1 (2018-10-01) * Allow statement_descriptor to be set on products - Thanks to @jeanmartin ## 1.5.0 (2018-09-27) * Add Webhook Signature Validation - Thanks to @wkirby * Include nickname in the payload for plans - Thanks to @jeanmartin ## 1.4.2 (2018-08-06) * New attributes for Stripe Billing Plans. ## 1.4.1 (2018-08-03) * Fixes ActionController::UnknownFormat errors - Thanks to @ndbroadbent ! ## 1.4.0 (2018-07-30) * Spanking new products builder for Stripe Billing (#117) - Thanks to @renchap for suggesting this and to @henryaj for reviewing it. ## 1.3.0 (2018-07-23) * do not create new product when product id is provided (#115) - Thanks to @renchap for reporting this * updates travis to latest rubies (#112) - Note that after this change we will only run tests on Ruby 2.5, 2.4 and 2.3 ## 1.2.2 (2018-04-16) * adds callback form `customer.source.expiring`. Thanks @Japestrale! ## 1.2.1 (2018-03-22) * Fixes Stripe API update on 2018-02-05 that breaks the plan builder (thanks to @georgecheng for reporting this!) ## 1.2.0 (2018-01-02) * Added additional callbacks (thanks @lloydpick & @dja) ## 1.1.2 (2017-10-25) * Fixes js partial crash if stripe_js_version is not defined ## 1.1.1 (2017-08-31) * Make stripe-ruby-mock an optional dependency (thanks @gaffneyc) ## 1.1.0 (2017-08-29) * Adds a testing module for testing callbacks (thanks @Pyo25) * Fixes loading with ActionController::API (thanks @gaffneyc) * Fixes `NoMethodError: NoMethodError (undefined method `object' for #ActionController::Parameters` (thanks to a whole bunch of people for reporting this) ## 1.0.2 (2017-08-15) * Remove authenticity token check (thanks @lewispb) * Adding timeout options to config (thanks @rgerard) * Add 'day' as possible plan interval (thanks @vdragsic and @artemave) ## 1.0.1 (2017-08-08) * Fixes a bug with Stripe JS V3, i.e. `Stripe.setPublishableKey` is no longer a function. Thanks to @kartikluke for reporting this. ## 1.0.0 (2017-07-24 - Breaky McBreakface) * [BREAKING] Update to latest stripe events (thanks @hopsoft). Note that if you are using the `after_customer_card_created`, `after_customer_card_updated` or `after_customer_card_deleted` callbacks, you MUST update them to `after_customer_source_created`, `after_customer_source_updated` or `after_customer_source_deleted` respectively. You also need to start using [Stripe API Version > 2015-02-18](https://stripe.com/docs/upgrades#2015-02-18) or else the webhook might not work as expected. * [BREAKING] Updates to the [latest version of Stripe JS](https://github.com/Everapps/stripe-rails/pull/69). If you were using `stripe_javascript_tag` without specifying the version number, note that it will now default to Stripe JS v3. This version is incompatible with the previous default. * The gem will only be tested on Rails 4 and 5 [from now on](https://github.com/Everapps/stripe-rails/pull/62). * Gem will henceforth only [be tested](https://github.com/Everapps/stripe-rails/pull/68) on Ruby >= 2.1.9. * add statement descriptor to plan attributes (thanks @jbender) * Relax version constraint on the responders gem ## 0.4.1 (2017-06-01) * Support for api_version header parameter (thanks @kiddrew) * Relax version constraint on stripe gem (thanks @gaffneyc) ## 0.4.0 (2017-05-24) * Support alternate versions of stripe js ## 0.3.2 (2017-03-06) * add `responders` gem as dependency to support `respond_to` method * fix unit tests with Rails 4.2 and Rails 5.0 ## 0.3.1 (2014-08-07) * add `eager_load` option to load callbacks into classes in non-eager-loaded enviroments ## 0.3.0 (2014-04-29) * Rename api_key to secret_key ## 0.2.6 (2013-10-17) * add `auto_mount` option to allow for manually mounting the webhook endpoints ## 0.2.5 (2013-03-18) * make the default max redemptions 1 * add stripe::coupons::reset! task to redefine all coupons ## 0.2.2 (2013-01-09) * bugfix allowing creation of coupons without max_redemptions ## 0.2.1 (2012-12-17) * manage coupons with config/stripe/coupons.rb ## 0.2.0 (2012-12-13) * out of the box support for webhooks and critical/non-critical event handlers * add :only guards for which webhooks you respond to- * move stripe.js out of asset pipeline, and insert it with utility functions ## 0.1.0 (2012-11-14) * add config/stripe/plans.rb to define and create plans * use `STRIPE_API_KEY` as default value of `config.stripe.api_key` * require stripe.js from asset pipeline * autoconfigure stripe.js with config.stripe.publishable_key. * add rake stripe:verify to ensure stripe.com authentication is configured properly ## 0.0.1 (2012-10-12) * basic railtie ================================================ FILE: Gemfile ================================================ source 'https://rubygems.org' # Specify your gem's dependencies in stripe-rails.gemspec gemspec gem 'rake' group :development, :test do gem 'm' end group :test do gem 'mocha' gem 'simplecov', '< 0.18', require: false gem 'stripe-ruby-mock' gem 'webmock' # System tests gem 'capybara' gem 'puma' gem 'selenium-webdriver', '>= 4' end ================================================ FILE: LICENSE ================================================ Copyright (c) 2012-2017 The Frontside Software, Inc. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Stripe::Rails: A Rails Engine for use with [stripe.com](https://stripe.com) [![Gem Version](https://badge.fury.io/rb/stripe-rails.svg)](https://badge.fury.io/rb/stripe-rails) [![Build Status](https://travis-ci.org/tansengming/stripe-rails.svg?branch=master)](https://travis-ci.org/tansengming/stripe-rails) [![Code Climate](https://codeclimate.com/github/tansengming/stripe-rails/badges/gpa.svg)](https://codeclimate.com/github/tansengming/stripe-rails) [![Test Coverage](https://codeclimate.com/github/tansengming/stripe-rails/badges/coverage.svg)](https://codeclimate.com/github/tansengming/stripe-rails/coverage) [![Tidelift](https://tidelift.com/badges/github/tansengming/stripe-rails)](#) This gem can help your rails application integrate with Stripe in the following ways * manage stripe configurations in a single place. * makes stripe.js available from the asset pipeline. * manage product, prices, plans and coupons from within your app. * painlessly receive and validate webhooks from stripe. [Professionally supported stripe-rails is coming soon](https://tidelift.com/subscription/pkg/rubygems-stripe-rails?utm_source=rubygems-stripe-rails&utm_medium=referral&utm_campaign=readme) --- [Installation](#installation) - [Setup your API keys](#setup-your-api-keys) - [Manually set your API version (optional)](#manually-set-your-api-version-optional) [Setup your payment configuration](#setup-your-payment-configuration) - [Configuring your plans and coupons](#configuring-your-plans-and-coupons) [Stripe Elements](#stripe-elements) - [Custom Elements](#custom-elements) [Webhooks](#webhooks) - [Signed Webhooks](#signed-webhooks) - [Testing Signed Webhooks Locally](#testing-signed-webhooks-locally) - [Disabling auto mount](#disabling-auto-mount) - [Responding to webhooks](#responding-to-webhooks) - [Critical and non-critical hooks](#critical-and-non-critical-hooks) - [Filtering Callbacks](#filtering-callbacks) - [Catchall Callback](#catchall-callback) [Unit testing](#unit-testing) [Thanks](#thanks) [Code of Conduct](#code-of-conduct) ## Installation Add this line to your application's Gemfile: ```ruby gem 'stripe-rails' ``` If you are going to be using [stripe.js][1] to securely collect credit card information on the client, then you will need to add the stripe javascript tags into your template. stripe-rails provides a helper to make this easy: ```erb <%= stripe_javascript_tag %> ``` or, you can render it as a partial: ```erb <%= render :partial => 'stripe/js' %> ``` In both cases, stripe-rails will choose a version of stripe.js appropriate for your development environment and automatically configure it to use your publishable API key. By default it uses `stripe-debug.js` for your `development` environment and `stripe.js` for everything else, but you can manually configure it per environment. ```ruby config.stripe.debug_js = true # use stripe-debug.js config.stripe.debug_js = false # use stripe.js ``` By default the helper renders the `v3` version of `stripe.js`. You can provide an alternate version to the helper to generate the appropriate tag: ```erb <%= stripe_javascript_tag(:v2) %> ``` ### Setup your API keys. You will need to configure your application to authenticate with stripe.com using [your api key][1]. There are two methods to do this, you can either set the environment variable `STRIPE_SECRET_KEY`: ```sh export STRIPE_SECRET_KEY=sk_test_xxyyzz ``` or if you are on heroku: ```sh heroku config:add STRIPE_SECRET_KEY=sk_test_xxyyzz ``` You can also set this value from inside ruby configuration code: ```ruby config.stripe.secret_key = "sk_test_xxyyzz" ``` In either case, it is recommended that you *not* check in this value into source control. You can verify that your api is set up and functioning properly by running the following command: ```sh rake stripe:verify ``` If you are going to be using stripe.js, then you will also need to set the value of your publishable key. A nice way to do it is to set your test publishable for all environments: ```ruby # config/application.rb # ... config.stripe.publishable_key = 'pk_test_XXXYYYZZZ' ``` And then override it to use your live key in production only ```ruby # config/environments/production.rb # ... config.stripe.publishable_key = 'pk_live_XXXYYYZZZ' ``` This key will be publicly visible on the internet, so it is ok to put in your source. If you prefer to environment variables, you can also set `STRIPE_PUBLISHABLE_KEY`: ```sh export STRIPE_PUBLISHABLE_KEY=pk_test_XXXYYYZZZ ``` If no API key is provided, `stripe-rails` will show a warning: "No stripe.com API key was configured ...". You can silence this warning by setting the `ignore_missing_secret_key` option to `true`: ```ruby # config/environments/production.rb # ... config.stripe.ignore_missing_secret_key = true ``` ### Manually set your API version (optional) If you need to test a new API version in development, you can override the version number manually. ```ruby # config/environments/development.rb # ... config.stripe.api_version = '2015-10-16' ``` ## Setup your payment configuration If you're using subscriptions, then you'll need to set up your application's payment plans and discounts. `Stripe::Rails` lets you automate the management of these definitions from within the application itself. To get started: ```sh rails generate stripe:install ``` this will generate the configuration files containing your plan and coupon definitions: ```console create config/stripe/products.rb create config/stripe/plans.rb create config/stripe/prices.rb create config/stripe/coupons.rb ``` ### Configuring your plans and coupons Use the plan builder to define as many plans as you want in `config/stripe/plans.rb` ```ruby Stripe.plan :silver do |plan| plan.name = 'ACME Silver' plan.amount = 699 # $6.99 plan.interval = 'month' end Stripe.plan :gold do |plan| plan.name = 'ACME Gold' plan.amount = 999 # $9.99 plan.interval = 'month' end Stripe.plan :bronze do |plan| # Use an existing product id to prevent a new plan from # getting created plan.product_id = 'prod_XXXXXXXXXXXXXX' plan.amount = 999 # $9.99 plan.interval = 'month' # Use graduated pricing tiers # ref: https://stripe.com/docs/api/plans/object#plan_object-tiers plan.tiers = [ { unit_amount: 1500, up_to: 10 }, { unit_amount: 1000, up_to: 'inf' } ] plan.tiers_mode = 'graduated' # set the usage type to 'metered' plan.usage_type = 'metered' end ``` This will define constants for these plans in the Stripe::Plans module so that you can refer to them by reference as opposed to an id string. ```ruby Stripe::Plans::SILVER # => 'silver: ACME Silver' Stripe::Plans::GOLD # => 'gold: ACME Gold' ``` If you have to support an existing plan with a Stripe plan id that can not be used as a Ruby constant, provide the plan id as a symbol when defining the plan, but provide the name for the constant to define with `constant_name`: ```ruby Stripe.plan "Silver-Plan".to_sym do |plan| plan.constant_name = 'SILVER_PLAN' # <--- plan.name = 'ACME Silver' plan.amount = 699 plan.interval = 'month' end Stripe::Plans::SILVER_PLAN # => will be defined # Will map to plan :id => "Silver-Plan" on Stripe ``` **Note** - If you're planning on running `rake stripe:prepare` to create your subscription plans, Stripe will restrict plan ids to match this regexp (`/\A[a-zA-Z0-9_\-]+\z/`) when created via API but still allows creation of plan ids that don't follow this restriction when manually created on stripe.com. Coupons are created in much the same way: ```ruby Stripe.coupon :super_elite_free_vip do |coupon| coupon.duration = 'forever' coupon.percent_off = 100 coupon.max_redemptions = 5 end ``` As are Products: ```ruby Stripe.product :primo do |product| product.name = 'PRIMO as a service' product.type = 'service' product.statement_descriptor = 'PRIMO' end ``` And Prices: ```ruby Stripe.price :bronze do |price| # Use an existing product id to prevent a new product from # getting created price.product_id = Stripe::Products::PRIMO.id price.billing_scheme = 'tiered' price.recurring = { interval: 'month', usage_type: 'metered' } # Use graduated pricing tiers # ref: https://stripe.com/docs/api/prices/object#price_object-tiers price.tiers = [ { unit_amount: 1500, up_to: 10 }, { unit_amount: 1000, up_to: 'inf' } ] price.tiers_mode = 'graduated' end ```` To upload your plans, products, prices and coupons onto stripe.com, run: ```sh rake stripe:prepare ``` This will create any plans, products, prices and coupons that do not currently exist, and treat as a NOOP any objects that already exist, so you can run this command safely as many times as you wish. Now you can use any of these objects in your application. NOTE: You must destroy plans and prices manually from your stripe dashboard. ## Stripe Elements Stripe::Rails allows you to easily include [Stripe Elements](https://stripe.com/payments/elements) in your application. > Stripe Elements are rich, pre-built UI components that help you create your own pixel-perfect checkout flows across desktop and mobile. Simply include the `stripe_elements_tag` anywhere below the `stripe_javascript_tag` and pass it the path to the controller action which will handle the Stripe token once the form is submitted: ```erb <%= stripe_javascript_tag %> <%= stripe_elements_tag submit_path: billing_path %> ``` Additionally, you can pass a block containing custom form elements to stripe_elements_tag: ## Custom Elements > Stripe::Rails allows you to easily include your own custom form elements > within the Stripe form by including those form elements in a block passed to > `stripe_elements_tag`: ```erb <%= stripe_javascript_tag %> <%= stripe_elements_tag(submit_path: billing_path) do %> <%= label_tag 'email', 'Email' %> <%= text_field :user, :email %> <% end %> ``` ### Configuration options Stripe::Rails comes bundled with default CSS and Javascript for Stripe elements, making it easy to drop in to your app. You can also specify your own assets paths: ```erb <%= stripe_elements_tag submit_path: billing_path, css_path: 'your/asset/path', js_path: 'your/asset/path' %> ``` If you decide to use your own CSS and Javascript for Stripe Elements, please refer to the [Stripe elements docs](https://stripe.com/docs/stripe-js/elements/quickstart). To change the form text you can add the following keys to your locale files ```yaml # config/locales/en.yml en: stripe_rails: elements: label_text: Your label text submit_button_text: Your button text ``` ## Webhooks Stripe::Rails automatically sets up your application to receive webhooks from stripe.com whenever a payment event is generated. To enable this, you will need to configure your [stripe webhooks][3] to point back to your application. By default, the webhook controller is mounted at '/stripe/events' so you would want to enter in `http://myproductionapp.com/stripe/events` as your url for live mode, and `http://mystagingapp.com/stripe/events` for your test mode. If you want to mount the stripe engine somewhere else, you can do so by setting the `stripe.endpoint` parameter. E.g. ```ruby config.stripe.endpoint = '/payment/stripe-integration' ``` Your new webhook URL would then be `http://myproductionapp/payment/stripe-integration/events` ### Signed Webhooks Validation of your webhook's signature uses your webhook endpoint signing secret. Before you can verify signatures, you need to retrieve your endpoint’s secret from your Stripe Dashboard. Select an endpoint for which you want to obtain the secret, then select the Click to reveal button. ```ruby # config/application.rb # ... config.stripe.signing_secrets = ['whsec_XXXYYYZZZ'] ``` Each secret is unique to the endpoint to which it corresponds. If you use multiple endpoint, you must obtain a secret for each one. After this setup, Stripe starts to sign each webhook it sends to the endpoint. Because of this, we recommend setting this variable with environment variables: ```sh export STRIPE_SIGNING_SECRET=whsec_XXXYYYZZZ export STRIPE_CONNECT_SIGNING_SECRET=whsec_AAABBBCCC ``` ```ruby config.stripe.signing_secrets = [ENV.fetch('STRIPE_SIGNING_SECRET'), ENV.fetch('STRIPE_CONNECT_SIGNING_SECRET')] ``` The first secret that successfully matches for each incoming webhook will be used to verify the incoming events. #### Testing Signed Webhooks Locally In order to test signed webhooks, you'll need to trigger test webhooks from your Stripe dashboard, and configure your local environment to receive remote network requests. To do so, we recommend using [ngrok](https://ngrok.com/) to configure a secure tunnel to `localhost`. Once configured and running, `ngrok` will give you a unique URL which can be used to set up a webhook endpoint. Webhook endpoints are configured in your Dashboard's [Webhook settings](https://dashboard.stripe.com/account/webhooks) section. Make sure you are in **Test** mode and click `Add endpoint`, and provide your `ngrok` URL along with the `stripe.endpoint` suffix. An example webhook URL would then be `https://bf2a5d21.ngrok.io/stripe/events`. Once your endpoint is configured, you can reveal the **Signing secret**. This will need to be set as documented above: ```ruby # config/application.rb # ... config.stripe.signing_secrets = ['whsec_XXXYYYZZZ'] ``` And you'll need to restart your rails server with: ```sh rails restart ``` Now you're ready to click **Send test webhook**, and trigger whichever events you'd like to test from Stripe itself. ### Disabling auto mount Sometimes, you don't want the stripe engine to be auto-mounted so that you control *exactly* what priority it will take in your routing table. This is especially important if you have a catch-all route which should appear after all other routes. In order to disable auto-mounting of the Stripe engine: ```ruby # in application.rb config.stripe.auto_mount = false ``` Then, you will have to manually mount the engine in your main application. ```ruby # in your application's routes.rb: mount Stripe::Engine => "/stripe" ``` ### Responding to webhooks Once you have your webhook URL configured you can respond to a stripe webhook *anywhere* in your application just by including the Stripe::Callbacks module into your class and declaring a callback with one of the callback methods. For example, to update a customer's payment status: ```ruby class User < ActiveRecord::Base include Stripe::Callbacks after_customer_updated! do |customer, event| user = User.find_by_stripe_customer_id(customer.id) if customer.delinquent user.is_account_current = false user.save! end end end ``` or to send an email with one of your customer's monthly invoices ```ruby class InvoiceMailer < ActionMailer::Base include Stripe::Callbacks after_invoice_created! do |invoice, event| user = User.find_by_stripe_customer(invoice.customer) new_invoice(user, invoice).deliver end def new_invoice(user, invoice) @user = user @invoice = invoice mail :to => user.email, :subject => '[Acme.com] Your new invoice' end end ``` **Note:** `Stripe::Callbacks` won't get included until the including class has been loaded. This is usually not an issue in the production environment as eager loading is enabled by default (`config.eager_load = true`). You may run into an issue in your development environment where eager loading is disabled by default. If you don't wish to enable eager loading in development, you can configure the classes to be eager loaded like so ```ruby # in your application's config/environments/development.rb config.stripe.eager_load = 'account', 'module/some_class', 'etc' ``` This will ensure that callbacks will get loaded in those configured classes if eager loading is disabled. The naming convention for the callback events is after__{callback_name}! where `callback_name` is name of the stripe event with all `.` characters substituted with underscores. So, for example, the stripe event `customer.discount.created` can be hooked by `after_customer_discount_created!` and so on... Each web hook is passed an instance of the stripe object to which the event corresponds ([`Stripe::Customer`][8], [`Stripe::Invoice`][9], [`Stripe::Charge`][10], etc...) as well as the [`Stripe::Event`][4] which contains metadata about the event being raised. By default, the event is re-fetched securely from stripe.com to prevent damage to your system by a malicious system spoofing real stripe events. ### Critical and non-critical hooks So far, the examples have all used critical hooks, but in fact, each callback method comes in two flavors: "critical", specified with a trailing `!` character, and "non-critical", which has no "bang" character at all. What distinguishes one from the other is that _if an exception is raised in a critical callback, it will cause the entire webhook to fail_. This will indicate to stripe.com that you did not receive the webhook at all, and that it should retry it again later until it receives a successful response. On the other hand, there are some tasks that are more tangential to the payment work flow and aren't such a big deal if they get dropped on the floor. For example, A non-critical hook can be used to do things like have a bot notify your company's chatroom that something a credit card was successfully charged: ```ruby class AcmeBot include Stripe::Callbacks after_charge_succeeded do |charge| announce "Attention all Dudes and Dudettes. Ya'll are so PAID!!!" end end ``` Chances are that if you experience a momentary failure in connectivity to your chatroom, you don't want the whole payment notification to fail. ### Filtering Callbacks Certain stripe events represent updates to existing data. You may want to only fire the event when certain attributes of that data are updated. You can pass an `:only` option to your callback to filter to specify which attribute updates you're interested in. For example, to warn users whenever their credit card has changed: ```ruby class StripeMailer include Stripe::Callbacks after_customer_updated! :only => :active_card do |customer, evt| your_credit_card_on_file_was_updated_are_you_sure_this_was_you(customer).deliver end end ``` Filters can be specified as an array as well: ```ruby module Accounting include Stripe::Callbacks after_invoice_updated! :only => [:amount, :subtotal] do # update our records end end ``` Alternatively, you can just pass a proc to filter the event manually. It will receive an instance of [`Stripe::Event`][4] as its parameter: ```ruby module StagingOnly include Stripe::Callbacks after_charge_succeeded! :only => proc {|charge, evt| unless evt.livemode} do |charge| puts "FAKE DATA, PLEASE IGNORE!" end end ``` ### Catchall Callback The special 'stripe.event' callback will be invoked for every single event received from stripe.com. This can be useful for things like logging and analytics: ```ruby class StripeFirehose include Stripe::Callbacks after_stripe_event do |target, event| # do something useful end end ``` See the [complete listing of all stripe events][5], and the [webhook tutorial][6] for more great information on this subject. ## Unit testing If you want to test your callbacks, you can use the `Stripe::Rails::Testing` module to send mocked Stripe events. ```ruby require 'stripe/rails/testing' test "my callback handles new subscription" do Stripe::Rails::Testing.send_event "customer.subscription.created" # Assertions end ``` You can also overwrite some event properties: ([More info](https://github.com/rebelidealist/stripe-ruby-mock#customizing-webhooks)) ```ruby require 'stripe/rails/testing' test "my callback handles new subscription" do Stripe::Rails::Testing.send_event "customer.subscription.created", { :email => "john@doe.com", :account_balance => 40 } # Assertions end ``` The default fixtures come from [the `stripe-ruby-mock` gem](https://github.com/rebelidealist/stripe-ruby-mock/tree/master/lib/stripe_mock/webhook_fixtures). ## Thanks ![Frontside](http://frontside.io/images/logo.svg) `Stripe::Rails` was originally developed with love and fondness by your friends at [Frontside][7]. They are available for your custom software development needs, including integration with stripe.com. ![Evercondo](https://dl.dropboxusercontent.com/s/m3ma9356uelep53/evercondo.png) `Stripe::Rails` has also been supported by the fine folks at [Evercondo][11], the next generation condo management software. [1]: https://stripe.com/docs/stripe.js [2]: https://manage.stripe.com/#account/apikeys [3]: https://manage.stripe.com/#account/webhooks [4]: https://stripe.com/docs/api?lang=ruby#events [5]: https://stripe.com/docs/api?lang=ruby#event_types [6]: https://stripe.com/docs/webhooks [7]: http://frontside.io [8]: https://stripe.com/docs/api?lang=ruby#customers [9]: https://stripe.com/docs/api?lang=ruby#invoices [10]: https://stripe.com/docs/api?lang=ruby#charges [11]: https://www.evercondo.com ## Code of Conduct Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms, which can be found in the `CODE_OF_CONDUCT.md` file in this repository. ================================================ FILE: Rakefile ================================================ #!/usr/bin/env rake require "bundler/gem_tasks" require 'rake/testtask' Rake::TestTask.new(:spec) do |t| t.libs << 'test' t.pattern = 'test/**/*_spec.rb' t.warning = false t.verbose = false end task default: :spec ================================================ FILE: app/assets/stripe/stripe_elements.css ================================================ .StripeElement { background-color: white; height: 40px; padding: 10px 12px; border-radius: 4px; border: 1px solid transparent; box-shadow: 0 1px 3px 0 #e6ebf1; -webkit-transition: box-shadow 150ms ease; transition: box-shadow 150ms ease; } .StripeElement--focus { box-shadow: 0 1px 3px 0 #cfd7df; } .StripeElement--invalid { border-color: #fa755a; } .StripeElement--webkit-autofill { background-color: #fefde5 !important; } ================================================ FILE: app/assets/stripe/stripe_elements.js ================================================ // Create a Stripe client. var stripe = Stripe(window.stripe_publishable_key); // Create an instance of Elements. var elements = stripe.elements(); var card = elements.create('card'); card.mount('#card-element'); card.addEventListener('change', function(event) { var displayError = document.getElementById('card-errors'); if (event.error) { displayError.textContent = event.error.message; } else { displayError.textContent = ''; } }); var form = document.getElementById('stripe-form'); form.addEventListener('submit', function(event) { event.preventDefault(); stripe.createToken(card).then(function(result) { if (result.error) { // Inform the user if there was an error. var errorElement = document.getElementById('card-errors'); errorElement.textContent = result.error.message; } else { // Send the token to your server. stripeTokenHandler(result.token); } }); }) function stripeTokenHandler(token) { // Insert the token ID into the form so it gets submitted to the server var form = document.getElementById('stripe-form'); var hiddenInput = document.createElement('input'); hiddenInput.setAttribute('type', 'hidden'); hiddenInput.setAttribute('name', 'stripeToken'); hiddenInput.setAttribute('value', token.id); form.appendChild(hiddenInput); // Submit the form form.submit(); } ================================================ FILE: app/controllers/stripe/application_controller.rb ================================================ module Stripe class ApplicationController < ActionController::Base skip_before_action(:verify_authenticity_token) if protect_from_forgery.any? # is anything stripe wide? end end ================================================ FILE: app/controllers/stripe/events_controller.rb ================================================ module Stripe class EventsController < ::Stripe::ApplicationController include Stripe::EventDispatch respond_to :json def create @event = dispatch_stripe_event(request) head :ok rescue JSON::ParserError => e ::Rails.logger.error e.message head :bad_request, status: 400 rescue Stripe::SignatureVerificationError => e ::Rails.logger.error e.message head :bad_request, status: 400 end end end ================================================ FILE: app/helpers/stripe/javascript_helper.rb ================================================ module Stripe module JavascriptHelper DEFAULT_STRIPE_JS_VERSION = 'v3' def stripe_javascript_tag(stripe_js_version = DEFAULT_STRIPE_JS_VERSION) stripe_js_version = stripe_js_version.to_s.downcase render 'stripe/js', stripe_js_version: stripe_js_version end def stripe_elements_tag(submit_path:, css_path: asset_path("stripe_elements.css"), js_path: asset_path("stripe_elements.js"), &block) render partial: 'stripe/elements', locals: { submit_path: submit_path, label_text: t('stripe_rails.elements.label_text'), submit_button_text: t('stripe_rails.elements.submit_button_text'), css_path: css_path, js_path: js_path, block: block } end end end ================================================ FILE: app/models/stripe/event_dispatch.rb ================================================ module Stripe module EventDispatch def dispatch_stripe_event(request) retrieve_stripe_event(request) do |evt| target = evt.data.object ::Stripe::Callbacks.run_callbacks(evt, target) end end def retrieve_stripe_event(request) id = request.params['id'] body = request.body.read sig_header = request.headers['HTTP_STRIPE_SIGNATURE'] endpoint_secrets = ::Rails.application.config.stripe.signing_secrets if Object.const_defined?('Stripe::Webhook') && sig_header && endpoint_secrets event = webhook_event(body, sig_header, endpoint_secrets) else event = Stripe::Event.retrieve(id) end yield event end private def webhook_event(body, sig_header, endpoint_secrets) endpoint_secrets.each_with_index do |secret, i| begin return ::Stripe::Webhook.construct_event(body, sig_header, secret.to_s) rescue ::Stripe::SignatureVerificationError raise if i == endpoint_secrets.length - 1 end end end end end ================================================ FILE: app/views/stripe/_elements.html.erb ================================================ <%= stylesheet_link_tag css_path, media: 'all' %>
<%= form_tag submit_path, id: "stripe-form" do %> <% if local_assigns[:block] %>
<%= capture(&local_assigns[:block]) %>
<% end %> <%= label_tag :card_element, label_text %>
<%= submit_tag submit_button_text %> <% end %>
<%= javascript_include_tag js_path, id: "stripe_elements_js" %> ================================================ FILE: app/views/stripe/_elements_js.html.erb ================================================ ================================================ FILE: app/views/stripe/_js.html.erb ================================================ <%- stripe_js_version = Stripe::JavascriptHelper::DEFAULT_STRIPE_JS_VERSION if (!defined?(stripe_js_version) || stripe_js_version.nil?) %> <%- case stripe_js_version %> <%- when 'v1', 'v2' %> <%- if ::Rails.application.config.stripe.debug_js %> <%- else %> <%- end %> <%- when 'v3' # the debug js for v3 isn't available %> <%- end %> ================================================ FILE: config/locales/en.yml ================================================ en: stripe_rails: elements: label_text: Credit or debit card submit_button_text: Submit payment ================================================ FILE: config/routes.rb ================================================ Rails.application.routes.draw do if Rails.application.config.stripe.auto_mount mount Stripe::Engine => Rails.application.config.stripe.endpoint end end Stripe::Engine.routes.draw do resources :events, only: :create end ================================================ FILE: gemfiles/gemfiles/rails70.gemfile ================================================ source :rubygems gem 'rails', '~> 7.0.0' gem 'rake' gem 'responders' gem 'stripe' group :development, :test do gem 'm' end group :test do gem 'mocha' gem 'simplecov', require: false gem 'stripe-ruby-mock' gem 'webmock' # Required for system tests gem 'capybara' gem 'puma', '< 6' # https://github.com/teamcapybara/capybara/issues/2598 gem 'selenium-webdriver' gem 'webdrivers' end ================================================ FILE: gemfiles/rails60.gemfile ================================================ source :rubygems gem 'rails', '~> 6.0.0' gem 'rake' gem 'responders' gem 'stripe' group :development, :test do gem 'm' end group :test do gem 'mocha' gem 'simplecov', require: false gem 'stripe-ruby-mock' gem 'webmock' # Required for system tests gem 'capybara' gem 'puma' gem 'selenium-webdriver' gem 'webdrivers' end ================================================ FILE: gemfiles/rails61.gemfile ================================================ source :rubygems gem 'rails', '~> 6.1.0' gem 'rake' gem 'responders' gem 'stripe' group :development, :test do gem 'm' end group :test do gem 'mocha' gem 'simplecov', require: false gem 'stripe-ruby-mock' gem 'webmock' # Required for system tests gem 'capybara' gem 'puma' gem 'selenium-webdriver' gem 'webdrivers' end ================================================ FILE: gemfiles/rails70.gemfile ================================================ source :rubygems gem 'rails', '~> 7.0.0' gem 'rake' gem 'responders' gem 'stripe' group :development, :test do gem 'm' end group :test do gem 'mocha' gem 'simplecov', require: false gem 'stripe-ruby-mock' gem 'webmock' # Required for system tests gem 'capybara' gem 'puma' gem 'selenium-webdriver' gem 'webdrivers' end ================================================ FILE: gemfiles/rails71.gemfile ================================================ source :rubygems gem 'rails', '~> 7.1.0' gem 'rake' gem 'responders' gem 'stripe' group :development, :test do gem 'm' end group :test do gem 'mocha' gem 'simplecov', require: false gem 'stripe-ruby-mock' gem 'webmock' # Required for system tests gem 'capybara' gem 'puma' gem 'selenium-webdriver' gem 'webdrivers' end ================================================ FILE: lib/generators/stripe/install_generator.rb ================================================ module Stripe class InstallGenerator < ::Rails::Generators::Base source_root ::File.expand_path("../../templates", __FILE__) desc "copy plans.rb" def copy_plans_file copy_file "products.rb", "config/stripe/products.rb" copy_file "plans.rb", "config/stripe/plans.rb" copy_file "prices.rb", "config/stripe/prices.rb" copy_file "coupons.rb", "config/stripe/coupons.rb" end end end ================================================ FILE: lib/generators/templates/coupons.rb ================================================ # This file contains descriptions of all your statically defined # stripe coupons. You may wish to define unique one-off coupons # elsewhere, but for ones you will use many times, and will be # shared between users, this is a good place. # Example # Stripe::Coupons::Gold25 #=> 'gold25' # Stripe.coupon :gold25 do |coupon| # # specify if this coupon is useable 'once', 'forever', or 'repeating' # coupon.duration = 'repeating' # # # absolute amount, in cents, to discount # coupon.amount_off = 199 # # # what currency to interpret the coupon amount # coupon.currency = 'usd' # # # how long will this coupon last? (only valid for duration of 'repeating') # coupon.duration_in_months = 6 # # # percentage off # coupon.percent_off = 25 # # UTC timestamp specifying the last time at which the coupon can be redeemed # coupon.redeem_by = (Time.now + 15.days).utc # # # How many times can this coupon be redeemed? # coupon.max_redemptions = 10 # end # # Once you have your coupons defined, you can run # # rake stripe:prepare # # This will export any new coupons to stripe.com so that you can # begin using them in your API calls. Any coupons found that are not in this # file will be left as-is. ================================================ FILE: lib/generators/templates/plans.rb ================================================ # This file contains descriptions of all your stripe plans # Example # Stripe::Plans::PRIMO #=> 'primo' # Stripe.plan :primo do |plan| # # plan name as it will appear on credit card statements # plan.name = 'Acme as a service PRIMO' # # # amount in cents. This is 6.99 # plan.amount = 699 # # # currency to use for the plan (default 'usd') # plan.currency = 'usd' # # # interval must be either 'day', 'week', 'month' or 'year' # plan.interval = 'month' # # # only bill once every three months (default 1) # plan.interval_count = 3 # # # number of days before charging customer's card (default 0) # plan.trial_period_days = 30 # end # Once you have your plans defined, you can run # # rake stripe:prepare # # This will export any new plans to stripe.com so that you can # begin using them in your API calls. ================================================ FILE: lib/generators/templates/prices.rb ================================================ # This file contains descriptions of all your stripe prices # Example # Stripe::Prices::LITE.lookup_key #=> 'lite' # Prices will have a stripe generated id. The lookup_key will match the # configuration below. You can fetch the ID or object from stripe: # # Stripe::Prices::LITE.stripe_id #=> 'price_0000sdfs2qfsdf' # Stripe::Prices::LITE.stripe_object #=> #... # Prices are not deletable via the API, the `reset!` method will instead # create a new price and transfer the lookup key to the new price. # Stripe.price :lite do |price| # # Prices may belong to a product, this will create a product along with the price # price.name = 'Acme as a service LITE' # # You can also specify an existing product ID # # price.product_id = Stripe::Products::PRIMO.id # # # amount in cents. This is 6.99 # price.unit_amount = 699 # # # currency to use for the price (default 'usd') # price.currency = 'usd' # # price.recurring = { # # interval must be either 'day', 'week', 'month' or 'year' # interval: 'month', # # only bill once every three months (default 1) # interval_count: 3, # # Must be either 'metered' or 'licensed' # usage_type: 'metered', # # Specifies a usage aggregation strategy for metered usage # aggregate_usage: 'sum' # } # # end # Once you have your prices defined, you can run # # rake stripe:prepare # # This will export any new prices to stripe.com so that you can # begin using them in your API calls. ================================================ FILE: lib/generators/templates/products.rb ================================================ # This file contains descriptions of all your stripe products # Example # Stripe::Products::PRIMO #=> 'primo' # Stripe.product :primo do |product| # # product's name as it will appear on credit card statements # product.name = 'Acme as a service PRIMO' # # # Product, either 'service' or 'good' # product.type = 'service' # end # Once you have your products defined, you can run # # rake stripe:prepare # # This will export any new products to stripe.com so that you can # begin using them in your API calls. ================================================ FILE: lib/stripe/billing_tier.rb ================================================ module Stripe module Plans class BillingTier include ActiveModel::Validations validates_presence_of :up_to validates_presence_of :flat_amount, if: ->(tier) { tier.unit_amount.nil? }, message: 'one of `flat_amount` or `unit_amount` must be specified!' validates_presence_of :unit_amount, if: ->(tier) { tier.flat_amount.nil? }, message: 'one of `flat_amount` or `unit_amount` must be specified!' validates_absence_of :flat_amount, if: ->(tier) { tier.unit_amount.present? }, message: 'only one of `flat_amount` or `unit_amount` should be specified!' validates_absence_of :unit_amount, if: ->(tier) { tier.flat_amount.present? }, message: 'only one of `flat_amount` or `unit_amount` should be specified!' attr_accessor :up_to, :flat_amount, :unit_amount def initialize(attrs) @up_to = attrs[:up_to] @flat_amount = attrs[:flat_amount] @unit_amount = attrs[:unit_amount] end def to_h { up_to: up_to, flat_amount: flat_amount, unit_amount: unit_amount }.compact end end end end ================================================ FILE: lib/stripe/callbacks/builder.rb ================================================ module Stripe module Callbacks module Builder extend ActiveSupport::Concern included do extend ActiveSupport::Concern @critical_callbacks = Hash.new do |h, k| h[k] = [] end @noncritical_callbacks = Hash.new do |h, k| h[k] = [] end module ClassMethods end class << self attr_reader :critical_callbacks, :noncritical_callbacks def clear_callbacks! critical_callbacks.clear noncritical_callbacks.clear end def callback(name) method_name = "after_#{name.gsub('.', '_')}" self::ClassMethods.send(:define_method, method_name) do |options = {}, &block| ::Stripe::Callbacks::noncritical_callbacks[name] << ::Stripe::Callbacks.callback_matcher(options, block) end self::ClassMethods.send(:define_method, "#{method_name}!") do |options = {}, &block| ::Stripe::Callbacks::critical_callbacks[name] << ::Stripe::Callbacks.callback_matcher(options, block) end end def callback_matcher(options, block) case only = options[:only] when Proc, Method proc do |target, evt| block.call(target, evt) if only.call(target, evt) end when Array, Set stringified_keys = only.map(&:to_s) proc do |target, evt| stringified_previous_attributes_keys = evt.data.previous_attributes.keys.map(&:to_s) intersection = stringified_previous_attributes_keys - stringified_keys block.call(target, evt) if intersection != stringified_previous_attributes_keys end when nil block else callback_matcher options.merge(:only => [only]), block end end end end end end end ================================================ FILE: lib/stripe/callbacks.rb ================================================ require 'stripe/callbacks/builder' module Stripe module Callbacks include Callbacks::Builder callback 'account.updated' callback 'account.application.deauthorized' callback 'account.external_account.created' callback 'account.external_account.deleted' callback 'account.external_account.updated' callback 'application_fee.created' callback 'application_fee.refunded' callback 'application_fee.refund.updated' callback 'balance.available' callback 'charge.captured' callback 'charge.failed' callback 'charge.pending' callback 'charge.refunded' callback 'charge.succeeded' callback 'charge.updated' callback 'charge.dispute.closed' callback 'charge.dispute.created' callback 'charge.dispute.funds_reinstated' callback 'charge.dispute.funds_withdrawn' callback 'charge.dispute.updated' callback 'charge.refund.updated' callback 'checkout.session.async_payment_failed' callback 'checkout.session.async_payment_succeeded' callback 'checkout.session.completed' callback 'checkout.session.expired' callback 'coupon.created' callback 'coupon.deleted' callback 'coupon.updated' callback 'customer.created' callback 'customer.deleted' callback 'customer.updated' callback 'customer.discount.created' callback 'customer.discount.deleted' callback 'customer.discount.updated' callback 'customer.source.created' callback 'customer.source.deleted' callback 'customer.source.expiring' callback 'customer.source.updated' callback 'customer.subscription.created' callback 'customer.subscription.deleted' callback 'customer.subscription.paused' callback 'customer.subscription.pending_update_applied' callback 'customer.subscription.pending_update_expired' callback 'customer.subscription.resumed' callback 'customer.subscription.trial_will_end' callback 'customer.subscription.updated' callback 'file.created' callback 'invoice.created' callback 'invoice.deleted' callback 'invoice.finalization_failed' callback 'invoice.finalized' callback 'invoice.marked_uncollectible' callback 'invoice.overdue' callback 'invoice.paid' callback 'invoice.payment_action_required' callback 'invoice.payment_failed' callback 'invoice.payment_succeeded' callback 'invoice.sent' callback 'invoice.upcoming' callback 'invoice.updated' callback 'invoice.voided' callback 'invoice.will_be_due' callback 'invoiceitem.created' callback 'invoiceitem.deleted' callback 'invoiceitem.updated' callback 'order.created' callback 'order.payment_failed' callback 'order.payment_succeeded' callback 'order.updated' callback 'order_return.created' callback 'payment_intent.amount_capturable_updated' callback 'payment_intent.canceled' callback 'payment_intent.created' callback 'payment_intent.payment_failed' callback 'payment_intent.processing' callback 'payment_intent.requires_action' callback 'payment_intent.succeeded' callback 'payment_method.attached' callback 'payment_method.card_automatically_updated' callback 'payment_method.detached' callback 'payment_method.updated' callback 'payout.canceled' callback 'payout.created' callback 'payout.failed' callback 'payout.paid' callback 'payout.updated' callback 'plan.created' callback 'plan.deleted' callback 'plan.updated' callback 'price.created' callback 'price.deleted' callback 'price.updated' callback 'product.created' callback 'product.deleted' callback 'product.updated' callback 'recipient.created' callback 'recipient.deleted' callback 'recipient.updated' callback 'review.closed' callback 'review.opened' callback 'setup_intent.canceled' callback 'setup_intent.created' callback 'setup_intent.requires_action' callback 'setup_intent.setup_failed' callback 'setup_intent.succeeded' callback 'sigma.scheduled_query_run.created' callback 'sku.created' callback 'sku.deleted' callback 'sku.updated' callback 'source.canceled' callback 'source.chargeable' callback 'source.failed' callback 'source.transaction.created' callback 'subscription_schedule.aborted' callback 'subscription_schedule.canceled' callback 'subscription_schedule.completed' callback 'subscription_schedule.created' callback 'subscription_schedule.expiring' callback 'subscription_schedule.released' callback 'subscription_schedule.updated' callback 'tax_rate.created' callback 'tax_rate.updated' callback 'transfer.created' callback 'transfer.reversed' callback 'transfer.updated' callback 'ping' callback 'stripe.event' class << self def run_callbacks(evt, target) _run_callbacks evt.type, evt, target _run_callbacks 'stripe.event', evt, target end def _run_callbacks(type, evt, target) run_critical_callbacks type, evt, target run_noncritical_callbacks type, evt, target end def run_critical_callbacks(type, evt, target) ::Stripe::Callbacks::critical_callbacks[type].each do |callback| callback.call(target, evt) end end def run_noncritical_callbacks(type, evt, target) ::Stripe::Callbacks::noncritical_callbacks[type].each do |callback| begin callback.call(target, evt) rescue Exception => e ::Rails.logger.error e.message ::Rails.logger.error e.backtrace.join("\n") end end end end end end ================================================ FILE: lib/stripe/configuration_builder.rb ================================================ require "active_model" module Stripe module ConfigurationBuilder extend ActiveSupport::Concern included do class << self def configuration_for(class_id, &block) @_configuration_storage = "@#{class_id.to_s.pluralize}" instance_variable_set(@_configuration_storage, {}) configuration_class = Class.new(Stripe::ConfigurationBuilder::Configuration) const_set(:Configuration, configuration_class) configuration_class.class_eval(&block) stripe_class = Stripe.const_get(class_id.to_s.camelize) stripe_configuration_class = self send(:define_method, class_id) do |id, &block| config = configuration_class.new(id, stripe_class, stripe_configuration_class) block.call config config.finalize! end ::Stripe.send(:extend, self) end def configurations instance_variable_get(@_configuration_storage) end def all configurations.values end def [](key) configurations[key.to_s] end def []=(key, value) configurations[key.to_s] = value end def put! all.each(&:put!) end def reset! all.each(&:reset!) end end end class Configuration include ActiveModel::Validations attr_reader :id def initialize(id, stripe_class, stripe_configuration_class) @id = id @stripe_class = stripe_class @stripe_configuration_class = stripe_configuration_class end def finalize! validate! globalize! end def validate! fail Stripe::InvalidConfigurationError, errors if invalid? end def globalize! id_to_use = @constant_name || @id @stripe_configuration_class[id_to_use.to_s.downcase] = self @stripe_configuration_class.const_set(id_to_use.to_s.upcase, self) end def put! if exists? puts "[EXISTS] - #{@stripe_class}:#{@id}" unless Stripe::Engine.testing else object = @stripe_class.create({:id => @id}.merge compact_create_options) puts "[CREATE] - #{@stripe_class}:#{object}" unless Stripe::Engine.testing end end def reset! if object = exists? object.delete end object = @stripe_class.create({:id => @id}.merge compact_create_options) puts "[RESET] - #{@stripe_class}:#{object}" unless Stripe::Engine.testing end def compact_create_options create_options.delete_if { |_, v| v.nil? } end def to_s @id.to_s end def exists? @stripe_class.retrieve(to_s) rescue Stripe::InvalidRequestError false end end end class InvalidConfigurationError < StandardError attr_reader :errors def initialize(errors) super errors.messages @errors = errors end end end ================================================ FILE: lib/stripe/coupons.rb ================================================ module Stripe module Coupons include ConfigurationBuilder configuration_for :coupon do attr_accessor :name, :duration, :amount_off, :currency, :duration_in_months, :max_redemptions, :percent_off, :redeem_by validates_presence_of :id, :duration validates_presence_of :duration_in_months, :if => :repeating? validates_inclusion_of :duration, :in => %w(forever once repeating), :message => "'%{value}' is not one of 'forever', 'once' or 'repeating'" validates_inclusion_of :percent_off, in: 1..100, unless: ->(coupon) {coupon.percent_off.nil?} validates_numericality_of :percent_off, :greater_than => 0, unless: ->(coupon) {coupon.percent_off.nil?} validates_numericality_of :duration_in_months, :greater_than => 0, :if => :repeating? validates_numericality_of :max_redemptions, greater_than: 0, unless: ->(coupon) {coupon.max_redemptions.nil?} def initialize(*args) super @currency = 'usd' @max_redemptions = 1 end def repeating? duration == 'repeating' end def create_options { :name => name, :duration => duration, :percent_off => percent_off, :amount_off => amount_off, :currency => currency, :duration_in_months => duration_in_months, :max_redemptions => max_redemptions, :redeem_by => redeem_by } end end end end ================================================ FILE: lib/stripe/current_api_version.rb ================================================ class CurrentApiVersion def self.call Stripe.api_version || begin resp, _ = Stripe::Plan.request(:get, Stripe::Plan.resource_url) resp.http_headers['stripe-version'] end end def self.after_switch_to_products_in_plans? Date.parse(call) >= Date.parse('2018-02-05') end end ================================================ FILE: lib/stripe/engine.rb ================================================ require 'stripe' module Stripe class Engine < ::Rails::Engine isolate_namespace Stripe class << self attr_accessor :testing end stripe_config = config.stripe = Struct.new(:api_base, :api_version, :secret_key, :ignore_missing_secret_key, :verify_ssl_certs, :signing_secret, :signing_secrets, :publishable_key, :endpoint, :debug_js, :auto_mount, :eager_load, :open_timeout, :read_timeout) do # for backwards compatibility treat signing_secret as an alias for signing_secrets def signing_secret=(value) self.signing_secrets = value.nil? ? value : Array(value) end def signing_secret self.signing_secrets && self.signing_secrets.first end end.new def stripe_config.api_key=(key) warn "[DEPRECATION] to align with stripe nomenclature, stripe.api_key has been renamed to config.stripe.secret_key" self.secret_key = key end initializer 'stripe.configure.defaults', :before => 'stripe.configure' do |app| stripe = app.config.stripe stripe.secret_key ||= ENV['STRIPE_SECRET_KEY'] stripe.publishable_key ||= ENV['STRIPE_PUBLISHABLE_KEY'] stripe.endpoint ||= '/stripe' stripe.auto_mount = true if stripe.auto_mount.nil? stripe.eager_load ||= [] if stripe.debug_js.nil? stripe.debug_js = ::Rails.env.development? end end initializer 'stripe.configure' do |app| [:api_base, :verify_ssl_certs, :api_version, :open_timeout, :read_timeout].each do |key| value = app.config.stripe.send(key) Stripe.send("#{key}=", value) unless value.nil? end secret_key = app.config.stripe.secret_key Stripe.api_key = secret_key unless secret_key.nil? $stderr.puts <<-MSG unless Stripe.api_key || app.config.stripe.ignore_missing_secret_key No stripe.com API key was configured for environment #{::Rails.env}! this application will be unable to interact with stripe.com. You can set your API key with either the environment variable `STRIPE_SECRET_KEY` (recommended) or by setting `config.stripe.secret_key` in your environment file directly. MSG end eager_load_classes = -> class_names { class_names.each do |constant| begin constant.to_s.camelize.constantize rescue NameError require constant end end } initializer 'stripe.callbacks.clear_after_unload' do |app| # Skip Rails 4 for now. next unless app.respond_to?(:reloader) # Clear callbacks after all autoloaded classes are removed. # This prevents duplicate callbacks being added during development. app.reloader.after_class_unload do ::Stripe::Callbacks.clear_callbacks! eager_load_classes.call(app.config.stripe.eager_load) end end initializer 'stripe.callbacks.eager_load' do |app| app.config.after_initialize do eager_load_classes.call(app.config.stripe.eager_load) end end config.to_prepare do ActiveSupport.on_load :action_controller do # ActionController::API does not have a helper method if respond_to?(:helper) helper Stripe::JavascriptHelper end end end initializer 'stripe.plans_and_coupons' do |app| for configuration in %w(products plans coupons prices) path = app.root.join("config/stripe/#{configuration}.rb") load path if path.exist? end end initializer 'stripe.assets.precompile' do |app| if app.config.respond_to?(:assets) app.config.assets.precompile += %w( stripe_elements.js stripe_elements.css ) end end rake_tasks do load 'stripe/rails/tasks.rake' end end end ================================================ FILE: lib/stripe/plans.rb ================================================ module Stripe module Plans include ConfigurationBuilder configuration_for :plan do attr_accessor :active, :aggregate_usage, :amount, :billing_scheme, :constant_name, :currency, :interval, :interval_count, :metadata, :name, :nickname, :product_id, :statement_descriptor, :tiers, :tiers_mode, :transform_usage, :trial_period_days, :usage_type validates_presence_of :id, :currency validates_presence_of :amount, unless: ->(p) { p.billing_scheme == 'tiered' } validates_absence_of :transform_usage, if: ->(p) { p.billing_scheme == 'tiered' } validates_presence_of :tiers_mode, if: ->(p) { p.billing_scheme == 'tiered' } validates_inclusion_of :interval, in: %w(day week month year), message: "'%{value}' is not one of 'day', 'week', 'month' or 'year'" validates :statement_descriptor, length: { maximum: 22 } validates :active, inclusion: { in: [true, false] }, allow_nil: true validates :usage_type, inclusion: { in: %w{ metered licensed } }, allow_nil: true validates :billing_scheme, inclusion: { in: %w{ per_unit tiered } }, allow_nil: true validates :aggregate_usage, inclusion: { in: %w{ sum last_during_period last_ever max } }, allow_nil: true validates :tiers_mode, inclusion: { in: %w{ graduated volume } }, allow_nil: true validate :name_or_product_id validate :aggregate_usage_must_be_metered, if: ->(p) { p.aggregate_usage.present? } validate :valid_constant_name, unless: ->(p) { p.constant_name.nil? } # validations for when using tiered billing validate :tiers_must_be_array, if: ->(p) { p.tiers.present? } validate :billing_scheme_must_be_tiered, if: ->(p) { p.tiers.present? } validate :validate_tiers, if: ->(p) { p.billing_scheme == 'tiered' } def initialize(*args) super(*args) @currency = 'usd' @interval_count = 1 @trial_period_days = 0 end private def aggregate_usage_must_be_metered errors.add(:aggregate_usage, 'usage_type must be metered') unless (usage_type == 'metered') end def name_or_product_id errors.add(:base, 'must have a product_id or a name') unless (@product_id.present? ^ @name.present?) end def billing_scheme_must_be_tiered errors.add(:billing_scheme, 'must be set to `tiered` when specifying `tiers`') unless billing_scheme == 'tiered' end def tiers_must_be_array errors.add(:tiers, 'must be an Array') unless tiers.is_a?(Array) end def billing_tiers @billing_tiers = tiers.map { |t| Stripe::Plans::BillingTier.new(t) } if tiers end def validate_tiers billing_tiers.all?(&:valid?) end module ConstTester; end def valid_constant_name ConstTester.const_set(constant_name.to_s.upcase, constant_name) ConstTester.send(:remove_const, constant_name.to_s.upcase.to_sym) rescue NameError errors.add(:constant_name, 'is not a valid Ruby constant name.') end def create_options if CurrentApiVersion.after_switch_to_products_in_plans? default_create_options else create_options_without_products end end def default_create_options { currency: currency, product: product_options, amount: amount, interval: interval, interval_count: interval_count, trial_period_days: trial_period_days, metadata: metadata, usage_type: usage_type, aggregate_usage: aggregate_usage, billing_scheme: billing_scheme, nickname: nickname, tiers: tiers ? tiers.map(&:to_h) : nil, tiers_mode: tiers_mode, transform_usage: transform_usage }.compact end def product_options product_id.presence || { name: name, statement_descriptor: statement_descriptor } end # Note: these options serve an older API, as such they should # probably never be updated. def create_options_without_products { currency: currency, name: name, amount: amount, interval: interval, interval_count: interval_count, trial_period_days: trial_period_days, metadata: metadata, statement_descriptor: statement_descriptor } end end end end ================================================ FILE: lib/stripe/prices.rb ================================================ module Stripe module Prices include ConfigurationBuilder VALID_TIME_UNITS = %i(day week month year) configuration_for :price do attr_reader :lookup_key attr_accessor :active, :billing_scheme, :constant_name, :currency, :metadata, :name, :nickname, :object, :product_id, :recurring, :statement_descriptor, :tax_behavior, :tiers, :tiers_mode, :transform_quantity, :type, :unit_amount validates_presence_of :id, :currency validates_presence_of :unit_amount, unless: ->(p) { p.billing_scheme == 'tiered' } validates_absence_of :transform_quantity, if: ->(p) { p.billing_scheme == 'tiered' } validates_presence_of :tiers_mode, :tiers, if: ->(p) { p.billing_scheme == 'tiered' } validates_numericality_of :recurring_interval_count, allow_nil: true validates_inclusion_of :recurring_interval, in: VALID_TIME_UNITS.collect(&:to_s), message: "'%{value}' is not one of #{VALID_TIME_UNITS.to_sentence(last_word_connector: ', or ')}", if: ->(p) { p.recurring.present? } validates :statement_descriptor, length: { maximum: 22 } validates :active, inclusion: { in: [true, false] }, allow_nil: true validates :billing_scheme, inclusion: { in: %w{ per_unit tiered } }, allow_nil: true validates :recurring_aggregate_usage, inclusion: { in: %w{ sum last_during_period last_ever max } }, allow_nil: true validates :recurring_usage_type, inclusion: { in: %w{ metered licensed } }, allow_nil: true validates :tax_behavior, inclusion: { in: %w{ inclusive exclusive unspecified } }, allow_nil: true validates :tiers_mode, inclusion: { in: %w{ graduated volume } }, allow_nil: true validate :name_or_product_id validate :recurring_aggregate_usage_must_be_metered, if: ->(p) { p.recurring_aggregate_usage.present? } validate :recurring_interval_count_maximum, if: ->(p) { p.recurring_interval_count.present? } validate :valid_constant_name, unless: ->(p) { p.constant_name.nil? } # validations for when using tiered billing validate :tiers_must_be_array, if: ->(p) { p.tiers.present? } validate :billing_scheme_must_be_tiered, if: ->(p) { p.tiers.present? } validate :validate_tiers, if: ->(p) { p.billing_scheme == 'tiered' } def initialize(*args) super(*args) @currency = 'usd' @lookup_key = @id.to_s @recurring = (recurring || {}).symbolize_keys end # We're overriding a handful of the Configuration methods so that # we find and create by lookup_key instead of by ID. The ID is assigned # by stripe and out of our control def put! if exists? puts "[EXISTS] - #{@stripe_class}:#{@id}:#{stripe_id}" unless Stripe::Engine.testing else object = @stripe_class.create({:lookup_key => @lookup_key}.merge compact_create_options) puts "[CREATE] - #{@stripe_class}:#{object}" unless Stripe::Engine.testing end end # You can't delete prices, but you can transfer the lookup key to a new price def reset! object = @stripe_class.create(reset_options) puts "[RESET] - #{@stripe_class}:#{object}" unless Stripe::Engine.testing end def exists? stripe_object.presence rescue Stripe::InvalidRequestError false end def stripe_object @stripe_class.list({lookup_keys: [@lookup_key]}).data.first.presence || nil rescue Stripe::InvalidRequestError nil end def stripe_id @stripe_id ||= stripe_object.try(:id) end def recurring_interval recurring[:interval] end def recurring_aggregate_usage recurring[:aggregate_usage] end def recurring_usage_type recurring[:usage_type] end def recurring_interval_count recurring[:interval_count] end private def recurring_aggregate_usage_must_be_metered errors.add(:recurring_aggregate_usage, 'recurring[:usage_type] must be metered') unless (recurring_usage_type == 'metered') end def recurring_interval_count_maximum time_unit = recurring_interval.to_sym return unless VALID_TIME_UNITS.include?(time_unit) && recurring_interval_count.respond_to?(time_unit) too_long = recurring_interval_count.send(time_unit) > 1.year errors.add(:recurring_interval_count, 'recurring[:interval_count] Maximum is one year (1 year, 12 months, or 52 weeks') if too_long end def name_or_product_id errors.add(:base, 'must have a product_id or a name') unless (@product_id.present? ^ @name.present?) end def billing_scheme_must_be_tiered errors.add(:billing_scheme, 'must be set to `tiered` when specifying `tiers`') unless billing_scheme == 'tiered' end def tiers_must_be_array errors.add(:tiers, 'must be an Array') unless tiers.is_a?(Array) end def billing_tiers @billing_tiers = tiers.map { |t| Stripe::Plans::BillingTier.new(t) } if tiers end def validate_tiers billing_tiers.all?(&:valid?) end module ConstTester; end def valid_constant_name ConstTester.const_set(constant_name.to_s.upcase, constant_name) ConstTester.send(:remove_const, constant_name.to_s.upcase.to_sym) rescue NameError errors.add(:constant_name, 'is not a valid Ruby constant name.') end def reset_options existing_object = stripe_object # Lookup and set the existing product ID if unset @product_id ||= existing_object.product if existing_object.present? { transfer_lookup_key: existing_object.present? }.merge(compact_create_options) end def create_options { currency: currency, unit_amount: unit_amount, active: active, metadata: metadata, nickname: nickname.presence || @lookup_key, recurring: recurring.compact, tiers: tiers ? tiers.map(&:to_h) : nil, tiers_mode: tiers_mode, billing_scheme: billing_scheme, lookup_key: @lookup_key, tax_behavior: tax_behavior, transform_quantity: transform_quantity, }.merge(product_options).compact end def product_options if product_id.present? { product: product_id } else { product_data: { name: name, statement_descriptor: statement_descriptor } } end end end end end ================================================ FILE: lib/stripe/products.rb ================================================ module Stripe module Products include ConfigurationBuilder configuration_for :product do attr_accessor :name, :type, :active, :attributes, :description, :caption, :metadata, :shippable, :unit_label, :url, :statement_descriptor validates_presence_of :name, :type validates :statement_descriptor, length: { maximum: 22 } validates :active, :shippable, inclusion: { in: [true, false] }, allow_nil: true validates :type, inclusion: { in: %w(service good) } validates :caption, :description, :shippable, :url, absence: true, unless: :good? validates :statement_descriptor, absence: true, unless: :service? private def good? type == 'good' end def service? type == 'service' end def create_options { name: name, type: type, active: active, attributes: attributes, description: description, caption: caption, metadata: metadata, shippable: shippable, unit_label: unit_label, url: url, statement_descriptor: statement_descriptor } end end end end ================================================ FILE: lib/stripe/rails/tasks.rake ================================================ namespace :stripe do desc 'verify your stripe.com authentication configuration' task 'verify' => :environment do begin Stripe::Plan.list puts "[OK] - connection to stripe.com is functioning properly" rescue Stripe::AuthenticationError => e puts "[FAIL] - authentication failed" end end task 'products:prepare' => 'environment' do if CurrentApiVersion.after_switch_to_products_in_plans? Stripe::Products.put! else puts '[SKIPPED] Current API version does not support Products' end end task 'plans:prepare' => 'environment' do Stripe::Plans.put! end task 'coupons:prepare' => 'environment' do Stripe::Coupons.put! end desc 'delete and redefine all coupons defined in config/stripe/coupons.rb' task 'coupons:reset!' => 'environment' do Stripe::Coupons.reset! end task 'prices:prepare' => 'environment' do Stripe::Prices.put! end desc 'delete and redefine all prices defined in config/stripe/prices.rb' task 'prices:reset!' => 'environment' do Stripe::Prices.reset! end desc "create all plans and coupons defined in config/stripe/{products|plans|prices|coupons}.rb" task 'prepare' => ['products:prepare', 'plans:prepare', 'prices:prepare', 'coupons:prepare'] end ================================================ FILE: lib/stripe/rails/testing.rb ================================================ begin require 'stripe_mock' rescue LoadError warn %q{Please add "gem 'stripe-ruby-mock', group: 'test'"" to the Gemfile to use Stripe::Rails::Testing"} exit end require 'stripe/callbacks' module Stripe module Rails module Testing def self.send_event(event, properties = {}) evt = StripeMock.mock_webhook_event(event, properties) target = evt.data.object ::Stripe::Callbacks.run_callbacks(evt, target) end end end end ================================================ FILE: lib/stripe/rails/version.rb ================================================ module Stripe module Rails VERSION = '2.6.0'.freeze end end ================================================ FILE: lib/stripe/rails.rb ================================================ require 'responders' require "stripe/rails/version" require 'stripe/engine' require 'stripe/configuration_builder' require 'stripe/current_api_version' require 'stripe/plans' require 'stripe/prices' require 'stripe/billing_tier' require 'stripe/coupons' require 'stripe/products' require 'stripe/callbacks' ================================================ FILE: lib/stripe-rails.rb ================================================ require 'stripe/rails' ================================================ FILE: stripe-rails.gemspec ================================================ require File.expand_path('lib/stripe/rails/version', __dir__) Gem::Specification.new do |gem| gem.authors = ["Charles Lowell", "Nola Stowe", "SengMing Tan"] gem.email = ["sengming@sanemen.com"] gem.description = "A gem to integrate stripe into your rails app" gem.summary = "A gem to integrate stripe into your rails app" gem.homepage = "https://github.com/tansengming/stripe-rails" gem.license = 'MIT' gem.files = `git ls-files`.split($\) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.name = "stripe-rails" gem.require_paths = ["lib"] gem.version = Stripe::Rails::VERSION gem.add_dependency 'rails', '>= 5.1' gem.add_dependency 'stripe', '>= 3.15.0' gem.add_dependency 'responders' end ================================================ FILE: test/callbacks_spec.rb ================================================ require 'spec_helper' describe Stripe::Callbacks do include Rack::Test::Methods include CallbackHelpers let(:app) { Rails.application } let(:event) { JSON.parse(File.read File.expand_path('event.json', __dir__)) } let(:invoice) { JSON.parse(File.read File.expand_path('invoice.json', __dir__)) } let(:content) { event } let(:observer) { Class.new } before do header 'Accept', 'application/json' header 'Content-Type', 'application/json' observer.include Stripe::Callbacks event['data']['object'] = invoice self.type = content['type'] end after { ::Stripe::Callbacks.clear_callbacks! } subject { post 'stripe/events', JSON.pretty_generate(content) } describe 'defined with a bang' do let(:callback) { :after_invoice_payment_succeeded! } before { run_callback_with(callback) {|target, e| @event = e; @target = target} } describe 'when it is invoked for the invoice.payment_succeeded event' do it 'is invoked for the invoice.payment_succeeded event' do subject _(@event).wont_be_nil _(@event.type).must_equal 'invoice.payment_succeeded' _(@target.total).must_equal 6999 end end describe 'when the invoked.payment_failed webhook is called' do before { self.type = 'invoked.payment_failed' } it 'the invoice.payment_succeeded callback is not invoked' do subject _(@event).must_be_nil end end describe 'if it raises an exception' do before { run_callback_with(callback) { fail } } it 'causes the whole webhook to fail' do _(-> { subject }).must_raise RuntimeError end end end describe 'defined without a bang and raising an exception' do let(:callback) { :after_invoice_payment_succeeded } before { run_callback_with(callback) { fail } } it 'does not cause the webhook to fail' do subject _(last_response.status).must_be :>=, 200 _(last_response.status).must_be :<, 300 end end describe 'the after_stripe_event callback to catch any event' do let(:events) { [] } before { run_callback_with(:after_stripe_event) { |_, evt| events << evt } } describe 'when it gets invoked for a standard event' do before { self.type = 'invoice.payment_failed' } it 'it will be run' do subject _(events.first.type).must_equal 'invoice.payment_failed' end end describe 'when it gets invoked for an arbitrary event' do before { self.type = 'foo.bar.baz' } it 'it will be run' do subject _(events.first.type).must_equal 'foo.bar.baz' end end end describe 'filtering on specific changed attributes' do events = nil before do events = [] self.type = 'invoice.updated' @stubbed_event.data.previous_attributes = {} end describe 'specified as an single symbol' do before do observer.class_eval do after_invoice_updated! :only => :closed do |invoice, evt| events << evt end end end describe 'when a prior attribute was not specified' do it 'does not fire events' do subject _(events.length).must_equal 0 end end describe 'when a prior attribute was specified' do before { @stubbed_event.data.previous_attributes['closed'] = true } it 'fires events' do subject _(events.length).must_equal 1 end end end describe 'specified as an array' do before do observer.class_eval do after_invoice_updated! :only => [:currency, :subtotal] do |invoice, evt| events << evt end end end describe 'when a prior attribute was not specified' do it 'does not fire events' do subject _(events.length).must_equal 0 end end describe 'when prior attributes were specified' do before { @stubbed_event.data.previous_attributes['subtotal'] = 699 } it 'fire events' do subject _(events.length).must_equal 1 end end end describe 'specified as a lambda' do before do observer.class_eval do after_invoice_updated :only => proc {|target, evt| evt.data.previous_attributes.to_hash.has_key? :closed} do |i,e| events << e end end end describe 'when the lambda is not true' do it 'does not fire events' do subject _(events.length).must_equal 0 end end describe 'when the lambda is not true' do before { @stubbed_event.data.previous_attributes['closed'] = 'false' } it 'fires events' do subject _(events.length).must_equal 1 end end end end describe 'with forgery protection enabled' do before do ActionController::Base.allow_forgery_protection = true ActionController::Base.protect_from_forgery with: :exception end after { ActionController::Base.allow_forgery_protection = false } it { subject } # must_not raise error end end ================================================ FILE: test/coupon_builder_spec.rb ================================================ require 'spec_helper' describe 'building coupons' do before do Stripe::Coupons.configurations.clear end describe 'default values' do before do @coupon = Stripe.coupon(:firesale) do |coupon| coupon.name = '100% OFF FIRESALE' coupon.duration = 'once' coupon.duration_in_months = 100 coupon.amount_off = 100 end end it "allows a single redemption by default" do _(@coupon.max_redemptions).must_equal 1 end end describe 'simply' do before do @now = Time.now.utc Stripe.coupon(:gold25) do |coupon| coupon.duration = 'repeating' coupon.duration_in_months = 10 coupon.amount_off = 100 coupon.currency = 'USD' coupon.max_redemptions = 3 coupon.percent_off = 25 coupon.redeem_by = @now end end after {Stripe::Coupons.send(:remove_const, :GOLD25)} it 'is accessible via hash lookup (symbol/string agnostic)' do _(Stripe::Coupons[:gold25]).must_equal Stripe::Coupons::GOLD25 _(Stripe::Coupons['gold25']).must_equal Stripe::Coupons::GOLD25 end describe 'uploading' do describe 'when none exists on stripe.com' do before do Stripe::Coupon.stubs(:retrieve).raises(Stripe::InvalidRequestError.new("not found", "id")) end it 'creates the plan online' do Stripe::Coupon.expects(:create).with( :id => :gold25, :duration => 'repeating', :duration_in_months => 10, :amount_off => 100, :currency => 'USD', :max_redemptions => 3, :percent_off => 25, :redeem_by => @now ) Stripe::Coupons.put! end end describe 'when it is already present on stripe.com' do before do Stripe::Coupon.stubs(:retrieve).returns(Stripe::Coupon.construct_from({ :id => :gold25, })) end it 'is a no-op' do Stripe::Coupon.expects(:create).never Stripe::Coupons.put! end end end describe 'reseting' do describe 'when it does not exist on stripe.com'do before do Stripe::Coupon.stubs(:retrieve).raises(Stripe::InvalidRequestError.new("not found", "id")) end it 'creates the plan' do Stripe::Coupon.expects(:create).with( :id => :gold25, :duration => 'repeating', :duration_in_months => 10, :amount_off => 100, :currency => 'USD', :max_redemptions => 3, :percent_off => 25, :redeem_by => @now ) Stripe::Coupons.reset! end end describe 'when it does exist on stripe.com already' do before do @coupon = Stripe::Coupon.construct_from({ :id => :gold25, }) Stripe::Coupon.stubs(:retrieve).returns(@coupon) end it 'first deletes the coupon then re-adds it' do @coupon.expects(:delete) Stripe::Coupon.expects(:create).with( :id => :gold25, :duration => 'repeating', :duration_in_months => 10, :amount_off => 100, :currency => 'USD', :max_redemptions => 3, :percent_off => 25, :redeem_by => @now ) Stripe::Coupons.reset! end end end end describe 'with missing mandatory values' do it 'raises an exception after configuring it' do _(proc {Stripe.coupon(:bad) {}}).must_raise Stripe::InvalidConfigurationError end end end ================================================ FILE: test/dummy/README.rdoc ================================================ == Welcome to Rails Rails is a web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Control pattern. This pattern splits the view (also called the presentation) into "dumb" templates that are primarily responsible for inserting pre-built data in between HTML tags. The model contains the "smart" domain objects (such as Account, Product, Person, Post) that holds all the business logic and knows how to persist themselves to a database. The controller handles the incoming requests (such as Save New Account, Update Product, Show Post) by manipulating the model and directing data to the view. In Rails, the model is handled by what's called an object-relational mapping layer entitled Active Record. This layer allows you to present the data from database rows as objects and embellish these data objects with business logic methods. You can read more about Active Record in link:files/vendor/rails/activerecord/README.html. The controller and view are handled by the Action Pack, which handles both layers by its two parts: Action View and Action Controller. These two layers are bundled in a single package due to their heavy interdependence. This is unlike the relationship between the Active Record and Action Pack that is much more separate. Each of these packages can be used independently outside of Rails. You can read more about Action Pack in link:files/vendor/rails/actionpack/README.html. == Getting Started 1. At the command prompt, create a new Rails application: rails new myapp (where myapp is the application name) 2. Change directory to myapp and start the web server: cd myapp; rails server (run with --help for options) 3. Go to http://localhost:3000/ and you'll see: "Welcome aboard: You're riding Ruby on Rails!" 4. Follow the guidelines to start developing your application. You can find the following resources handy: * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html * Ruby on Rails Tutorial Book: http://www.railstutorial.org/ == Debugging Rails Sometimes your application goes wrong. Fortunately there are a lot of tools that will help you debug it and get it back on the rails. First area to check is the application log files. Have "tail -f" commands running on the server.log and development.log. Rails will automatically display debugging and runtime information to these files. Debugging info will also be shown in the browser on requests from 127.0.0.1. You can also log your own messages directly into the log file from your code using the Ruby logger class from inside your controllers. Example: class WeblogController < ActionController::Base def destroy @weblog = Weblog.find(params[:id]) @weblog.destroy logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") end end The result will be a message in your log file along the lines of: Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! More information on how to use the logger is at http://www.ruby-doc.org/core/ Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are several books available online as well: * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) These two books will bring you up to speed on the Ruby language and also on programming in general. == Debugger Debugger support is available through the debugger command when you start your Mongrel or WEBrick server with --debugger. This means that you can break out of execution at any point in the code, investigate and change the model, and then, resume execution! You need to install ruby-debug to run the server in debugging mode. With gems, use sudo gem install ruby-debug. Example: class WeblogController < ActionController::Base def index @posts = Post.all debugger end end So the controller will accept the action, run the first line, then present you with a IRB prompt in the server window. Here you can do things like: >> @posts.inspect => "[#nil, "body"=>nil, "id"=>"1"}>, #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" >> @posts.first.title = "hello from a debugger" => "hello from a debugger" ...and even better, you can examine how your runtime objects actually work: >> f = @posts.first => #nil, "body"=>nil, "id"=>"1"}> >> f. Display all 152 possibilities? (y or n) Finally, when you're ready to resume execution, you can enter "cont". == Console The console is a Ruby shell, which allows you to interact with your application's domain model. Here you'll have all parts of the application configured, just like it is when the application is running. You can inspect domain models, change values, and save to the database. Starting the script without arguments will launch it in the development environment. To start the console, run rails console from the application directory. Options: * Passing the -s, --sandbox argument will rollback any modifications made to the database. * Passing an environment name as an argument will load the corresponding environment. Example: rails console production. To reload your controllers and models after launching the console run reload! More information about irb can be found at: link:http://www.rubycentral.org/pickaxe/irb.html == dbconsole You can go to the command line of your database directly through rails dbconsole. You would be connected to the database with the credentials defined in database.yml. Starting the script without arguments will connect you to the development database. Passing an argument will connect you to a different database, like rails dbconsole production. Currently works for MySQL, PostgreSQL and SQLite 3. == Description of Contents The default directory structure of a generated Ruby on Rails application: |-- app | |-- assets | |-- images | |-- javascripts | `-- stylesheets | |-- controllers | |-- helpers | |-- mailers | |-- models | `-- views | `-- layouts |-- config | |-- environments | |-- initializers | `-- locales |-- db |-- doc |-- lib | `-- tasks |-- log |-- public |-- script |-- test | |-- fixtures | |-- functional | |-- integration | |-- performance | `-- unit |-- tmp | |-- cache | |-- pids | |-- sessions | `-- sockets `-- vendor |-- assets `-- stylesheets `-- plugins app Holds all the code that's specific to this particular application. app/assets Contains subdirectories for images, stylesheets, and JavaScript files. app/controllers Holds controllers that should be named like weblogs_controller.rb for automated URL mapping. All controllers should descend from ApplicationController which itself descends from ActionController::Base. app/models Holds models that should be named like post.rb. Models descend from ActiveRecord::Base by default. app/views Holds the template files for the view that should be named like weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby syntax by default. app/views/layouts Holds the template files for layouts to be used with views. This models the common header/footer method of wrapping views. In your views, define a layout using the layout :default and create a file named default.html.erb. Inside default.html.erb, call <% yield %> to render the view using this layout. app/helpers Holds view helpers that should be named like weblogs_helper.rb. These are generated for you automatically when using generators for controllers. Helpers can be used to wrap functionality for your views into methods. config Configuration files for the Rails environment, the routing map, the database, and other dependencies. db Contains the database schema in schema.rb. db/migrate contains all the sequence of Migrations for your schema. doc This directory is where your application documentation will be stored when generated using rake doc:app lib Application specific libraries. Basically, any kind of custom code that doesn't belong under controllers, models, or helpers. This directory is in the load path. public The directory available for the web server. Also contains the dispatchers and the default HTML files. This should be set as the DOCUMENT_ROOT of your web server. script Helper scripts for automation and generation. test Unit and functional tests along with fixtures. When using the rails generate command, template test files will be generated for you and placed in this directory. vendor External libraries that the application depends on. Also includes the plugins subdirectory. If the app has frozen rails, those gems also go here, under vendor/rails/. This directory is in the load path. ================================================ FILE: test/dummy/Rakefile ================================================ #!/usr/bin/env rake # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require File.expand_path('../config/application', __FILE__) Dummy::Application.load_tasks ================================================ FILE: test/dummy/app/assets/javascripts/application.js ================================================ // This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // the compiled file. // // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD // GO AFTER THE REQUIRES BELOW. // //= require jquery //= require jquery_ujs //= require_tree . ================================================ FILE: test/dummy/app/assets/stylesheets/application.css ================================================ /* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the top of the * compiled file, but it's generally better to create a new file per style scope. * *= require_self *= require_tree . */ ================================================ FILE: test/dummy/app/controllers/apis_controller.rb ================================================ ApiControllerKlass = defined?(ActionController::API) ? ActionController::API : ApplicationController class ApisController < ApiControllerKlass def index render json: :ok end end ================================================ FILE: test/dummy/app/controllers/application_controller.rb ================================================ class ApplicationController < ActionController::Base protect_from_forgery end ================================================ FILE: test/dummy/app/controllers/stripes_controller.rb ================================================ class StripesController < ApplicationController def new end end ================================================ FILE: test/dummy/app/helpers/application_helper.rb ================================================ module ApplicationHelper end ================================================ FILE: test/dummy/app/mailers/.gitkeep ================================================ ================================================ FILE: test/dummy/app/models/.gitkeep ================================================ ================================================ FILE: test/dummy/app/models/dummy/model_with_callbacks.rb ================================================ class Dummy::ModelWithCallbacks end ================================================ FILE: test/dummy/app/views/layouts/application.html.erb ================================================ Dummy <%= csrf_meta_tags %> <%= yield %> ================================================ FILE: test/dummy/app/views/stripes/new.html.erb ================================================ <%= stripe_javascript_tag (params[:version] || :v3) %> ================================================ FILE: test/dummy/config/application.rb ================================================ require File.expand_path('../boot', __FILE__) require 'rails' require 'action_controller/railtie' require 'rails/test_unit/railtie' Bundler.require require "stripe-rails" module Dummy class Application < Rails::Application config.stripe.publishable_key = 'pk_test_XXXYYYZZZ' # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. # config.autoload_paths += %W(#{config.root}/extras) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. # config.plugins = [ :exception_notification, :ssl_requirement, :all ] # Activate observers that should always be running. # config.active_record.observers = :cacher, :garbage_collector, :forum_observer # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # config.time_zone = 'Central Time (US & Canada)' # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] # Enable escaping HTML in JSON. config.active_support.escape_html_entities_in_json = true # Use SQL instead of Active Record's schema dumper when creating the database. # This is necessary if your schema can't be completely dumped by the schema dumper, # like if you have constraints or database-specific column types # config.active_record.schema_format = :sql # Enforce whitelist mode for mass assignment. # This will create an empty whitelist of attributes available for mass-assignment for all models # in your app. As such, your models will need to explicitly whitelist or blacklist accessible # parameters by using an attr_accessible or attr_protected declaration. # Enable the asset pipeline #config.assets.enabled = true # Version of your assets, change this if you want to expire all your assets #config.assets.version = '1.0' end end ================================================ FILE: test/dummy/config/boot.rb ================================================ require 'rubygems' gemfile = File.expand_path('../../../../Gemfile', __FILE__) if File.exist?(gemfile) ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' Bundler.setup end $:.unshift File.expand_path('../../../../lib', __FILE__) ================================================ FILE: test/dummy/config/database.yml ================================================ # SQLite version 3.x # gem install sqlite3 # # Ensure the SQLite 3 gem is defined in your Gemfile # gem 'sqlite3' development: adapter: sqlite3 database: db/development.sqlite3 pool: 5 timeout: 5000 # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: adapter: sqlite3 database: db/test.sqlite3 pool: 5 timeout: 5000 production: adapter: sqlite3 database: db/production.sqlite3 pool: 5 timeout: 5000 ================================================ FILE: test/dummy/config/environment.rb ================================================ # Load the rails application require File.expand_path('../application', __FILE__) # Initialize the rails application Dummy::Application.initialize! ================================================ FILE: test/dummy/config/environments/development.rb ================================================ Dummy::Application.configure do config.eager_load = false # Settings specified here will take precedence over those in config/application.rb # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false # Don't care if the mailer can't send # config.action_mailer.raise_delivery_errors = false # Print deprecation notices to the Rails logger config.active_support.deprecation = :log # Only use best-standards-support built into browsers config.action_dispatch.best_standards_support = :builtin # Raise exception on mass assignment protection for Active Record models # config.active_record.mass_assignment_sanitizer = :strict # Log the query plan for queries taking more than this (works # with SQLite, MySQL, and PostgreSQL) # config.active_record.auto_explain_threshold_in_seconds = 0.5 # Do not compress assets # config.assets.compress = false # Expands the lines which load the assets # config.assets.debug = true end ================================================ FILE: test/dummy/config/environments/production.rb ================================================ Dummy::Application.configure do # Settings specified here will take precedence over those in config/application.rb # Code is not reloaded between requests config.cache_classes = true # Full error reports are disabled and caching is turned on config.consider_all_requests_local = false config.action_controller.perform_caching = true # Disable Rails's static asset server (Apache or nginx will already do this) config.serve_static_assets = false # Compress JavaScripts and CSS config.assets.compress = true # Don't fallback to assets pipeline if a precompiled asset is missed config.assets.compile = false # Generate digests for assets URLs config.assets.digest = true # Defaults to nil and saved in location specified by config.assets.prefix # config.assets.manifest = YOUR_PATH # Specifies the header that your server uses for sending files # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true # See everything in the log (default is :info) # config.log_level = :debug # Prepend all log lines with the following tags # config.log_tags = [ :subdomain, :uuid ] # Use a different logger for distributed setups # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) # Use a different cache store in production # config.cache_store = :mem_cache_store # Enable serving of images, stylesheets, and JavaScripts from an asset server # config.action_controller.asset_host = "http://assets.example.com" # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) # config.assets.precompile += %w( search.js ) # Disable delivery errors, bad email addresses will be ignored # config.action_mailer.raise_delivery_errors = false # Enable threaded mode # config.threadsafe! # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation can not be found) config.i18n.fallbacks = true # Send deprecation notices to registered listeners config.active_support.deprecation = :notify # Log the query plan for queries taking more than this (works # with SQLite, MySQL, and PostgreSQL) # config.active_record.auto_explain_threshold_in_seconds = 0.5 end ================================================ FILE: test/dummy/config/environments/test.rb ================================================ Dummy::Application.configure do # Settings specified here will take precedence over those in config/application.rb # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Configure static asset server for tests with Cache-Control for performance config.serve_static_assets = true # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false # Raise exceptions instead of rendering exception templates config.action_dispatch.show_exceptions = false # Disable request forgery protection in test environment config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. #config.action_mailer.delivery_method = :test # Raise exception on mass assignment protection for Active Record models #config.active_record.mass_assignment_sanitizer = :strict # Print deprecation notices to the stderr config.active_support.deprecation = :stderr # Set eager_load to false as it's unneeded config.eager_load = false end ================================================ FILE: test/dummy/config/initializers/backtrace_silencers.rb ================================================ # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. # Rails.backtrace_cleaner.remove_silencers! ================================================ FILE: test/dummy/config/initializers/inflections.rb ================================================ # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format # (all these examples are active by default): # ActiveSupport::Inflector.inflections do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end # # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections do |inflect| # inflect.acronym 'RESTful' # end ================================================ FILE: test/dummy/config/initializers/mime_types.rb ================================================ # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf # Mime::Type.register_alias "text/html", :iphone ================================================ FILE: test/dummy/config/initializers/secret_token.rb ================================================ # Be sure to restart your server when you modify this file. # Your secret key for verifying the integrity of signed cookies. # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. Dummy::Application.config.secret_token = '5e507bc08a89b8b07e1ce08c932ec89771be6500e4d2d64c38164d2405d27578c33edc2982dd9ee67d4c49e0dd75992ac2ed6eb120bea0e16516549466906d06' Dummy::Application.config.secret_key_base = 'a87ae91f28326ebe41f7ce9b7b799bb9' ================================================ FILE: test/dummy/config/initializers/session_store.rb ================================================ # Be sure to restart your server when you modify this file. Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information # (create the session table with "rails generate session_migration") # Dummy::Application.config.session_store :active_record_store ================================================ FILE: test/dummy/config/initializers/wrap_parameters.rb ================================================ # Be sure to restart your server when you modify this file. # # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] end # Disable root element in JSON by default. ActiveSupport.on_load(:active_record) do self.include_root_in_json = false end ================================================ FILE: test/dummy/config/locales/en.yml ================================================ # Sample localization file for English. Add more files in this directory for other locales. # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. en: hello: "Hello world" ================================================ FILE: test/dummy/config/routes.rb ================================================ Dummy::Application.routes.draw do resources :stripes, only: :new resources :apis, only: :index # The priority is based upon order of creation: # first created -> highest priority. # Sample of regular route: # match 'products/:id' => 'catalog#view' # Keep in mind you can assign values other than :controller and :action # Sample of named route: # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase # This route can be invoked with purchase_url(:id => product.id) # Sample resource route (maps HTTP verbs to controller actions automatically): # resources :products # Sample resource route with options: # resources :products do # member do # get 'short' # post 'toggle' # end # # collection do # get 'sold' # end # end # Sample resource route with sub-resources: # resources :products do # resources :comments, :sales # resource :seller # end # Sample resource route with more complex sub-resources # resources :products do # resources :comments # resources :sales do # get 'recent', :on => :collection # end # end # Sample resource route within a namespace: # namespace :admin do # # Directs /admin/products/* to Admin::ProductsController # # (app/controllers/admin/products_controller.rb) # resources :products # end # You can have the root of your site routed with "root" # just remember to delete public/index.html. # root :to => 'welcome#index' # See how all your routes lay out with "rake routes" # This is a legacy wild controller route that's not recommended for RESTful applications. # Note: This route will make all actions in every controller accessible via GET requests. # match ':controller(/:action(/:id))(.:format)' end ================================================ FILE: test/dummy/config/stripe/plans.rb ================================================ Stripe.plan :gold do |plan| plan.name = 'Solid Gold' plan.amount = 699 plan.interval = 'month' end Stripe.plan "Solid Gold".to_sym do |plan| plan.constant_name = 'SOLID_GOLD' plan.name = 'Solid Gold' plan.amount = 699 plan.interval = 'month' end Stripe.plan :alternative_currency do |plan| plan.name = 'Alternative Currency' plan.amount = 699 plan.interval = 'month' plan.currency = 'cad' end Stripe.plan :metered do |plan| plan.name = 'Metered' plan.amount = 699 plan.interval = 'month' plan.usage_type = 'metered' plan.aggregate_usage = 'max' plan.billing_scheme = 'per_unit' end Stripe.plan :tiered do |plan| plan.name = 'Tiered' plan.aggregate_usage = 'max' plan.billing_scheme = 'tiered' # interval must be either 'day', 'week', 'month' or 'year' plan.interval = 'month' plan.interval_count = 1 plan.tiers = [ { unit_amount: 1500, up_to: 10 }, { unit_amount: 1000, up_to: 'inf' } ] plan.tiers_mode = 'graduated' plan.usage_type = 'metered' end ================================================ FILE: test/dummy/config/stripe/prices.rb ================================================ Stripe.price :gold do |price| price.name = 'Solid Gold' price.unit_amount = 699 price.recurring = { interval: 'month' } end Stripe.price :taxable_gold do |price| price.name = 'Taxable Gold' price.unit_amount = 699 price.tax_behavior = 'exclusive' end Stripe.price "Solid Gold".to_sym do |price| price.constant_name = 'SOLID_GOLD' price.name = 'Solid Gold' price.unit_amount = 699 price.recurring = { interval: 'month' } end Stripe.price :alternative_currency do |price| price.name = 'Alternative Currency' price.unit_amount = 699 price.recurring = { interval: 'month' } price.currency = 'cad' end Stripe.price :metered do |price| price.name = 'Metered' price.unit_amount = 699 price.recurring = { interval: 'month', aggregate_usage: 'max', usage_type: 'metered' } price.billing_scheme = 'per_unit' end Stripe.price :tiered do |price| price.name = 'Tiered' price.billing_scheme = 'tiered' # interval must be either 'day', 'week', 'month' or 'year' price.recurring = { interval: 'month', interval_count: 2, aggregate_usage: 'max', usage_type: 'metered' } price.tiers = [ { unit_amount: 1500, up_to: 10 }, { unit_amount: 1000, up_to: 'inf' } ] price.tiers_mode = 'graduated' end ================================================ FILE: test/dummy/config.ru ================================================ # This file is used by Rack-based servers to start the application. require ::File.expand_path('../config/environment', __FILE__) run Dummy::Application ================================================ FILE: test/dummy/lib/assets/.gitkeep ================================================ ================================================ FILE: test/dummy/lib/dummy/module_with_callbacks.rb ================================================ class Dummy::ModuleWithCallbacks end ================================================ FILE: test/dummy/log/.gitkeep ================================================ ================================================ FILE: test/dummy/public/404.html ================================================ The page you were looking for doesn't exist (404)

The page you were looking for doesn't exist.

You may have mistyped the address or the page may have moved.

================================================ FILE: test/dummy/public/422.html ================================================ The change you wanted was rejected (422)

The change you wanted was rejected.

Maybe you tried to change something you didn't have access to.

================================================ FILE: test/dummy/public/500.html ================================================ We're sorry, but something went wrong (500)

We're sorry, but something went wrong.

================================================ FILE: test/dummy/script/rails ================================================ #!/usr/bin/env ruby # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. APP_PATH = File.expand_path('../../config/application', __FILE__) require File.expand_path('../../config/boot', __FILE__) require 'rails/commands' ================================================ FILE: test/dummy_apis_controller_spec.rb ================================================ require 'spec_helper' describe ApisController do include Rack::Test::Methods let(:app) { Rails.application } before do header 'Accept', 'application/json' header 'Content-Type', 'application/json' end describe 'the apis interface' do subject { get '/apis/' } it { _(subject).must_be :ok? } end end ================================================ FILE: test/dummy_stripes_controller_spec.rb ================================================ require 'spec_helper' class DummyStripesControllerSpec < ApplicationSystemTestCase setup do Dummy::Application.configure do config.stripe.publishable_key = 'pk_test_XXXYYYZZZ' end end test "loading the default javascript helper" do visit new_stripe_url assert_text 'This page tests the loading and initialization of Stripe JS' end test "loading the v2 version of the javascript helper" do visit new_stripe_url(version: 'v2') assert_text 'This page tests the loading and initialization of Stripe JS' end end ================================================ FILE: test/event.json ================================================ { "api_version": "2017-02-14", "created": 1234567890, "customer_email": "", "data": { "object": { } }, "id": "evt_19tLKfDSlTMT26MkKD3pohqX", "livemode": false, "object": "event", "pending_webhooks": 0, "recipient_best_description": "", "request": { "id": "req_ArJ9y3DcEShbw2", "idempotency_key": null }, "type": "invoice.payment_succeeded" } ================================================ FILE: test/events_controller_spec.rb ================================================ require 'spec_helper' describe Stripe::EventsController do include Rack::Test::Methods let(:app) { Rails.application } before do header 'Accept', 'application/json' header 'Content-Type', 'application/json' end describe 'the events interface' do subject { post '/stripe/events', params.to_json } before { stripe_events_stub } let(:params) { { id: 'evt_00000000000000', type: 'customer.updated', data: {object: 'customer'}, } } let(:stripe_events_stub) do stub_request(:get, "https://api.stripe.com/v1/events/evt_00000000000000"). to_return(status: 200, body: Stripe::Event.construct_from(params).to_json, headers: {}) end it { _(subject).must_be :ok? } it 'should call the stripe_events_stub' do subject assert_requested(stripe_events_stub) end describe 'when signing_secret is nil' do before do header 'Stripe-Signature', 't=1537832721,v1=123,v0=123' app.config.stripe.signing_secret = nil end it 'should call the stripe_events_stub' do subject assert_requested(stripe_events_stub) end end end describe 'signed webhooks' do before do header 'Stripe-Signature', 't=1537832721,v1=123,v0=123' app.config.stripe.signing_secret = 'SECRET' end after { app.config.stripe.signing_secret = nil } let(:params) { { id: 'evt_00000000000001', type: 'customer.updated', data: { object: 'customer', fingerprint: 'xxxyyyzzz' }, } } subject { post '/stripe/events', params.to_json } it 'returns bad_request when invalid' do Stripe::Webhook.expects(:construct_event).raises(Stripe::SignatureVerificationError.new('msg', 'sig_header')) _(subject).must_be :bad_request? end it 'returns ok when valid' do Stripe::Webhook.expects(:construct_event).returns(Stripe::Event.construct_from(params)) _(subject).must_be :ok? end end describe 'multiple signed webhooks' do before do header 'Stripe-Signature', 't=1537832721,v1=123,v0=123' app.config.stripe.signing_secrets = ['SECRET1', 'SECRET2'] end after { app.config.stripe.signing_secrets = nil } let(:params) { { id: 'evt_00000000000001', type: 'customer.updated', data: { object: 'customer', fingerprint: 'xxxyyyzzz' }, } } subject { post '/stripe/events', params.to_json } it 'returns bad_request when invalid' do Stripe::Webhook.expects(:construct_event).twice.raises(Stripe::SignatureVerificationError.new('msg', 'sig_header')) _(subject).must_be :bad_request? end it 'returns ok when valid' do Stripe::Webhook.expects(:construct_event).returns(Stripe::Event.construct_from(params)) _(subject).must_be :ok? end end end ================================================ FILE: test/fixtures/stripe_plans.json ================================================ { "object": "list", "count": 0, "data": [], "has_more": false, "url": "/v1/plans" } ================================================ FILE: test/fixtures/stripe_plans_headers.json ================================================ { "Server": "nginx", "Date": "Wed, 21 Mar 2018 20:04:56 GMT", "Content-Type": "application/json", "Content-Length": "5807", "Connection": "close", "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS, DELETE", "Access-Control-Allow-Origin": "*", "Access-Control-Expose-Headers": "Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required", "Access-Control-Max-Age": "300", "Cache-Control": "no-cache, no-store", "Request-Id": "req_UCuedZo4sKqUXn", "Stripe-Version": "2018-02-05", "Strict-Transport-Security": "max-age=31556926; includeSubDomains; preload" } ================================================ FILE: test/fixtures/stripe_plans_headers_2017.json ================================================ { "Server": "nginx", "Date": "Wed, 21 Mar 2018 20:04:56 GMT", "Content-Type": "application/json", "Content-Length": "5807", "Connection": "close", "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS, DELETE", "Access-Control-Allow-Origin": "*", "Access-Control-Expose-Headers": "Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required", "Access-Control-Max-Age": "300", "Cache-Control": "no-cache, no-store", "Request-Id": "req_UCuedZo4sKqUXn", "Stripe-Version": "2017-02-05", "Strict-Transport-Security": "max-age=31556926; includeSubDomains; preload" } ================================================ FILE: test/fixtures/stripe_prices.json ================================================ { "object": "list", "count": 0, "data": [], "has_more": false, "url": "/v1/prices" } ================================================ FILE: test/invoice.json ================================================ { "amount_due": 0, "application_fee": 0, "attempt_count": 0, "attempted": true, "billing": "", "charge": "", "closed": true, "currency": "usd", "customer": "cus_ADmuABetLS15eF", "date": 1234567890, "description": "", "discount": { }, "due_date": 1234567890, "ending_balance": 0, "forgiven": false, "id": "in_19zuuiDSlTMT26Mk1XitxqCb", "lines": { "data": [ { "amount": 2000, "currency": "usd", "description": null, "discountable": true, "id": "sub_AKa4uss8vCMAZC", "livemode": true, "metadata": { }, "object": "line_item", "period": { "end": 1495403592, "start": 1492811592 }, "plan": { "amount": 2000, "created": 1488566449, "currency": "usd", "id": "gold", "interval": "month", "interval_count": 1, "livemode": false, "metadata": { }, "name": "Gold Special", "object": "plan", "statement_descriptor": null, "trial_period_days": null }, "proration": false, "quantity": 1, "subscription": null, "subscription_item": "si_19zuuiDSlTMT26Mk9HayBPe9", "type": "subscription" } ], "object": "list", "total_count": 1, "url": "/v1/invoices/in_19zuuiDSlTMT26Mk1XitxqCb/lines" }, "livemode": false, "metadata": { }, "next_payment_attempt": 1234567890, "number": "", "object": "invoice", "paid": true, "period_end": 1234567890, "period_start": 1234567890, "receipt_number": "", "send_next_at": 1234567890, "starting_balance": 0, "statement_descriptor": "", "subscription": "", "subscription_proration_date": 0, "subtotal": 0, "tax": 0, "tax_percent": 0.0, "total": 6999, "webhooks_delivered_at": 1234567890 } ================================================ FILE: test/javascript_helper_spec.rb ================================================ require 'spec_helper' describe Stripe::JavascriptHelper do before { Rails.application.config.stripe.publishable_key = 'pub_xxxx' } let(:controller) { ActionView::TestCase::TestController.new } let(:view) { controller.view_context } describe '#stripe_javascript_tag' do describe 'when no options are passed' do it 'should default to v3' do _(view.stripe_javascript_tag).must_include 'https://js.stripe.com/v3/' end end describe 'when the v2 option is passed' do it 'should default to v2' do _(view.stripe_javascript_tag(:v2)).must_include 'https://js.stripe.com/v2/' end end describe 'when the debug flag is enabled' do before { Rails.application.config.stripe.debug_js = true } after { Rails.application.config.stripe.debug_js = false } it 'should render the debug js' do _(view.stripe_javascript_tag(:v1)).must_include 'https://js.stripe.com/v1/stripe-debug.js' end describe 'when v3 is selected' do it 'should not render debug js' do _(view.stripe_javascript_tag(:v3)).wont_include 'https://js.stripe.com/v1/stripe-debug.js' end end end end describe "render :partial => 'stripe/js'" do subject { view.render :partial => 'stripe/js' } it 'should render correctly' do _(subject).must_include 'https://js.stripe.com/v3/' end end describe "render :partial => 'stripe/js', local: {stripe_js_version: 'v2'}" do subject { view.render :partial => 'stripe/js', locals: {stripe_js_version: 'v2'} } it 'should render correctly' do _(subject).must_include 'https://js.stripe.com/v2/' end end describe '#stripe_elements_tag' do describe 'when no options are passed' do it 'should display the form' do _(view.stripe_elements_tag( submit_path: '/charge', )).must_include 'Credit or debit card' end end describe 'with options' do describe 'without default js' do it 'wont include the default script tag' do _(view.stripe_elements_tag( submit_path: '/charge', js_path: 'another/path' )).wont_include '