pay-lago 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +27 -0
  3. data/.gitignore +28 -0
  4. data/.rspec +3 -0
  5. data/.standard.yml +3 -0
  6. data/CHANGELOG.md +2 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/Gemfile +25 -0
  9. data/Gemfile.lock +292 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +85 -0
  12. data/Rakefile +32 -0
  13. data/app/controllers/pay/webhooks/lago_controller.rb +41 -0
  14. data/app/models/concerns/pay/lago/pay_customer_extensions.rb +15 -0
  15. data/app/models/concerns/pay/lago/pay_extensions.rb +15 -0
  16. data/bin/console +15 -0
  17. data/bin/rails +16 -0
  18. data/bin/setup +8 -0
  19. data/config/routes.rb +5 -0
  20. data/docs/1_customers.md +18 -0
  21. data/docs/2_charges.md +72 -0
  22. data/docs/3_subscriptions.md +67 -0
  23. data/docs/4_webhooks.md +66 -0
  24. data/lib/pay/lago/attributes.rb +24 -0
  25. data/lib/pay/lago/billable.rb +148 -0
  26. data/lib/pay/lago/charge.rb +82 -0
  27. data/lib/pay/lago/engine.rb +27 -0
  28. data/lib/pay/lago/error.rb +9 -0
  29. data/lib/pay/lago/payment_method.rb +37 -0
  30. data/lib/pay/lago/subscription.rb +134 -0
  31. data/lib/pay/lago/version.rb +7 -0
  32. data/lib/pay/lago/webhook_extensions.rb +10 -0
  33. data/lib/pay/lago/webhooks/customer_payment_provider_created.rb +18 -0
  34. data/lib/pay/lago/webhooks/invoice_created.rb +15 -0
  35. data/lib/pay/lago/webhooks/invoice_drafted.rb +11 -0
  36. data/lib/pay/lago/webhooks/invoice_one_off_created.rb +15 -0
  37. data/lib/pay/lago/webhooks/invoice_payment_status_updated.rb +14 -0
  38. data/lib/pay/lago/webhooks/subscription_started.rb +12 -0
  39. data/lib/pay/lago/webhooks/subscription_terminated.rb +12 -0
  40. data/lib/pay/lago.rb +91 -0
  41. data/pay-lago.gemspec +27 -0
  42. data/sig/pay/lago.rbs +6 -0
  43. data/test/controllers/pay/webhooks/lago_controller_test.rb +37 -0
  44. data/test/dummy/.browserslistrc +1 -0
  45. data/test/dummy/Rakefile +6 -0
  46. data/test/dummy/app/assets/config/manifest.js +4 -0
  47. data/test/dummy/app/assets/images/.keep +0 -0
  48. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  49. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  50. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  51. data/test/dummy/app/controllers/application_controller.rb +4 -0
  52. data/test/dummy/app/controllers/concerns/.keep +0 -0
  53. data/test/dummy/app/controllers/main_controller.rb +4 -0
  54. data/test/dummy/app/controllers/payment_methods_controller.rb +5 -0
  55. data/test/dummy/app/helpers/application_helper.rb +2 -0
  56. data/test/dummy/app/helpers/current_helper.rb +9 -0
  57. data/test/dummy/app/javascript/application.js +9 -0
  58. data/test/dummy/app/javascript/controllers/application.js +10 -0
  59. data/test/dummy/app/javascript/controllers/index.js +11 -0
  60. data/test/dummy/app/jobs/application_job.rb +2 -0
  61. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  62. data/test/dummy/app/models/account.rb +3 -0
  63. data/test/dummy/app/models/application_record.rb +3 -0
  64. data/test/dummy/app/models/concerns/.keep +0 -0
  65. data/test/dummy/app/models/team.rb +10 -0
  66. data/test/dummy/app/models/user.rb +3 -0
  67. data/test/dummy/app/views/layouts/application.html.erb +36 -0
  68. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  69. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  70. data/test/dummy/app/webhooks/charge_succeeded.rb +5 -0
  71. data/test/dummy/bin/importmap +4 -0
  72. data/test/dummy/bin/rails +4 -0
  73. data/test/dummy/bin/rake +4 -0
  74. data/test/dummy/bin/setup +33 -0
  75. data/test/dummy/bin/webpack +18 -0
  76. data/test/dummy/bin/webpack-dev-server +18 -0
  77. data/test/dummy/config/application.rb +25 -0
  78. data/test/dummy/config/boot.rb +6 -0
  79. data/test/dummy/config/cable.yml +9 -0
  80. data/test/dummy/config/database.yml +3 -0
  81. data/test/dummy/config/environment.rb +5 -0
  82. data/test/dummy/config/environments/development.rb +70 -0
  83. data/test/dummy/config/environments/production.rb +86 -0
  84. data/test/dummy/config/environments/test.rb +60 -0
  85. data/test/dummy/config/importmap.rb +8 -0
  86. data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
  87. data/test/dummy/config/initializers/assets.rb +11 -0
  88. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  89. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  90. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  91. data/test/dummy/config/initializers/inflections.rb +16 -0
  92. data/test/dummy/config/initializers/mime_types.rb +4 -0
  93. data/test/dummy/config/initializers/pay.rb +11 -0
  94. data/test/dummy/config/initializers/session_store.rb +3 -0
  95. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  96. data/test/dummy/config/locales/en.yml +23 -0
  97. data/test/dummy/config/puma.rb +47 -0
  98. data/test/dummy/config/routes.rb +4 -0
  99. data/test/dummy/config/secrets.yml +22 -0
  100. data/test/dummy/config/spring.rb +6 -0
  101. data/test/dummy/config/storage.yml +34 -0
  102. data/test/dummy/config.ru +6 -0
  103. data/test/dummy/db/migrate/20170205000000_create_users.rb +22 -0
  104. data/test/dummy/db/schema.rb +125 -0
  105. data/test/dummy/lib/assets/.keep +0 -0
  106. data/test/dummy/log/.keep +0 -0
  107. data/test/dummy/public/404.html +67 -0
  108. data/test/dummy/public/422.html +67 -0
  109. data/test/dummy/public/500.html +66 -0
  110. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  111. data/test/dummy/public/apple-touch-icon.png +0 -0
  112. data/test/dummy/public/favicon.ico +0 -0
  113. data/test/dummy/vendor/javascript/.keep +0 -0
  114. data/test/fixtures/accounts.yml +2 -0
  115. data/test/fixtures/pay/customers.yml +7 -0
  116. data/test/fixtures/teams.yml +4 -0
  117. data/test/fixtures/users.yml +11 -0
  118. data/test/pay/lago/billable_test.rb +42 -0
  119. data/test/pay/lago/charge_test.rb +23 -0
  120. data/test/pay/lago/subscription_test.rb +48 -0
  121. data/test/support/fixtures/lago/invoice.one_off_created.json +120 -0
  122. data/test/support/vcr.rb +28 -0
  123. data/test/test_helper.rb +66 -0
  124. data/test/vcr_cassettes/test_add_payment_method_to_lago.yml +56 -0
  125. data/test/vcr_cassettes/test_can_get_lago_processor_customer.yml +56 -0
  126. data/test/vcr_cassettes/test_can_make_a_lago_processor_charge.yml +163 -0
  127. data/test/vcr_cassettes/test_generates_lago_processor_id.yml +107 -0
  128. data/test/vcr_cassettes/test_lago_cannot_change_quantity.yml +108 -0
  129. data/test/vcr_cassettes/test_lago_processor_cancel.yml +212 -0
  130. data/test/vcr_cassettes/test_lago_processor_charge.yml +325 -0
  131. data/test/vcr_cassettes/test_lago_processor_on_grace_period__is_always_false.yml +108 -0
  132. data/test/vcr_cassettes/test_lago_processor_pause_raises_an_error.yml +108 -0
  133. data/test/vcr_cassettes/test_lago_processor_paused__is_always_false.yml +108 -0
  134. data/test/vcr_cassettes/test_lago_processor_refund_with_premium.yml +378 -0
  135. data/test/vcr_cassettes/test_lago_processor_refund_without_premium.yml +375 -0
  136. data/test/vcr_cassettes/test_lago_processor_resume_raises_an_error.yml +108 -0
  137. data/test/vcr_cassettes/test_lago_processor_subscribe.yml +108 -0
  138. data/test/vcr_cassettes/test_lago_processor_subscription.yml +160 -0
  139. data/test/vcr_cassettes/test_lago_processor_swap.yml +212 -0
  140. metadata +212 -0
@@ -0,0 +1,125 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema[7.1].define(version: 2022_08_31_153001) do
14
+ create_table "accounts", force: :cascade do |t|
15
+ t.string "email"
16
+ t.string "merchant_processor"
17
+ t.string "pay_data"
18
+ end
19
+
20
+ create_table "pay_charges", force: :cascade do |t|
21
+ t.integer "customer_id", null: false
22
+ t.integer "subscription_id"
23
+ t.string "processor_id", null: false
24
+ t.integer "amount", null: false
25
+ t.string "currency"
26
+ t.integer "application_fee_amount"
27
+ t.integer "amount_refunded"
28
+ t.json "metadata"
29
+ t.json "data"
30
+ t.datetime "created_at", null: false
31
+ t.datetime "updated_at", null: false
32
+ t.index ["customer_id", "processor_id"], name: "index_pay_charges_on_customer_id_and_processor_id", unique: true
33
+ t.index ["subscription_id"], name: "index_pay_charges_on_subscription_id"
34
+ end
35
+
36
+ create_table "pay_customers", force: :cascade do |t|
37
+ t.string "owner_type"
38
+ t.bigint "owner_id"
39
+ t.string "processor", null: false
40
+ t.string "processor_id"
41
+ t.boolean "default"
42
+ t.json "data"
43
+ t.datetime "deleted_at"
44
+ t.datetime "created_at", null: false
45
+ t.datetime "updated_at", null: false
46
+ t.index ["owner_type", "owner_id", "deleted_at", "default"], name: "pay_customer_owner_index"
47
+ t.index ["processor", "processor_id"], name: "index_pay_customers_on_processor_and_processor_id", unique: true
48
+ end
49
+
50
+ create_table "pay_merchants", force: :cascade do |t|
51
+ t.string "owner_type"
52
+ t.bigint "owner_id"
53
+ t.string "processor", null: false
54
+ t.string "processor_id"
55
+ t.boolean "default"
56
+ t.json "data"
57
+ t.datetime "created_at", null: false
58
+ t.datetime "updated_at", null: false
59
+ t.index ["owner_type", "owner_id", "processor"], name: "index_pay_merchants_on_owner_type_and_owner_id_and_processor"
60
+ end
61
+
62
+ create_table "pay_payment_methods", force: :cascade do |t|
63
+ t.integer "customer_id", null: false
64
+ t.string "processor_id", null: false
65
+ t.boolean "default"
66
+ t.string "type"
67
+ t.json "data"
68
+ t.datetime "created_at", null: false
69
+ t.datetime "updated_at", null: false
70
+ t.index ["customer_id", "processor_id"], name: "index_pay_payment_methods_on_customer_id_and_processor_id", unique: true
71
+ end
72
+
73
+ create_table "pay_subscriptions", force: :cascade do |t|
74
+ t.integer "customer_id", null: false
75
+ t.string "name", null: false
76
+ t.string "processor_id", null: false
77
+ t.string "processor_plan", null: false
78
+ t.integer "quantity", default: 1, null: false
79
+ t.string "status", null: false
80
+ t.datetime "current_period_start"
81
+ t.datetime "current_period_end"
82
+ t.datetime "trial_ends_at"
83
+ t.datetime "ends_at"
84
+ t.decimal "application_fee_percent", precision: 8, scale: 2
85
+ t.json "metadata"
86
+ t.json "data"
87
+ t.datetime "created_at", null: false
88
+ t.datetime "updated_at", null: false
89
+ t.boolean "metered"
90
+ t.string "pause_behavior"
91
+ t.datetime "pause_starts_at"
92
+ t.datetime "pause_resumes_at"
93
+ t.index ["customer_id", "processor_id"], name: "index_pay_subscriptions_on_customer_id_and_processor_id", unique: true
94
+ t.index ["metered"], name: "index_pay_subscriptions_on_metered"
95
+ t.index ["pause_starts_at"], name: "index_pay_subscriptions_on_pause_starts_at"
96
+ end
97
+
98
+ create_table "pay_webhooks", force: :cascade do |t|
99
+ t.string "processor"
100
+ t.string "event_type"
101
+ t.json "event"
102
+ t.datetime "created_at", null: false
103
+ t.datetime "updated_at", null: false
104
+ end
105
+
106
+ create_table "teams", force: :cascade do |t|
107
+ t.string "email"
108
+ t.string "name"
109
+ t.string "owner_type"
110
+ t.bigint "owner_id"
111
+ t.index ["owner_type", "owner_id"], name: "index_teams_on_owner_type_and_owner_id"
112
+ end
113
+
114
+ create_table "users", force: :cascade do |t|
115
+ t.string "email"
116
+ t.string "first_name"
117
+ t.string "last_name"
118
+ t.text "extra_billing_info"
119
+ end
120
+
121
+ add_foreign_key "pay_charges", "pay_customers", column: "customer_id"
122
+ add_foreign_key "pay_charges", "pay_subscriptions", column: "subscription_id"
123
+ add_foreign_key "pay_payment_methods", "pay_customers", column: "customer_id"
124
+ add_foreign_key "pay_subscriptions", "pay_customers", column: "customer_id"
125
+ end
File without changes
File without changes
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/404.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The page you were looking for doesn't exist.</h1>
62
+ <p>You may have mistyped the address or the page may have moved.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/422.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The change you wanted was rejected.</h1>
62
+ <p>Maybe you tried to change something you didn't have access to.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,66 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/500.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>We're sorry, but something went wrong.</h1>
62
+ </div>
63
+ <p>If you are the application owner check the logs for more information.</p>
64
+ </div>
65
+ </body>
66
+ </html>
File without changes
File without changes
File without changes
@@ -0,0 +1,2 @@
1
+ one:
2
+ email: one@team.org
@@ -0,0 +1,7 @@
1
+ # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2
+
3
+ lago:
4
+ owner: lago (User)
5
+ processor: lago
6
+ processor_id: gid://dummy/Pay::Customer/772847388
7
+ default: true
@@ -0,0 +1,4 @@
1
+ one:
2
+ email: one@team.org
3
+ name: "Team One"
4
+ owner: none (User)
@@ -0,0 +1,11 @@
1
+ # User with no payment processors
2
+ none:
3
+ email: none@example.org
4
+ first_name: None
5
+ last_name: User
6
+
7
+ # User with lago_processor payment processor
8
+ lago:
9
+ email: lago@example.org
10
+ first_name: Lago
11
+ last_name: User
@@ -0,0 +1,42 @@
1
+ require "test_helper"
2
+
3
+ class Pay::Lago::Billable::Test < ActiveSupport::TestCase
4
+ setup do
5
+ @pay_customer = pay_customers(:lago)
6
+ @pay_customer.processor_id = @pay_customer.pay_external_id
7
+ end
8
+
9
+ test "can get lago processor customer" do
10
+ assert_equal @pay_customer.customer.external_id, @pay_customer.pay_external_id
11
+ end
12
+
13
+ test "can make a lago processor charge" do
14
+ assert_difference "Pay::Charge.count" do
15
+ @pay_customer.charge(10_00)
16
+ end
17
+ end
18
+
19
+ # Subscribe to a plan with the code "default" in Lago
20
+ test "lago processor subscribe" do
21
+ assert_difference "Pay::Subscription.count" do
22
+ @pay_customer.subscribe
23
+ end
24
+ end
25
+
26
+ test "generates lago processor_id" do
27
+ user = users(:none)
28
+ pay_customer = user.set_payment_processor :lago
29
+ assert_nil pay_customer.processor_id
30
+ pay_customer.customer
31
+ assert_not_nil pay_customer.processor_id
32
+ end
33
+
34
+ test "add payment method to lago" do
35
+ assert_difference "Pay::PaymentMethod.count" do
36
+ @pay_customer.add_payment_method("x", default: true)
37
+ end
38
+
39
+ payment_method = @pay_customer.default_payment_method
40
+ assert_equal "card", payment_method.type
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ require "test_helper"
2
+
3
+ class Pay::Lago::Charge::Test < ActiveSupport::TestCase
4
+ setup do
5
+ @pay_customer = pay_customers(:lago)
6
+ @charge = @pay_customer.charge(10_00)
7
+ @charge.payment_processor.update_charge!(payment_status: "succeeded")
8
+ end
9
+
10
+ test "lago processor charge" do
11
+ assert_equal @charge.processor_id, @charge.payment_processor.charge.lago_id
12
+ end
13
+
14
+ test "lago processor refund with premium" do
15
+ assert_nil @charge.amount_refunded
16
+ @charge.refund!(5_00)
17
+ assert_equal 5_00, @charge.reload.amount_refunded
18
+ end
19
+
20
+ test "lago processor refund without premium" do
21
+ assert_raises(Pay::Lago::Error, "Creating a credit note requires Lago Premium.") { @charge.refund!(5_00) }
22
+ end
23
+ end
@@ -0,0 +1,48 @@
1
+ require "test_helper"
2
+
3
+ class Pay::Lago::Subscription::Test < ActiveSupport::TestCase
4
+ setup do
5
+ @pay_customer = pay_customers(:lago)
6
+ @subscription = @pay_customer.subscribe
7
+ end
8
+
9
+ test "lago processor subscription" do
10
+ assert_equal @subscription.processor_id, @subscription.processor_subscription.external_id
11
+ end
12
+
13
+ test "lago processor cancel" do
14
+ freeze_time do
15
+ @subscription.cancel
16
+ @subscription.reload
17
+ assert_equal @subscription.cancelled?, true
18
+ end
19
+ end
20
+
21
+ test "lago processor on_grace_period? is always false" do
22
+ assert_equal @subscription.on_grace_period?, false
23
+ end
24
+
25
+ test "lago processor paused? is always false" do
26
+ assert_equal @subscription.paused?, false
27
+ end
28
+
29
+ test "lago processor resume raises an error" do
30
+ assert_raises(Pay::Lago::Error, "Lago subscriptions cannot be paused.") { @subscription.resume }
31
+ end
32
+
33
+ test "lago processor pause raises an error" do
34
+ assert_raises(Pay::Lago::Error, "Lago subscriptions cannot be paused.") { @subscription.pause }
35
+ end
36
+
37
+ test "lago processor swap" do
38
+ travel_to Date.parse("2023-09-18") do
39
+ @subscription.swap("another_plan")
40
+ assert_equal @subscription.changing_plan?, true
41
+ assert_equal @subscription.subscription.next_plan_code, "another_plan"
42
+ end
43
+ end
44
+
45
+ test "lago cannot change quantity" do
46
+ assert_raises(Pay::Lago::Error, "Lago subscriptions do not implement quantity.") { @subscription.change_quantity(3) }
47
+ end
48
+ end
@@ -0,0 +1,120 @@
1
+ {
2
+ "webhook_type": "invoice.one_off_created",
3
+ "object_type": "invoice",
4
+ "invoice": {
5
+ "lago_id": "b7b6a31f-b81b-4ae5-a9c8-05c407e47416",
6
+ "sequential_id": 4,
7
+ "number": "ONE-0499-001-004",
8
+ "issuing_date": "2023-05-05",
9
+ "invoice_type": "one_off",
10
+ "status": "finalized",
11
+ "payment_status": "pending",
12
+ "currency": "USD",
13
+ "fees_amount_cents": 25000,
14
+ "vat_amount_cents": 2500,
15
+ "coupons_amount_cents": 0,
16
+ "credit_notes_amount_cents": 0,
17
+ "sub_total_vat_excluded_amount_cents": 25000,
18
+ "sub_total_vat_included_amount_cents": 27500,
19
+ "total_amount_cents": 27500,
20
+ "prepaid_credit_amount_cents": 0,
21
+ "file_url": null,
22
+ "version_number": 3,
23
+ "legacy": false,
24
+ "amount_currency": "USD",
25
+ "vat_amount_currency": "USD",
26
+ "credit_amount_currency": "USD",
27
+ "total_amount_currency": "USD",
28
+ "amount_cents": 25000,
29
+ "credit_amount_cents": 0,
30
+ "customer": {
31
+ "lago_id": "37cee916-5751-4665-9774-aa05bb1a2ffd",
32
+ "external_id": "5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba",
33
+ "name": "Gavin Belson",
34
+ "sequential_id": 1,
35
+ "slug": "TWI-2B86-001",
36
+ "created_at": "2022-06-01T09:01:12Z",
37
+ "country": "US",
38
+ "address_line1": "5230 Penfield Ave",
39
+ "address_line2": null,
40
+ "state": "CA",
41
+ "zipcode": "91364",
42
+ "email": "dinesh@piedpiper.test",
43
+ "city": "Woodland Hills",
44
+ "url": "http://hooli.com",
45
+ "phone": "1-171-883-3711 x245",
46
+ "logo_url": "http://hooli.com/logo.png",
47
+ "legal_name": "Coleman-Blair",
48
+ "legal_number": "49-008-2965",
49
+ "currency": "USD",
50
+ "timezone": null,
51
+ "applicable_timezone": "UTC",
52
+ "billing_configuration": {
53
+ "invoice_grace_period": null,
54
+ "payment_provider": null,
55
+ "vat_rate": 0,
56
+ "document_locale": null
57
+ }
58
+ },
59
+ "fees": [
60
+ {
61
+ "lago_id": "5fe06606-25c8-4aa8-bce8-a19a12383d51",
62
+ "lago_group_id": null,
63
+ "lago_invoice_id": "b7b6a31f-b81b-4ae5-a9c8-05c407e47416",
64
+ "lago_true_up_fee_id": null,
65
+ "lago_true_up_parent_fee_id": null,
66
+ "item": {
67
+ "type": "add_on",
68
+ "code": "setup_fee",
69
+ "name": "Setup Fee",
70
+ "lago_item_id": "b3f1967d-bf84-44a7-b3e9-207024a6aaa5",
71
+ "item_type": "AddOn"
72
+ },
73
+ "amount_cents": 5000,
74
+ "amount_currency": "USD",
75
+ "vat_amount_cents": 500,
76
+ "vat_amount_currency": "USD",
77
+ "total_amount_cents": 5500,
78
+ "total_amount_currency": "USD",
79
+ "units": "5.0",
80
+ "description": "",
81
+ "unit_amount_cents": 1000,
82
+ "events_count": null,
83
+ "payment_status": "pending",
84
+ "created_at": "2023-05-05T15:05:27Z",
85
+ "succeeded_at": null,
86
+ "failed_at": null,
87
+ "refunded_at": null
88
+ },
89
+ {
90
+ "lago_id": "a74705a1-05f2-41cb-b315-ab0c858c215d",
91
+ "lago_group_id": null,
92
+ "lago_invoice_id": "b7b6a31f-b81b-4ae5-a9c8-05c407e47416",
93
+ "lago_true_up_fee_id": null,
94
+ "lago_true_up_parent_fee_id": null,
95
+ "item": {
96
+ "type": "add_on",
97
+ "code": "customer_success_fee",
98
+ "name": "Customer Success",
99
+ "lago_item_id": "66b75fbf-3410-4251-9105-b4b3d5922a1d",
100
+ "item_type": "AddOn"
101
+ },
102
+ "amount_cents": 20000,
103
+ "amount_currency": "USD",
104
+ "vat_amount_cents": 2000,
105
+ "vat_amount_currency": "USD",
106
+ "total_amount_cents": 22000,
107
+ "total_amount_currency": "USD",
108
+ "units": "1.0",
109
+ "description": "My new description",
110
+ "unit_amount_cents": 20000,
111
+ "events_count": null,
112
+ "payment_status": "pending",
113
+ "created_at": "2023-05-05T15:05:27Z",
114
+ "succeeded_at": null,
115
+ "failed_at": null,
116
+ "refunded_at": null
117
+ }
118
+ ]
119
+ }
120
+ }
@@ -0,0 +1,28 @@
1
+ require "vcr"
2
+
3
+ unless ENV["SKIP_VCR"]
4
+ require "webmock/minitest"
5
+
6
+ VCR.configure do |c|
7
+ c.cassette_library_dir = "test/vcr_cassettes"
8
+ c.hook_into :webmock
9
+ c.allow_http_connections_when_no_cassette = true
10
+ c.filter_sensitive_data("<LAGO_API_KEY>") { Pay::Lago.api_key }
11
+ end
12
+
13
+ class ActiveSupport::TestCase
14
+ setup do
15
+ # Test filenames are case sensitive in CI
16
+ VCR.insert_cassette name, allow_unused_http_interactions: false, record_on_error: false
17
+ end
18
+
19
+ teardown do
20
+ cassette = VCR.current_cassette
21
+ VCR.eject_cassette
22
+ rescue VCR::Errors::UnusedHTTPInteractionError
23
+ puts
24
+ puts "Unused HTTP requests in cassette: #{cassette.file}"
25
+ raise
26
+ end
27
+ end
28
+ end