chargebee_rails 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ # Default directory to look in is `/specs`
5
+ # Run with `rake spec`
6
+ RSpec::Core::RakeTask.new(:spec) do |task|
7
+ task.rspec_opts = ['--color', '--format', 'nested']
8
+ end
9
+
10
+ task :default => :spec
@@ -0,0 +1,40 @@
1
+ module ChargebeeRails
2
+ class WebhooksController < ActionController::Base
3
+ include WebhookHandler
4
+ before_filter :authenticate, if: "ChargebeeRails.configuration.secure_webhook_api"
5
+
6
+ # Handle ChargeBee webhook events
7
+ # From the post request received from chargebee, the event for which the
8
+ # webhook is triggered is found from the id parameter sent.
9
+ # The event is then handled by the WebhookHandler module.
10
+ # * *Raises* :
11
+ # - +ChargebeeRails::Error+ -> If event is not valid or if event unprocessable
12
+ def handle_event
13
+ event = ChargeBee::Event.retrieve(params[:id]).event
14
+ handle(event)
15
+ head :ok
16
+ rescue ChargebeeRails::Error => e
17
+ log_errors(e)
18
+ head :internal_server_error
19
+ end
20
+
21
+ private
22
+
23
+ def log_errors(e)
24
+ logger.error e.message
25
+ e.backtrace.each { |line| logger.error " #{line}" }
26
+ end
27
+
28
+ # Basic http authentication for ChargeBee webhook apis
29
+ # The username and password used to secure the webhook at chargebee
30
+ # is compared with the configured webhook_authentication user and
31
+ # secret in the application.
32
+ def authenticate
33
+ authenticate_or_request_with_http_basic do |user, password|
34
+ user == ChargebeeRails.configuration.webhook_authentication[:user] &&
35
+ password == ChargebeeRails.configuration.webhook_authentication[:secret]
36
+ end
37
+ end
38
+
39
+ end
40
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ Rails.application.routes.draw do #:nodoc:
2
+ # ChargeBee webhook route
3
+ # This is the default route that the chargebee's webhooks reach the application
4
+ # and can be configured.
5
+ # The default webhook_api_path is +chargebee_rails_event+ - can be configured;
6
+ # and the default webhook_handler (webhook handling controller) is
7
+ # +chargebee_rails/webhooks+ - can be configured as well.
8
+ post ChargebeeRails.configuration.webhook_api_path => "#{ChargebeeRails.configuration.webhook_handler}#handle_event"
9
+ end
@@ -0,0 +1,119 @@
1
+ require "chargebee_rails/version"
2
+ require "chargebee"
3
+ require "rails"
4
+ require "chargebee_rails/engine"
5
+ require "chargebee_rails/webhook_handler"
6
+ require "chargebee_rails/configuration"
7
+ require "chargebee_rails/errors"
8
+ require "generators/chargebee_rails/install_generator"
9
+ require "chargebee_rails/subscription"
10
+ require "chargebee_rails/subscription_builder"
11
+ require "chargebee_rails/hosted_page_subscription_manager"
12
+ require "chargebee_rails/customer"
13
+ require "chargebee_rails/metered_billing"
14
+
15
+ module ChargebeeRails
16
+ # This method is used to update the chargebee customer and also reflects the
17
+ # changes to the subscription owner in the application.
18
+ # * *Args* :
19
+ # - +customer+ -> the subscription owner
20
+ # - +options+ -> the options hash allowed for customer update in chargebee
21
+ # For more details on the options hash, refer the input parameters for
22
+ # https://apidocs.chargebee.com/docs/api/customers?lang=ruby#update_a_customer
23
+ # * *Returns* :
24
+ # - the updated subscription owner
25
+ # * *Raises* :
26
+ # - +ChargeBee::InvalidRequestError+ -> If customer or options is invalid
27
+ #
28
+ def self.update_customer(customer, options={})
29
+ chargebee_customer = ChargeBee::Customer.update(customer.chargebee_id, options).customer
30
+ customer.update(chargebee_id: chargebee_customer.id, chargebee_data: chargebee_customer_data(chargebee_customer))
31
+ customer
32
+ end
33
+
34
+ # Update the billing information of the chargebee customer. The changes in the
35
+ # billing details are also reflected in the subscription owner's
36
+ # +chargebee_customer_data+
37
+ # * *Args* :
38
+ # - +customer+ -> the subscription owner
39
+ # - +options+ -> the options hash allowed for updating customer billing info in chargebee
40
+ # For more details on the options hash, refer the input parameters for
41
+ # https://apidocs.chargebee.com/docs/api/customers?lang=ruby#update_billing_info_for_a_customer
42
+ # * *Returns* :
43
+ # - the updated subscription owner
44
+ # * *Raises* :
45
+ # - +ChargeBee::InvalidRequestError+ -> If customer or options is invalid
46
+ #
47
+ def self.update_billing_info(customer, options={})
48
+ chargebee_customer = ChargeBee::Customer.update_billing_info(customer.chargebee_id, options).customer
49
+ customer.update(chargebee_id: chargebee_customer.id, chargebee_data: chargebee_customer_data(chargebee_customer))
50
+ customer
51
+ end
52
+
53
+ # Add contacts to a chargebee customer, the subscription owner
54
+ # and the contact details hash is given as options.
55
+ # * *Args* :
56
+ # - +customer+ -> the subscription owner
57
+ # - +options+ -> the options hash allowed for adding contacts to customer in chargebee
58
+ # For more details on the options hash, refer the input parameters for
59
+ # https://apidocs.chargebee.com/docs/api/customers?lang=ruby#add_contacts_to_a_customer
60
+ # * *Returns* :
61
+ # - the updated chargebee customer
62
+ # * *Raises* :
63
+ # - +ChargeBee::InvalidRequestError+ -> If customer or options is invalid
64
+ #
65
+ def self.add_customer_contacts(customer, options={})
66
+ ChargeBee::Customer.add_contact(customer.chargebee_id, options).customer
67
+ end
68
+
69
+ # Update the contacts for a chargebee customer - the chargebee contact id
70
+ # must be passed in the options to update the existing contact for the
71
+ # subscription owner
72
+ # * *Args* :
73
+ # - +customer+ -> the subscription owner
74
+ # - +options+ -> the options hash allowed for updating customer contacts in chargebee
75
+ # For more details on the options hash, refer the input parameters for
76
+ # https://apidocs.chargebee.com/docs/api/customers?lang=ruby#update_contacts_for_a_customer
77
+ # * *Returns* :
78
+ # - the updated chargebee customer
79
+ # * *Raises* :
80
+ # - +ChargeBee::InvalidRequestError+ -> If customer or options is invalid
81
+ #
82
+ def self.update_customer_contacts(customer, options={})
83
+ ChargeBee::Customer.update_contact(customer.chargebee_id, options).customer
84
+ end
85
+
86
+ private
87
+
88
+ def chargebee_customer_data customer
89
+ {
90
+ customer_details: customer_details(customer),
91
+ billing_address: billing_address(customer.billing_address)
92
+ }
93
+ end
94
+
95
+ def customer_details customer
96
+ {
97
+ first_name: customer.first_name,
98
+ last_name: customer.last_name,
99
+ email: customer.email,
100
+ company: customer.company,
101
+ vat_number: customer.vat_number
102
+ }
103
+ end
104
+
105
+ def billing_address customer_billing_address
106
+ {
107
+ first_name: customer_billing_address.first_name,
108
+ last_name: customer_billing_address.last_name,
109
+ company: customer_billing_address.company,
110
+ address_line1: customer_billing_address.line1,
111
+ address_line2: customer_billing_address.line2,
112
+ address_line3: customer_billing_address.line3,
113
+ city: customer_billing_address.city,
114
+ state: customer_billing_address.state,
115
+ country: customer_billing_address.country,
116
+ zip: customer_billing_address.zip
117
+ } if customer_billing_address.present?
118
+ end
119
+ end
@@ -0,0 +1,47 @@
1
+ # :nodoc: all
2
+ module ChargebeeRails
3
+
4
+ def self.configuration
5
+ @configuration ||= Configuration.new
6
+ end
7
+
8
+ def self.setup
9
+ ::ChargeBee.configure(
10
+ site: configuration.chargebee_site,
11
+ api_key: configuration.chargebee_api_key
12
+ )
13
+ end
14
+
15
+ def self.configure
16
+ yield configuration
17
+ setup
18
+ end
19
+
20
+ class Configuration
21
+ attr_accessor :default_plan_id
22
+ attr_accessor :end_of_term
23
+ attr_accessor :proration
24
+ attr_accessor :include_delayed_charges
25
+ attr_accessor :chargebee_site
26
+ attr_accessor :chargebee_api_key
27
+ attr_accessor :currency
28
+ attr_accessor :webhook_handler
29
+ attr_accessor :webhook_api_path
30
+ attr_accessor :secure_webhook_api
31
+ attr_accessor :webhook_authentication
32
+
33
+ def initialize
34
+ @default_plan_id = nil
35
+ @end_of_term = false
36
+ @proration = true
37
+ @include_delayed_charges = {changes_estimate: false, renewal_estimate: true}
38
+ @webhook_handler = 'chargebee_rails/webhooks'
39
+ @webhook_api_path = 'chargebee_rails_event'
40
+ @secure_webhook_api = false
41
+ @webhook_authentication = {user: nil, secret: nil}
42
+ @chargebee_site = nil
43
+ @chargebee_api_key = nil
44
+ @currency = "US Dollars [USD]"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,85 @@
1
+ module ChargebeeRails
2
+ module Customer
3
+
4
+ # Subscribe customer to a new subscription in chargebee.
5
+ # * *Args* :
6
+ # - +options+ -> the options hash allowed for subscription update in chargebee
7
+ # For more details on the options hash, refer the input parameters for
8
+ # https://apidocs.chargebee.com/docs/api/subscriptions?lang=ruby#create_a_subscription
9
+ # * *Returns* :
10
+ # - the subscription
11
+ # * *Raises* :
12
+ # - +ChargeBee::InvalidRequestError+ -> If subscription options is invalid
13
+ #
14
+ def subscribe(options={})
15
+ SubscriptionBuilder.new(self, options).create
16
+ end
17
+
18
+ # Subscribe customer to a new subscription in chargebee via chargebee's hosted page.
19
+ # * *Args* :
20
+ # - +hosted_page+ -> the +hosted_page+ returned by chargebee
21
+ # The subscription for the customer is created from the +hosted_page+
22
+ # returned by chargebee. This +hosted_page+ object contains the details
23
+ # about the subscription in chargebee for the customer. For more on +hosted_page+
24
+ # https://apidocs.chargebee.com/docs/api/hosted_pages?lang=ruby#checkout_new_subscription
25
+ # * *Returns* :
26
+ # - the subscription
27
+ # * *Raises* :
28
+ # - +ChargeBee::InvalidRequestError+ -> If +hosted_page+ is invalid
29
+ #
30
+ def subscribe_via_hosted_page(hosted_page)
31
+ HostedPageSubscriptionManager.new(self, hosted_page).create
32
+ end
33
+
34
+ # Subscribe customer to a new subscription in chargebee via chargebee's hosted page.
35
+ # * *Args* :
36
+ # - +hosted_page+ -> the +hosted_page+ returned by chargebee
37
+ # The subscription for the customer is updated from the +hosted_page+
38
+ # returned by chargebee. This +hosted_page+ object contains the details
39
+ # about the updated subscription in chargebee for the customer.
40
+ # * *Returns* :
41
+ # - the subscription
42
+ # * *Raises* :
43
+ # - +ChargeBee::InvalidRequestError+ -> If +hosted_page+ is invalid
44
+ #
45
+ def update_subscription_via_hosted_page(hosted_page)
46
+ HostedPageSubscriptionManager.new(self, hosted_page).update
47
+ end
48
+
49
+ # Update the customer's subscription
50
+ # * *Args* :
51
+ # - +options+ -> the options hash allowed for subscription update in chargebee
52
+ # For more details on the options hash, refer the input parameters for
53
+ # https://apidocs.chargebee.com/docs/api/subscriptions?lang=ruby#update_a_subscription
54
+ # * *Returns* :
55
+ # - the updated subscription
56
+ # * *Raises* :
57
+ # - +ChargeBee::InvalidRequestError+ -> If subscription or options is invalid
58
+ #
59
+ def update_subscription(options={})
60
+ SubscriptionBuilder.new(self, options).update
61
+ end
62
+
63
+ # Retrieve the chargebee customer of the subscription owner -
64
+ # i.e subscription owner as chargebee customer
65
+ # * *Returns* :
66
+ # - the chargebee customer
67
+ # * *Raises* :
68
+ # - +ChargeBee::InvalidRequestError+ -> If subscription owner is invalid
69
+ #
70
+ def as_chargebee_customer
71
+ ChargeBee::Customer.retrieve(chargebee_id).customer
72
+ end
73
+
74
+ # List all invoices for the subscription owner (customer).
75
+ # * *Returns* :
76
+ # - the list of invoices for the customer in chargebee
77
+ # * *Raises* :
78
+ # - +ChargeBee::InvalidRequestError+ -> If customer is invalid
79
+ #
80
+ def invoices
81
+ ChargeBee::Invoice.invoices_for_customer(chargebee_id).map(&:invoice)
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,6 @@
1
+ # :nodoc: all
2
+ module ChargebeeRails
3
+ class EventEngine < ::Rails::Engine
4
+ # isolate_namespace ChargebeeRails
5
+ end
6
+ end
@@ -0,0 +1,26 @@
1
+ # :nodoc: all
2
+ module ChargebeeRails
3
+
4
+ class Error < StandardError
5
+ attr_reader :original_error
6
+
7
+ def initialize(message=nil, original_error=nil)
8
+ super message
9
+ @original_error = original_error
10
+ end
11
+ end
12
+
13
+ class PlanError < Error
14
+ # Raise this error when the plan is not present in active_record
15
+ def plan_not_found
16
+ "Plan Not Found"
17
+ end
18
+
19
+ # Raise this error when the plan is not setup in ChargeBee
20
+ def plan_not_configured
21
+ "Plan Not Configured"
22
+ end
23
+ end
24
+
25
+ class UnauthorizedError < Error; end
26
+ end
@@ -0,0 +1,134 @@
1
+ module ChargebeeRails
2
+ class HostedPageSubscriptionManager
3
+
4
+ def initialize(customer, hosted_page)
5
+ @customer = customer
6
+ @hosted_page = hosted_page
7
+ end
8
+
9
+ # Create a subscription for the customer in application,
10
+ # from the subscription details got from chargebee's hosted page
11
+ def create
12
+ @customer.update(
13
+ chargebee_id: hosted_customer.id,
14
+ chargebee_data: chargebee_customer_data
15
+ )
16
+ @subscription = @customer.create_subscription(subscription_attrs)
17
+ manage_payment_method if hosted_payment_method.present?
18
+ @subscription
19
+ end
20
+
21
+ # Update the subscription for the customer in application,
22
+ # from the subscription details got from chargebee's hosted page
23
+ def update
24
+ @subscription = @customer.subscription
25
+ @subscription.update(subscription_attrs)
26
+ manage_payment_method if hosted_payment_method.present?
27
+ @subscription
28
+ end
29
+
30
+ private
31
+
32
+ # Update payment method for subscrption if one exists or create new one
33
+ def manage_payment_method
34
+ @subscription.payment_method.present? &&
35
+ @subscription.payment_method.update(payment_method_attrs) ||
36
+ create_payment_method
37
+ end
38
+
39
+ # Create the payment method for the subscription
40
+ def create_payment_method
41
+ @subscription.create_payment_method(payment_method_attrs)
42
+ end
43
+
44
+ def hosted_subscription
45
+ @hosted_subscription ||= @hosted_page.content.subscription
46
+ end
47
+
48
+ def hosted_customer
49
+ @hosted_customer ||= @hosted_page.content.customer
50
+ end
51
+
52
+ def hosted_payment_method
53
+ @hosted_payment_method ||= hosted_customer.payment_method
54
+ end
55
+
56
+ def hosted_card
57
+ @hosted_card ||= @hosted_page.content.card
58
+ end
59
+
60
+ def hosted_billing_address
61
+ @hosted_billing_address ||= @hosted_customer.billing_address
62
+ end
63
+
64
+ def subscription_attrs
65
+ {
66
+ chargebee_id: hosted_subscription.id,
67
+ status: hosted_subscription.status,
68
+ plan_quantity: hosted_subscription.plan_quantity,
69
+ chargebee_data: chargebee_subscription_data,
70
+ plan: Plan.find_by(plan_id: hosted_subscription.plan_id)
71
+ }
72
+ end
73
+
74
+ def chargebee_subscription_data
75
+ {
76
+ trial_ends_at: hosted_subscription.trial_end,
77
+ next_renewal_at: hosted_subscription.current_term_end,
78
+ cancelled_at: hosted_subscription.cancelled_at,
79
+ is_scheduled_for_cancel: (hosted_subscription.status == 'non-renewing' ? true : false),
80
+ has_scheduled_changes: hosted_subscription.has_scheduled_changes
81
+ }
82
+ end
83
+
84
+ def chargebee_customer_data
85
+ {
86
+ customer_details: customer_details(hosted_customer),
87
+ billing_address: billing_address(hosted_customer.billing_address)
88
+ }
89
+ end
90
+
91
+ def customer_details customer
92
+ {
93
+ first_name: customer.first_name,
94
+ last_name: customer.last_name,
95
+ email: customer.email,
96
+ company: customer.company,
97
+ vat_number: customer.vat_number
98
+ }
99
+ end
100
+
101
+ def billing_address customer_billing_address
102
+ {
103
+ first_name: customer_billing_address.first_name,
104
+ last_name: customer_billing_address.last_name,
105
+ company: customer_billing_address.company,
106
+ address_line1: customer_billing_address.line1,
107
+ address_line2: customer_billing_address.line2,
108
+ address_line3: customer_billing_address.line3,
109
+ city: customer_billing_address.city,
110
+ state: customer_billing_address.state,
111
+ country: customer_billing_address.country,
112
+ zip: customer_billing_address.zip
113
+ } if customer_billing_address.present?
114
+ end
115
+
116
+ def payment_method_attrs
117
+ if hosted_payment_method.type == 'card'
118
+ card_last4, card_type = hosted_card.last4, hosted_card.card_type
119
+ else
120
+ card_last4, card_type = nil, nil
121
+ end
122
+ {
123
+ cb_customer_id: hosted_customer.id,
124
+ auto_collection: hosted_customer.auto_collection,
125
+ payment_type: hosted_payment_method.type,
126
+ reference_id: hosted_payment_method.reference_id,
127
+ card_last4: card_last4,
128
+ card_type: card_type,
129
+ status: hosted_payment_method.status
130
+ }
131
+ end
132
+
133
+ end
134
+ end