recurly 2.18.15 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (209) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +158 -110
  8. data/Rakefile +6 -0
  9. data/bin/bundle +105 -0
  10. data/bin/coderay +29 -0
  11. data/bin/console +14 -0
  12. data/bin/htmldiff +29 -0
  13. data/bin/ldiff +29 -0
  14. data/bin/pry +29 -0
  15. data/bin/rake +29 -0
  16. data/bin/rspec +29 -0
  17. data/bin/setup +8 -0
  18. data/bin/yard +29 -0
  19. data/bin/yardoc +29 -0
  20. data/bin/yri +29 -0
  21. data/lib/data/ca-certificates.crt +31 -0
  22. data/lib/recurly.rb +14 -145
  23. data/lib/recurly/client.rb +198 -0
  24. data/lib/recurly/client/operations.rb +935 -0
  25. data/lib/recurly/errors.rb +34 -0
  26. data/lib/recurly/errors/api_errors.rb +35 -0
  27. data/lib/recurly/errors/network_errors.rb +8 -0
  28. data/lib/recurly/pager.rb +119 -0
  29. data/lib/recurly/request.rb +30 -0
  30. data/lib/recurly/requests/account_acquisition_updatable.rb +22 -0
  31. data/lib/recurly/requests/account_create_only.rb +18 -0
  32. data/lib/recurly/requests/account_updatable.rb +50 -0
  33. data/lib/recurly/requests/add_on_create.rb +38 -0
  34. data/lib/recurly/requests/add_on_update.rb +38 -0
  35. data/lib/recurly/requests/address.rb +42 -0
  36. data/lib/recurly/requests/billing_info_create.rb +58 -0
  37. data/lib/recurly/requests/coupon_create_only.rb +66 -0
  38. data/lib/recurly/requests/coupon_updatable.rb +30 -0
  39. data/lib/recurly/requests/create_account.rb +62 -0
  40. data/lib/recurly/requests/create_coupon.rb +90 -0
  41. data/lib/recurly/requests/invoice_create.rb +42 -0
  42. data/lib/recurly/requests/invoice_refund.rb +30 -0
  43. data/lib/recurly/requests/line_item_create.rb +46 -0
  44. data/lib/recurly/requests/plan_create.rb +66 -0
  45. data/lib/recurly/requests/plan_update.rb +70 -0
  46. data/lib/recurly/requests/shipping_address_create.rb +58 -0
  47. data/lib/recurly/requests/shipping_address_update.rb +62 -0
  48. data/lib/recurly/requests/subscription_add_on_create.rb +22 -0
  49. data/lib/recurly/requests/subscription_change_create.rb +42 -0
  50. data/lib/recurly/requests/subscription_create.rb +86 -0
  51. data/lib/recurly/requests/subscription_update.rb +42 -0
  52. data/lib/recurly/requests/update_coupon.rb +30 -0
  53. data/lib/recurly/resource.rb +16 -1103
  54. data/lib/recurly/resources/account.rb +86 -0
  55. data/lib/recurly/resources/account_acquisition.rb +42 -0
  56. data/lib/recurly/resources/account_balance.rb +22 -0
  57. data/lib/recurly/resources/account_note.rb +30 -0
  58. data/lib/recurly/resources/add_on.rb +62 -0
  59. data/lib/recurly/resources/address.rb +42 -0
  60. data/lib/recurly/resources/billing_info.rb +62 -0
  61. data/lib/recurly/resources/coupon.rb +110 -0
  62. data/lib/recurly/resources/coupon_discount.rb +22 -0
  63. data/lib/recurly/resources/coupon_redemption.rb +46 -0
  64. data/lib/recurly/resources/credit_payment.rb +62 -0
  65. data/lib/recurly/resources/error.rb +18 -0
  66. data/lib/recurly/resources/error_may_have_transaction.rb +22 -0
  67. data/lib/recurly/resources/invoice.rb +138 -0
  68. data/lib/recurly/resources/invoice_collection.rb +18 -0
  69. data/lib/recurly/resources/line_item.rb +166 -0
  70. data/lib/recurly/resources/plan.rb +86 -0
  71. data/lib/recurly/resources/settings.rb +18 -0
  72. data/lib/recurly/resources/shipping_address.rb +74 -0
  73. data/lib/recurly/resources/site.rb +46 -0
  74. data/lib/recurly/resources/subscription.rb +134 -0
  75. data/lib/recurly/resources/subscription_add_on.rb +42 -0
  76. data/lib/recurly/resources/subscription_change.rb +54 -0
  77. data/lib/recurly/resources/tax_info.rb +18 -0
  78. data/lib/recurly/resources/transaction.rb +146 -0
  79. data/lib/recurly/resources/unique_coupon_code.rb +38 -0
  80. data/lib/recurly/resources/user.rb +38 -0
  81. data/lib/recurly/schema.rb +114 -0
  82. data/lib/recurly/schema/json_deserializer.rb +53 -0
  83. data/lib/recurly/schema/json_parser.rb +71 -0
  84. data/lib/recurly/schema/request_caster.rb +66 -0
  85. data/lib/recurly/schema/schema_factory.rb +50 -0
  86. data/lib/recurly/schema/schema_validator.rb +125 -0
  87. data/lib/recurly/version.rb +1 -10
  88. data/recurly.gemspec +32 -0
  89. data/scripts/build +4 -0
  90. data/scripts/clean +6 -0
  91. data/scripts/test +3 -0
  92. metadata +121 -245
  93. data/lib/recurly/account.rb +0 -190
  94. data/lib/recurly/account_acquisition.rb +0 -27
  95. data/lib/recurly/account_balance.rb +0 -21
  96. data/lib/recurly/add_on.rb +0 -44
  97. data/lib/recurly/address.rb +0 -25
  98. data/lib/recurly/adjustment.rb +0 -81
  99. data/lib/recurly/api.rb +0 -110
  100. data/lib/recurly/api/errors.rb +0 -208
  101. data/lib/recurly/api/net_http_adapter.rb +0 -111
  102. data/lib/recurly/billing_info.rb +0 -109
  103. data/lib/recurly/coupon.rb +0 -134
  104. data/lib/recurly/credit_payment.rb +0 -32
  105. data/lib/recurly/custom_field.rb +0 -15
  106. data/lib/recurly/delivery.rb +0 -19
  107. data/lib/recurly/error.rb +0 -13
  108. data/lib/recurly/gift_card.rb +0 -85
  109. data/lib/recurly/helper.rb +0 -51
  110. data/lib/recurly/invoice.rb +0 -297
  111. data/lib/recurly/invoice_collection.rb +0 -14
  112. data/lib/recurly/item.rb +0 -36
  113. data/lib/recurly/js.rb +0 -14
  114. data/lib/recurly/juris_detail.rb +0 -15
  115. data/lib/recurly/measured_unit.rb +0 -16
  116. data/lib/recurly/money.rb +0 -120
  117. data/lib/recurly/note.rb +0 -14
  118. data/lib/recurly/plan.rb +0 -43
  119. data/lib/recurly/purchase.rb +0 -234
  120. data/lib/recurly/redemption.rb +0 -46
  121. data/lib/recurly/resource/association.rb +0 -16
  122. data/lib/recurly/resource/errors.rb +0 -20
  123. data/lib/recurly/resource/pager.rb +0 -313
  124. data/lib/recurly/shipping_address.rb +0 -26
  125. data/lib/recurly/shipping_fee.rb +0 -17
  126. data/lib/recurly/shipping_method.rb +0 -13
  127. data/lib/recurly/subscription.rb +0 -365
  128. data/lib/recurly/subscription/add_ons.rb +0 -82
  129. data/lib/recurly/subscription_add_on.rb +0 -58
  130. data/lib/recurly/tax_detail.rb +0 -16
  131. data/lib/recurly/tax_type.rb +0 -13
  132. data/lib/recurly/tier.rb +0 -18
  133. data/lib/recurly/transaction.rb +0 -131
  134. data/lib/recurly/transaction/errors.rb +0 -115
  135. data/lib/recurly/usage.rb +0 -28
  136. data/lib/recurly/verify.rb +0 -12
  137. data/lib/recurly/webhook.rb +0 -111
  138. data/lib/recurly/webhook/account_notification.rb +0 -13
  139. data/lib/recurly/webhook/billing_info_update_failed_notification.rb +0 -6
  140. data/lib/recurly/webhook/billing_info_updated_notification.rb +0 -6
  141. data/lib/recurly/webhook/canceled_account_notification.rb +0 -6
  142. data/lib/recurly/webhook/canceled_gift_card_notification.rb +0 -6
  143. data/lib/recurly/webhook/canceled_subscription_notification.rb +0 -6
  144. data/lib/recurly/webhook/closed_credit_invoice_notification.rb +0 -6
  145. data/lib/recurly/webhook/closed_invoice_notification.rb +0 -6
  146. data/lib/recurly/webhook/credit_payment_notification.rb +0 -12
  147. data/lib/recurly/webhook/deactivated_item_notification.rb +0 -6
  148. data/lib/recurly/webhook/deleted_shipping_address_notification.rb +0 -6
  149. data/lib/recurly/webhook/dunning_notification.rb +0 -14
  150. data/lib/recurly/webhook/expired_subscription_notification.rb +0 -6
  151. data/lib/recurly/webhook/failed_charge_invoice_notification.rb +0 -6
  152. data/lib/recurly/webhook/failed_payment_notification.rb +0 -6
  153. data/lib/recurly/webhook/fraud_info_updated_notification.rb +0 -6
  154. data/lib/recurly/webhook/gift_card_notification.rb +0 -8
  155. data/lib/recurly/webhook/invoice_notification.rb +0 -12
  156. data/lib/recurly/webhook/item_notification.rb +0 -7
  157. data/lib/recurly/webhook/low_balance_gift_card_notification.rb +0 -6
  158. data/lib/recurly/webhook/new_account_notification.rb +0 -6
  159. data/lib/recurly/webhook/new_charge_invoice_notification.rb +0 -6
  160. data/lib/recurly/webhook/new_credit_invoice_notification.rb +0 -6
  161. data/lib/recurly/webhook/new_credit_payment_notification.rb +0 -6
  162. data/lib/recurly/webhook/new_dunning_event_notification.rb +0 -6
  163. data/lib/recurly/webhook/new_invoice_notification.rb +0 -6
  164. data/lib/recurly/webhook/new_item_notification.rb +0 -6
  165. data/lib/recurly/webhook/new_shipping_address_notification.rb +0 -6
  166. data/lib/recurly/webhook/new_subscription_notification.rb +0 -6
  167. data/lib/recurly/webhook/new_usage_notification.rb +0 -8
  168. data/lib/recurly/webhook/notification.rb +0 -18
  169. data/lib/recurly/webhook/paid_charge_invoice_notification.rb +0 -6
  170. data/lib/recurly/webhook/past_due_charge_invoice_notification.rb +0 -6
  171. data/lib/recurly/webhook/past_due_invoice_notification.rb +0 -6
  172. data/lib/recurly/webhook/paused_subscription_renewal_notification.rb +0 -6
  173. data/lib/recurly/webhook/processing_charge_invoice_notification.rb +0 -6
  174. data/lib/recurly/webhook/processing_credit_invoice_notification.rb +0 -6
  175. data/lib/recurly/webhook/processing_invoice_notification.rb +0 -6
  176. data/lib/recurly/webhook/processing_payment_notification.rb +0 -6
  177. data/lib/recurly/webhook/purchased_gift_card_notification.rb +0 -7
  178. data/lib/recurly/webhook/reactivated_account_notification.rb +0 -6
  179. data/lib/recurly/webhook/reactivated_item_notification.rb +0 -6
  180. data/lib/recurly/webhook/redeemed_gift_card_notification.rb +0 -7
  181. data/lib/recurly/webhook/regenerated_gift_card_notification.rb +0 -6
  182. data/lib/recurly/webhook/renewed_subscription_notification.rb +0 -6
  183. data/lib/recurly/webhook/reopened_charge_invoice_notification.rb +0 -6
  184. data/lib/recurly/webhook/reopened_credit_invoice_notification.rb +0 -6
  185. data/lib/recurly/webhook/scheduled_payment_notification.rb +0 -6
  186. data/lib/recurly/webhook/scheduled_subscription_pause_notification.rb +0 -6
  187. data/lib/recurly/webhook/subscription_notification.rb +0 -12
  188. data/lib/recurly/webhook/subscription_pause_canceled_notification.rb +0 -6
  189. data/lib/recurly/webhook/subscription_pause_modified_notification.rb +0 -6
  190. data/lib/recurly/webhook/subscription_paused_notification.rb +0 -6
  191. data/lib/recurly/webhook/subscription_resumed_notification.rb +0 -6
  192. data/lib/recurly/webhook/successful_payment_notification.rb +0 -6
  193. data/lib/recurly/webhook/successful_refund_notification.rb +0 -6
  194. data/lib/recurly/webhook/transaction_authorized_notification.rb +0 -6
  195. data/lib/recurly/webhook/transaction_notification.rb +0 -12
  196. data/lib/recurly/webhook/transaction_status_updated_notification.rb +0 -6
  197. data/lib/recurly/webhook/updated_account_notification.rb +0 -6
  198. data/lib/recurly/webhook/updated_balance_gift_card_notification.rb +0 -7
  199. data/lib/recurly/webhook/updated_gift_card_notification.rb +0 -6
  200. data/lib/recurly/webhook/updated_invoice_notification.rb +0 -6
  201. data/lib/recurly/webhook/updated_item_notification.rb +0 -6
  202. data/lib/recurly/webhook/updated_shipping_address_notification.rb +0 -6
  203. data/lib/recurly/webhook/updated_subscription_notification.rb +0 -6
  204. data/lib/recurly/webhook/void_payment_notification.rb +0 -6
  205. data/lib/recurly/webhook/voided_credit_invoice_notification.rb +0 -6
  206. data/lib/recurly/webhook/voided_credit_payment_notification.rb +0 -6
  207. data/lib/recurly/xml.rb +0 -122
  208. data/lib/recurly/xml/nokogiri.rb +0 -60
  209. data/lib/recurly/xml/rexml.rb +0 -52
@@ -0,0 +1,70 @@
1
+ module Recurly
2
+ module Requests
3
+ class PlanUpdate < Request
4
+
5
+ # @!attribute accounting_code
6
+ # @return [String] Accounting code for invoice line items for the plan. If no value is provided, it defaults to plan's code.
7
+ define_attribute :accounting_code, String
8
+
9
+ # @!attribute add_ons
10
+ # @return [Array[String]] Add Ons
11
+ define_attribute :add_ons, Array, {:item_type => String}
12
+
13
+ # @!attribute code
14
+ # @return [String] Unique code to identify the plan. This is used in Hosted Payment Page URLs and in the invoice exports.
15
+ define_attribute :code, String
16
+
17
+ # @!attribute currencies
18
+ # @return [Array[String]] Pricing
19
+ define_attribute :currencies, Array, {:item_type => String}
20
+
21
+ # @!attribute description
22
+ # @return [String] Optional description, not displayed.
23
+ define_attribute :description, String
24
+
25
+ # @!attribute hosted_pages
26
+ # @return [Hash] Hosted pages settings
27
+ define_attribute :hosted_pages, Hash
28
+
29
+ # @!attribute [r] id
30
+ # @return [String] Plan ID
31
+ define_attribute :id, String, {:read_only => true}
32
+
33
+ # @!attribute interval_length
34
+ # @return [Integer] Length of the plan's billing interval in `interval_unit`.
35
+ define_attribute :interval_length, Integer
36
+
37
+ # @!attribute interval_unit
38
+ # @return [String] Unit for the plan's billing interval.
39
+ define_attribute :interval_unit, String, {:enum => ["days", "months"]}
40
+
41
+ # @!attribute name
42
+ # @return [String] This name describes your plan and will appear on the Hosted Payment Page and the subscriber's invoice.
43
+ define_attribute :name, String
44
+
45
+ # @!attribute setup_fee_accounting_code
46
+ # @return [String] Accounting code for invoice line items for the plan's setup fee. If no value is provided, it defaults to plan's accounting code.
47
+ define_attribute :setup_fee_accounting_code, String
48
+
49
+ # @!attribute tax_code
50
+ # @return [String] Optional field for EU VAT merchants and Avalara AvaTax Pro merchants. If you are using Recurly's EU VAT feature, you can use values of 'unknown', 'physical', or 'digital'. If you have your own AvaTax account configured, you can use Avalara tax codes to assign custom tax rules.
51
+ define_attribute :tax_code, String
52
+
53
+ # @!attribute tax_exempt
54
+ # @return [Boolean] `true` exempts tax on the plan, `false` applies tax on the plan.
55
+ define_attribute :tax_exempt, :Boolean
56
+
57
+ # @!attribute total_billing_cycles
58
+ # @return [Integer] Automatically terminate plans after a defined number of billing cycles.
59
+ define_attribute :total_billing_cycles, Integer
60
+
61
+ # @!attribute trial_length
62
+ # @return [Integer] Length of plan's trial period in `trial_units`. `0` means `no trial`.
63
+ define_attribute :trial_length, Integer
64
+
65
+ # @!attribute trial_unit
66
+ # @return [String] Units for the plan's trial period.
67
+ define_attribute :trial_unit, String, {:enum => ["days", "months"]}
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,58 @@
1
+ module Recurly
2
+ module Requests
3
+ class ShippingAddressCreate < Request
4
+
5
+ # @!attribute city
6
+ # @return [String]
7
+ define_attribute :city, String
8
+
9
+ # @!attribute company
10
+ # @return [String]
11
+ define_attribute :company, String
12
+
13
+ # @!attribute country
14
+ # @return [String] Country, 2-letter ISO code.
15
+ define_attribute :country, String
16
+
17
+ # @!attribute email
18
+ # @return [String]
19
+ define_attribute :email, String
20
+
21
+ # @!attribute first_name
22
+ # @return [String]
23
+ define_attribute :first_name, String
24
+
25
+ # @!attribute last_name
26
+ # @return [String]
27
+ define_attribute :last_name, String
28
+
29
+ # @!attribute nickname
30
+ # @return [String]
31
+ define_attribute :nickname, String
32
+
33
+ # @!attribute phone
34
+ # @return [String]
35
+ define_attribute :phone, String
36
+
37
+ # @!attribute postal_code
38
+ # @return [String] Zip or postal code.
39
+ define_attribute :postal_code, String
40
+
41
+ # @!attribute region
42
+ # @return [String] State or province.
43
+ define_attribute :region, String
44
+
45
+ # @!attribute street1
46
+ # @return [String]
47
+ define_attribute :street1, String
48
+
49
+ # @!attribute street2
50
+ # @return [String]
51
+ define_attribute :street2, String
52
+
53
+ # @!attribute vat_number
54
+ # @return [String]
55
+ define_attribute :vat_number, String
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,62 @@
1
+ module Recurly
2
+ module Requests
3
+ class ShippingAddressUpdate < Request
4
+
5
+ # @!attribute city
6
+ # @return [String]
7
+ define_attribute :city, String
8
+
9
+ # @!attribute company
10
+ # @return [String]
11
+ define_attribute :company, String
12
+
13
+ # @!attribute country
14
+ # @return [String] Country, 2-letter ISO code.
15
+ define_attribute :country, String
16
+
17
+ # @!attribute email
18
+ # @return [String]
19
+ define_attribute :email, String
20
+
21
+ # @!attribute first_name
22
+ # @return [String]
23
+ define_attribute :first_name, String
24
+
25
+ # @!attribute [r] id
26
+ # @return [String] Shipping Address ID
27
+ define_attribute :id, String, {:read_only => true}
28
+
29
+ # @!attribute last_name
30
+ # @return [String]
31
+ define_attribute :last_name, String
32
+
33
+ # @!attribute nickname
34
+ # @return [String]
35
+ define_attribute :nickname, String
36
+
37
+ # @!attribute phone
38
+ # @return [String]
39
+ define_attribute :phone, String
40
+
41
+ # @!attribute postal_code
42
+ # @return [String] Zip or postal code.
43
+ define_attribute :postal_code, String
44
+
45
+ # @!attribute region
46
+ # @return [String] State or province.
47
+ define_attribute :region, String
48
+
49
+ # @!attribute street1
50
+ # @return [String]
51
+ define_attribute :street1, String
52
+
53
+ # @!attribute street2
54
+ # @return [String]
55
+ define_attribute :street2, String
56
+
57
+ # @!attribute vat_number
58
+ # @return [String]
59
+ define_attribute :vat_number, String
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,22 @@
1
+ module Recurly
2
+ module Requests
3
+ class SubscriptionAddOnCreate < Request
4
+
5
+ # @!attribute code
6
+ # @return [String] Add-on code
7
+ define_attribute :code, String
8
+
9
+ # @!attribute id
10
+ # @return [String] Id of an existing subscription add on.
11
+ define_attribute :id, String
12
+
13
+ # @!attribute quantity
14
+ # @return [Integer] Optionally override the default quantity.
15
+ define_attribute :quantity, Integer
16
+
17
+ # @!attribute unit_amount
18
+ # @return [Float] Override the default unit amount of the add-on by setting this value.
19
+ define_attribute :unit_amount, Float
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ module Recurly
2
+ module Requests
3
+ class SubscriptionChangeCreate < Request
4
+
5
+ # @!attribute add_ons
6
+ # @return [Array[SubscriptionAddOnCreate]] If you set this value you include all the add-ons and their quantities and amounts. The values you include will replace the previous values entirely.
7
+ define_attribute :add_ons, Array, {:item_type => :SubscriptionAddOnCreate}
8
+
9
+ # @!attribute collection_method
10
+ # @return [String] Collection method
11
+ define_attribute :collection_method, String, {:enum => ["automatic", "manual"]}
12
+
13
+ # @!attribute coupon_codes
14
+ # @return [Array[String]] A list of coupon_codes to be redeemed on the subscription during the change. Only allowed if timeframe is now and you change something about the subscription that creates an invoice.
15
+ define_attribute :coupon_codes, Array, {:item_type => String}
16
+
17
+ # @!attribute net_terms
18
+ # @return [Integer] Integer representing the number of days after an invoice's creation that the invoice will become past due. If an invoice's net terms are set to '0', it is due 'On Receipt' and will become past due 24 hours after it’s created. If an invoice is due net 30, it will become past due at 31 days exactly.
19
+ define_attribute :net_terms, Integer
20
+
21
+ # @!attribute plan_code
22
+ # @return [String] The plan code.
23
+ define_attribute :plan_code, String
24
+
25
+ # @!attribute po_number
26
+ # @return [String] For manual invoicing, this identifies the PO number associated with the subscription.
27
+ define_attribute :po_number, String
28
+
29
+ # @!attribute quantity
30
+ # @return [Integer] Optionally override the default quantity of 1.
31
+ define_attribute :quantity, Integer
32
+
33
+ # @!attribute timeframe
34
+ # @return [String] The timeframe parameter controls when the upgrade or downgrade takes place. The subscription change can occur now or when the subscription renews. Generally, if you're performing an upgrade, you will want the change to occur immediately (now). If you're performing a downgrade, you should set the timeframe to "renewal" so the change takes affect at the end of the current billing cycle.
35
+ define_attribute :timeframe, String, {:enum => ["now", "renewal"]}
36
+
37
+ # @!attribute unit_amount
38
+ # @return [Float] Optionally, sets custom pricing for the subscription, overriding the plan's default unit amount. The subscription's current currency will be used.
39
+ define_attribute :unit_amount, Float
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,86 @@
1
+ module Recurly
2
+ module Requests
3
+ class SubscriptionCreate < Request
4
+
5
+ # @!attribute account
6
+ # @return [Hash] Account
7
+ define_attribute :account, Hash
8
+
9
+ # @!attribute add_ons
10
+ # @return [Array[String]] Add-ons
11
+ define_attribute :add_ons, Array, {:item_type => String}
12
+
13
+ # @!attribute collection_method
14
+ # @return [String] Collection method
15
+ define_attribute :collection_method, String, {:enum => ["automatic", "manual"]}
16
+
17
+ # @!attribute coupon_code
18
+ # @return [String] Optional coupon code to redeem on the account and discount the subscription. Please note, the subscription request will fail if the coupon is invalid.
19
+ define_attribute :coupon_code, String
20
+
21
+ # @!attribute credit_customer_notes
22
+ # @return [String] If there are pending credits on the account that will be invoiced during the subscription creation, these will be used as the Customer Notes on the credit invoice.
23
+ define_attribute :credit_customer_notes, String
24
+
25
+ # @!attribute currency
26
+ # @return [String] 3-letter ISO 4217 currency code.
27
+ define_attribute :currency, String
28
+
29
+ # @!attribute customer_notes
30
+ # @return [String] This will default to the Customer Notes text specified on the Invoice Settings. Specify custom notes to add or override Customer Notes. Custom notes will stay with a subscription on all renewals.
31
+ define_attribute :customer_notes, String
32
+
33
+ # @!attribute first_renewal_date
34
+ # @return [DateTime] If set,indicates when the first renewal should occur. Subsequent renewals will be offset from this date. The first invoice will be prorated appropriately so that the customer only pays for the portion of the first billing period for which the subscription applies. Useful for forcing a subscription to renew on the first of the month.
35
+ define_attribute :first_renewal_date, DateTime
36
+
37
+ # @!attribute net_terms
38
+ # @return [Integer] Integer representing the number of days after an invoice's creation that the invoice will become past due. If an invoice's net terms are set to '0', it is due 'On Receipt' and will become past due 24 hours after it’s created. If an invoice is due net 30, it will become past due at 31 days exactly.
39
+ define_attribute :net_terms, Integer
40
+
41
+ # @!attribute plan_code
42
+ # @return [String] Plan code
43
+ define_attribute :plan_code, String
44
+
45
+ # @!attribute plan_id
46
+ # @return [String] Plan ID
47
+ define_attribute :plan_id, String
48
+
49
+ # @!attribute po_number
50
+ # @return [String] For manual invoicing, this identifies the PO number associated with the subscription.
51
+ define_attribute :po_number, String
52
+
53
+ # @!attribute quantity
54
+ # @return [Integer] Optionally override the default quantity of 1.
55
+ define_attribute :quantity, Integer
56
+
57
+ # @!attribute shipping_address
58
+ # @return [Hash] Create a shipping address on the account and assign it to the subscription.
59
+ define_attribute :shipping_address, Hash
60
+
61
+ # @!attribute shipping_address_id
62
+ # @return [String] Assign a shipping address from the account's existing shipping addresses. If this and `shipping_address` are both present, `shipping_address` will take precedence.
63
+ define_attribute :shipping_address_id, String
64
+
65
+ # @!attribute starts_at
66
+ # @return [DateTime] If set, the subscription will begin in the future on this date. The subscription will apply the setup fee and trial period, unless the plan has no trial.
67
+ define_attribute :starts_at, DateTime
68
+
69
+ # @!attribute terms_and_conditions
70
+ # @return [String] This will default to the Terms and Conditions text specified on the Invoice Settings page in your Recurly admin. Specify custom notes to add or override Terms and Conditions. Custom notes will stay with a subscription on all renewals.
71
+ define_attribute :terms_and_conditions, String
72
+
73
+ # @!attribute total_billing_cycles
74
+ # @return [Integer] Renews the subscription for a specified number of total cycles, then automatically cancels. Defaults to the subscription renewing indefinitely.
75
+ define_attribute :total_billing_cycles, Integer
76
+
77
+ # @!attribute trial_ends_at
78
+ # @return [DateTime] If set, overrides the default trial behavior for the subscription. The date must be in the future.
79
+ define_attribute :trial_ends_at, DateTime
80
+
81
+ # @!attribute unit_amount
82
+ # @return [Float] Override the unit amount of the subscription plan by setting this value in cents. If not provided, the subscription will inherit the price from the subscription plan for the provided currency.
83
+ define_attribute :unit_amount, Float
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,42 @@
1
+ module Recurly
2
+ module Requests
3
+ class SubscriptionUpdate < Request
4
+
5
+ # @!attribute collection_method
6
+ # @return [String] Change collection method
7
+ define_attribute :collection_method, String, {:enum => ["automatic", "manual"]}
8
+
9
+ # @!attribute customer_notes
10
+ # @return [String] Specify custom notes to add or override Customer Notes. Custom notes will stay with a subscription on all renewals.
11
+ define_attribute :customer_notes, String
12
+
13
+ # @!attribute net_terms
14
+ # @return [Integer] Integer representing the number of days after an invoice's creation that the invoice will become past due. If an invoice's net terms are set to '0', it is due 'On Receipt' and will become past due 24 hours after it’s created. If an invoice is due net 30, it will become past due at 31 days exactly.
15
+ define_attribute :net_terms, Integer
16
+
17
+ # @!attribute next_renewal_at
18
+ # @return [DateTime] For an active subscription, this will change the next renewal date. For a subscription in a trial period, modifying the renewal date will change when the trial expires.
19
+ define_attribute :next_renewal_at, DateTime
20
+
21
+ # @!attribute po_number
22
+ # @return [String] For manual invoicing, this identifies the PO number associated with the subscription.
23
+ define_attribute :po_number, String
24
+
25
+ # @!attribute remaining_billing_cycles
26
+ # @return [Integer] Renews the subscription for a specified number of cycles, then automatically cancels.
27
+ define_attribute :remaining_billing_cycles, Integer
28
+
29
+ # @!attribute shipping_address
30
+ # @return [Hash] Create a shipping address on the account and assign it to the subscription. If this and `shipping_address_id` are both present, `shipping_address_id` will take precedence."
31
+ define_attribute :shipping_address, Hash
32
+
33
+ # @!attribute shipping_address_id
34
+ # @return [String] Assign a shipping address from the account's existing shipping addresses.
35
+ define_attribute :shipping_address_id, String
36
+
37
+ # @!attribute terms_and_conditions
38
+ # @return [String] Specify custom notes to add or override Terms and Conditions. Custom notes will stay with a subscription on all renewals.
39
+ define_attribute :terms_and_conditions, String
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,30 @@
1
+ module Recurly
2
+ module Requests
3
+ class UpdateCoupon < Request
4
+
5
+ # @!attribute hosted_description
6
+ # @return [String] This description will show up when a customer redeems a coupon on your Hosted Payment Pages, or if you choose to show the description on your own checkout page.
7
+ define_attribute :hosted_description, String
8
+
9
+ # @!attribute invoice_description
10
+ # @return [String] Description of the coupon on the invoice.
11
+ define_attribute :invoice_description, String
12
+
13
+ # @!attribute max_redemptions
14
+ # @return [Integer] A maximum number of redemptions for the coupon. The coupon will expire when it hits its maximum redemptions.
15
+ define_attribute :max_redemptions, Integer
16
+
17
+ # @!attribute max_redemptions_per_account
18
+ # @return [Integer] Redemptions per account is the number of times a specific account can redeem the coupon. Set redemptions per account to `1` if you want to keep customers from gaming the system and getting more than one discount from the coupon campaign.
19
+ define_attribute :max_redemptions_per_account, Integer
20
+
21
+ # @!attribute name
22
+ # @return [String] The internal name for the coupon.
23
+ define_attribute :name, String
24
+
25
+ # @!attribute redeem_by_date
26
+ # @return [String] The date and time the coupon will expire and can no longer be redeemed. Time is always 11:59:59, the end-of-day Pacific time.
27
+ define_attribute :redeem_by_date, String
28
+ end
29
+ end
30
+ end
@@ -1,1122 +1,35 @@
1
- require 'date'
2
- require 'erb'
3
-
4
1
  module Recurly
5
- # The base class for all Recurly resources (e.g. {Account}, {Subscription},
6
- # {Transaction}).
7
- #
8
- # Resources behave much like
9
- # {ActiveModel}[http://rubydoc.info/gems/activemodel] classes, especially
10
- # like {ActiveRecord}[http://rubydoc.info/gems/activerecord].
11
- #
12
- # == Life Cycle
13
- #
14
- # To take you through the typical life cycle of a resource, we'll use
15
- # {Recurly::Account} as an example.
16
- #
17
- # === Creating a Record
18
- #
19
- # You can instantiate a record before attempting to save it.
20
- #
21
- # account = Recurly::Account.new :first_name => 'Walter'
22
- #
23
- # Once instantiated, you can assign and reassign any attribute.
24
- #
25
- # account.first_name = 'Walt'
26
- # account.last_name = 'White'
27
- #
28
- # When you're ready to save, do so.
29
- #
30
- # account.save # => false
31
- #
32
- # If save returns +false+, validation likely failed. You can check the record
33
- # for errors.
34
- #
35
- # account.errors # => {"account_code"=>["can't be blank"]}
36
- #
37
- # Once the errors are fixed, you can try again.
38
- #
39
- # account.account_code = 'heisenberg'
40
- # account.save # => true
41
- #
42
- # The object will be updated with any information provided by the server
43
- # (including any UUIDs set).
44
- #
45
- # account.created_at # => 2011-04-30 07:13:35 -0700
46
- #
47
- # You can also create accounts in one fell swoop.
48
- #
49
- # Recurly::Account.create(
50
- # :first_name => 'Jesse'
51
- # :last_name => 'Pinkman'
52
- # :account_code => 'capn_cook'
53
- # )
54
- # # => #<Recurly::Account account_code: "capn_cook" ...>
55
- #
56
- # You can use alternative "bang" methods for exception control. If the record
57
- # fails to save, a Recurly::Resource::Invalid exception will be raised.
58
- #
59
- # begin
60
- # account = Recurly::Account.new :first_name => 'Junior'
61
- # account.save!
62
- # rescue Recurly::Resource::Invalid
63
- # p account.errors
64
- # end
65
- #
66
- # You can access the invalid record from the exception itself (if, for
67
- # example, you use the <tt>create!</tt> method).
68
- #
69
- # begin
70
- # Recurly::Account.create! :first_name => 'Skylar', :last_name => 'White'
71
- # rescue Recurly::Resource::Invalid => e
72
- # p e.record.errors
73
- # end
74
- #
75
- # === Fetching a Record
76
- #
77
- # Records are fetched by their unique identifiers.
78
- #
79
- # account = Recurly::Account.find 'better_call_saul'
80
- # # => #<Recurly::Account account_code: "better_call_saul" ...>
81
- #
82
- # If the record doesn't exist, a Recurly::Resource::NotFound exception will
83
- # be raised.
84
- #
85
- # === Updating a Record
86
- #
87
- # Once fetched, a record can be updated with a hash of attributes.
88
- #
89
- # account.update_attributes :first_name => 'Saul', :last_name => 'Goodman'
90
- # # => true
91
- #
92
- # (A bang method, update_attributes!, will raise Recurly::Resource::Invalid.)
93
- #
94
- # You can also update a record by setting attributes and calling save.
95
- #
96
- # account.last_name = 'McGill'
97
- # account.save # Alternatively, call save!
98
- #
99
- # === Deleting a Record
100
- #
101
- # To delete (deactivate, close, etc.) a fetched record, merely call destroy
102
- # on it.
103
- #
104
- # account.destroy # => true
105
- #
106
- # === Fetching a List of Records
107
- #
108
- # If you want to iterate over a list of accounts, you can use a Pager.
109
- #
110
- # pager = Account.paginate :per_page => 50
111
- #
112
- # If you want to iterate over _every_ record, a convenience method will
113
- # automatically paginate:
114
- #
115
- # Account.find_each { |account| p account }
2
+ # This class represents an object as it exists on the
3
+ # Recurly servers. It is generated from a response. If you wish to
4
+ # update or change a resource, you need to send a request to the server
5
+ # and get a new Resource.
116
6
  class Resource
117
- require 'recurly/resource/errors'
118
- require 'recurly/resource/pager'
119
- require 'recurly/resource/association'
120
-
121
- # Raised when a record cannot be found.
122
- #
123
- # @example
124
- # begin
125
- # Recurly::Account.find 'tortuga'
126
- # rescue Recurly::Resource::NotFound => e
127
- # e.message # => "Can't find Account with account_code = tortuga"
128
- # end
129
- class NotFound < API::NotFound
130
- def initialize(message)
131
- set_message message
132
- end
133
- end
134
-
135
- # Raised when a record is invalid.
136
- #
137
- # @example
138
- # begin
139
- # Recurly::Account.create! :first_name => "Flynn"
140
- # rescue Recurly::Resource::Invalid => e
141
- # e.record.errors # => errors: {"account_code"=>["can't be blank"]}>
142
- # end
143
- class Invalid < Error
144
- # @return [Resource, nil] The invalid record.
145
- attr_reader :record
146
-
147
- def initialize(message)
148
- if message.is_a? Resource
149
- @record = message
150
- set_message(record_to_message)
151
- else
152
- set_message(message)
153
- end
154
- end
155
-
156
- private
157
-
158
- def record_to_message
159
- @record.errors.map do |k, v|
160
- message = v.join(', ')
161
- k == 'base' ? message : "#{k} #{message}"
162
- end.join('; ')
163
- end
164
- end
165
-
166
- class << self
167
- # @return [String] The demodulized name of the resource class.
168
- # @example
169
- # Recurly::Account.name # => "Account"
170
- def resource_name
171
- Helper.demodulize name
172
- end
173
-
174
- # @return [String] The underscored, pluralized name of the resource
175
- # class.
176
- # @example
177
- # Recurly::Account.collection_name # => "accounts"
178
- def collection_name
179
- Helper.pluralize Helper.underscore(resource_name)
180
- end
181
- alias collection_path collection_name
182
-
183
- # @return [String] The underscored name of the resource class.
184
- # @example
185
- # Recurly::Account.member_name # => "account"
186
- def member_name
187
- Helper.underscore resource_name
188
- end
189
-
190
- # @return [String] The relative path to a resource's identifier from the
191
- # API's base URI.
192
- # @param uuid [String, nil]
193
- # @example
194
- # Recurly::Account.member_path "code" # => "accounts/code"
195
- # Recurly::Account.member_path nil # => "accounts"
196
- def member_path(uuid)
197
- uuid = ERB::Util.url_encode(uuid) if uuid
198
- [collection_path, uuid].compact.join '/'
199
- end
200
-
201
- # @return [String] The root key for this resource's xml document
202
- def xml_root_key
203
- self.member_name
204
- end
205
-
206
- # @return [Array] Per attribute, defines readers, writers, boolean and
207
- # change-tracking methods.
208
- # @param attribute_names [Array] An array of attribute names.
209
- # @example
210
- # class Account < Resource
211
- # define_attribute_methods [:name]
212
- # end
213
- #
214
- # a = Account.new
215
- # a.name? # => false
216
- # a.name # => nil
217
- # a.name = "Stephen"
218
- # a.name? # => true
219
- # a.name # => "Stephen"
220
- # a.name_changed? # => true
221
- # a.name_was # => nil
222
- # a.name_change # => [nil, "Stephen"]
223
- def define_attribute_methods(attribute_names)
224
- @attribute_names = attribute_names.map! { |m| m.to_s }.sort!.freeze
225
- remove_const :AttributeMethods if constants.include? :AttributeMethods
226
- include const_set :AttributeMethods, Module.new {
227
- attribute_names.each do |name|
228
- define_method(name) { self[name] } # Get.
229
- define_method("#{name}=") { |value| self[name] = value } # Set.
230
- define_method("#{name}?") { !!self[name] } # Present.
231
- define_method("#{name}_change") { changes[name] } # Dirt...
232
- define_method("#{name}_changed?") { changed_attributes.key? name }
233
- define_method("#{name}_was") { changed_attributes[name] }
234
- define_method("#{name}_previously_changed?") {
235
- previous_changes.key? name
236
- }
237
- define_method("#{name}_previously_was") {
238
- previous_changes[name].first if previous_changes.key? name
239
- }
240
- end
241
- }
242
- end
243
-
244
- # @return [Array, nil] The list of attribute names defined for the
245
- # resource class.
246
- attr_reader :attribute_names
247
-
248
- # @return [Pager] A pager with an iterable collection of records
249
- # @param options [Hash] A hash of pagination options
250
- # @option options [Integer] :per_page The number of records returned per
251
- # page
252
- # @option options [DateTime, Time, Integer] :cursor A timestamp that the
253
- # pager will skim back to and return records created before it
254
- # @option options [String] :etag When set, will raise
255
- # {Recurly::API::NotModified} if the pager's loaded page content has
256
- # not changed
257
- # @example Fetch 50 records and iterate over them
258
- # Recurly::Account.paginate(:per_page => 50).each { |a| p a }
259
- # @example Fetch records before January 1, 2011
260
- # Recurly::Account.paginate(:cursor => Time.new(2011, 1, 1))
261
- def paginate(options = {})
262
- Pager.new self, options
263
- end
264
- alias scoped paginate
265
- alias where paginate
266
-
267
- def all(options = {})
268
- paginate(options).to_a
269
- end
270
-
271
- # @return [Hash] Defined scopes per resource.
272
- def scopes
273
- @scopes ||= Recurly::Helper.hash_with_indifferent_read_access
274
- end
275
-
276
- # @return [Module] Module of scopes methods.
277
- def scopes_helper
278
- @scopes_helper ||= Module.new.tap { |helper| extend helper }
279
- end
280
-
281
- # Defines a new resource scope.
282
- #
283
- # @return [Proc]
284
- # @param [Symbol] name the scope name
285
- # @param [Hash] params the scope params
286
- def scope(name, params = {})
287
- scopes[name = name.to_s] = params
288
- scopes_helper.send(:define_method, name) { paginate scopes[name] }
289
- end
290
-
291
- # Iterates through every record by automatically paging.
292
- #
293
- # @option options [Hash] Optional hash to pass to Pager#paginate
294
- #
295
- # @return [nil]
296
- # @param [Integer] per_page The number of records returned per request.
297
- # @yield [record]
298
- # @see Pager#paginate
299
- # @example
300
- # Recurly::Account.find_each { |a| p a }
301
- # @example With sorting and filter
302
- # opts = {
303
- # begin_time: DateTime.new(2016,1,1),
304
- # sort: :updated_at
305
- # }
306
- # Recurly::Account.find_each(opts) do |a|
307
- # puts a.inspect
308
- # end
309
- def find_each(options = {}, &block)
310
- paginate(options).find_each(&block)
311
- end
312
-
313
- # @return [Integer] The total record count of the resource in question.
314
- # @see Pager#count
315
- # @example
316
- # Recurly::Account.count # => 42
317
- def count
318
- paginate.count
319
- end
320
-
321
- # @api internal
322
- # @return [Resource, nil]
323
- def first
324
- paginate(:per_page => 1).first
325
- end
326
-
327
- # @return [Resource] A record matching the designated unique identifier.
328
- # @param [String] uuid The unique identifier of the resource to be
329
- # retrieved.
330
- # @param [Hash] options A hash of options.
331
- # @option options [String] :etag When set, will raise {API::NotModified}
332
- # if the record content has not changed.
333
- # @raise [Error] If the resource has no identifier (and thus cannot be
334
- # retrieved).
335
- # @raise [NotFound] If no resource can be found for the supplied
336
- # identifier (or the supplied identifier is +nil+).
337
- # @raise [API::NotModified] If the <tt>:etag</tt> option is set and
338
- # matches the server's.
339
- # @example
340
- # Recurly::Account.find "heisenberg"
341
- # # => #<Recurly::Account account_code: "heisenberg", ...>
342
- # Use the following identifiers for these types of objects:
343
- # for accounts use account_code
344
- # for plans use plan_code
345
- # for invoices use invoice_number
346
- # for subscriptions use uuid
347
- # for transactions use uuid
348
- def find(uuid, options = {})
349
- if uuid.nil? || uuid.to_s.empty?
350
- raise NotFound, "can't find a record with nil identifier"
351
- end
352
-
353
- begin
354
- from_response API.get(member_path(uuid), {}, options)
355
- rescue API::NotFound => e
356
- raise NotFound, e.description
357
- end
358
- end
359
-
360
- # Instantiates and attempts to save a record.
361
- #
362
- # @return [Resource] The record.
363
- # @raise [Transaction::Error] A monetary transaction failed.
364
- # @see create!
365
- def create(attributes = {})
366
- new(attributes) { |record| record.save }
367
- end
368
-
369
- # Instantiates and attempts to save a record.
370
- #
371
- # @return [Resource] The saved record.
372
- # @raise [Invalid] The record is invalid.
373
- # @raise [Transaction::Error] A monetary transaction failed.
374
- # @see create
375
- def create!(attributes = {})
376
- new(attributes) { |record| record.save! }
377
- end
378
-
379
- # Instantiates a record from an HTTP response, setting the record's
380
- # response attribute in the process.
381
- #
382
- # @return [Resource]
383
- # @param response [Net::HTTPResponse]
384
- def from_response(response)
385
- content_type = response['Content-Type']
386
-
387
- case content_type
388
- when %r{application/pdf}
389
- response.body
390
- when %r{application/xml}
391
- record = from_xml response.body
392
- record.instance_eval { @etag, @response = response['ETag'], response }
393
- record
394
- else
395
- raise Recurly::Error, "Content-Type \"#{content_type}\" is not accepted"
396
- end
397
- end
398
-
399
- # Instantiates a record from an XML blob: either a String or XML element.
400
- #
401
- # Assuming the record is from an API response, the record is flagged as
402
- # persisted.
403
- #
404
- # @return [Resource]
405
- # @param xml [String, REXML::Element, Nokogiri::XML::Node]
406
- # @see from_response
407
- def from_xml(xml)
408
- xml = XML.new xml
409
- record = new
410
-
411
- xml.root.attributes.each do |name, value|
412
- record.instance_variable_set "@#{name}", value.to_s
413
- end
414
-
415
- xml.each_element do |el|
416
- # skip this element if it's an xml comment
417
- next if defined?(Nokogiri::XML::Node::TEXT_NODE) && el.is_a?(Nokogiri::XML::Comment)
418
-
419
- if el.name == 'a'
420
- record.links[el.attribute('name').value] = {
421
- :method => el.attribute('method').to_s,
422
- :href => el.attribute('href').value
423
- }
424
- next
425
- end
7
+ extend Schema::SchemaFactory
8
+ extend Schema::JsonDeserializer
9
+ include Schema::SchemaValidator
426
10
 
427
- # Nokogiri on Jruby-1.7.19 likes to throw NullPointer exceptions
428
- # if you try to run certian operations like el.attribute(''). Since
429
- # we dont care about text nodes, let's just skip them
430
- next if defined?(Nokogiri::XML::Node::TEXT_NODE) && el.node_type == Nokogiri::XML::Node::TEXT_NODE
431
-
432
- if association = find_association(el.name)
433
- class_name = association_class_name(association, el.name)
434
- resource_class = Recurly.const_get(class_name)
435
- is_many = association.relation == :has_many
436
-
437
- # Is this a link, or is it embedded data?
438
- if el.children.empty? && href = el.attribute('href')
439
- if is_many
440
- record[el.name] = Pager.new(
441
- resource_class, :uri => href.value, :parent => record
442
- )
443
- else
444
- record.links[el.name] = {
445
- :resource_class => resource_class,
446
- :method => :get,
447
- :href => href.value
448
- }
449
- end
450
- else
451
- if is_many
452
- resources = el.elements.map { |e| resource_class.from_xml(e) }
453
- record[el.name] = resources
454
- else
455
- record[el.name] = resource_class.from_xml(el)
456
- end
457
- end
458
- else
459
- # TODO name tax_type conflicts with the TaxType
460
- # class so if we get to this point was can assume
461
- # it's the string. Will need to refactor this
462
- if el.name == 'tax_type'
463
- record[el.name] = el.text
464
- else
465
- val = XML.cast(el)
466
-
467
- # TODO we have to clear changed attributes after
468
- # parsing here or else it always serializes. Need
469
- # a better way of handling changed attributes
470
- if el.name == 'address' && val.kind_of?(Hash)
471
- address = Address.new(val)
472
- address.instance_variable_set(:@changed_attributes, {})
473
- record[el.name] = address
474
- else
475
- record[el.name] = val
476
- end
477
- end
478
- end
479
- end
480
-
481
- record.persist! if record.respond_to? :persist!
482
- record
483
- end
484
-
485
- # @return [Array] A list of associations for the current class.
486
- def associations
487
- @associations ||= []
488
- end
489
-
490
- # @return [Array] A list of associated resource classes with
491
- # the relation [:has_many, :has_one, :belongs_to] for the current class.
492
- def associations_for_relation(relation)
493
- associations.select{ |a| a.relation == relation }.map(&:resource_class)
494
- end
495
-
496
- def association_class_name(association, el_name)
497
- return association.class_name if association.class_name
498
- Helper.classify(el_name)
499
- end
500
-
501
- # @return [Association, nil] Find association for the current class
502
- # with resource class name.
503
- def find_association(resource_class)
504
- associations.find{ |a| a.resource_class.to_s == resource_class.to_s }
505
- end
506
-
507
- def associations_helper
508
- @associations_helper ||= Module.new.tap { |helper| include helper }
509
- end
510
-
511
- # Establishes a has_many association.
512
- #
513
- # @return [Proc, nil]
514
- # @param collection_name [Symbol] Association name.
515
- # @param options [Hash] A hash of association options.
516
- # @option options [true, false] :readonly Define a setter when false, defaults to true
517
- # [String] :class_name Actual associated resource class name
518
- # if not same as collection_name.
519
- def has_many(collection_name, options = {})
520
- associations << Association.new(:has_many, collection_name.to_s, options)
521
- associations_helper.module_eval do
522
- define_method collection_name do
523
- if self[collection_name]
524
- self[collection_name]
525
- else
526
- attributes[collection_name.to_s] = []
527
- end
528
- end
529
- if options.key?(:readonly) && options[:readonly] == false
530
- define_method "#{collection_name}=" do |collection|
531
- self[collection_name] = collection
532
- end
533
- end
534
- end
535
- end
536
-
537
- # Establishes a has_one association.
538
- #
539
- # @return [Proc, nil]
540
- # @param member_name [Symbol] Association name.
541
- # @param options [Hash] A hash of association options.
542
- # @option options [true, false] :readonly Don't define a setter.
543
- # [String] :class_name Actual associated resource class name
544
- # if not same as member_name.
545
- def has_one(member_name, options = {})
546
- associations << Association.new(:has_one, member_name.to_s, options)
547
- associations_helper.module_eval do
548
- define_method(member_name) { self[member_name] }
549
- if options.key?(:readonly) && options[:readonly] == false
550
- associated = Recurly.const_get Helper.classify(member_name), false
551
- define_method "#{member_name}=" do |member|
552
- associated_uri = "#{path}/#{member_name}"
553
- self[member_name] = case member
554
- when Hash
555
- associated.send :new, member.merge(:uri => associated_uri)
556
- when associated
557
- member.uri = associated_uri and member
558
- else
559
- raise ArgumentError, "expected #{associated}"
560
- end
561
- end
562
- define_method "build_#{member_name}" do |*args|
563
- attributes = args.shift || {}
564
- self[member_name] = associated.send(
565
- :new, attributes.merge(:uri => "#{path}/#{associated.member_name}")
566
- ).tap { |child| child.attributes[self.class.member_name] = self }
567
- end
568
- define_method "create_#{member_name}" do |*args|
569
- send("build_#{member_name}", *args).tap { |child| child.save }
570
- end
571
- end
572
- end
573
- end
574
-
575
- # Establishes a belongs_to association.
576
- #
577
- # @return [Proc]
578
- # @param parent_name [Symbol] Association name.
579
- # @param options [Hash] A hash of association options.
580
- # @option options [true, false] :readonly Don't define a setter.
581
- # [String] :class_name Actual associated resource class name
582
- # if not same as parent_name.
583
- def belongs_to(parent_name, options = {})
584
- associations << Association.new(:belongs_to, parent_name.to_s, options)
585
- associations_helper.module_eval do
586
- define_method(parent_name) { self[parent_name] }
587
- if options.key?(:readonly) && options[:readonly] == false
588
- define_method "#{parent_name}=" do |parent|
589
- self[parent_name] = parent
590
- end
591
- end
592
- end
593
- end
594
-
595
- # @return [:has_many, :has_one, :belongs_to, nil] An association type.
596
- def reflect_on_association(name)
597
- a = find_association(name)
598
- a.relation if a
599
- end
600
-
601
- def embedded!(root_index = false)
602
- protected :initialize
603
- private_class_method(*%w(create create!))
604
- unless root_index
605
- private_class_method(*%w(all find_each first paginate scoped where))
606
- end
607
- end
608
-
609
- def find_resource_class(name)
610
- resource_name = Helper.classify(name)
611
- if Recurly.const_defined?(resource_name, false)
612
- Recurly.const_get(resource_name, false)
613
- end
614
- end
615
- end
616
-
617
- # @return [Hash] The raw hash of record attributes.
618
11
  attr_reader :attributes
619
12
 
620
- # @return [Net::HTTPResponse, nil] The most recent response object for the
621
- # record (updated during {#save} and {#destroy}).
622
- attr_reader :response
623
-
624
- # @return [String, nil] An ETag for the current record.
625
- attr_reader :etag
626
-
627
- # @return [String, nil] A writer to override the URI the record saves to.
628
- attr_writer :uri
629
-
630
- # @return [Resource] A new resource instance.
631
- # @param attributes [Hash] A hash of attributes.
632
- def initialize(attributes = {})
633
- if instance_of? Resource
634
- raise Error,
635
- "#{self.class} is an abstract class and cannot be instantiated"
636
- end
637
-
638
- @attributes, @new_record, @destroyed, @uri, @href = {}, true, false
639
- self.attributes = attributes
640
- yield self if block_given?
641
- end
642
-
643
- # @return [self] Reloads the record from the server.
644
- def reload(response = nil)
645
- if response
646
- return if response.body.to_s.length.zero?
647
- fresh = self.class.from_response response
648
- else
649
- options = {:etag => (etag unless changed?)}
650
- fresh = if @href
651
- self.class.from_response API.get(@href, {}, options)
652
- else
653
- self.class.find(to_param, options)
654
- end
655
- end
656
- fresh and copy_from fresh
657
- persist! true
658
- self
659
- rescue API::NotModified
660
- self
661
- end
662
-
663
- # @return [Hash] Hash of changed attributes.
664
- # @see #changes
665
- def changed_attributes
666
- @changed_attributes ||= {}
667
- end
668
-
669
- # @return [Array] A list of changed attribute keys.
670
- def changed
671
- changed_attributes.keys
672
- end
673
-
674
- # Do any attributes have unsaved changes?
675
- # @return [true, false]
676
- def changed?
677
- !changed_attributes.empty?
678
- end
679
-
680
- # @return [Hash] Map of changed attributes to original value and new value.
681
- def changes
682
- changed_attributes.inject({}) { |changes, (key, original_value)|
683
- changes[key] = [original_value, self[key]] and changes
684
- }
685
- end
686
-
687
- # @return [Hash] Previously-changed attributes.
688
- # @see #changes
689
- def previous_changes
690
- @previous_changes ||= {}
691
- end
692
-
693
- # Is the record new (i.e., not saved on Recurly's servers)?
694
- #
695
- # @return [true, false]
696
- # @see #persisted?
697
- # @see #destroyed?
698
- def new_record?
699
- @new_record
700
- end
701
-
702
- # Has the record been destroyed? (Set +true+ after a successful destroy.)
703
- # @return [true, false]
704
- # @see #new_record?
705
- # @see #persisted?
706
- def destroyed?
707
- @destroyed
708
- end
709
-
710
- # Has the record persisted (i.e., saved on Recurly's servers)?
711
- #
712
- # @return [true, false]
713
- # @see #new_record?
714
- # @see #destroyed?
715
- def persisted?
716
- !(new_record? || destroyed?)
717
- end
718
-
719
- # The value of a specified attribute, lazily fetching any defined
720
- # association.
721
- #
722
- # @param key [Symbol, String] The name of the attribute to be fetched.
723
- # @example
724
- # account.read_attribute :first_name # => "Ted"
725
- # account[:last_name] # => "Beneke"
726
- # @see #write_attribute
727
- def read_attribute(key)
728
- key = key.to_s
729
- if attributes.key? key
730
- value = attributes[key]
731
- elsif links.key?(key) && self.class.reflect_on_association(key)
732
- value = attributes[key] = follow_link key
733
- end
734
- value
735
- end
736
- alias [] read_attribute
737
-
738
- # Sets the value of a specified attribute.
739
- #
740
- # @param key [Symbol, String] The name of the attribute to be set.
741
- # @param value [Object] The value the attribute will be set to.
742
- # @example
743
- # account.write_attribute :first_name, 'Gus'
744
- # account[:company_name] = 'Los Pollos Hermanos'
745
- # @see #read_attribute
746
- def write_attribute(key, value)
747
- if changed_attributes.key?(key = key.to_s)
748
- changed_attributes.delete key if changed_attributes[key] == value
749
- elsif self[key] != value
750
- changed_attributes[key] = self[key]
751
- end
752
-
753
- association = self.class.find_association(key)
754
- if association
755
- value = fetch_associated(key, value)
756
- # FIXME: More explicit; less magic.
757
- elsif value && key.end_with?('_in_cents') && !respond_to?(:currency)
758
- value = Money.new(value, self, key) unless value.is_a?(Money)
759
- end
760
-
761
- attributes[key] = value
762
- end
763
- alias []= write_attribute
764
-
765
- # Apply a given hash of attributes to a record.
766
- #
767
- # @return [Hash]
768
- # @param attributes [Hash] A hash of attributes.
769
- def attributes=(attributes = {})
770
- attributes.each_pair { |k, v|
771
- respond_to?(name = "#{k}=") and send(name, v) or self[k] = v
772
- }
773
- end
774
-
775
- def as_json(options = nil)
776
- attributes.reject { |k, v| v.is_a?(Recurly::Resource::Pager) }
777
- end
778
-
779
- # @return [Hash] The raw hash of record href links.
780
- def links
781
- @links ||= {}
782
- end
783
-
784
- # Whether a record has a link with the given name.
785
- #
786
- # @param key [Symbol, String] The name of the link to check for.
787
- # @example
788
- # account.link? :billing_info # => true
789
- def link?(key)
790
- links.key?(key.to_s)
791
- end
792
-
793
- # Fetch the value of a link by following the associated href.
794
- #
795
- # @param key [Symbol, String] The name of the link to be followed.
796
- # @param options [Hash] A hash of API options.
797
- # @example
798
- # account.read_link :billing_info # => <Recurly::BillingInfo>
799
- def follow_link(key, options = {})
800
- if link = links[key = key.to_s]
801
- response = API.send link[:method], link[:href], options[:body], options
802
- if resource_class = link[:resource_class]
803
- response = resource_class.from_response response
804
- response.attributes[self.class.member_name] = self
805
- end
806
- response
807
- end
808
- rescue Recurly::API::NotFound
809
- raise unless resource_class
810
- end
811
-
812
- # Serializes the record to XML.
813
- #
814
- # @return [String] An XML string.
815
- # @param options [Hash] A hash of XML options.
816
- # @example
817
- # Recurly::Account.new(:account_code => 'code').to_xml
818
- # # => "<account><account_code>code</account_code></account>"
819
- def to_xml(options = {})
820
- builder = options[:builder] || XML.new("<#{self.class.xml_root_key}/>")
821
- xml_keys.each { |key|
822
- value = respond_to?(key) ? send(key) : self[key]
823
- node = builder.add_element key
824
-
825
- # Duck-typing here is problematic because of ActiveSupport's #to_xml.
826
- case value
827
- when Resource
828
- value.to_xml options.merge(:builder => node)
829
- when Array, Subscription::AddOns
830
- value.each do |e|
831
- if e.is_a? Recurly::Resource
832
- # create a node to hold this resource
833
- e_node = node.add_element Helper.singularize(key)
834
- # serialize the resource into this node
835
- e.to_xml(options.merge(builder: e_node))
836
- else
837
- # it's just a primitive value
838
- node.add_element(Helper.singularize(key), e)
839
- end
840
- end
841
- when Hash, Recurly::Money
842
- value.each_pair { |k, v| node.add_element k.to_s, v }
843
- else
844
- node.text = value
845
- end
846
- }
847
- builder.to_s
848
- end
849
-
850
- # Attempts to save the record, returning the success of the request.
851
- #
852
- # @return [true, false]
853
- # @raise [Transaction::Error] A monetary transaction failed.
854
- # @example
855
- # account = Recurly::Account.new
856
- # account.save # => false
857
- # account.account_code = 'account_code'
858
- # account.save # => true
859
- # @see #save!
860
- def save
861
- if new_record? || changed?
862
- clear_errors
863
- @response = API.send(
864
- persisted? ? :put : :post, path, to_xml
865
- )
866
- reload response
867
- persist! true
868
- end
869
- true
870
- rescue API::UnprocessableEntity => e
871
- apply_errors e
872
- Transaction::Error.validate! e, (self if is_a?(Transaction))
13
+ def requires_client?
873
14
  false
874
15
  end
875
16
 
876
- # Attempts to save the record, returning +true+ if the record was saved and
877
- # raising {Invalid} otherwise.
878
- #
879
- # @return [true]
880
- # @raise [Invalid] The record was invalid.
881
- # @raise [Transaction::Error] A monetary transaction failed.
882
- # @example
883
- # account = Recurly::Account.new
884
- # account.save! # raises Recurly::Resource::Invalid
885
- # account.account_code = 'account_code'
886
- # account.save! # => true
887
- # @see #save
888
- def save!
889
- save || raise(Invalid.new(self))
890
- end
891
-
892
- # @return [true, false, nil] The validity of the record: +true+ if the
893
- # record was successfully saved (or persisted and unchanged), +false+ if
894
- # the record was not successfully saved, or +nil+ for a record with an
895
- # unknown state (i.e. (i.e. new records that haven't been saved and
896
- # persisted records with changed attributes).
897
- # @example
898
- # account = Recurly::Account.new
899
- # account.valid? # => nil
900
- # account.save # => false
901
- # account.valid? # => false
902
- # account.account_code = 'account_code'
903
- # account.save # => true
904
- # account.valid? # => true
905
- def valid?
906
- return true if persisted? && !changed?
907
- errors_empty = errors.values.flatten.empty?
908
- return if errors_empty && changed?
909
- errors_empty
910
- end
911
-
912
- # Update a record with a given hash of attributes.
913
- #
914
- # @return [true, false] The success of the update.
915
- # @param attributes [Hash] A hash of attributes.
916
- # @raise [Transaction::Error] A monetary transaction failed.
917
- # @example
918
- # account = Account.find 'junior'
919
- # account.update_attributes :account_code => 'flynn' # => true
920
- # @see #update_attributes!
921
- def update_attributes(attributes = {})
922
- self.attributes = attributes and save
923
- end
924
-
925
- # Update a record with a given hash of attributes.
926
- #
927
- # @return [true] The update was successful.
928
- # @param attributes [Hash] A hash of attributes.
929
- # @raise [Invalid] The record was invalid.
930
- # @raise [Transaction::Error] A monetary transaction failed.
931
- # @example
932
- # account = Account.find 'gale_boetticher'
933
- # account.update_attributes! :account_code => nil # Raises an exception.
934
- # @see #update_attributes
935
- def update_attributes!(attributes = {})
936
- self.attributes = attributes and save!
937
- end
938
-
939
- # @return [Hash] A hash with indifferent read access containing any
940
- # validation errors where the key is the attribute name and the value is
941
- # an array of error messages.
942
- # @example
943
- # account.errors # => {"account_code"=>["can't be blank"]}
944
- # account.errors[:account_code] # => ["can't be blank"]
945
- def errors
946
- @errors ||= Errors.new { |h, k| h[k] = [] }
947
- end
948
-
949
- # Marks a record as persisted, i.e. not a new or deleted record, resetting
950
- # any tracked attribute changes in the process. (This is an internal method
951
- # and should probably not be called unless you know what you're doing.)
952
- #
953
- # @api internal
954
- # @return [true]
955
- def persist!(saved = false)
956
- @new_record, @uri = false
957
- if changed?
958
- @previous_changes = changes if saved
959
- changed_attributes.clear
960
- end
961
- true
962
- end
963
-
964
- # @return [String, nil] The unique resource identifier (URI) of the record
965
- # (if persisted).
966
- # @example
967
- # Recurly::Account.new(:account_code => "account_code").uri # => nil
968
- # Recurly::Account.find("account_code").uri
969
- # # => "https://api.recurly.com/v2/accounts/account_code"
970
- def uri
971
- @href ||= ((API.base_uri + path).to_s if persisted?)
972
- end
973
-
974
- # Attempts to destroy the record.
975
- #
976
- # @return [true, false] +true+ if successful, +false+ if unable to destroy
977
- # (if the record does not persist on Recurly).
978
- # @raise [NotFound] The record cannot be found.
979
- # @example
980
- # account = Recurly::Account.find account_code
981
- # race_condition = Recurly::Account.find account_code
982
- # account.destroy # => true
983
- # account.destroy # => false (already destroyed)
984
- # race_condition.destroy # raises Recurly::Resource::NotFound
985
- def destroy
986
- return false unless persisted?
987
- @response = API.delete uri
988
- @destroyed = true
989
- rescue API::NotFound => e
990
- raise NotFound, e.description
991
- end
992
-
993
- def signable_attributes
994
- Hash[xml_keys.map { |key| [key, self[key]] }]
995
- end
996
-
997
- def ==(other)
998
- other.is_a?(self.class) && other.to_s == to_s
999
- end
1000
-
1001
- def marshal_dump
1002
- [
1003
- @attributes.reject { |k, v| v.is_a?(Proc) },
1004
- @new_record,
1005
- @destroyed,
1006
- @uri,
1007
- @href,
1008
- changed_attributes,
1009
- previous_changes,
1010
- response,
1011
- etag,
1012
- links,
1013
- @type
1014
- ]
1015
- end
1016
-
1017
- def marshal_load(serialization)
1018
- @attributes,
1019
- @new_record,
1020
- @destroyed,
1021
- @uri,
1022
- @href,
1023
- @changed_attributes,
1024
- @previous_changes,
1025
- @response,
1026
- @etag,
1027
- @links,
1028
- @type = serialization
1029
- end
1030
-
1031
- # @return [String]
1032
- def inspect(attributes = self.class.attribute_names.to_a)
1033
- string = "#<#{self.class}"
1034
- string << "##@type" if respond_to?(:type)
1035
- attributes += %w(errors) if errors.any?
1036
- string << " %s" % attributes.map { |k|
1037
- "#{k}: #{self.send(k).inspect}"
1038
- }.join(', ')
1039
- string << '>'
1040
- end
1041
- alias to_s inspect
1042
-
1043
- def apply_errors(exception)
1044
- @response = exception.response
1045
- document = XML.new exception.response.body
1046
-
1047
- if document.root.name == 'error'
1048
- # Single error is returned from the API
1049
- attribute_path = document['symbol'].text.split '.'
1050
- invalid! [attribute_path[1]], document['description'].text
1051
- else
1052
- # Array of errors was returned by the API
1053
- document.each_element 'error' do |el|
1054
- attribute_path = el.attribute('field').value.split '.'
1055
- invalid! attribute_path[1, attribute_path.length], el.text
1056
- end
1057
- end
17
+ def ==(other_resource)
18
+ self.attributes == other_resource.attributes
1058
19
  end
1059
20
 
1060
21
  protected
1061
22
 
1062
- def path
1063
- @href or @uri or if persisted?
1064
- self.class.member_path to_param
1065
- else
1066
- self.class.collection_path
1067
- end
1068
- end
1069
-
1070
- def invalid!(attribute_path, error)
1071
- if attribute_path.length == 1
1072
- errors[attribute_path[0]] << error
1073
- else
1074
- child, k, v = attribute_path.shift.scan(/[^\[\]=]+/)
1075
- if c = k ? self[child].find { |d| d[k] == v } : self[child]
1076
- c.invalid! attribute_path, error if c.methods.include? :invalid!
1077
- e = errors[child] << 'is invalid' and e.uniq!
1078
- end
1079
- end
1080
- end
1081
-
1082
- def clear_errors
1083
- errors.clear
1084
- self.class.associations do |association|
1085
- next unless respond_to? "#{association}=" # Clear writable only.
1086
- [*self[association]].each do |associated|
1087
- associated.clear_errors if associated.respond_to? :clear_errors
1088
- end
1089
- end
1090
- end
1091
-
1092
- def copy_from(other)
1093
- other.instance_variables.each do |ivar|
1094
- instance_variable_set ivar, other.instance_variable_get(ivar)
1095
- end
23
+ def initialize(attributes = {})
24
+ @attributes = attributes.clone
1096
25
  end
1097
26
 
1098
- private
1099
-
1100
- def fetch_associated(name, value, options = {})
1101
- case value
1102
- when Array
1103
- value.map do |v|
1104
- fetch_associated(Helper.singularize(name), v, association_name: name)
1105
- end
1106
- when Hash
1107
- association_name = options[:association_name] || name
1108
- associated_class_name = self.class.find_association(association_name).class_name
1109
- associated_class_name ||= Helper.classify(name)
1110
- Recurly.const_get(associated_class_name, false).send(:new, value)
1111
- when Proc, Resource, Resource::Pager, nil
1112
- value
1113
- else
1114
- raise "unexpected association #{name.inspect}=#{value.inspect}"
1115
- end
27
+ def to_s
28
+ self.inspect
1116
29
  end
1117
30
 
1118
- def xml_keys
1119
- changed_attributes.keys.sort
31
+ def schema
32
+ self.class.schema
1120
33
  end
1121
34
  end
1122
35
  end