├── spec ├── fixtures │ ├── credit_success.xml │ ├── invalid_update.xml │ ├── error_string.txt │ ├── free_plan_not_set.xml │ ├── invalid_subscriber.xml │ ├── free_plan_not_elligable.xml │ ├── free_plan_not_free.xml │ ├── subscriber_not_found.xml │ ├── existing_subscriber.xml │ ├── payment_already_paid.xml │ ├── payment_invalid.xml │ ├── no_plans.xml │ ├── no_transactions.xml │ ├── payment_not_found.xml │ ├── invoice_invalid.xml │ ├── no_subscribers.xml │ ├── plan_not_found.xml │ ├── complimentary_failed_active.xml │ ├── complimentary_failed_inactive.xml │ ├── plan_disabled.xml │ ├── error.xml │ ├── feature_level_blank.xml │ ├── complimentary_not_valid.xml │ ├── errors.xml │ ├── credit_not_valid.xml │ ├── create_subscriber.xml │ ├── free_plan_success.xml │ ├── complimentary_success.xml │ ├── lifetime_subscription_success.xml │ ├── invoice_created.xml │ ├── payment_success.xml │ ├── transactions.xml │ ├── subscribers.xml │ ├── subscription_plan_list.xml │ └── subscriber.xml ├── spec_helper.rb ├── transaction_spec.rb ├── subscription_plan_spec.rb ├── base_spec.rb ├── config_spec.rb ├── invoice_spec.rb └── subscriber_spec.rb ├── .gitignore ├── .document ├── lib ├── rspreedly │ ├── credit.rb │ ├── line_item.rb │ ├── lifetime_complimentary_subscription.rb │ ├── complimentary_time_extension.rb │ ├── complimentary_subscription.rb │ ├── payment_method.rb │ ├── error.rb │ ├── transaction.rb │ ├── subscription_plan.rb │ ├── invoice.rb │ ├── config.rb │ ├── base.rb │ └── subscriber.rb └── rspreedly.rb ├── LICENSE ├── Rakefile ├── README.rdoc └── rspreedly.gemspec /spec/fixtures/credit_success.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | .DS_Store 3 | coverage 4 | rdoc 5 | pkg 6 | .idea -------------------------------------------------------------------------------- /spec/fixtures/invalid_update.xml: -------------------------------------------------------------------------------- 1 | You must specify subscriber parameters -------------------------------------------------------------------------------- /spec/fixtures/error_string.txt: -------------------------------------------------------------------------------- 1 | Some error back from the response as a string -------------------------------------------------------------------------------- /spec/fixtures/free_plan_not_set.xml: -------------------------------------------------------------------------------- 1 | You must specify the subscription plan id -------------------------------------------------------------------------------- /spec/fixtures/invalid_subscriber.xml: -------------------------------------------------------------------------------- 1 | undefined method `delete' for nil:NilClass -------------------------------------------------------------------------------- /spec/fixtures/free_plan_not_elligable.xml: -------------------------------------------------------------------------------- 1 | The subscriber is not eligible for a free trial -------------------------------------------------------------------------------- /spec/fixtures/free_plan_not_free.xml: -------------------------------------------------------------------------------- 1 | The subscription plan must be a free trial plan. -------------------------------------------------------------------------------- /spec/fixtures/subscriber_not_found.xml: -------------------------------------------------------------------------------- 1 | There is no subscriber with a customer id of 401 -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /spec/fixtures/existing_subscriber.xml: -------------------------------------------------------------------------------- 1 | A subscriber with a customer-id of 8889 already exists. -------------------------------------------------------------------------------- /spec/fixtures/payment_already_paid.xml: -------------------------------------------------------------------------------- 1 | Unable to pay an invoice which has already been closed. -------------------------------------------------------------------------------- /spec/fixtures/payment_invalid.xml: -------------------------------------------------------------------------------- 1 | You must specify credit card parameters for the payment. -------------------------------------------------------------------------------- /spec/fixtures/no_plans.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /spec/fixtures/no_transactions.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /spec/fixtures/payment_not_found.xml: -------------------------------------------------------------------------------- 1 | Unable to find invoice "5b1af186651dd988865c6573921ec87fa4bec23b8" -------------------------------------------------------------------------------- /spec/fixtures/invoice_invalid.xml: -------------------------------------------------------------------------------- 1 | You must specify both the subscription plan id and subscriber information. -------------------------------------------------------------------------------- /spec/fixtures/no_subscribers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /spec/fixtures/plan_not_found.xml: -------------------------------------------------------------------------------- 1 | There is no subscription plan with an ID of 9 for the site you're accessing -------------------------------------------------------------------------------- /spec/fixtures/complimentary_failed_active.xml: -------------------------------------------------------------------------------- 1 | Complimentary subscriptions cannot be given to active subscribers. -------------------------------------------------------------------------------- /lib/rspreedly/credit.rb: -------------------------------------------------------------------------------- 1 | module RSpreedly 2 | class Credit < Base 3 | attr_accessor :amount 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/complimentary_failed_inactive.xml: -------------------------------------------------------------------------------- 1 | Complimentary subscriptions cannot be given to inactive subscribers. -------------------------------------------------------------------------------- /spec/fixtures/plan_disabled.xml: -------------------------------------------------------------------------------- 1 | The subscription plan is temporarily unavailable for purchase. Please try again later. -------------------------------------------------------------------------------- /spec/fixtures/error.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Email can't be blank 4 | 5 | -------------------------------------------------------------------------------- /spec/fixtures/feature_level_blank.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Feature level can't be blank 4 | 5 | -------------------------------------------------------------------------------- /lib/rspreedly/line_item.rb: -------------------------------------------------------------------------------- 1 | module RSpreedly 2 | class LineItem < Base 3 | attr_accessor :price, :notes, :amount, :description, :currency_code 4 | end 5 | end -------------------------------------------------------------------------------- /spec/fixtures/complimentary_not_valid.xml: -------------------------------------------------------------------------------- 1 | Duration units must be either 'days' or 'months' 2 | Duration quantity can't be blank 3 | Duration quantity is not a number -------------------------------------------------------------------------------- /lib/rspreedly/lifetime_complimentary_subscription.rb: -------------------------------------------------------------------------------- 1 | module RSpreedly 2 | class LifetimeComplimentarySubscription < Base 3 | attr_accessor :feature_level 4 | end 5 | end -------------------------------------------------------------------------------- /lib/rspreedly/complimentary_time_extension.rb: -------------------------------------------------------------------------------- 1 | module RSpreedly 2 | class ComplimentaryTimeExtension < Base 3 | attr_accessor :duration_quantity, :duration_units 4 | end 5 | end -------------------------------------------------------------------------------- /spec/fixtures/errors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Email can't be blank 4 | Name can't be blank 5 | 6 | -------------------------------------------------------------------------------- /lib/rspreedly/complimentary_subscription.rb: -------------------------------------------------------------------------------- 1 | module RSpreedly 2 | class ComplimentarySubscription < Base 3 | attr_accessor :duration_quantity, :duration_units, :feature_level 4 | end 5 | end -------------------------------------------------------------------------------- /spec/fixtures/credit_not_valid.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Amount is invalid. The total store credit for a subscriber must be greater than or equal to 0. 4 | 5 | -------------------------------------------------------------------------------- /lib/rspreedly/payment_method.rb: -------------------------------------------------------------------------------- 1 | module RSpreedly 2 | module PaymentMethod 3 | class CreditCard < Base 4 | attr_accessor :number, :verification_value, :month, 5 | :year, :first_name, :last_name, :card_type, 6 | :address1, :address2, :city, :state, :zip, :country, 7 | :phone_number 8 | end 9 | class OnFile < Base 10 | def to_xml(opts=nil) 11 | " on-file " 12 | end 13 | end 14 | end 15 | end -------------------------------------------------------------------------------- /lib/rspreedly/error.rb: -------------------------------------------------------------------------------- 1 | module RSpreedly 2 | module Error 3 | 4 | class Base < StandardError; 5 | attr_reader :response 6 | 7 | def initialize(response=nil) 8 | @response = response 9 | end 10 | end 11 | 12 | class AccessDenied < Base; end # 401 errors 13 | class Forbidden < Base; end # 403 errors 14 | class BadRequest < Base; end # 422 errors 15 | class NotFound < Base; end # 404 errors 16 | class GatewayTimeout < Base; end # 504 errors 17 | class ConnectionFailed< Base; end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 2 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 3 | 4 | require 'rubygems' 5 | require 'rspec' 6 | require 'webmock/rspec' 7 | require 'rspreedly' 8 | 9 | RSpec.configure do |config| 10 | config.before(:each) do 11 | RSpreedly::Config.reset 12 | end 13 | end 14 | 15 | def spreedly_url(path) 16 | "https://your-api-key:X@spreedly.com/api/v4/your-site-name#{path}" 17 | end 18 | 19 | def fixture(file) 20 | response_file = File.join(File.dirname(__FILE__), 'fixtures', file) 21 | File.read(response_file) 22 | end -------------------------------------------------------------------------------- /lib/rspreedly.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'httparty' 3 | 4 | require 'rspreedly/error' 5 | require 'rspreedly/config' 6 | require 'rspreedly/base' 7 | require 'rspreedly/credit' 8 | require 'rspreedly/invoice' 9 | require 'rspreedly/line_item' 10 | require 'rspreedly/subscriber' 11 | require 'rspreedly/subscription_plan' 12 | require 'rspreedly/payment_method' 13 | require 'rspreedly/complimentary_subscription' 14 | require 'rspreedly/complimentary_time_extension' 15 | require 'rspreedly/lifetime_complimentary_subscription' 16 | require 'rspreedly/transaction' 17 | 18 | module RSpreedly 19 | 20 | # a few utility methods 21 | 22 | # pretty much stolen from Rails 23 | def self.underscore(camel_cased_word) 24 | camel_cased_word.to_s.gsub(/\w+::/, ''). 25 | gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). 26 | gsub(/([a-z\d])([A-Z])/,'\1_\2'). 27 | tr("-", "_"). 28 | downcase 29 | end 30 | 31 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Richard Livsey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/rspreedly/transaction.rb: -------------------------------------------------------------------------------- 1 | module RSpreedly 2 | 3 | class Transaction < Base 4 | 5 | attr_accessor :active, 6 | :amount, 7 | :created_at, 8 | :currency_code, 9 | :description, 10 | :detail_type, 11 | :expires, 12 | :id, 13 | :invoice_id, 14 | :start_time, 15 | :terms, 16 | :updated_at, 17 | :price, 18 | :subscriber_customer_id, 19 | :detail 20 | 21 | class << self 22 | 23 | # Get a list of 50 transactions 24 | # Passing :since => id will get the 50 transactions since that one 25 | # GET /api/v4/[short site name]/transactions.xml 26 | def all(opts={}) 27 | query_opts = {} 28 | if opts[:since] 29 | query_opts[:query] = {:since_id => opts[:since]} 30 | end 31 | 32 | response = api_request(:get, "/transactions.xml", query_opts) 33 | return [] unless response.has_key?("transactions") 34 | response["transactions"].collect{|data| Transaction.new(data)} 35 | end 36 | 37 | end 38 | end 39 | end -------------------------------------------------------------------------------- /spec/transaction_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | describe RSpreedly::Transaction do 3 | 4 | describe ".all" do 5 | it "should return an empty array if there are no transactions" do 6 | stub_request(:get, spreedly_url("/transactions.xml")). 7 | to_return(:body => fixture("no_transactions.xml"), :status => 200) 8 | 9 | RSpreedly::Transaction.all.should == [] 10 | end 11 | 12 | it "should return an array of transactions if there are any to find" do 13 | stub_request(:get, spreedly_url("/transactions.xml")). 14 | to_return(:body => fixture("transactions.xml"), :status => 200) 15 | 16 | RSpreedly::Transaction.all.size.should == 3 # there are 3 in the fixture 17 | RSpreedly::Transaction.all.select{|x| x.is_a?(RSpreedly::Transaction )}.size.should == 3 18 | end 19 | 20 | it "should allow specifying the ID of the transaction to start (since_id)" do 21 | stub_request(:get, spreedly_url("/transactions.xml?since_id=123")). 22 | to_return(:body => fixture("transactions.xml"), :status => 200) 23 | 24 | RSpreedly::Transaction.all(:since => 123) 25 | WebMock.should have_requested(:get, spreedly_url("/transactions.xml?since_id=123")) 26 | end 27 | end 28 | end -------------------------------------------------------------------------------- /spec/fixtures/create_subscriber.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 2009-10-28T22:16:20Z 7 | 8889 8 | true 9 | 10 | 11 | false 12 | false 13 | freddy 14 | 0.0 15 | USD 16 | 6af9994a57e420345897b1abb4c27a9db27fa4d0 17 | 2009-10-28T22:16:20Z 18 | false 19 | false 20 | 21 | false 22 | 23 | false 24 | 25 | 26 | -------------------------------------------------------------------------------- /spec/fixtures/free_plan_success.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 2009-10-28T22:27:38Z 7 | 42 8 | true 9 | new@email.com 10 | 2013-05-02T00:07:37Z 11 | false 12 | false 13 | bob 14 | 0.0 15 | USD 16 | 81ba778a5849f461ebc0d77e78b9221af2210c6b 17 | 2009-10-28T22:55:37Z 18 | false 19 | false 20 | 21 | false 22 | 23 | true 24 | 25 | 26 | -------------------------------------------------------------------------------- /spec/fixtures/complimentary_success.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2010-02-21T19:04:28Z 4 | 5 | 6 | 2009-10-28T22:27:38Z 7 | 42 8 | true 9 | new@email.com 10 | 2013-05-02T00:07:37Z 11 | false 12 | false 13 | bob 14 | 0.0 15 | USD 16 | 81ba778a5849f461ebc0d77e78b9221af2210c6b 17 | 2009-10-28T22:55:37Z 18 | false 19 | false 20 | 21 | false 22 | 23 | false 24 | Pro 25 | 26 | -------------------------------------------------------------------------------- /spec/fixtures/lifetime_subscription_success.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2010-02-21T19:04:28Z 4 | 5 | 6 | 2009-10-28T22:27:38Z 7 | 42 8 | true 9 | new@email.com 10 | 2013-05-02T00:07:37Z 11 | true 12 | false 13 | bob 14 | 0.0 15 | USD 16 | 81ba778a5849f461ebc0d77e78b9221af2210c6b 17 | 2009-10-28T22:55:37Z 18 | false 19 | false 20 | 21 | false 22 | 23 | false 24 | Something 25 | 26 | -------------------------------------------------------------------------------- /lib/rspreedly/subscription_plan.rb: -------------------------------------------------------------------------------- 1 | module RSpreedly 2 | 3 | class SubscriptionPlan < Base 4 | 5 | attr_accessor :amount, 6 | :charge_after_first_period, 7 | :charge_later_duration_quantity, 8 | :charge_later_duration_units, 9 | :created_at, 10 | :currency_code, 11 | :description, 12 | :duration_quantity, 13 | :duration_units, 14 | :enabled, 15 | :feature_level, 16 | :force_recurring, 17 | :id, 18 | :name, 19 | :needs_to_be_renewed, 20 | :plan_type, 21 | :price, 22 | :return_url, 23 | :terms, 24 | :updated_at 25 | 26 | # there's no API method for just getting one plan, so we fake it! 27 | def self.find(id) 28 | return all if id == :all 29 | all.find{|plan| plan.id == id.to_i} 30 | end 31 | 32 | # Get a list of all subscription plans (more) 33 | # GET /api/v4/[short site name]/subscription_plans.xml 34 | def self.all 35 | response = api_request(:get, "/subscription_plans.xml") 36 | return [] unless response.has_key?("subscription_plans") 37 | response["subscription_plans"].collect{|data| SubscriptionPlan.new(data)} 38 | end 39 | 40 | # Get a list of all active subscription plans (more) 41 | # GET /api/v4/[short site name]/subscription_plans.xml 42 | def self.active 43 | all.reject { |plan| !plan.enabled } 44 | end 45 | 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/rspreedly/invoice.rb: -------------------------------------------------------------------------------- 1 | module RSpreedly 2 | 3 | class Invoice < Base 4 | 5 | attr_accessor :subscription_plan_id, 6 | :subscriber, 7 | :closed, 8 | :created_at, 9 | :token, 10 | :updated_at, 11 | :price, 12 | :amount, 13 | :currency_code, 14 | :line_items 15 | 16 | # Create an Invoice (more) 17 | # POST /api/v4/[short site name]/invoices.xml 18 | def create 19 | begin 20 | create! 21 | rescue RSpreedly::Error::Base 22 | nil 23 | end 24 | end 25 | 26 | def create! 27 | result = api_request(:post, "/invoices.xml", :body => self.to_xml) 28 | self.attributes = result["invoice"] 29 | true 30 | end 31 | 32 | alias_method :save, :create 33 | alias_method :save!, :create! 34 | 35 | def subscriber=(data) 36 | if data.is_a? Hash 37 | data = RSpreedly::Subscriber.new(data) 38 | end 39 | @subscriber = data 40 | end 41 | 42 | def line_items=(data) 43 | @line_items = [] 44 | data.each do |item| 45 | if item.is_a? Hash 46 | item = RSpreedly::LineItem.new(item) 47 | end 48 | @line_items << item 49 | end 50 | end 51 | 52 | # Pay an Invoice (more) 53 | # PUT /api/v4/[short site name]/invoices/[invoice token]/pay.xml 54 | def pay(payment) 55 | begin 56 | pay!(payment) 57 | rescue RSpreedly::Error::Base 58 | nil 59 | end 60 | end 61 | 62 | def pay!(payment) 63 | result = api_request(:put, "/invoices/#{self.token}/pay.xml", :body => payment.to_xml(:outer => 'payment')) 64 | self.attributes = result["invoice"] 65 | true 66 | end 67 | 68 | end 69 | end -------------------------------------------------------------------------------- /spec/fixtures/invoice_created.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | 2009-10-29T02:04:10Z 5 | d06df8274ba261c203f62f7a64c2b18d6dff588e 6 | 2009-10-29T02:04:10Z 7 | $0.00 8 | 0.0 9 | USD 10 | 11 | 12 | 13 | 14 | 2009-10-29T02:04:10Z 15 | 44 16 | true 17 | joe@example.com 18 | 19 | false 20 | false 21 | joe 22 | 0.0 23 | USD 24 | 784d2d4199d6de062a27c2b84f0ad71aad1e14f1 25 | 2009-10-29T02:04:10Z 26 | false 27 | false 28 | 29 | false 30 | 31 | false 32 | 33 | 34 | 35 | 36 | 0.0 37 | USD 38 | Initial 1 month 39 | Subsequent Time Periods are $19.00 every 1 month 40 | $0.00 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /spec/fixtures/payment_success.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 2009-10-29T02:34:14Z 5 | 5b1f186651dd988865c6573921ec87fa4bec23b8 6 | 2009-10-29T02:42:47Z 7 | $0.00 8 | 0.0 9 | USD 10 | 11 | 2009-11-29T02:42:47Z 12 | Joe 13 | Bob 14 | 2009-10-29T02:34:14Z 15 | 400 16 | false 17 | test@example.com 18 | 2009-12-02T02:42:47Z 19 | false 20 | false 21 | bob 22 | 0.0 23 | USD 24 | c385a23a76d85400371d82cb24f383759cb28721 25 | 2009-10-29T02:42:47Z 26 | true 27 | false 28 | Personal 29 | true 30 | false 31 | false 32 | personal 33 | 34 | 35 | 36 | 0.0 37 | USD 38 | Initial 1 month 39 | Subsequent Time Periods are $19.00 every 1 month 40 | $0.00 41 | 42 | 43 | -------------------------------------------------------------------------------- /lib/rspreedly/config.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | module RSpreedly 3 | module Config 4 | class << self 5 | 6 | # the configuration hash itself 7 | def configuration 8 | @configuration ||= defaults 9 | end 10 | 11 | def defaults 12 | { 13 | :logger => defined?(RAILS_DEFAULT_LOGGER) ? RAILS_DEFAULT_LOGGER : Logger.new(STDOUT), 14 | :debug => false, 15 | :site_name => "your-site-name", 16 | :api_key => "your-api-key" 17 | } 18 | end 19 | 20 | def [](key) 21 | configuration[key] 22 | end 23 | 24 | def []=(key, val) 25 | configuration[key] = val 26 | end 27 | 28 | # remove an item from the configuration 29 | def delete(key) 30 | configuration.delete(key) 31 | end 32 | 33 | # Return the value of the key, or the default if doesn't exist 34 | # 35 | # ==== Examples 36 | # 37 | # RSpreedly::Config.fetch(:monkey, false) 38 | # => false 39 | # 40 | def fetch(key, default) 41 | configuration.fetch(key, default) 42 | end 43 | 44 | def to_hash 45 | configuration 46 | end 47 | 48 | # Yields the configuration. 49 | # 50 | # ==== Examples 51 | # RSpreedly::Config.use do |config| 52 | # config[:debug] = true 53 | # config.something = false 54 | # end 55 | # 56 | def setup 57 | yield self 58 | nil 59 | end 60 | 61 | def clear 62 | @configuration = {} 63 | end 64 | 65 | def reset 66 | @configuration = defaults 67 | end 68 | 69 | # allow getting and setting properties via RSpreedly::Config.xxx 70 | # 71 | # ==== Examples 72 | # RSpreedly::Config.debug 73 | # RSpreedly::Config.debug = false 74 | # 75 | def method_missing(method, *args) 76 | if method.to_s[-1,1] == '=' 77 | # splat with 1 item is just the item in 1.8, but an array in 1.9 78 | configuration[method.to_s.tr('=','').to_sym] = args.is_a?(Array) ? args.first : args 79 | else 80 | configuration[method] 81 | end 82 | end 83 | 84 | end 85 | end 86 | end -------------------------------------------------------------------------------- /spec/subscription_plan_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | describe RSpreedly::SubscriptionPlan do 4 | 5 | describe ".find" do 6 | describe "with the symbol :all" do 7 | it "should pass to RSpreedly::SubscriptionPlan.all" do 8 | RSpreedly::SubscriptionPlan.should_receive(:all) 9 | RSpreedly::SubscriptionPlan.find(:all) 10 | end 11 | end 12 | 13 | it "should return nil with an id for a plan which doesn't exist" do 14 | stub_request(:get, spreedly_url("/subscription_plans.xml")). 15 | to_return(:body => fixture("subscription_plan_list.xml"), :status => 200) 16 | 17 | RSpreedly::SubscriptionPlan.find(99).should be_nil 18 | end 19 | 20 | it "should return a SubscriptionPlan with an id for an existing plan" do 21 | stub_request(:get, spreedly_url("/subscription_plans.xml")). 22 | to_return(:body => fixture("subscription_plan_list.xml"), :status => 200) 23 | 24 | RSpreedly::SubscriptionPlan.find(42).should be_a(RSpreedly::SubscriptionPlan) 25 | end 26 | end 27 | 28 | describe ".all" do 29 | it "should return an empty array if there are no plans" do 30 | stub_request(:get, spreedly_url("/subscription_plans.xml")). 31 | to_return(:body => fixture("no_plans.xml"), :status => 200) 32 | 33 | RSpreedly::SubscriptionPlan.all.should == [] 34 | end 35 | 36 | it "should return an array of SubscriptionPlans if there are any to find" do 37 | stub_request(:get, spreedly_url("/subscription_plans.xml")). 38 | to_return(:body => fixture("subscription_plan_list.xml"), :status => 200) 39 | 40 | RSpreedly::SubscriptionPlan.all.size.should == 4 # there are 4 in the fixture 41 | RSpreedly::SubscriptionPlan.all.select{|x| x.is_a?(RSpreedly::SubscriptionPlan )}.size.should == 4 42 | end 43 | end 44 | 45 | describe ".active" do 46 | it "should return an empty array if there are no plans at all" do 47 | stub_request(:get, spreedly_url("/subscription_plans.xml")). 48 | to_return(:body => fixture("no_plans.xml"), :status => 200) 49 | RSpreedly::SubscriptionPlan.active.should == [] 50 | end 51 | 52 | it "should return an array of active SubscriptionPlans if there are any to find" do 53 | stub_request(:get, spreedly_url("/subscription_plans.xml")). 54 | to_return(:body => fixture("subscription_plan_list.xml"), :status => 200) 55 | 56 | RSpreedly::SubscriptionPlan.active.size.should == 2 57 | RSpreedly::SubscriptionPlan.active.select{|x| x.is_a?(RSpreedly::SubscriptionPlan )}.size.should == 2 58 | end 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "rubygems/package_task" 3 | require "rdoc/task" 4 | 5 | require "rspec" 6 | require "rspec/core/rake_task" 7 | RSpec::Core::RakeTask.new do |t| 8 | t.rspec_opts = %w(--format documentation --colour) 9 | end 10 | 11 | 12 | task :default => ["spec"] 13 | 14 | # This builds the actual gem. For details of what all these options 15 | # mean, and other ones you can add, check the documentation here: 16 | # 17 | # http://rubygems.org/read/chapter/20 18 | # 19 | spec = Gem::Specification.new do |s| 20 | 21 | # Change these as appropriate 22 | s.name = "rspreedly" 23 | s.version = "0.1.17" 24 | s.summary = "Ruby library for the Spreedly API" 25 | s.author = "Richard Livsey" 26 | s.email = "richard@livsey.org" 27 | s.homepage = "http://github.com/rlivsey/rspreedly" 28 | 29 | s.has_rdoc = true 30 | s.extra_rdoc_files = %w(README.rdoc) 31 | s.rdoc_options = %w(--main README.rdoc) 32 | 33 | # Add any extra files to include in the gem 34 | s.files = %w(LICENSE Rakefile README.rdoc rspreedly.gemspec) + Dir.glob("{spec,lib}/**/*") 35 | s.require_paths = ["lib"] 36 | 37 | # If you want to depend on other gems, add them here, along with any 38 | # relevant versions 39 | s.add_dependency("httparty", ">= 0.5.0") 40 | 41 | # If your tests use any gems, include them here 42 | s.add_development_dependency("rspec") 43 | s.add_development_dependency("webmock") 44 | end 45 | 46 | # This task actually builds the gem. We also regenerate a static 47 | # .gemspec file, which is useful if something (i.e. GitHub) will 48 | # be automatically building a gem for this project. If you're not 49 | # using GitHub, edit as appropriate. 50 | # 51 | # To publish your gem online, install the 'gemcutter' gem; Read more 52 | # about that here: http://gemcutter.org/pages/gem_docs 53 | Gem::PackageTask.new(spec) do |pkg| 54 | pkg.gem_spec = spec 55 | end 56 | 57 | desc "Build the gemspec file #{spec.name}.gemspec" 58 | task :gemspec do 59 | file = File.dirname(__FILE__) + "/#{spec.name}.gemspec" 60 | File.open(file, "w") {|f| f << spec.to_ruby } 61 | end 62 | 63 | # If you don't want to generate the .gemspec file, just remove this line. Reasons 64 | # why you might want to generate a gemspec: 65 | # - using bundler with a git source 66 | # - building the gem without rake (i.e. gem build blah.gemspec) 67 | # - maybe others? 68 | task :package => :gemspec 69 | 70 | # Generate documentation 71 | RDoc::Task.new do |rd| 72 | rd.main = "README.rdoc" 73 | rd.rdoc_files.include("README.rdoc", "lib/**/*.rb") 74 | rd.rdoc_dir = "rdoc" 75 | end 76 | 77 | desc 'Clear out RDoc and generated packages' 78 | task :clean => [:clobber_rdoc, :clobber_package] do 79 | rm "#{spec.name}.gemspec" 80 | end 81 | -------------------------------------------------------------------------------- /spec/fixtures/transactions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24.0 5 | 2009-09-26T03:06:30Z 6 | USD 7 | Subscription 8 | Subscription 9 | 2009-12-26T04:06:30Z 10 | 20 11 | 64 12 | 2009-09-26T03:06:30Z 13 | 3 months 14 | 2009-09-26T03:06:30Z 15 | $24.00 16 | 39053 17 | 18 | visa 19 | false 20 | example 21 | 22 | 23 | 24 | 24.0 25 | 2009-09-26T03:06:30Z 26 | USD 27 | Subscription 28 | Subscription 29 | 2009-12-26T04:06:30Z 30 | 20 31 | 64 32 | 2009-09-26T03:06:30Z 33 | 3 months 34 | 2009-09-26T03:06:30Z 35 | $24.00 36 | 39053 37 | 38 | visa 39 | false 40 | example 41 | 42 | 43 | 44 | 24.0 45 | 2009-09-26T03:06:30Z 46 | USD 47 | Subscription 48 | Subscription 49 | 2009-12-26T04:06:30Z 50 | 20 51 | 64 52 | 2009-09-26T03:06:30Z 53 | 3 months 54 | 2009-09-26T03:06:30Z 55 | $24.00 56 | 39053 57 | 58 | visa 59 | false 60 | example 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = RSpreedly 2 | 3 | Ruby library to access the Spreedly API (all of it). 4 | 5 | == Why another one? 6 | 7 | There are a few of these knocking about, but I couldn't find one particular to my needs: 8 | 9 | * Well covered with specs - currently 100% coverage 10 | * Covers the entire API, not just parts of it 11 | * Rubyish/ActiveRecord style access 12 | 13 | == Installation 14 | 15 | It's on Gemcutter, so as long as you're setup to use that: 16 | 17 | gem install rspreedly 18 | 19 | Otherwise 20 | 21 | gem install gemcutter 22 | gem tumble 23 | gem install rspreedly 24 | 25 | == Usage 26 | 27 | Configure your API key and site name for your account: 28 | 29 | RSpreedly::Config.setup do |config| 30 | config.api_key = "your-api-key" 31 | config.site_name = "your-site-name" 32 | end 33 | 34 | List subscribers 35 | 36 | RSpreedly::Subscriber.find(:all).each do |subscriber| 37 | # do something 38 | end 39 | 40 | CRUD access to subscribers 41 | 42 | sub = RSpreedly::Subscriber.new(:customer_id => 42, :email => "somewhere@example.com") 43 | sub.token => nil 44 | sub.save 45 | sub.token => "6af9994a57e420345897b1abb4c27a9db27fa4d0" 46 | 47 | sub = RSpreedly::Subscriber.find(42) 48 | sub.email = "new-email@example.com" 49 | sub.save 50 | 51 | sub = RSpreedly::Subscriber.find(69) 52 | sub.destroy 53 | 54 | Comp subscriptions 55 | 56 | comp = RSpreedly::ComplimentarySubscription.new(:duration_quantity => 3, :duration_units => "months", :feature_level => "Pro") 57 | sub = RSpreedly::Subscriber.find(69) 58 | sub.comp_subscription(comp) 59 | 60 | View all plans 61 | 62 | plans = RSpreedly::SubscriptionPlan.find(:all) 63 | 64 | Raise an invoice, and pay it 65 | 66 | invoice = RSpreedly::Invoice.new(:subscription_plan_id => 5, :subscriber => sub) 67 | invoice.save 68 | 69 | payment = RSpreedly::PaymentMethod::CreditCard.new(:card_type => 'visa', :number => '123456', ...) 70 | invoice.pay(payment) 71 | 72 | Error messages 73 | 74 | invoice = RSpreedly::Invoice.new 75 | invoice.save => false 76 | invoice.errors => ["You must specify both the subscription plan id and subscriber information."] 77 | 78 | See the specs and API docs at Spreedly for more details and the rest of the things it can do. 79 | 80 | https://www.spreedly.com/manual/integration-reference/url-reference/ 81 | 82 | == Todo 83 | 84 | * Better specs for data we send to Spreedly. 85 | * Better docs. 86 | 87 | == Thanks 88 | 89 | I studied the other Spreedly APIs and there may well be a line or three this library has in common with: 90 | 91 | * http://github.com/terralien/spreedly-gem 92 | * http://github.com/erbmicha/spreedly/ 93 | 94 | == Copyright 95 | 96 | Copyright (c) 2009 Richard Livsey. See LICENSE for details. 97 | -------------------------------------------------------------------------------- /spec/base_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | class TestAPI < RSpreedly::Base 4 | attr_accessor :something 5 | end 6 | 7 | describe RSpreedly::Base do 8 | 9 | describe ".api_request" do 10 | it "should raise AccessDenied if a 401 is received" do 11 | stub_request(:put, spreedly_url("/")).to_return(:status => 401) 12 | 13 | lambda{ 14 | RSpreedly::Base.api_request(:put, '/') 15 | }.should raise_error(RSpreedly::Error::AccessDenied) 16 | end 17 | end 18 | 19 | describe "attributes=" do 20 | before(:each) do 21 | @api = TestAPI.new 22 | end 23 | 24 | it "should assign attributes if they exist" do 25 | @api.attributes = {:something => "test"} 26 | @api.something.should == "test" 27 | end 28 | 29 | it "should not fail if an attribute does not exist" do 30 | lambda{ 31 | @api.attributes = {:foo => true} 32 | }.should_not raise_error 33 | end 34 | end 35 | 36 | describe "error messages" do 37 | 38 | before(:each) do 39 | @api = TestAPI.new 40 | end 41 | 42 | def do_request 43 | @api.api_request(:post, "/") 44 | end 45 | 46 | def failing_request 47 | lambda{ 48 | do_request 49 | }.should raise_error 50 | end 51 | 52 | it "should not set any errors on a successful request" do 53 | stub_request(:post, spreedly_url("/")).to_return(:status => 200) 54 | do_request 55 | @api.errors.should be_empty 56 | end 57 | 58 | it "should set one error in the error array if a string is return " do 59 | stub_request(:post, spreedly_url("/")).to_return(:body => fixture("error_string.txt"), :status => 422) 60 | failing_request 61 | @api.errors.should == ["Some error back from the response as a string"] 62 | end 63 | 64 | it "should set one error in the error array if an xml error with one item is returned" do 65 | stub_request(:post, spreedly_url("/")).to_return(:body => fixture("error.xml"), :status => 422) 66 | failing_request 67 | @api.errors.should == ["Email can't be blank"] 68 | end 69 | 70 | it "should set multiple errors in the error array if an xml error with multiple items is returned" do 71 | stub_request(:post, spreedly_url("/")).to_return(:body => fixture("errors.xml"), :status => 422) 72 | failing_request 73 | @api.errors.should == ["Email can't be blank", "Name can't be blank"] 74 | end 75 | 76 | it "should reset errors on each call" do 77 | # failing one first, which will generate some errors 78 | stub_request(:post, spreedly_url("/")).to_return(:body => fixture("errors.xml"), :status => 422) 79 | failing_request 80 | @api.errors.should_not be_empty 81 | 82 | # now a successful one should clear the errors 83 | stub_request(:post, spreedly_url("/")).to_return(:status => 200) 84 | do_request 85 | @api.errors.should be_empty 86 | end 87 | end 88 | end -------------------------------------------------------------------------------- /rspreedly.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |s| 4 | s.name = %q{rspreedly} 5 | s.version = "0.1.17" 6 | 7 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 8 | s.authors = ["Richard Livsey"] 9 | s.date = %q{2011-08-17} 10 | s.email = %q{richard@livsey.org} 11 | s.extra_rdoc_files = ["README.rdoc"] 12 | s.files = ["LICENSE", "Rakefile", "README.rdoc", "rspreedly.gemspec", "spec/base_spec.rb", "spec/config_spec.rb", "spec/fixtures", "spec/fixtures/complimentary_failed_active.xml", "spec/fixtures/complimentary_failed_inactive.xml", "spec/fixtures/complimentary_not_valid.xml", "spec/fixtures/complimentary_success.xml", "spec/fixtures/create_subscriber.xml", "spec/fixtures/credit_not_valid.xml", "spec/fixtures/credit_success.xml", "spec/fixtures/error.xml", "spec/fixtures/error_string.txt", "spec/fixtures/errors.xml", "spec/fixtures/existing_subscriber.xml", "spec/fixtures/feature_level_blank.xml", "spec/fixtures/free_plan_not_elligable.xml", "spec/fixtures/free_plan_not_free.xml", "spec/fixtures/free_plan_not_set.xml", "spec/fixtures/free_plan_success.xml", "spec/fixtures/invalid_subscriber.xml", "spec/fixtures/invalid_update.xml", "spec/fixtures/invoice_created.xml", "spec/fixtures/invoice_invalid.xml", "spec/fixtures/lifetime_subscription_success.xml", "spec/fixtures/no_plans.xml", "spec/fixtures/no_subscribers.xml", "spec/fixtures/no_transactions.xml", "spec/fixtures/payment_already_paid.xml", "spec/fixtures/payment_invalid.xml", "spec/fixtures/payment_not_found.xml", "spec/fixtures/payment_success.xml", "spec/fixtures/plan_disabled.xml", "spec/fixtures/plan_not_found.xml", "spec/fixtures/subscriber.xml", "spec/fixtures/subscriber_not_found.xml", "spec/fixtures/subscribers.xml", "spec/fixtures/subscription_plan_list.xml", "spec/fixtures/transactions.xml", "spec/invoice_spec.rb", "spec/spec_helper.rb", "spec/subscriber_spec.rb", "spec/subscription_plan_spec.rb", "spec/transaction_spec.rb", "lib/rspreedly", "lib/rspreedly/base.rb", "lib/rspreedly/complimentary_subscription.rb", "lib/rspreedly/complimentary_time_extension.rb", "lib/rspreedly/config.rb", "lib/rspreedly/credit.rb", "lib/rspreedly/error.rb", "lib/rspreedly/invoice.rb", "lib/rspreedly/lifetime_complimentary_subscription.rb", "lib/rspreedly/line_item.rb", "lib/rspreedly/payment_method.rb", "lib/rspreedly/subscriber.rb", "lib/rspreedly/subscription_plan.rb", "lib/rspreedly/transaction.rb", "lib/rspreedly.rb"] 13 | s.homepage = %q{http://github.com/rlivsey/rspreedly} 14 | s.rdoc_options = ["--main", "README.rdoc"] 15 | s.require_paths = ["lib"] 16 | s.rubygems_version = %q{1.6.2} 17 | s.summary = %q{Ruby library for the Spreedly API} 18 | 19 | if s.respond_to? :specification_version then 20 | s.specification_version = 3 21 | 22 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 23 | s.add_runtime_dependency(%q, [">= 0.5.0"]) 24 | s.add_development_dependency(%q, [">= 0"]) 25 | s.add_development_dependency(%q, [">= 0"]) 26 | else 27 | s.add_dependency(%q, [">= 0.5.0"]) 28 | s.add_dependency(%q, [">= 0"]) 29 | s.add_dependency(%q, [">= 0"]) 30 | end 31 | else 32 | s.add_dependency(%q, [">= 0.5.0"]) 33 | s.add_dependency(%q, [">= 0"]) 34 | s.add_dependency(%q, [">= 0"]) 35 | s.add_development_dependency(%q) 36 | s.add_development_dependency(%q) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/rspreedly/base.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | 3 | module RSpreedly 4 | 5 | class Base 6 | include HTTParty 7 | format :xml 8 | base_uri "https://spreedly.com/api/v4" 9 | 10 | attr_reader :errors 11 | 12 | def self.api_request(type, path, options={}) 13 | site_name = RSpreedly::Config.site_name 14 | api_key = RSpreedly::Config.api_key 15 | path = "/#{site_name}#{path}" 16 | 17 | options.merge!({ 18 | :basic_auth => {:username => api_key, :password => 'X'}, 19 | :headers => {"Content-Type" => 'application/xml'} 20 | }) 21 | self.do_request(type, path, options) 22 | end 23 | 24 | def self.do_request(type, path, options) 25 | begin 26 | response = self.send(type.to_s, path, options) 27 | rescue SocketError 28 | raise(RSpreedly::Error::ConnectionFailed.new, "Failed to connect to payment gateway.") 29 | end 30 | 31 | case response.code.to_i 32 | when 401 33 | raise(RSpreedly::Error::AccessDenied.new(response), response.body) 34 | when 403 35 | raise(RSpreedly::Error::Forbidden.new(response), response.body) 36 | when 422 37 | raise(RSpreedly::Error::BadRequest.new(response), response.body) 38 | when 404 39 | raise(RSpreedly::Error::NotFound.new(response), response.body) 40 | when 504 41 | raise(RSpreedly::Error::GatewayTimeout.new(response), response.body) 42 | end 43 | 44 | response 45 | end 46 | 47 | def initialize(attrs={}) 48 | @errors = [] 49 | self.attributes = attrs 50 | end 51 | 52 | def attributes=(attrs) 53 | attrs.each do |k, v| 54 | self.send(:"#{k}=", v) if self.respond_to?(:"#{k}=") 55 | end 56 | end 57 | 58 | def api_request(type, path, options={}) 59 | @errors = [] 60 | begin 61 | self.class.api_request(type, path, options) 62 | rescue RSpreedly::Error::Base => e 63 | if e.response.is_a?(Hash) 64 | if e.response.has_key?("errors") 65 | @errors = [*e.response["errors"]["error"]] 66 | else 67 | @errors = [e.response.body] 68 | end 69 | else 70 | @errors = [e.message] 71 | end 72 | raise 73 | end 74 | end 75 | 76 | # TODO - do this nicer 77 | # rather eew at the moment and hand made XML is not nice 78 | def to_xml(opts={}) 79 | exclude = opts[:exclude] || [] 80 | exclude << :errors 81 | 82 | tag = opts[:tag] || RSpreedly.underscore(self.class.to_s) 83 | inner = opts[:inner] 84 | outer = opts[:outer] 85 | no_tag = opts[:no_tag] 86 | 87 | xml = "" 88 | xml << "<#{outer}>" if outer 89 | xml << "<#{tag}>" unless no_tag 90 | xml << "<#{inner}>" if inner 91 | self.instance_variables.each do |var| 92 | name = var.to_s.gsub('@', '') 93 | next if exclude.include?(name.to_sym) 94 | value = self.instance_variable_get(var) 95 | if value.respond_to?(:to_xml) 96 | value = value.to_xml(:no_tag => (RSpreedly.underscore(value.class.to_s) == name)) 97 | end 98 | xml << "<#{name}>#{value}" 99 | end 100 | xml << "" if inner 101 | xml << "" unless no_tag 102 | xml << "" if outer 103 | xml 104 | end 105 | end 106 | end -------------------------------------------------------------------------------- /spec/config_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | describe RSpreedly::Config do 4 | 5 | before :each do 6 | 7 | RSpreedly::Config.clear 8 | RSpreedly::Config.setup do |c| 9 | c[:one] = 1 10 | c[:two] = 2 11 | end 12 | 13 | end 14 | 15 | describe "logger" do 16 | 17 | before :each do 18 | Object.send(:remove_const, :RAILS_DEFAULT_LOGGER) if defined?(RAILS_DEFAULT_LOGGER) 19 | end 20 | 21 | it "should default to RAILS_DEFAULT_LOGGER if defined" do 22 | RAILS_DEFAULT_LOGGER = "something" 23 | RSpreedly::Config.reset 24 | RSpreedly::Config.logger.should == "something" 25 | end 26 | 27 | it "should default to a Logger if RAILS_DEFAULT_LOGGER is not defined" do 28 | RSpreedly::Config.reset 29 | RSpreedly::Config.logger.should be_a(Logger) 30 | end 31 | 32 | end 33 | 34 | describe "configuration" do 35 | 36 | it "should return the configuration hash" do 37 | RSpreedly::Config.configuration.should == {:one => 1, :two => 2} 38 | end 39 | 40 | end 41 | 42 | describe "[]" do 43 | 44 | it "should return the config option matching the key" do 45 | RSpreedly::Config[:one].should == 1 46 | end 47 | 48 | it "should return nil if the key doesn't exist" do 49 | RSpreedly::Config[:monkey].should be_nil 50 | end 51 | 52 | end 53 | 54 | describe "[]=" do 55 | 56 | it "should set the config option for the key" do 57 | lambda{ 58 | RSpreedly::Config[:banana] = :yellow 59 | }.should change(RSpreedly::Config, :banana).from(nil).to(:yellow) 60 | end 61 | 62 | end 63 | 64 | describe "delete" do 65 | 66 | it "should delete the config option for the key" do 67 | lambda{ 68 | RSpreedly::Config.delete(:one) 69 | }.should change(RSpreedly::Config, :one).from(1).to(nil) 70 | end 71 | 72 | it "should leave the config the same if the key doesn't exist" do 73 | lambda{ 74 | RSpreedly::Config.delete(:test) 75 | }.should_not change(RSpreedly::Config, :configuration) 76 | end 77 | 78 | end 79 | 80 | describe "fetch" do 81 | 82 | it "should return the config option matching the key if it exists" do 83 | RSpreedly::Config.fetch(:one, 100).should == 1 84 | end 85 | 86 | it "should return the config default if the key doesn't exist" do 87 | RSpreedly::Config.fetch(:other, 100).should == 100 88 | end 89 | 90 | end 91 | 92 | describe "to_hash" do 93 | 94 | it "should return a hash of the configuration" do 95 | RSpreedly::Config.to_hash.should == {:one => 1, :two => 2} 96 | end 97 | 98 | end 99 | 100 | describe "setup" do 101 | 102 | it "should yield self" do 103 | RSpreedly::Config.setup do |c| 104 | c.should == RSpreedly::Config 105 | end 106 | end 107 | 108 | it "should let you set items on the configuration object as a hash" do 109 | lambda{ 110 | RSpreedly::Config.setup do |c| 111 | c[:bananas] = 100 112 | end 113 | }.should change(RSpreedly::Config, :bananas).from(nil).to(100) 114 | end 115 | 116 | it "should let you set items on the configuration object as a method" do 117 | lambda{ 118 | RSpreedly::Config.setup do |c| 119 | c.monkeys = 100 120 | end 121 | }.should change(RSpreedly::Config, :monkeys).from(nil).to(100) 122 | end 123 | 124 | end 125 | 126 | describe "calling a missing method" do 127 | 128 | it "should retreive the config if the method matches a key" do 129 | RSpreedly::Config.one.should == 1 130 | end 131 | 132 | it "should retreive nil if the method doesn't match a key" do 133 | RSpreedly::Config.moo.should be_nil 134 | end 135 | 136 | it "should set the value of the config item matching the method name if it's an assignment" do 137 | lambda{ 138 | RSpreedly::Config.trees = 3 139 | }.should change(RSpreedly::Config, :trees).from(nil).to(3) 140 | end 141 | 142 | end 143 | 144 | end 145 | -------------------------------------------------------------------------------- /spec/fixtures/subscribers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 2009-10-28T22:16:20Z 8 | 8889 9 | true 10 | 11 | 12 | false 13 | false 14 | freddy 15 | 0.0 16 | USD 17 | 6af9994a57e420345897b1abb4c27a9db27fa4d0 18 | 2009-10-28T22:16:20Z 19 | false 20 | false 21 | 22 | false 23 | 24 | false 25 | 26 | 27 | 28 | 29 | 30 | 31 | 2009-10-28T22:26:57Z 32 | 1889 33 | true 34 | 35 | 36 | false 37 | false 38 | freddy 39 | 0.0 40 | USD 41 | 9638cba1d938e1bd8e18b8075e077775a0bb9d21 42 | 2009-10-28T22:26:57Z 43 | false 44 | false 45 | 46 | false 47 | 48 | false 49 | 50 | 51 | 52 | 53 | 54 | 55 | 2009-10-28T22:27:38Z 56 | 42 57 | true 58 | new@email.com 59 | 60 | false 61 | false 62 | bob 63 | 0.0 64 | USD 65 | 81ba778a5849f461ebc0d77e78b9221af2210c6b 66 | 2009-10-28T22:55:37Z 67 | false 68 | false 69 | 70 | false 71 | 72 | false 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /spec/fixtures/subscription_plan_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19.0 5 | true 6 | 1 7 | months 8 | 2009-10-26T00:28:41Z 9 | USD 10 | 11 | 1 12 | months 13 | true 14 | personal 15 | true 16 | 42 17 | Personal 18 | true 19 | regular 20 | http://example.com 21 | 2009-10-26T00:28:41Z 22 | 1 month 23 | 19.0 24 | 25 | 26 | 49.0 27 | true 28 | 1 29 | months 30 | 2009-10-26T00:29:07Z 31 | USD 32 | 33 | 1 34 | months 35 | true 36 | team 37 | true 38 | 43 39 | Team 40 | true 41 | regular 42 | http://example.com 43 | 2009-10-26T00:29:07Z 44 | 1 month 45 | 49.0 46 | 47 | 48 | 99.0 49 | true 50 | 1 51 | months 52 | 2009-10-26T00:29:39Z 53 | USD 54 | 55 | 1 56 | months 57 | false 58 | corporate 59 | true 60 | 44 61 | Corporate 62 | true 63 | regular 64 | http://example.com 65 | 2009-10-26T00:29:39Z 66 | 1 month 67 | 99.0 68 | 69 | 70 | 0.0 71 | false 72 | 73 | 74 | 2009-10-29T00:31:15Z 75 | USD 76 | 77 | 0 78 | days 79 | false 80 | free 81 | false 82 | 45 83 | Free Trial 84 | false 85 | free_trial 86 | http://example.com 87 | 2009-10-29T00:31:15Z 88 | Lifetime 89 | 0.0 90 | 91 | -------------------------------------------------------------------------------- /spec/fixtures/subscriber.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 2009-10-28T22:27:38Z 7 | 42 8 | true 9 | new@email.com 10 | 11 | false 12 | false 13 | bob 14 | 0.0 15 | USD 16 | 81ba778a5849f461ebc0d77e78b9221af2210c6b 17 | 2009-10-28T22:55:37Z 18 | false 19 | false 20 | 21 | false 22 | 23 | false 24 | 25 | 26 | 27 | true 28 | 2011-07-21T19:04:45Z 29 | 30 | 31 | 32 | 46089ee32310b4983c6acb0bc02dec955886d411 33 | 2011-07-21T19:05:07Z 34 | $0.05 35 | 0.05 36 | USD 37 | 38 | 39 | 99.0 40 | USD 41 | Every 1 month 42 | 43 | $99.00 44 | 45 | 46 | -48.99 47 | USD 48 | Credit from current subscription 49 | 50 | $-48.99 51 | 52 | 53 | -49.96 54 | USD 55 | Your store credit 56 | 57 | $-49.96 58 | 59 | 60 | 61 | 62 | true 63 | 2011-07-21T18:56:52Z 64 | 65 | 66 | 67 | 4730937c43a57b333b78ba474e6384bfb434bee3 68 | 2011-07-21T18:57:12Z 69 | $-49.96 70 | -49.96 71 | USD 72 | 73 | 74 | 49.0 75 | USD 76 | Every 1 month 77 | 78 | $49.00 79 | 80 | 81 | -98.96 82 | USD 83 | Credit from current subscription 84 | 85 | $-98.96 86 | 87 | 88 | 89 | 90 | true 91 | 2011-07-21T18:39:17Z 92 | 93 | 94 | 95 | 4bf20627d684dbaab231b349b1ed1ae6388f4ffc 96 | 2011-07-21T18:39:32Z 97 | $50.00 98 | 50.0 99 | USD 100 | 101 | 102 | 99.0 103 | USD 104 | Every 1 month 105 | 106 | $99.00 107 | 108 | 109 | -49.0 110 | USD 111 | Credit from current subscription 112 | 113 | $-49.00 114 | 115 | 116 | 117 | 118 | true 119 | 2011-07-21T18:15:11Z 120 | 121 | 122 | 123 | a5190437c9651aa77ac6e39e9833b579bff4658e 124 | 2011-07-21T18:15:36Z 125 | $49.00 126 | 49.0 127 | USD 128 | 129 | 130 | 49.0 131 | USD 132 | Every 1 month 133 | 134 | $49.00 135 | 136 | 137 | 138 | 139 | true 140 | 2011-07-21T18:14:07Z 141 | 142 | 143 | 144 | ea7a657f68908279fdc33c8cf1c7037c4a2eec2d 145 | 2011-07-21T18:14:32Z 146 | $0.00 147 | 0.0 148 | USD 149 | 150 | 151 | 0.0 152 | USD 153 | Initial 1 month 154 | Subsequent Time Periods are $49.00 every 1 month 155 | $0.00 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /spec/invoice_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | describe RSpreedly::Invoice do 4 | 5 | describe "#create!" do 6 | 7 | before(:each) do 8 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42, :screen_name => "bob", :email => "test@example.com") 9 | @invoice = RSpreedly::Invoice.new(:subscriber => @subscriber, :subscription_plan_id => 2502) 10 | end 11 | 12 | it "should return true if successful" do 13 | stub_request(:post, spreedly_url("/invoices.xml")). 14 | to_return(:body => fixture("invoice_created.xml"), :status => 201) 15 | 16 | @invoice.create.should be_true 17 | end 18 | 19 | it "should update the invoice if successful" do 20 | stub_request(:post, spreedly_url("/invoices.xml")). 21 | to_return(:body => fixture("invoice_created.xml"), :status => 201) 22 | 23 | lambda{ 24 | @invoice.create 25 | }.should change(@invoice, :price).from(nil).to("$0.00") 26 | end 27 | 28 | it "should setup line items in the invoice if successful" do 29 | stub_request(:post, spreedly_url("/invoices.xml")). 30 | to_return(:body => fixture("invoice_created.xml"), :status => 201) 31 | 32 | @invoice.create 33 | @invoice.line_items.size.should == 1 34 | @invoice.line_items[0].should be_a(RSpreedly::LineItem) 35 | end 36 | 37 | it "should raise NotFound if the plan doesn't exist" do 38 | stub_request(:post, spreedly_url("/invoices.xml")). 39 | to_return(:body => fixture("plan_not_found.xml"), :status => 404) 40 | 41 | lambda{ 42 | @invoice.create! 43 | }.should raise_error(RSpreedly::Error::NotFound) 44 | end 45 | 46 | it "should raise BadRequest if the invoice is invalid" do 47 | stub_request(:post, spreedly_url("/invoices.xml")). 48 | to_return(:body => fixture("invoice_invalid.xml"), :status => 422) 49 | 50 | lambda{ 51 | @invoice.create! 52 | }.should raise_error(RSpreedly::Error::BadRequest) 53 | end 54 | 55 | it "should raise Forbidden if the plan is disabled" do 56 | stub_request(:post, spreedly_url("/invoices.xml")). 57 | to_return(:body => fixture("plan_disabled.xml"), :status => 403) 58 | 59 | lambda{ 60 | @invoice.create! 61 | }.should raise_error(RSpreedly::Error::Forbidden) 62 | end 63 | end 64 | 65 | describe "#create" do 66 | 67 | before(:each) do 68 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42, :screen_name => "bob", :email => "test@example.com") 69 | @invoice = RSpreedly::Invoice.new(:subscriber => @subscriber, :subscription_plan_id => 2502) 70 | end 71 | 72 | it "should return true if successful" do 73 | stub_request(:post, spreedly_url("/invoices.xml")). 74 | to_return(:body => fixture("invoice_created.xml"), :status => 201) 75 | 76 | @invoice.create.should be_true 77 | end 78 | 79 | it "should return nil if the plan doesn't exist" do 80 | stub_request(:post, spreedly_url("/invoices.xml")). 81 | to_return(:body => fixture("plan_not_found.xml"), :status => 404) 82 | 83 | @invoice.create.should be_nil 84 | end 85 | 86 | it "should return nil if the invoice is invalid" do 87 | stub_request(:post, spreedly_url("/invoices.xml")). 88 | to_return(:body => fixture("invoice_invalid.xml"), :status => 422) 89 | 90 | @invoice.create.should be_nil 91 | end 92 | 93 | it "should return nil if the plan is disabled" do 94 | stub_request(:post, spreedly_url("/invoices.xml")). 95 | to_return(:body => fixture("plan_disabled.xml"), :status => 403) 96 | 97 | @invoice.create.should be_nil 98 | end 99 | end 100 | 101 | 102 | describe "#pay" do 103 | 104 | before(:each) do 105 | @invoice = RSpreedly::Invoice.new(:token => "5b1f186651dd988865c6573921ec87fa4bec23b8") 106 | @payment = RSpreedly::PaymentMethod::CreditCard.new(:number => "4222222222222", 107 | :card_type => "visa", 108 | :verification_value => "234", 109 | :month => 1, 110 | :year => 2011, 111 | :first_name => "Joe", 112 | :last_name => "Bob") 113 | end 114 | 115 | it "should return true if successful" do 116 | stub_request(:put, spreedly_url("/invoices/5b1f186651dd988865c6573921ec87fa4bec23b8/pay.xml")). 117 | to_return(:body => fixture("payment_success.xml"), :status => 200) 118 | 119 | @invoice.pay(@payment).should be_true 120 | end 121 | 122 | it "should return nil if not successful" do 123 | stub_request(:put, spreedly_url("/invoices/5b1f186651dd988865c6573921ec87fa4bec23b8/pay.xml")). 124 | to_return(:body => fixture("payment_not_found.xml"), :status => 404) 125 | 126 | @invoice.pay(@payment).should be_nil 127 | end 128 | 129 | end 130 | 131 | describe "#pay!" do 132 | 133 | before(:each) do 134 | @invoice = RSpreedly::Invoice.new(:token => "5b1f186651dd988865c6573921ec87fa4bec23b8") 135 | @payment = RSpreedly::PaymentMethod::CreditCard.new(:number => "4222222222222", 136 | :card_type => "visa", 137 | :verification_value => "234", 138 | :month => 1, 139 | :year => 2011, 140 | :first_name => "Joe", 141 | :last_name => "Bob") 142 | end 143 | 144 | it "should return true if successful" do 145 | stub_request(:put, spreedly_url("/invoices/5b1f186651dd988865c6573921ec87fa4bec23b8/pay.xml")). 146 | to_return(:body => fixture("payment_success.xml"), :status => 200) 147 | 148 | @invoice.pay!(@payment).should be_true 149 | end 150 | 151 | it "should update the Invoice if successful" do 152 | stub_request(:put, spreedly_url("/invoices/5b1f186651dd988865c6573921ec87fa4bec23b8/pay.xml")). 153 | to_return(:body => fixture("payment_success.xml"), :status => 200) 154 | 155 | lambda{ 156 | @invoice.pay!(@payment) 157 | }.should change(@invoice, :closed).to(true) 158 | end 159 | 160 | it "should raise NotFound if the invoice doesn't exist" do 161 | stub_request(:put, spreedly_url("/invoices/5b1f186651dd988865c6573921ec87fa4bec23b8/pay.xml")). 162 | to_return(:body => fixture("payment_not_found.xml"), :status => 404) 163 | 164 | lambda{ 165 | @invoice.pay!(@payment) 166 | }.should raise_error(RSpreedly::Error::NotFound) 167 | end 168 | 169 | it "should raise GatewayTimeout if the payment gateway times out" do 170 | stub_request(:put, spreedly_url("/invoices/5b1f186651dd988865c6573921ec87fa4bec23b8/pay.xml")). 171 | to_return(:status => 504) 172 | 173 | lambda{ 174 | @invoice.pay!(@payment) 175 | }.should raise_error(RSpreedly::Error::GatewayTimeout) 176 | end 177 | 178 | it "should raise BadRequest if the payment method is invalid" do 179 | stub_request(:put, spreedly_url("/invoices/5b1f186651dd988865c6573921ec87fa4bec23b8/pay.xml")). 180 | to_return(:body => fixture("payment_invalid.xml"), :status => 422) 181 | 182 | lambda{ 183 | @invoice.pay!(@payment) 184 | }.should raise_error(RSpreedly::Error::BadRequest) 185 | end 186 | 187 | it "should raise Forbidden if the invoice is already paid" do 188 | stub_request(:put, spreedly_url("/invoices/5b1f186651dd988865c6573921ec87fa4bec23b8/pay.xml")). 189 | to_return(:body => fixture("payment_already_paid.xml"), :status => 403) 190 | 191 | lambda{ 192 | @invoice.pay!(@payment) 193 | }.should raise_error(RSpreedly::Error::Forbidden) 194 | end 195 | end 196 | end -------------------------------------------------------------------------------- /lib/rspreedly/subscriber.rb: -------------------------------------------------------------------------------- 1 | module RSpreedly 2 | 3 | class Subscriber < Base 4 | 5 | attr_accessor :active, 6 | :active_until, 7 | :billing_first_name, 8 | :billing_last_name, 9 | :card_expires_before_next_auto_renew, 10 | :created_at, 11 | :customer_id, 12 | :eligible_for_free_trial, 13 | :email, 14 | :feature_level, 15 | :grace_until, 16 | :in_grace_period, 17 | :lifetime_subscription, 18 | :new_customer_id, 19 | :on_trial, 20 | :payment_method, 21 | :ready_to_renew, 22 | :ready_to_renew_since, 23 | :recurring, 24 | :screen_name, 25 | :store_credit, 26 | :store_credit_currency_code, 27 | :subscription_plan_name, 28 | :token, 29 | :updated_at, 30 | :invoices 31 | 32 | class << self 33 | 34 | # Get a subscriber’s details 35 | # GET /api/v4/[short site name]/subscribers/[subscriber id].xml 36 | def find(id) 37 | return all if id == :all 38 | 39 | begin 40 | data = api_request(:get, "/subscribers/#{id}.xml") 41 | Subscriber.new(data["subscriber"]) 42 | rescue RSpreedly::Error::NotFound 43 | nil 44 | end 45 | end 46 | 47 | # Get a list of all subscribers (more) 48 | # GET /api/v4/[short site name]/subscribers.xml 49 | def all 50 | response = api_request(:get, "/subscribers.xml") 51 | return [] unless response.has_key?("subscribers") 52 | response["subscribers"].collect{|data| Subscriber.new(data)} 53 | end 54 | 55 | # Find subscriber's by a given attribute and get their details 56 | def find_by_email(email) 57 | subscribers = [] 58 | all.each do |subscriber| 59 | subscribers << subscriber if subscriber.email == email 60 | end 61 | 62 | subscribers.count > 1 ? subscribers : subscribers.first 63 | end 64 | 65 | # Clear all subscribers from a *test* site (more) 66 | # DELETE /api/v4/[short site name]/subscribers.xml 67 | def delete_all 68 | !! api_request(:delete, "/subscribers.xml") 69 | end 70 | 71 | alias_method :destroy_all, :delete_all 72 | 73 | end 74 | 75 | def invoices=(data) 76 | @invoices = [] 77 | data.each do |item| 78 | if item.is_a? Hash 79 | item = RSpreedly::Invoice.new(item) 80 | end 81 | @invoices << item 82 | end 83 | end 84 | 85 | def new_record? 86 | !self.token 87 | end 88 | 89 | def save 90 | self.new_record? ? self.create : self.update 91 | end 92 | 93 | def save! 94 | self.new_record? ? self.create! : self.update! 95 | end 96 | 97 | # Create a subscriber (more) 98 | # POST /api/v4/[short site name]/subscribers.xml 99 | def create! 100 | result = api_request(:post, "/subscribers.xml", :body => self.to_xml) 101 | self.attributes = result["subscriber"] 102 | true 103 | end 104 | 105 | def create 106 | begin 107 | create! 108 | rescue RSpreedly::Error::Base 109 | # gulp those errors down 110 | # TODO - set self.errors or something? 111 | nil 112 | end 113 | end 114 | 115 | # Update a Subscriber (more) 116 | # PUT /api/v4/[short site name]/subscribers/[subscriber id].xml 117 | def update! 118 | !! api_request(:put, "/subscribers/#{self.customer_id}.xml", :body => self.to_xml(:exclude => [:customer_id])) 119 | end 120 | 121 | def update 122 | begin 123 | update! 124 | rescue RSpreedly::Error::Base 125 | # gulp those errors down 126 | # TODO - set self.errors or something? 127 | nil 128 | end 129 | end 130 | 131 | # Delete one subscriber from a *test* site (more) 132 | # DELETE /api/v4/[short site name]/subscribers/[subscriber id].xml 133 | def destroy 134 | begin 135 | !! api_request(:delete, "/subscribers/#{self.customer_id}.xml") 136 | rescue RSpreedly::Error::NotFound 137 | nil 138 | end 139 | end 140 | alias_method :delete, :destroy 141 | 142 | # Give a subscriber a complimentary subscription (more) 143 | # POST /api/v4/[short site name]/subscribers/[subscriber id]/complimentary_subscriptions.xml 144 | def comp_subscription(subscription) 145 | result = api_request(:post, "/subscribers/#{self.customer_id}/complimentary_subscriptions.xml", :body => subscription.to_xml) 146 | self.attributes = result["subscriber"] 147 | true 148 | end 149 | 150 | # Give a subscriber a complimentary time extension (more) 151 | # POST /api/v4/[short site name]/subscribers/[subscriber id]/complimentary_time_extension.xml 152 | def comp_time_extension(extension) 153 | result = api_request(:post, "/subscribers/#{self.customer_id}/complimentary_time_extensions.xml", :body => extension.to_xml) 154 | self.attributes = result["subscriber"] 155 | true 156 | end 157 | 158 | # Give a subscriber a credit (or reduce credit by supplying a negative value (more) 159 | # POST /api/v4[short site name]/subscribers/[subscriber id]/credits.xml 160 | def credit(amount) 161 | credit = Credit.new(:amount => amount) 162 | result = api_request(:post, "/subscribers/#{self.customer_id}/credits.xml", :body => credit.to_xml) 163 | self.store_credit = (self.store_credit || 0) + amount 164 | true 165 | end 166 | 167 | # Programatically Stopping Auto Renew of a Subscriber (more) 168 | # POST /api/v4/[short site name]/subscribers/[subscriber id]/stop_auto_renew.xml 169 | def stop_auto_renew 170 | !! api_request(:post, "/subscribers/#{self.customer_id}/stop_auto_renew.xml") 171 | end 172 | 173 | # Programatically Subscribe a Subscriber to a Free Trial Plan (more) 174 | # POST /api/v4/[short site name]/subscribers/[subscriber id]/subscribe_to_free_trial.xml 175 | def subscribe_to_free_trial(plan) 176 | result = api_request(:post, "/subscribers/#{self.customer_id}/subscribe_to_free_trial.xml", :body => plan.to_xml) 177 | self.attributes = result["subscriber"] 178 | true 179 | end 180 | 181 | # Programatically Allow Another Free Trial (more) 182 | # POST /api/v4/[short site name]/subscribers/[subscriber id]/allow_free_trial.xml 183 | def allow_free_trial 184 | result = api_request(:post, "/subscribers/#{self.customer_id}/allow_free_trial.xml") 185 | self.attributes = result["subscriber"] 186 | true 187 | end 188 | 189 | def grant_lifetime_subscription(feature_level) 190 | subscription = LifetimeComplimentarySubscription.new(:feature_level => feature_level) 191 | result = api_request(:post, "/subscribers/#{self.customer_id}/lifetime_complimentary_subscriptions.xml", :body => subscription.to_xml) 192 | self.attributes = result["subscriber"] 193 | true 194 | end 195 | 196 | def subscribe_link(subscription_plan_id, screen_name, return_url=nil) 197 | params = return_url.nil? ? "" : "?return_url=" + return_url 198 | "https://spreedly.com/#{RSpreedly::Config.site_name}/subscribers/#{self.customer_id}/subscribe/#{subscription_plan_id}/#{screen_name}#{params}" 199 | end 200 | 201 | def subscription_link(return_url=nil) 202 | params = return_url.nil? ? "" : "?return_url=" + return_url 203 | "https://spreedly.com/#{RSpreedly::Config.site_name}/subscriber_accounts/#{self.token}#{params}" 204 | end 205 | 206 | def to_xml(opts={}) 207 | 208 | # the api doesn't let us send these things 209 | # so let's strip them out of the XML 210 | exclude = [ 211 | :active, :active_until, :card_expires_before_next_auto_renew, 212 | :created_at, :eligible_for_free_trial, :feature_level, 213 | :grace_until, :in_grace_period, :lifetime_subscription, 214 | :on_trial, :ready_to_renew, :recurring, 215 | :store_credit, :store_credit_currency_code, :subscription_plan_name, 216 | :token, :updated_at, :ready_to_renew_since, 217 | :invoices 218 | ] 219 | 220 | opts[:exclude] ||= [] 221 | opts[:exclude] |= exclude 222 | 223 | super(opts) 224 | end 225 | end 226 | end -------------------------------------------------------------------------------- /spec/subscriber_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | describe RSpreedly::Subscriber do 3 | 4 | describe ".find" do 5 | describe "with the symbol :all" do 6 | it "should pass to RSpreedly::Subscriber.all" do 7 | RSpreedly::Subscriber.should_receive(:all) 8 | RSpreedly::Subscriber.find(:all) 9 | end 10 | end 11 | 12 | it "should return a Subscriber with an email for an existing subscriber" do 13 | stub_request(:get, spreedly_url("/subscribers.xml")). 14 | to_return(body: fixture("subscribers.xml"), status: 200) 15 | 16 | subscriber = RSpreedly::Subscriber.find_by_email "new@email.com" 17 | subscriber.email.should == "new@email.com" 18 | end 19 | 20 | it "should return an array of Subscriber filtered by email" do 21 | stub_request(:get, spreedly_url("/subscribers.xml")). 22 | to_return(body: fixture("subscribers.xml"), status: 200) 23 | 24 | subscribers = RSpreedly::Subscriber.find_by_email nil 25 | subscribers.class.should == Array 26 | end 27 | 28 | it "should return nil with an id for a subscriber who doesn't exist" do 29 | stub_request(:get, spreedly_url("/subscribers/42.xml")). 30 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 31 | 32 | RSpreedly::Subscriber.find(42).should be_nil 33 | end 34 | 35 | it "should return a Subscriber with an id for an existing subscriber" do 36 | stub_request(:get, spreedly_url("/subscribers/42.xml")). 37 | to_return(:body => fixture("subscriber.xml"), :status => 200) 38 | 39 | RSpreedly::Subscriber.find(42).should be_a(RSpreedly::Subscriber) 40 | end 41 | 42 | it "should include invoices on the Subscriber" do 43 | stub_request(:get, spreedly_url("/subscribers/42.xml")). 44 | to_return(:body => fixture("subscriber.xml"), :status => 200) 45 | 46 | subscriber = RSpreedly::Subscriber.find(42) 47 | subscriber.invoices.size.should == 5 48 | subscriber.invoices.select{|x| x.is_a?(RSpreedly::Invoice )}.size.should == 5 49 | end 50 | end 51 | 52 | describe ".all" do 53 | it "should return an empty array if there are no subscribers" do 54 | stub_request(:get, spreedly_url("/subscribers.xml")). 55 | to_return(:body => fixture("no_subscribers.xml"), :status => 200) 56 | 57 | RSpreedly::Subscriber.all.should == [] 58 | end 59 | 60 | it "should return an array of subscribers if there are any to find" do 61 | stub_request(:get, spreedly_url("/subscribers.xml")). 62 | to_return(:body => fixture("subscribers.xml"), :status => 200) 63 | 64 | RSpreedly::Subscriber.all.size.should == 3 # there are 3 in the fixture 65 | RSpreedly::Subscriber.all.select{|x| x.is_a?(RSpreedly::Subscriber )}.size.should == 3 66 | end 67 | end 68 | 69 | describe ".destroy_all" do 70 | it "should return true if successful" do 71 | stub_request(:delete, spreedly_url("/subscribers.xml")). 72 | to_return(:status => 200) 73 | 74 | RSpreedly::Subscriber.destroy_all.should be_true 75 | end 76 | end 77 | 78 | describe "#to_xml" do 79 | 80 | before(:each) do 81 | # use the XML to build a subscriber 82 | stub_request(:get, spreedly_url("/subscribers/42.xml")). 83 | to_return(:body => fixture("subscriber.xml"), :status => 200) 84 | 85 | @subscriber = RSpreedly::Subscriber.find(42) 86 | end 87 | 88 | it "should strip fields the API can't handle" do 89 | fields = [ 90 | :active, :active_until, :card_expires_before_next_auto_renew, 91 | :created_at, :eligible_for_free_trial, :feature_level, 92 | :grace_until, :in_grace_period, :lifetime_subscription, 93 | :on_trial, :ready_to_renew, :recurring, 94 | :store_credit, :store_credit_currency_code, :subscription_plan_name, 95 | :token, :updated_at 96 | ] 97 | 98 | xml = @subscriber.to_xml 99 | fields.each do |field| 100 | xml.should_not =~ /<#{field}>/ 101 | end 102 | end 103 | 104 | it "should not strip fields the API can handle" do 105 | fields = [ 106 | :billing_first_name, 107 | :billing_last_name, 108 | :customer_id, :email, 109 | :screen_name 110 | ] 111 | 112 | xml = @subscriber.to_xml 113 | fields.each do |field| 114 | xml.should =~ /<#{field}>/ 115 | end 116 | end 117 | end 118 | 119 | describe "#new_record?" do 120 | before(:each) do 121 | @subscriber = RSpreedly::Subscriber.new 122 | end 123 | 124 | it "should return true if the subscriber doesn't have a token" do 125 | @subscriber.new_record?.should be_true 126 | end 127 | 128 | it "should return false if the subscriber has a token" do 129 | @subscriber.token = "something" 130 | @subscriber.new_record?.should be_false 131 | end 132 | end 133 | 134 | describe "#save" do 135 | before(:each) do 136 | @subscriber = RSpreedly::Subscriber.new 137 | end 138 | 139 | it "should call #create for a non existing subscriber" do 140 | @subscriber.should_receive(:create) 141 | @subscriber.save 142 | end 143 | 144 | it "should call update for an existing subscriber" do 145 | @subscriber.token = "something" 146 | @subscriber.should_receive(:update) 147 | @subscriber.save 148 | end 149 | end 150 | 151 | describe "#save!" do 152 | before(:each) do 153 | @subscriber = RSpreedly::Subscriber.new 154 | end 155 | 156 | it "should call #create! for a non existing subscriber" do 157 | @subscriber.should_receive(:create!) 158 | @subscriber.save! 159 | end 160 | 161 | it "should call update! for an existing subscriber" do 162 | @subscriber.token = "something" 163 | @subscriber.should_receive(:update!) 164 | @subscriber.save! 165 | end 166 | end 167 | 168 | describe "#create!" do 169 | it "should return true if successful" do 170 | stub_request(:post, spreedly_url("/subscribers.xml")). 171 | to_return(:body => fixture("create_subscriber.xml"), :status => 201) 172 | 173 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42, :screen_name => "bob") 174 | @subscriber.create!.should be_true 175 | end 176 | 177 | it "should raise a BadRequest error if the data is invalid" do 178 | stub_request(:post, spreedly_url("/subscribers.xml")). 179 | to_return(:body => fixture("invalid_subscriber.xml"), :status => 422) 180 | 181 | @subscriber = RSpreedly::Subscriber.new 182 | lambda{ 183 | @subscriber.create! 184 | }.should raise_error(RSpreedly::Error::BadRequest) 185 | end 186 | 187 | it "should update the subscriber if successful" do 188 | stub_request(:post, spreedly_url("/subscribers.xml")). 189 | to_return(:body => fixture("create_subscriber.xml"), :status => 200) 190 | 191 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42, :screen_name => "bob") 192 | lambda{ 193 | @subscriber.create! 194 | }.should change(@subscriber, :token).to("6af9994a57e420345897b1abb4c27a9db27fa4d0") 195 | end 196 | 197 | it "should raise a Forbidden error if there is already a subscriber with that ID" do 198 | stub_request(:post, spreedly_url("/subscribers.xml")). 199 | to_return(:body => fixture("existing_subscriber.xml"), :status => 403) 200 | 201 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42, :screen_name => "bob") 202 | lambda{ 203 | @subscriber.create! 204 | }.should raise_error(RSpreedly::Error::Forbidden) 205 | end 206 | end 207 | 208 | describe "#create" do 209 | it "should return true if successful" do 210 | stub_request(:post, spreedly_url("/subscribers.xml")). 211 | to_return(:body => fixture("create_subscriber.xml"), :status => 201) 212 | 213 | @subscriber = RSpreedly::Subscriber.new 214 | @subscriber.customer_id = 42 215 | @subscriber.screen_name = "bob" 216 | @subscriber.create.should be_true 217 | end 218 | 219 | it "should return nil if the data is invalid" do 220 | stub_request(:post, spreedly_url("/subscribers.xml")). 221 | to_return(:body => fixture("invalid_subscriber.xml"), :status => 422) 222 | 223 | @subscriber = RSpreedly::Subscriber.new 224 | @subscriber.create.should be_nil 225 | end 226 | 227 | it "should return nil if there is already a subscriber with that ID" do 228 | stub_request(:post, spreedly_url("/subscribers.xml")). 229 | to_return(:body => fixture("existing_subscriber.xml"), :status => 403) 230 | 231 | @subscriber = RSpreedly::Subscriber.new 232 | @subscriber.customer_id = 42 233 | @subscriber.screen_name = "bob" 234 | @subscriber.create.should be_nil 235 | end 236 | end 237 | 238 | describe "#update!" do 239 | before(:each) do 240 | @subscriber = RSpreedly::Subscriber.new 241 | end 242 | 243 | describe "with basic information" do 244 | it "should return true if successful" do 245 | stub_request(:put, spreedly_url("/subscribers/42.xml")). 246 | to_return(:status => 200) 247 | 248 | @subscriber.customer_id = 42 249 | @subscriber.email = "new@email.com" 250 | @subscriber.update!.should be_true 251 | end 252 | 253 | it "should raise NotFound if the subscriber doesn't exist" do 254 | stub_request(:put, spreedly_url("/subscribers/400.xml")). 255 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 256 | 257 | @subscriber.customer_id = 400 258 | lambda{ 259 | @subscriber.update! 260 | }.should raise_error(RSpreedly::Error::NotFound) 261 | end 262 | 263 | it "should raise BadRequest if the data is invalid" do 264 | stub_request(:put, spreedly_url("/subscribers/42.xml")). 265 | to_return(:body => fixture("invalid_update.xml"), :status => 422) 266 | 267 | @subscriber.customer_id = 42 268 | lambda{ 269 | @subscriber.update! 270 | }.should raise_error(RSpreedly::Error::BadRequest) 271 | end 272 | end 273 | 274 | describe "with new_customer_id" do 275 | it "should return true if successful" do 276 | stub_request(:put, spreedly_url("/subscribers/42.xml")). 277 | to_return(:status => 200) 278 | 279 | @subscriber.customer_id = 42 280 | @subscriber.new_customer_id = 43 281 | @subscriber.email = "new@email.com" 282 | @subscriber.update!.should be_true 283 | end 284 | 285 | it "should raise NotFound if the subscriber doesn't exist" do 286 | stub_request(:put, spreedly_url("/subscribers/400.xml")). 287 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 288 | 289 | @subscriber.customer_id = 400 290 | @subscriber.new_customer_id = 401 291 | lambda{ 292 | @subscriber.update! 293 | }.should raise_error(RSpreedly::Error::NotFound) 294 | end 295 | 296 | it "should raise BadRequest if the data is invalid" do 297 | stub_request(:put, spreedly_url("/subscribers/.xml")). 298 | to_return(:body => fixture("invalid_update.xml"), :status => 422) 299 | 300 | @subscriber.new_customer_id = 43 301 | lambda{ 302 | @subscriber.update! 303 | }.should raise_error(RSpreedly::Error::BadRequest) 304 | end 305 | end 306 | 307 | describe "with payment information" do 308 | before(:each) do 309 | @subscriber.customer_id = 42 310 | @subscriber.payment_method = RSpreedly::PaymentMethod::CreditCard.new(:number => "1233445", :verification_value => "234") 311 | end 312 | 313 | it "should return true if successful" do 314 | stub_request(:put, spreedly_url("/subscribers/42.xml")). 315 | to_return(:status => 200) 316 | 317 | @subscriber.update!.should be_true 318 | end 319 | 320 | it "should raise NotFound if the subscriber doesn't exist" do 321 | stub_request(:put, spreedly_url("/subscribers/42.xml")). 322 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 323 | 324 | lambda{ 325 | @subscriber.update! 326 | }.should raise_error(RSpreedly::Error::NotFound) 327 | end 328 | 329 | it "should raise BadRequest if the data is invalid" do 330 | stub_request(:put, spreedly_url("/subscribers/42.xml")). 331 | to_return(:body => fixture("invalid_update.xml"), :status => 422) 332 | 333 | lambda{ 334 | @subscriber.update! 335 | }.should raise_error(RSpreedly::Error::BadRequest) 336 | end 337 | 338 | it "should raise Forbidden if the data is invalid" do 339 | stub_request(:put, spreedly_url("/subscribers/42.xml")). 340 | to_return(:body => fixture("invalid_update.xml"), :status => 403) 341 | 342 | lambda{ 343 | @subscriber.update! 344 | }.should raise_error(RSpreedly::Error::Forbidden) 345 | end 346 | end 347 | end 348 | 349 | describe "#update" do 350 | before(:each) do 351 | @subscriber = RSpreedly::Subscriber.new 352 | end 353 | 354 | describe "with basic information" do 355 | it "should return true if successful" do 356 | stub_request(:put, spreedly_url("/subscribers/42.xml")). 357 | to_return(:status => 200) 358 | 359 | @subscriber.customer_id = 42 360 | @subscriber.email = "new@email.com" 361 | @subscriber.update!.should be_true 362 | end 363 | 364 | it "should return nil if the subscriber doesn't exist" do 365 | stub_request(:put, spreedly_url("/subscribers/400.xml")). 366 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 367 | 368 | @subscriber.customer_id = 400 369 | @subscriber.update.should be_nil 370 | end 371 | 372 | it "should return nil if the data is invalid" do 373 | stub_request(:put, spreedly_url("/subscribers/42.xml")). 374 | to_return(:body => fixture("invalid_update.xml"), :status => 422) 375 | 376 | @subscriber.customer_id = 42 377 | @subscriber.update.should be_nil 378 | end 379 | end 380 | end 381 | 382 | describe "#destroy" do 383 | before(:each) do 384 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42) 385 | end 386 | 387 | it "should return nil if the subscriber doesn't exist" do 388 | stub_request(:delete, spreedly_url("/subscribers/42.xml")). 389 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 390 | 391 | @subscriber.destroy.should be_nil 392 | end 393 | 394 | it "should return true if successful" do 395 | stub_request(:delete, spreedly_url("/subscribers/42.xml")). 396 | to_return(:status => 200) 397 | 398 | @subscriber.destroy.should be_true 399 | end 400 | end 401 | 402 | describe "#comp_subscription" do 403 | before(:each) do 404 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42, :feature_level => "Lowly") 405 | @subscription = RSpreedly::ComplimentarySubscription.new(:duration_quantity => 42, :duration_units => "months", :feature_level => "Pro") 406 | end 407 | 408 | it "should return true if successful" do 409 | stub_request(:post, spreedly_url("/subscribers/42/complimentary_subscriptions.xml")). 410 | to_return(:body => fixture("complimentary_success.xml"), :status => 201) 411 | 412 | @subscriber.comp_subscription(@subscription).should be_true 413 | end 414 | 415 | it "should update the subscriber if successful" do 416 | stub_request(:post, spreedly_url("/subscribers/42/complimentary_subscriptions.xml")). 417 | to_return(:body => fixture("complimentary_success.xml"), :status => 201) 418 | 419 | lambda{ 420 | @subscriber.comp_subscription(@subscription) 421 | }.should change(@subscriber, :feature_level).to("Pro") 422 | end 423 | 424 | it "should raise NotFound if the subscriber doesn't exist" do 425 | stub_request(:post, spreedly_url("/subscribers/42/complimentary_subscriptions.xml")). 426 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 427 | 428 | lambda{ 429 | @subscriber.comp_subscription(@subscription) 430 | }.should raise_error(RSpreedly::Error::NotFound) 431 | end 432 | 433 | it "should raise BadRequest if validation fails on the subscription" do 434 | stub_request(:post, spreedly_url("/subscribers/42/complimentary_subscriptions.xml")). 435 | to_return(:body => fixture("complimentary_not_valid.xml"), :status => 422) 436 | 437 | lambda{ 438 | @subscriber.comp_subscription(@subscription) 439 | }.should raise_error(RSpreedly::Error::BadRequest) 440 | end 441 | 442 | it "should raise Forbidden if the subscriber is active" do 443 | stub_request(:post, spreedly_url("/subscribers/42/complimentary_subscriptions.xml")). 444 | to_return(:body => fixture("complimentary_failed_active.xml"), :status => 403) 445 | 446 | lambda{ 447 | @subscriber.comp_subscription(@subscription) 448 | }.should raise_error(RSpreedly::Error::Forbidden) 449 | end 450 | end 451 | 452 | describe "#comp_time_extension" do 453 | before(:each) do 454 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42, :feature_level => "Lowly") 455 | @subscription = RSpreedly::ComplimentaryTimeExtension.new(:duration_quantity => 42, :duration_units => "months") 456 | end 457 | 458 | it "should return true if successful" do 459 | stub_request(:post, spreedly_url("/subscribers/42/complimentary_time_extensions.xml")). 460 | to_return(:body => fixture("complimentary_success.xml"), :status => 201) 461 | 462 | @subscriber.comp_time_extension(@subscription).should be_true 463 | end 464 | 465 | it "should update the subscriber if successful" do 466 | stub_request(:post, spreedly_url("/subscribers/42/complimentary_time_extensions.xml")). 467 | to_return(:body => fixture("complimentary_success.xml"), :status => 201) 468 | 469 | lambda{ 470 | @subscriber.comp_time_extension(@subscription) 471 | }.should change(@subscriber, :active_until).to(Time.parse("Sun Feb 21 19:04:28 UTC 2010")) 472 | end 473 | 474 | it "should raise NotFound if the subscriber doesn't exist" do 475 | stub_request(:post, spreedly_url("/subscribers/42/complimentary_time_extensions.xml")). 476 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 477 | 478 | lambda{ 479 | @subscriber.comp_time_extension(@subscription) 480 | }.should raise_error(RSpreedly::Error::NotFound) 481 | end 482 | 483 | it "should raise BadRequest if validation fails on the subscription" do 484 | stub_request(:post, spreedly_url("/subscribers/42/complimentary_time_extensions.xml")). 485 | to_return(:body => fixture("complimentary_not_valid.xml"), :status => 422) 486 | 487 | lambda{ 488 | @subscriber.comp_time_extension(@subscription) 489 | }.should raise_error(RSpreedly::Error::BadRequest) 490 | end 491 | 492 | it "should raise Forbidden if the subscriber is inactive" do 493 | stub_request(:post, spreedly_url("/subscribers/42/complimentary_time_extensions.xml")). 494 | to_return(:body => fixture("complimentary_failed_inactive.xml"), :status => 403) 495 | 496 | lambda{ 497 | @subscriber.comp_time_extension(@subscription) 498 | }.should raise_error(RSpreedly::Error::Forbidden) 499 | end 500 | end 501 | 502 | describe "#credit" do 503 | before(:each) do 504 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42, :feature_level => "Lowly") 505 | @credit_amount = 5 506 | end 507 | 508 | it "should return true if successful" do 509 | stub_request(:post, spreedly_url("/subscribers/42/credit.xml")). 510 | to_return(:body => fixture("credit_success.xml"), :status => 201) 511 | 512 | @subscriber.credit(@credit_amount).should be_true 513 | @subscriber.store_credit.should == @credit_amount 514 | end 515 | 516 | it "should raise NotFound if the subscriber doesn't exist" do 517 | stub_request(:post, spreedly_url("/subscribers/42/credit.xml")). 518 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 519 | 520 | lambda{ 521 | @subscriber.credit(@credit_amount) 522 | }.should raise_error(RSpreedly::Error::NotFound) 523 | end 524 | 525 | it "should raise BadRequest if validation fails on the subscription" do 526 | stub_request(:post, spreedly_url("/subscribers/42/credit.xml")). 527 | to_return(:body => fixture("credit_not_valid.xml"), :status => 422) 528 | 529 | lambda{ 530 | @subscriber.credit(@credit_amount) 531 | }.should raise_error(RSpreedly::Error::BadRequest) 532 | end 533 | 534 | it "should increment store_credit" do 535 | stub_request(:post, spreedly_url("/subscribers/42/credit.xml")). 536 | to_return(:body => fixture("credit_success.xml"), :status => 201) 537 | 538 | @subscriber.store_credit = 5 539 | @subscriber.credit(5).should be_true 540 | @subscriber.store_credit.should == 10 541 | end 542 | 543 | it "should decrement store_credit" do 544 | stub_request(:post, spreedly_url("/subscribers/42/credit.xml")). 545 | to_return(:body => fixture("credit_success.xml"), :status => 201) 546 | 547 | @subscriber.store_credit = 5 548 | @subscriber.credit(-5).should be_true 549 | @subscriber.store_credit.should == 0 550 | end 551 | end 552 | 553 | describe "#stop_auto_renew" do 554 | before(:each) do 555 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42) 556 | end 557 | 558 | it "should return true if successful" do 559 | stub_request(:post, spreedly_url("/subscribers/42/stop_auto_renew.xml")). 560 | to_return(:status => 200) 561 | 562 | @subscriber.stop_auto_renew.should be_true 563 | end 564 | 565 | it "should raise NotFound if the subscriber doesn't exist" do 566 | stub_request(:post, spreedly_url("/subscribers/42/stop_auto_renew.xml")). 567 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 568 | 569 | lambda{ 570 | @subscriber.stop_auto_renew 571 | }.should raise_error(RSpreedly::Error::NotFound) 572 | end 573 | end 574 | 575 | describe "#subscribe_to_free_trial" do 576 | before(:each) do 577 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42) 578 | @plan = RSpreedly::SubscriptionPlan.new(:id => 2533) 579 | end 580 | 581 | it "should return true if successful" do 582 | stub_request(:post, spreedly_url("/subscribers/42/subscribe_to_free_trial.xml")). 583 | to_return(:body => fixture("free_plan_success.xml"), :status => 200) 584 | 585 | @subscriber.subscribe_to_free_trial(@plan).should be_true 586 | end 587 | 588 | it "should update the subscriber if successful" do 589 | stub_request(:post, spreedly_url("/subscribers/42/subscribe_to_free_trial.xml")). 590 | to_return(:body => fixture("free_plan_success.xml"), :status => 200) 591 | 592 | lambda{ 593 | @subscriber.subscribe_to_free_trial(@plan) 594 | }.should change(@subscriber, :grace_until).to(Time.parse("2013-05-02T00:07:37Z")) 595 | end 596 | 597 | it "should raise NotFound if the subscriber doesn't exist" do 598 | stub_request(:post, spreedly_url("/subscribers/42/subscribe_to_free_trial.xml")). 599 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 600 | 601 | lambda{ 602 | @subscriber.subscribe_to_free_trial(@plan) 603 | }.should raise_error(RSpreedly::Error::NotFound) 604 | end 605 | 606 | it "should raise NotFound if the plan doesn't exist" do 607 | stub_request(:post, spreedly_url("/subscribers/42/subscribe_to_free_trial.xml")). 608 | to_return(:body => fixture("plan_not_found.xml"), :status => 404) 609 | 610 | lambda{ 611 | @subscriber.subscribe_to_free_trial(@plan) 612 | }.should raise_error(RSpreedly::Error::NotFound) 613 | end 614 | 615 | it "should raise BadRequest if no plan is specified" do 616 | stub_request(:post, spreedly_url("/subscribers/42/subscribe_to_free_trial.xml")). 617 | to_return(:body => fixture("free_plan_not_set.xml"), :status => 422) 618 | 619 | @plan.id = nil 620 | lambda{ 621 | @subscriber.subscribe_to_free_trial(@plan) 622 | }.should raise_error(RSpreedly::Error::BadRequest) 623 | end 624 | 625 | it "should raise Forbidden if the subscriber isn't elligable" do 626 | stub_request(:post, spreedly_url("/subscribers/42/subscribe_to_free_trial.xml")). 627 | to_return(:body => fixture("free_plan_not_elligable.xml"), :status => 403) 628 | 629 | lambda{ 630 | @subscriber.subscribe_to_free_trial(@plan) 631 | }.should raise_error(RSpreedly::Error::Forbidden) 632 | end 633 | 634 | it "should raise Forbidden if the plan isn't a free trial" do 635 | stub_request(:post, spreedly_url("/subscribers/42/subscribe_to_free_trial.xml")). 636 | to_return(:body => fixture("free_plan_not_free.xml"), :status => 403) 637 | 638 | lambda{ 639 | @subscriber.subscribe_to_free_trial(@plan) 640 | }.should raise_error(RSpreedly::Error::Forbidden) 641 | end 642 | end 643 | 644 | describe "#allow_free_trial" do 645 | before(:each) do 646 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42) 647 | end 648 | 649 | it "should return true if successful" do 650 | stub_request(:post, spreedly_url("/subscribers/42/allow_free_trial.xml")). 651 | to_return(:body => fixture("free_plan_success.xml"), :status => 200) 652 | 653 | @subscriber.allow_free_trial.should be_true 654 | end 655 | 656 | it "should update the subscriber if successful" do 657 | stub_request(:post, spreedly_url("/subscribers/42/allow_free_trial.xml")). 658 | to_return(:body => fixture("free_plan_success.xml"), :status => 200) 659 | 660 | lambda{ 661 | @subscriber.allow_free_trial 662 | }.should change(@subscriber, :grace_until).to(Time.parse("2013-05-02T00:07:37Z")) 663 | end 664 | 665 | it "should raise NotFound if the subscriber doesn't exist" do 666 | stub_request(:post, spreedly_url("/subscribers/42/allow_free_trial.xml")). 667 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 668 | 669 | lambda{ 670 | @subscriber.allow_free_trial 671 | }.should raise_error(RSpreedly::Error::NotFound) 672 | end 673 | end 674 | 675 | describe "#grant_lifetime_subscription" do 676 | 677 | before(:each) do 678 | @subscriber = RSpreedly::Subscriber.new(:customer_id => 42) 679 | end 680 | 681 | it "should return true if successful" do 682 | stub_request(:post, spreedly_url("/subscribers/42/lifetime_complimentary_subscriptions.xml")). 683 | to_return(:body => fixture("lifetime_subscription_success.xml"), :status => 200) 684 | 685 | @subscriber.grant_lifetime_subscription("Something").should be_true 686 | end 687 | 688 | it "should update the subscriber's lifetime_subscription if successful" do 689 | stub_request(:post, spreedly_url("/subscribers/42/lifetime_complimentary_subscriptions.xml")). 690 | to_return(:body => fixture("lifetime_subscription_success.xml"), :status => 200) 691 | 692 | lambda{ 693 | @subscriber.grant_lifetime_subscription("Something") 694 | }.should change(@subscriber, :lifetime_subscription).to(true) 695 | end 696 | 697 | it "should update the subscriber's feature_level if successful" do 698 | stub_request(:post, spreedly_url("/subscribers/42/lifetime_complimentary_subscriptions.xml")). 699 | to_return(:body => fixture("lifetime_subscription_success.xml"), :status => 200) 700 | 701 | lambda{ 702 | @subscriber.grant_lifetime_subscription("Something") 703 | }.should change(@subscriber, :feature_level).to("Something") 704 | end 705 | 706 | it "should raise NotFound if the subscriber doesn't exist" do 707 | stub_request(:post, spreedly_url("/subscribers/42/lifetime_complimentary_subscriptions.xml")). 708 | to_return(:body => fixture("subscriber_not_found.xml"), :status => 404) 709 | 710 | lambda{ 711 | @subscriber.grant_lifetime_subscription("Something") 712 | }.should raise_error(RSpreedly::Error::NotFound) 713 | end 714 | 715 | it "should raise BadRequest if the feature level is blank" do 716 | stub_request(:post, spreedly_url("/subscribers/42/lifetime_complimentary_subscriptions.xml")). 717 | to_return(:body => fixture("feature_level_blank.xml"), :status => 422) 718 | 719 | lambda{ 720 | @subscriber.grant_lifetime_subscription("Something") 721 | }.should raise_error(RSpreedly::Error::BadRequest) 722 | end 723 | 724 | it "should return subscribe_link for subscriber without return_url" do 725 | @subscriber.subscribe_link('99', 'spreedster').should == "https://spreedly.com/your-site-name/subscribers/42/subscribe/99/spreedster" 726 | end 727 | 728 | it "should return subscribe_link for subscriber with return_url" do 729 | return_url = "http://mydomain.com/response" 730 | @subscriber.subscribe_link('99', 'spreedster', return_url).should == "https://spreedly.com/your-site-name/subscribers/42/subscribe/99/spreedster?return_url=http://mydomain.com/response" 731 | end 732 | 733 | it "should return subscription_link for subscriber without return_url" do 734 | @subscriber.stub(:token).and_return("my-token") 735 | @subscriber.subscription_link.should == "https://spreedly.com/your-site-name/subscriber_accounts/my-token" 736 | end 737 | 738 | it "should return subscription_link for subscriber with return_url" do 739 | @subscriber.stub(:token).and_return("my-token") 740 | return_url = "http://mydomain.com/response" 741 | @subscriber.subscription_link(return_url).should == "https://spreedly.com/your-site-name/subscriber_accounts/my-token?return_url=http://mydomain.com/response" 742 | end 743 | end 744 | end 745 | --------------------------------------------------------------------------------