stripe-ruby-mock 3.1.0.rc3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/rspec_tests.yml +38 -0
- data/.rspec +2 -1
- data/CHANGELOG.md +28 -0
- data/README.md +5 -3
- data/lib/stripe_mock/api/webhooks.rb +65 -26
- data/lib/stripe_mock/data.rb +80 -11
- data/lib/stripe_mock/instance.rb +6 -2
- data/lib/stripe_mock/request_handlers/accounts.rb +17 -6
- data/lib/stripe_mock/request_handlers/charges.rb +5 -0
- data/lib/stripe_mock/request_handlers/checkout_session.rb +158 -1
- data/lib/stripe_mock/request_handlers/events.rb +30 -3
- data/lib/stripe_mock/request_handlers/helpers/coupon_helpers.rb +1 -0
- data/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb +28 -9
- data/lib/stripe_mock/request_handlers/invoices.rb +6 -1
- data/lib/stripe_mock/request_handlers/payment_intents.rb +13 -2
- data/lib/stripe_mock/request_handlers/payment_methods.rb +5 -1
- data/lib/stripe_mock/request_handlers/prices.rb +18 -0
- data/lib/stripe_mock/request_handlers/promotion_codes.rb +43 -0
- data/lib/stripe_mock/request_handlers/refunds.rb +13 -2
- data/lib/stripe_mock/request_handlers/setup_intents.rb +16 -9
- data/lib/stripe_mock/request_handlers/subscriptions.rb +93 -4
- data/lib/stripe_mock/request_handlers/transfers.rb +12 -1
- data/lib/stripe_mock/request_handlers/validators/param_validators.rb +5 -4
- data/lib/stripe_mock/test_strategies/base.rb +51 -24
- data/lib/stripe_mock/version.rb +1 -1
- data/lib/stripe_mock/webhook_fixtures/account.updated.json +1 -1
- data/lib/stripe_mock/webhook_fixtures/balance.available.json +26 -20
- data/lib/stripe_mock/webhook_fixtures/charge.captured.json +143 -0
- data/lib/stripe_mock/webhook_fixtures/charge.dispute.created.json +63 -16
- data/lib/stripe_mock/webhook_fixtures/charge.failed.json +49 -120
- data/lib/stripe_mock/webhook_fixtures/charge.refund.updated.json +35 -0
- data/lib/stripe_mock/webhook_fixtures/charge.refunded.json +145 -50
- data/lib/stripe_mock/webhook_fixtures/charge.succeeded.json +114 -43
- data/lib/stripe_mock/webhook_fixtures/checkout.session.completed.json +79 -0
- data/lib/stripe_mock/webhook_fixtures/checkout.session.completed.payment_mode.json +53 -0
- data/lib/stripe_mock/webhook_fixtures/checkout.session.completed.setup_mode.json +45 -0
- data/lib/stripe_mock/webhook_fixtures/customer.created.json +37 -46
- data/lib/stripe_mock/webhook_fixtures/customer.deleted.json +36 -32
- data/lib/stripe_mock/webhook_fixtures/customer.source.created.json +31 -22
- data/lib/stripe_mock/webhook_fixtures/customer.source.updated.json +36 -25
- data/lib/stripe_mock/webhook_fixtures/customer.subscription.created.json +135 -47
- data/lib/stripe_mock/webhook_fixtures/customer.subscription.deleted.json +134 -45
- data/lib/stripe_mock/webhook_fixtures/customer.subscription.updated.json +135 -56
- data/lib/stripe_mock/webhook_fixtures/customer.updated.json +38 -47
- data/lib/stripe_mock/webhook_fixtures/invoice.created.json +176 -49
- data/lib/stripe_mock/webhook_fixtures/invoice.finalized.json +171 -0
- data/lib/stripe_mock/webhook_fixtures/invoice.paid.json +171 -0
- data/lib/stripe_mock/webhook_fixtures/invoice.payment_action_required.json +171 -0
- data/lib/stripe_mock/webhook_fixtures/invoice.payment_failed.json +149 -83
- data/lib/stripe_mock/webhook_fixtures/invoice.payment_succeeded.json +149 -90
- data/lib/stripe_mock/webhook_fixtures/invoice.upcoming.json +70 -0
- data/lib/stripe_mock/webhook_fixtures/invoice.updated.json +178 -50
- data/lib/stripe_mock/webhook_fixtures/invoiceitem.created.json +87 -13
- data/lib/stripe_mock/webhook_fixtures/invoiceitem.updated.json +88 -14
- data/lib/stripe_mock/webhook_fixtures/mandate.updated.json +34 -0
- data/lib/stripe_mock/webhook_fixtures/payment_intent.amount_capturable_updated.json +170 -0
- data/lib/stripe_mock/webhook_fixtures/payment_intent.canceled.json +73 -0
- data/lib/stripe_mock/webhook_fixtures/payment_intent.created.json +86 -0
- data/lib/stripe_mock/webhook_fixtures/payment_intent.payment_failed.json +118 -79
- data/lib/stripe_mock/webhook_fixtures/payment_intent.processing.json +162 -0
- data/lib/stripe_mock/webhook_fixtures/payment_intent.requires_action.json +191 -0
- data/lib/stripe_mock/webhook_fixtures/payment_intent.succeeded.json +85 -53
- data/lib/stripe_mock/webhook_fixtures/payment_link.created.json +47 -0
- data/lib/stripe_mock/webhook_fixtures/payment_link.updated.json +50 -0
- data/lib/stripe_mock/webhook_fixtures/payment_method.attached.json +63 -0
- data/lib/stripe_mock/webhook_fixtures/payment_method.detached.json +62 -0
- data/lib/stripe_mock/webhook_fixtures/payout.created.json +40 -0
- data/lib/stripe_mock/webhook_fixtures/payout.paid.json +40 -0
- data/lib/stripe_mock/webhook_fixtures/payout.updated.json +46 -0
- data/lib/stripe_mock/webhook_fixtures/plan.created.json +30 -13
- data/lib/stripe_mock/webhook_fixtures/plan.deleted.json +30 -13
- data/lib/stripe_mock/webhook_fixtures/plan.updated.json +34 -14
- data/lib/stripe_mock/webhook_fixtures/price.created.json +42 -0
- data/lib/stripe_mock/webhook_fixtures/price.deleted.json +42 -0
- data/lib/stripe_mock/webhook_fixtures/price.updated.json +48 -0
- data/lib/stripe_mock/webhook_fixtures/product.created.json +19 -13
- data/lib/stripe_mock/webhook_fixtures/product.deleted.json +20 -14
- data/lib/stripe_mock/webhook_fixtures/product.updated.json +24 -15
- data/lib/stripe_mock/webhook_fixtures/quote.accepted.json +92 -0
- data/lib/stripe_mock/webhook_fixtures/quote.canceled.json +92 -0
- data/lib/stripe_mock/webhook_fixtures/quote.created.json +92 -0
- data/lib/stripe_mock/webhook_fixtures/quote.finalized.json +92 -0
- data/lib/stripe_mock/webhook_fixtures/setup_intent.canceled.json +46 -0
- data/lib/stripe_mock/webhook_fixtures/setup_intent.created.json +51 -0
- data/lib/stripe_mock/webhook_fixtures/setup_intent.setup_failed.json +100 -0
- data/lib/stripe_mock/webhook_fixtures/setup_intent.succeeded.json +46 -0
- data/lib/stripe_mock/webhook_fixtures/subscription_schedule.canceled.json +119 -0
- data/lib/stripe_mock/webhook_fixtures/subscription_schedule.created.json +114 -0
- data/lib/stripe_mock/webhook_fixtures/subscription_schedule.released.json +111 -0
- data/lib/stripe_mock/webhook_fixtures/subscription_schedule.updated.json +125 -0
- data/lib/stripe_mock/webhook_fixtures/tax_rate.created.json +32 -0
- data/lib/stripe_mock/webhook_fixtures/tax_rate.updated.json +37 -0
- data/lib/stripe_mock.rb +3 -1
- data/spec/instance_spec.rb +3 -1
- data/spec/integration_examples/completing_checkout_sessions_example.rb +37 -0
- data/spec/readme_spec.rb +1 -1
- data/spec/shared_stripe_examples/account_examples.rb +9 -1
- data/spec/shared_stripe_examples/checkout_session_examples.rb +99 -0
- data/spec/shared_stripe_examples/invoice_examples.rb +21 -0
- data/spec/shared_stripe_examples/payment_intent_examples.rb +74 -0
- data/spec/shared_stripe_examples/payment_method_examples.rb +32 -27
- data/spec/shared_stripe_examples/price_examples.rb +42 -2
- data/spec/shared_stripe_examples/promotion_code_examples.rb +68 -0
- data/spec/shared_stripe_examples/refund_examples.rb +13 -0
- data/spec/shared_stripe_examples/setup_intent_examples.rb +17 -0
- data/spec/shared_stripe_examples/subscription_examples.rb +213 -2
- data/spec/shared_stripe_examples/transfer_examples.rb +10 -1
- data/spec/shared_stripe_examples/webhook_event_examples.rb +51 -5
- data/spec/support/stripe_examples.rb +3 -1
- data/stripe-ruby-mock.gemspec +1 -1
- metadata +54 -11
- data/.travis.yml +0 -25
- data/lib/stripe_mock/request_handlers/checkout.rb +0 -15
- data/spec/shared_stripe_examples/checkout_examples.rb +0 -47
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7b5b1b83df91e8e6fd700456710804749b9b1008e572fd8c74eec4f1519e8a49
|
|
4
|
+
data.tar.gz: bf362cf2fe9aa556e155df2b61076248335e3b2a43f69e0e6fc046406d65da57
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a397bf1b196f62bb7c8d76f79e2582f6d6594d20ddb9473b8dd5e7680c1844620a29456cddd8be8c5d9b785f4317ea1b092b340f841ccdbef92d849c6bb116e7
|
|
7
|
+
data.tar.gz: 90f97fe65c4dcd10eaa853aec740bce21c56b8df57bc1a589acc6f13ebf1b65943731b50f820d9f8eaa77954673f78b34bb62b326cbb7c71f5692a0b74a347c0
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
|
2
|
+
# They are provided by a third-party and are governed by
|
|
3
|
+
# separate terms of service, privacy policy, and support
|
|
4
|
+
# documentation.
|
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
|
7
|
+
|
|
8
|
+
name: Tests
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
branches: [ "master" ]
|
|
13
|
+
pull_request:
|
|
14
|
+
branches: [ "master" ]
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
test:
|
|
21
|
+
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
strategy:
|
|
24
|
+
matrix:
|
|
25
|
+
ruby-version: ['2.6', '2.7', '3.0']
|
|
26
|
+
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v3
|
|
29
|
+
- name: Set up Ruby
|
|
30
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
|
31
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
|
32
|
+
# uses: ruby/setup-ruby@v1
|
|
33
|
+
uses: ruby/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf # v1.146.0
|
|
34
|
+
with:
|
|
35
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
36
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
37
|
+
- name: Run tests
|
|
38
|
+
run: bundle exec rspec
|
data/.rspec
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
--colour
|
|
1
|
+
--colour
|
|
2
|
+
--format progress
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
### Unreleased
|
|
2
|
+
|
|
3
|
+
- [#693](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/693) gemspec: add change,issue,source_code URL by [@mtmail](https://github.com/mtmail)
|
|
4
|
+
- [#700](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/700) update the balance API to respond with instant_available by [@iamnader](https://github.com/iamnader)
|
|
5
|
+
- [#687](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/687) Add PaymentIntent Webhooks by [@klaustopher](https://github.com/klaustopher)
|
|
6
|
+
- [#708](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/708) Implement Stripe::Checkout::Session.retrieve by [@coorasse](https://github.com/coorasse)
|
|
7
|
+
- [#711](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/711) Adding account link mock by [@amenon](https://github.com/amenon)
|
|
8
|
+
- [#715](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/715) Added application_fee_amount to mock charge object by [@espen](https://github.com/espen)
|
|
9
|
+
- [#694](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/694) Introduce ideal and sepa_debit types for PaymentMethod by [@mnin](https://github.com/mnin)
|
|
10
|
+
- [#720](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/720) Add additional parameters by [@rpietraszko](https://github.com/rpietraszko)
|
|
11
|
+
- [#695](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/695) Add `has_more` attribute to all ListObject instances by [@gbp](https://github.com/gbp)
|
|
12
|
+
- [#727](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/727) Fix List initialize error with deleted records by [@jmulieri](https://github.com/jmulieri)
|
|
13
|
+
- [#739](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/739) Add data to account links by [@dudyn5ky1](https://github.com/dudyn5ky1)
|
|
14
|
+
- [#756](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/756) Added #715 PR to changelog by [@espen](https://github.com/espen)
|
|
15
|
+
- [#759](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/759) Adding express dashboard login link mock by [@rohitbegani](https://github.com/rohitbegani)
|
|
16
|
+
- [#747](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/747) Fix ruby 2.7 deprecation warnings by [@coding-chimp](https://github.com/coding-chimp)
|
|
17
|
+
- [#762](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/762) Support Stripe Connect by adding stripe_account header namespace for customers by [@csalvato](https://github.com/csalvato)
|
|
18
|
+
- [#758](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/758) Create price by [@jamesprior](https://github.com/jamesprior)
|
|
19
|
+
- [#730](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/730) support on price api by [@hidenba](https://github.com/hidenba)
|
|
20
|
+
- [#764](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/764) Fixes erroneous error message when fetching upcoming invoices. by [@csalvato](https://github.com/csalvato)
|
|
21
|
+
- [#765](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/765) Properly set the status of a trialing subscription by [@csalvato](https://github.com/csalvato)
|
|
22
|
+
- [#755](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/755) Add allowed params to subscriptions by [@dominikdarnel](https://github.com/dominikdarnel)
|
|
23
|
+
- [#709](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/709) Remove unnecessary check on customer's currency by [@coorasse](https://github.com/coorasse)
|
|
24
|
+
|
|
25
|
+
- [#806](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/806) - Remove `payment_method_types` from required arguments for `Stripe::Checkout::Session`
|
|
26
|
+
- [#806](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/806) - Raise more helpful exception when Stripe::Price cannot be found within a `Stripe::Checkout::Session` `line_items` argument.
|
|
27
|
+
|
|
28
|
+
|
|
1
29
|
### 3.1.0.rc3 (pre-release 2021-07-14)
|
|
2
30
|
|
|
3
31
|
- [#785](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/785): `Stripe::Product` no longer requires `type`. [@TastyPi](https://github.com/TastyPi)
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# stripe-ruby-mock [](https://github.com/stripe-ruby-mock/stripe-ruby-mock/actions/workflows/rspec_tests.yml)
|
|
2
2
|
|
|
3
3
|
* Homepage: https://github.com/stripe-ruby-mock/stripe-ruby-mock
|
|
4
4
|
* Issues: https://github.com/stripe-ruby-mock/stripe-ruby-mock/issues
|
|
@@ -12,7 +12,7 @@ This gem has unexpectedly grown in popularity and I've gotten pretty busy, so I'
|
|
|
12
12
|
|
|
13
13
|
In your gemfile:
|
|
14
14
|
|
|
15
|
-
gem 'stripe-ruby-mock', '~> 3.0
|
|
15
|
+
gem 'stripe-ruby-mock', '~> 3.1.0', :require => 'stripe_mock'
|
|
16
16
|
|
|
17
17
|
## !!! Important
|
|
18
18
|
|
|
@@ -29,7 +29,7 @@ version `3.0.0` has [breaking changes](https://github.com/stripe-ruby-mock/strip
|
|
|
29
29
|
|
|
30
30
|
### Requirements
|
|
31
31
|
|
|
32
|
-
* ruby >= 2.
|
|
32
|
+
* ruby >= 2.6.0
|
|
33
33
|
* stripe >= 5.0.0
|
|
34
34
|
|
|
35
35
|
### Specifications
|
|
@@ -406,6 +406,8 @@ Patches are welcome and greatly appreciated! If you're contributing to fix a pro
|
|
|
406
406
|
be sure to write tests that illustrate the problem being fixed.
|
|
407
407
|
This will help ensure that the problem remains fixed in future updates.
|
|
408
408
|
|
|
409
|
+
Note: You may need to `ulimit -n 4048` before running the test suite to get all tests to pass.
|
|
410
|
+
|
|
409
411
|
## Copyright
|
|
410
412
|
|
|
411
413
|
Copyright (c) 2013 Gilbert
|
|
@@ -4,7 +4,7 @@ module StripeMock
|
|
|
4
4
|
|
|
5
5
|
fixture_file = File.join(@webhook_fixture_path, "#{type}.json")
|
|
6
6
|
|
|
7
|
-
unless File.
|
|
7
|
+
unless File.exist?(fixture_file)
|
|
8
8
|
unless Webhooks.event_list.include?(type)
|
|
9
9
|
raise UnsupportedRequestError.new "Unsupported webhook event `#{type}` (Searched in #{@webhook_fixture_path})"
|
|
10
10
|
end
|
|
@@ -37,55 +37,94 @@ module StripeMock
|
|
|
37
37
|
module Webhooks
|
|
38
38
|
def self.event_list
|
|
39
39
|
@__list = [
|
|
40
|
-
'account.updated',
|
|
41
40
|
'account.application.deauthorized',
|
|
42
41
|
'account.external_account.created',
|
|
43
|
-
'account.external_account.updated',
|
|
44
42
|
'account.external_account.deleted',
|
|
43
|
+
'account.external_account.updated',
|
|
44
|
+
'account.updated',
|
|
45
45
|
'balance.available',
|
|
46
|
-
'charge.
|
|
47
|
-
'charge.updated',
|
|
48
|
-
'charge.failed',
|
|
49
|
-
'charge.refunded',
|
|
50
|
-
'charge.dispute.created',
|
|
51
|
-
'charge.dispute.updated',
|
|
46
|
+
'charge.captured',
|
|
52
47
|
'charge.dispute.closed',
|
|
48
|
+
'charge.dispute.created',
|
|
53
49
|
'charge.dispute.funds_reinstated',
|
|
54
50
|
'charge.dispute.funds_withdrawn',
|
|
51
|
+
'charge.dispute.updated',
|
|
52
|
+
'charge.failed',
|
|
53
|
+
'charge.refund.updated',
|
|
54
|
+
'charge.refunded',
|
|
55
|
+
'charge.succeeded',
|
|
56
|
+
'charge.updated',
|
|
57
|
+
'checkout.session.completed',
|
|
58
|
+
'checkout.session.completed.payment_mode',
|
|
59
|
+
'checkout.session.completed.setup_mode',
|
|
60
|
+
'coupon.created',
|
|
61
|
+
'coupon.deleted',
|
|
62
|
+
'customer.created',
|
|
63
|
+
'customer.deleted',
|
|
64
|
+
'customer.discount.created',
|
|
65
|
+
'customer.discount.deleted',
|
|
66
|
+
'customer.discount.updated',
|
|
55
67
|
'customer.source.created',
|
|
56
68
|
'customer.source.deleted',
|
|
57
69
|
'customer.source.updated',
|
|
58
|
-
'customer.created',
|
|
59
|
-
'customer.updated',
|
|
60
|
-
'customer.deleted',
|
|
61
70
|
'customer.subscription.created',
|
|
62
|
-
'customer.subscription.updated',
|
|
63
71
|
'customer.subscription.deleted',
|
|
64
72
|
'customer.subscription.trial_will_end',
|
|
65
|
-
'customer.
|
|
66
|
-
'customer.
|
|
67
|
-
'customer.discount.deleted',
|
|
73
|
+
'customer.subscription.updated',
|
|
74
|
+
'customer.updated',
|
|
68
75
|
'invoice.created',
|
|
69
|
-
'invoice.
|
|
70
|
-
'invoice.
|
|
76
|
+
'invoice.finalized',
|
|
77
|
+
'invoice.paid',
|
|
78
|
+
'invoice.payment_action_required',
|
|
71
79
|
'invoice.payment_failed',
|
|
80
|
+
'invoice.payment_succeeded',
|
|
81
|
+
'invoice.upcoming',
|
|
82
|
+
'invoice.updated',
|
|
72
83
|
'invoiceitem.created',
|
|
73
|
-
'invoiceitem.updated',
|
|
74
84
|
'invoiceitem.deleted',
|
|
75
|
-
'
|
|
85
|
+
'invoiceitem.updated',
|
|
86
|
+
'mandate.updated',
|
|
87
|
+
'payment_intent.amount_capturable_updated',
|
|
88
|
+
'payment_intent.canceled',
|
|
89
|
+
'payment_intent.created',
|
|
76
90
|
'payment_intent.payment_failed',
|
|
91
|
+
'payment_intent.processing',
|
|
92
|
+
'payment_intent.requires_action',
|
|
93
|
+
'payment_intent.succeeded',
|
|
94
|
+
'payment_link.created',
|
|
95
|
+
'payment_link.updated',
|
|
96
|
+
'payment_method.attached',
|
|
97
|
+
'payment_method.detached',
|
|
98
|
+
'payout.created',
|
|
99
|
+
'payout.paid',
|
|
100
|
+
'payout.updated',
|
|
77
101
|
'plan.created',
|
|
78
|
-
'plan.updated',
|
|
79
102
|
'plan.deleted',
|
|
103
|
+
'plan.updated',
|
|
104
|
+
'price.created',
|
|
105
|
+
'price.deleted',
|
|
106
|
+
'price.updated',
|
|
80
107
|
'product.created',
|
|
81
|
-
'product.updated',
|
|
82
108
|
'product.deleted',
|
|
83
|
-
'
|
|
84
|
-
'
|
|
109
|
+
'product.updated',
|
|
110
|
+
'quote.accepted',
|
|
111
|
+
'quote.canceled',
|
|
112
|
+
'quote.created',
|
|
113
|
+
'quote.finalized',
|
|
114
|
+
'setup_intent.canceled',
|
|
115
|
+
'setup_intent.created',
|
|
116
|
+
'setup_intent.setup_failed',
|
|
117
|
+
'setup_intent.succeeded',
|
|
118
|
+
'subscription_schedule.canceled',
|
|
119
|
+
'subscription_schedule.created',
|
|
120
|
+
'subscription_schedule.released',
|
|
121
|
+
'subscription_schedule.updated',
|
|
122
|
+
'tax_rate.created',
|
|
123
|
+
'tax_rate.updated',
|
|
85
124
|
'transfer.created',
|
|
125
|
+
'transfer.failed',
|
|
86
126
|
'transfer.paid',
|
|
87
|
-
'transfer.updated'
|
|
88
|
-
'transfer.failed'
|
|
127
|
+
'transfer.updated'
|
|
89
128
|
]
|
|
90
129
|
end
|
|
91
130
|
end
|
data/lib/stripe_mock/data.rb
CHANGED
|
@@ -193,8 +193,30 @@ module StripeMock
|
|
|
193
193
|
currency: currency,
|
|
194
194
|
destination: nil,
|
|
195
195
|
fraud_details: {},
|
|
196
|
+
payment_method_details: {
|
|
197
|
+
card: {
|
|
198
|
+
brand: "visa",
|
|
199
|
+
checks: {
|
|
200
|
+
address_line1_check: nil,
|
|
201
|
+
address_postal_code_check: nil,
|
|
202
|
+
cvc_check: "pass"
|
|
203
|
+
},
|
|
204
|
+
country: "US",
|
|
205
|
+
exp_month: 12,
|
|
206
|
+
exp_year: 2013,
|
|
207
|
+
fingerprint: "3TQGpK9JoY1GgXPw",
|
|
208
|
+
funding: "credit",
|
|
209
|
+
installments: nil,
|
|
210
|
+
last4: "4242",
|
|
211
|
+
network: "visa",
|
|
212
|
+
three_d_secure: nil,
|
|
213
|
+
wallet: nil
|
|
214
|
+
},
|
|
215
|
+
type: "card"
|
|
216
|
+
},
|
|
196
217
|
receipt_email: nil,
|
|
197
218
|
receipt_number: nil,
|
|
219
|
+
receipt_url: nil,
|
|
198
220
|
refunded: false,
|
|
199
221
|
shipping: {},
|
|
200
222
|
statement_descriptor: "Charge #{charge_id}",
|
|
@@ -255,7 +277,8 @@ module StripeMock
|
|
|
255
277
|
charge: "ch_4fWhYjzQ23UFWT",
|
|
256
278
|
receipt_number: nil,
|
|
257
279
|
status: "succeeded",
|
|
258
|
-
reason: "requested_by_customer"
|
|
280
|
+
reason: "requested_by_customer",
|
|
281
|
+
receipt_url: nil
|
|
259
282
|
}.merge(params)
|
|
260
283
|
end
|
|
261
284
|
|
|
@@ -365,7 +388,7 @@ module StripeMock
|
|
|
365
388
|
canceled_at: nil,
|
|
366
389
|
collection_method: 'charge_automatically',
|
|
367
390
|
ended_at: nil,
|
|
368
|
-
|
|
391
|
+
start_date: 1308595038,
|
|
369
392
|
object: 'subscription',
|
|
370
393
|
trial_start: 1308595038,
|
|
371
394
|
trial_end: 1308681468,
|
|
@@ -427,7 +450,8 @@ module StripeMock
|
|
|
427
450
|
next_payment_attempt: 1349825350,
|
|
428
451
|
charge: nil,
|
|
429
452
|
discount: nil,
|
|
430
|
-
subscription: nil
|
|
453
|
+
subscription: nil,
|
|
454
|
+
number: "6C41730-0001"
|
|
431
455
|
}.merge(params)
|
|
432
456
|
if invoice[:discount]
|
|
433
457
|
invoice[:total] = [0, invoice[:subtotal] - invoice[:discount][:coupon][:amount_off]].max if invoice[:discount][:coupon][:amount_off]
|
|
@@ -634,6 +658,44 @@ module StripeMock
|
|
|
634
658
|
}.merge(params)
|
|
635
659
|
end
|
|
636
660
|
|
|
661
|
+
def self.mock_promotion_code(params={})
|
|
662
|
+
{
|
|
663
|
+
id: "mock_promo_abc123",
|
|
664
|
+
object: "promotion_code",
|
|
665
|
+
active: true,
|
|
666
|
+
code: "TESTCODE",
|
|
667
|
+
coupon: {
|
|
668
|
+
id: "mock_coupon_abc123",
|
|
669
|
+
object: "coupon",
|
|
670
|
+
amount_off: nil,
|
|
671
|
+
created: 1665773498,
|
|
672
|
+
currency: "usd",
|
|
673
|
+
duration: "repeating",
|
|
674
|
+
duration_in_months: 1,
|
|
675
|
+
livemode: false,
|
|
676
|
+
max_redemptions: nil,
|
|
677
|
+
metadata: {},
|
|
678
|
+
name: "Mock Coupon",
|
|
679
|
+
percent_off: 10.0,
|
|
680
|
+
redeem_by: nil,
|
|
681
|
+
times_redeemed: 0,
|
|
682
|
+
valid: true
|
|
683
|
+
},
|
|
684
|
+
created: 1665773499,
|
|
685
|
+
customer: nil,
|
|
686
|
+
expires_at: nil,
|
|
687
|
+
livemode: false,
|
|
688
|
+
max_redemptions: nil,
|
|
689
|
+
metadata: {},
|
|
690
|
+
restrictions: {
|
|
691
|
+
first_time_transaction: false,
|
|
692
|
+
minimum_amount: nil,
|
|
693
|
+
minimum_amount_currency: nil
|
|
694
|
+
},
|
|
695
|
+
times_redeemed: 0
|
|
696
|
+
}.merge(params)
|
|
697
|
+
end
|
|
698
|
+
|
|
637
699
|
def self.mock_recipient(cards, params={})
|
|
638
700
|
rp_id = params[:id] || "test_rp_default"
|
|
639
701
|
cards.each {|card| card[:recipient] = rp_id}
|
|
@@ -1144,8 +1206,7 @@ module StripeMock
|
|
|
1144
1206
|
id: id,
|
|
1145
1207
|
object: 'subscription_item',
|
|
1146
1208
|
created: 1504716183,
|
|
1147
|
-
metadata: {
|
|
1148
|
-
},
|
|
1209
|
+
metadata: {},
|
|
1149
1210
|
plan: {
|
|
1150
1211
|
id: 'PER_USER_PLAN1',
|
|
1151
1212
|
object: 'plan',
|
|
@@ -1237,21 +1298,29 @@ module StripeMock
|
|
|
1237
1298
|
payment_method_id = params[:id] || 'pm_1ExEuFL2DI6wht39WNJgbybl'
|
|
1238
1299
|
|
|
1239
1300
|
type = params[:type].to_sym
|
|
1301
|
+
last4 = params.dig(:card, :number)
|
|
1240
1302
|
data = {
|
|
1241
1303
|
card: {
|
|
1242
|
-
brand:
|
|
1304
|
+
brand: case last4&.to_s
|
|
1305
|
+
when /^4/, nil
|
|
1306
|
+
'visa'
|
|
1307
|
+
when /^5[1-5]/
|
|
1308
|
+
'mastercard'
|
|
1309
|
+
else
|
|
1310
|
+
'unknown'
|
|
1311
|
+
end,
|
|
1243
1312
|
checks: {
|
|
1244
1313
|
address_line1_check: nil,
|
|
1245
1314
|
address_postal_code_check: nil,
|
|
1246
1315
|
cvc_check: 'pass'
|
|
1247
1316
|
},
|
|
1248
1317
|
country: 'FR',
|
|
1249
|
-
exp_month: 2,
|
|
1250
|
-
exp_year: 2022,
|
|
1318
|
+
exp_month: params.dig(:card, :exp_month) || 2,
|
|
1319
|
+
exp_year: params.dig(:card, :exp_year) || 2022,
|
|
1251
1320
|
fingerprint: 'Hr3Ly5z5IYxsokWA',
|
|
1252
1321
|
funding: 'credit',
|
|
1253
1322
|
generated_from: nil,
|
|
1254
|
-
last4: '3155',
|
|
1323
|
+
last4: last4.nil? ? '3155' : last4.to_s[-4..],
|
|
1255
1324
|
three_d_secure_usage: { supported: true },
|
|
1256
1325
|
wallet: nil
|
|
1257
1326
|
},
|
|
@@ -1266,7 +1335,7 @@ module StripeMock
|
|
|
1266
1335
|
branch_code: '',
|
|
1267
1336
|
country: 'DE',
|
|
1268
1337
|
fingerprint: 'FD81kbVPe7M05BMj',
|
|
1269
|
-
last4: '3000'
|
|
1338
|
+
last4: params.dig(:sepa_debit, :iban)&.[](-4..) || '3000'
|
|
1270
1339
|
}
|
|
1271
1340
|
}
|
|
1272
1341
|
|
|
@@ -1291,7 +1360,7 @@ module StripeMock
|
|
|
1291
1360
|
metadata: {
|
|
1292
1361
|
order_id: '123456789'
|
|
1293
1362
|
}
|
|
1294
|
-
}.merge(type => data[type])
|
|
1363
|
+
}.merge(params).merge(type => data[type])
|
|
1295
1364
|
end
|
|
1296
1365
|
|
|
1297
1366
|
def self.mock_setup_intent(params = {})
|
data/lib/stripe_mock/instance.rb
CHANGED
|
@@ -44,6 +44,7 @@ module StripeMock
|
|
|
44
44
|
include StripeMock::RequestHandlers::Plans
|
|
45
45
|
include StripeMock::RequestHandlers::Prices
|
|
46
46
|
include StripeMock::RequestHandlers::Products
|
|
47
|
+
include StripeMock::RequestHandlers::PromotionCodes
|
|
47
48
|
include StripeMock::RequestHandlers::Refunds
|
|
48
49
|
include StripeMock::RequestHandlers::Recipients
|
|
49
50
|
include StripeMock::RequestHandlers::Transfers
|
|
@@ -57,8 +58,9 @@ module StripeMock
|
|
|
57
58
|
|
|
58
59
|
attr_reader :accounts, :balance, :balance_transactions, :bank_tokens, :charges, :coupons, :customers,
|
|
59
60
|
:disputes, :events, :invoices, :invoice_items, :orders, :payment_intents, :payment_methods,
|
|
60
|
-
:setup_intents, :plans, :prices, :recipients, :refunds, :transfers, :payouts,
|
|
61
|
-
:subscriptions_items, :products, :tax_rates, :checkout_sessions
|
|
61
|
+
:setup_intents, :plans, :prices, :promotion_codes, :recipients, :refunds, :transfers, :payouts,
|
|
62
|
+
:subscriptions, :country_spec, :subscriptions_items, :products, :tax_rates, :checkout_sessions,
|
|
63
|
+
:checkout_session_line_items
|
|
62
64
|
|
|
63
65
|
attr_accessor :error_queue, :debug, :conversion_rate, :account_balance
|
|
64
66
|
|
|
@@ -83,6 +85,7 @@ module StripeMock
|
|
|
83
85
|
@plans = {}
|
|
84
86
|
@prices = {}
|
|
85
87
|
@products = {}
|
|
88
|
+
@promotion_codes = {}
|
|
86
89
|
@recipients = {}
|
|
87
90
|
@refunds = {}
|
|
88
91
|
@transfers = {}
|
|
@@ -92,6 +95,7 @@ module StripeMock
|
|
|
92
95
|
@country_spec = {}
|
|
93
96
|
@tax_rates = {}
|
|
94
97
|
@checkout_sessions = {}
|
|
98
|
+
@checkout_session_line_items = {}
|
|
95
99
|
|
|
96
100
|
@debug = false
|
|
97
101
|
@error_queue = ErrorQueue.new
|
|
@@ -4,12 +4,13 @@ module StripeMock
|
|
|
4
4
|
VALID_START_YEAR = 2009
|
|
5
5
|
|
|
6
6
|
def Accounts.included(klass)
|
|
7
|
-
klass.add_handler 'post /v1/accounts',
|
|
8
|
-
klass.add_handler 'get /v1/account',
|
|
9
|
-
klass.add_handler 'get /v1/accounts/(.*)',
|
|
10
|
-
klass.add_handler 'post /v1/accounts/(.*)',
|
|
11
|
-
klass.add_handler 'get /v1/accounts',
|
|
12
|
-
klass.add_handler 'post /oauth/deauthorize'
|
|
7
|
+
klass.add_handler 'post /v1/accounts', :new_account
|
|
8
|
+
klass.add_handler 'get /v1/account', :get_account
|
|
9
|
+
klass.add_handler 'get /v1/accounts/(.*)', :get_account
|
|
10
|
+
klass.add_handler 'post /v1/accounts/(.*)', :update_account
|
|
11
|
+
klass.add_handler 'get /v1/accounts', :list_accounts
|
|
12
|
+
klass.add_handler 'post /oauth/deauthorize', :deauthorize
|
|
13
|
+
klass.add_handler 'delete /v1/accounts/(.*)', :delete_account
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def new_account(route, method_url, params, headers)
|
|
@@ -48,6 +49,16 @@ module StripeMock
|
|
|
48
49
|
Stripe::StripeObject.construct_from(:stripe_user_id => params[:stripe_user_id])
|
|
49
50
|
end
|
|
50
51
|
|
|
52
|
+
def delete_account(route, method_url, params, headers)
|
|
53
|
+
init_account
|
|
54
|
+
route =~ method_url
|
|
55
|
+
assert_existence :account, $1, accounts[$1]
|
|
56
|
+
accounts[$1] = {
|
|
57
|
+
id: accounts[$1][:id],
|
|
58
|
+
deleted: true
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
51
62
|
private
|
|
52
63
|
|
|
53
64
|
def init_account
|
|
@@ -171,6 +171,11 @@ module StripeMock
|
|
|
171
171
|
params[:refunds].has_key?(:data) && params[:refunds][:data].nil?)
|
|
172
172
|
allowed << :refunds
|
|
173
173
|
end
|
|
174
|
+
if params.has_key?(:payment_method_details) && (params[:payment_method_details].empty? ||
|
|
175
|
+
params[:payment_method_details].has_key?(:card) && (params[:payment_method_details][:card].empty? ||
|
|
176
|
+
params[:payment_method_details][:card].has_key?(:checks) && params[:payment_method_details][:card][:checks].empty?))
|
|
177
|
+
allowed << :payment_method_details
|
|
178
|
+
end
|
|
174
179
|
|
|
175
180
|
allowed
|
|
176
181
|
end
|
|
@@ -3,7 +3,135 @@ module StripeMock
|
|
|
3
3
|
module Checkout
|
|
4
4
|
module Session
|
|
5
5
|
def Session.included(klass)
|
|
6
|
-
klass.add_handler '
|
|
6
|
+
klass.add_handler 'post /v1/checkout/sessions', :new_session
|
|
7
|
+
klass.add_handler 'get /v1/checkout/sessions', :list_checkout_sessions
|
|
8
|
+
klass.add_handler 'get /v1/checkout/sessions/([^/]*)', :get_checkout_session
|
|
9
|
+
klass.add_handler 'get /v1/checkout/sessions/([^/]*)/line_items', :list_line_items
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def new_session(route, method_url, params, headers)
|
|
13
|
+
id = params[:id] || new_id('cs')
|
|
14
|
+
|
|
15
|
+
[:cancel_url, :success_url].each do |p|
|
|
16
|
+
require_param(p) if params[p].nil? || params[p].empty?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
line_items = nil
|
|
20
|
+
if params[:line_items]
|
|
21
|
+
line_items = params[:line_items].each_with_index.map do |line_item, i|
|
|
22
|
+
throw Stripe::InvalidRequestError("Quantity is required. Add `quantity` to `line_items[#{i}]`") unless line_item[:quantity]
|
|
23
|
+
unless line_item[:price] || line_item[:price_data] || (line_item[:amount] && line_item[:currency] && line_item[:name])
|
|
24
|
+
throw Stripe::InvalidRequestError("Price or amount and currency is required. Add `price`, `price_data`, or `amount`, `currency` and `name` to `line_items[#{i}]`")
|
|
25
|
+
end
|
|
26
|
+
{
|
|
27
|
+
id: new_id("li"),
|
|
28
|
+
price: if line_item[:price]
|
|
29
|
+
line_item[:price]
|
|
30
|
+
elsif line_item[:price_data]
|
|
31
|
+
new_price(nil, nil, line_item[:price_data], nil)[:id]
|
|
32
|
+
else
|
|
33
|
+
new_price(nil, nil, {
|
|
34
|
+
unit_amount: line_item[:amount],
|
|
35
|
+
currency: line_item[:currency],
|
|
36
|
+
product_data: {
|
|
37
|
+
name: line_item[:name]
|
|
38
|
+
}
|
|
39
|
+
}, nil)[:id]
|
|
40
|
+
end,
|
|
41
|
+
quantity: line_item[:quantity]
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
amount = nil
|
|
47
|
+
currency = nil
|
|
48
|
+
if line_items
|
|
49
|
+
amount = 0
|
|
50
|
+
|
|
51
|
+
line_items.each do |line_item|
|
|
52
|
+
price = prices[line_item[:price]]
|
|
53
|
+
|
|
54
|
+
if price.nil?
|
|
55
|
+
raise StripeMock::StripeMockError.new("Price not found for ID: #{line_item[:price]}")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
amount += (price[:unit_amount] * line_item[:quantity])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
currency = prices[line_items.first[:price]][:currency]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
payment_status = "unpaid"
|
|
65
|
+
payment_intent = nil
|
|
66
|
+
setup_intent = nil
|
|
67
|
+
case params[:mode]
|
|
68
|
+
when nil, "payment"
|
|
69
|
+
params[:customer] ||= new_customer(nil, nil, {email: params[:customer_email]}, nil)[:id]
|
|
70
|
+
require_param(:line_items) if params[:line_items].nil? || params[:line_items].empty?
|
|
71
|
+
payment_intent = new_payment_intent(nil, nil, {
|
|
72
|
+
amount: amount,
|
|
73
|
+
currency: currency,
|
|
74
|
+
customer: params[:customer],
|
|
75
|
+
payment_method_options: params[:payment_method_options],
|
|
76
|
+
payment_method_types: params[:payment_method_types]
|
|
77
|
+
}.merge(params[:payment_intent_data] || {}), nil)[:id]
|
|
78
|
+
checkout_session_line_items[id] = line_items
|
|
79
|
+
when "setup"
|
|
80
|
+
if !params[:line_items].nil? && !params[:line_items].empty?
|
|
81
|
+
throw Stripe::InvalidRequestError.new("You cannot pass `line_items` in `setup` mode", :line_items, http_status: 400)
|
|
82
|
+
end
|
|
83
|
+
setup_intent = new_setup_intent(nil, nil, {
|
|
84
|
+
customer: params[:customer],
|
|
85
|
+
payment_method_options: params[:payment_method_options],
|
|
86
|
+
payment_method_types: params[:payment_method_types]
|
|
87
|
+
}.merge(params[:setup_intent_data] || {}), nil)[:id]
|
|
88
|
+
payment_status = "no_payment_required"
|
|
89
|
+
when "subscription"
|
|
90
|
+
params[:customer] ||= new_customer(nil, nil, {email: params[:customer_email]}, nil)[:id]
|
|
91
|
+
require_param(:line_items) if params[:line_items].nil? || params[:line_items].empty?
|
|
92
|
+
checkout_session_line_items[id] = line_items
|
|
93
|
+
else
|
|
94
|
+
throw Stripe::InvalidRequestError.new("Invalid mode: must be one of payment, setup, or subscription", :mode, http_status: 400)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
checkout_sessions[id] = {
|
|
98
|
+
id: id,
|
|
99
|
+
object: "checkout.session",
|
|
100
|
+
allow_promotion_codes: nil,
|
|
101
|
+
amount_subtotal: amount,
|
|
102
|
+
amount_total: amount,
|
|
103
|
+
automatic_tax: {
|
|
104
|
+
enabled: false,
|
|
105
|
+
status: nil
|
|
106
|
+
},
|
|
107
|
+
billing_address_collection: nil,
|
|
108
|
+
cancel_url: params[:cancel_url],
|
|
109
|
+
client_reference_id: nil,
|
|
110
|
+
currency: currency,
|
|
111
|
+
customer: params[:customer],
|
|
112
|
+
customer_details: nil,
|
|
113
|
+
customer_email: params[:customer_email],
|
|
114
|
+
livemode: false,
|
|
115
|
+
locale: nil,
|
|
116
|
+
metadata: params[:metadata],
|
|
117
|
+
mode: params[:mode],
|
|
118
|
+
payment_intent: payment_intent,
|
|
119
|
+
payment_method_options: params[:payment_method_options],
|
|
120
|
+
payment_method_types: params[:payment_method_types],
|
|
121
|
+
payment_status: payment_status,
|
|
122
|
+
setup_intent: setup_intent,
|
|
123
|
+
shipping: nil,
|
|
124
|
+
shipping_address_collection: nil,
|
|
125
|
+
submit_type: nil,
|
|
126
|
+
subscription: nil,
|
|
127
|
+
success_url: params[:success_url],
|
|
128
|
+
total_details: nil,
|
|
129
|
+
url: URI.join(StripeMock.checkout_base, id).to_s
|
|
130
|
+
}
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def list_checkout_sessions(route, method_url, params, headers)
|
|
134
|
+
Data.mock_list_object(checkout_sessions.values)
|
|
7
135
|
end
|
|
8
136
|
|
|
9
137
|
def get_checkout_session(route, method_url, params, headers)
|
|
@@ -16,6 +144,35 @@ module StripeMock
|
|
|
16
144
|
end
|
|
17
145
|
checkout_session
|
|
18
146
|
end
|
|
147
|
+
|
|
148
|
+
def list_line_items(route, method_url, params, headers)
|
|
149
|
+
route =~ method_url
|
|
150
|
+
checkout_session = assert_existence :checkout_session, $1, checkout_sessions[$1]
|
|
151
|
+
|
|
152
|
+
case checkout_session[:mode]
|
|
153
|
+
when "payment", "subscription"
|
|
154
|
+
line_items = assert_existence :checkout_session_line_items, $1, checkout_session_line_items[$1]
|
|
155
|
+
line_items.map do |line_item|
|
|
156
|
+
price = prices[line_item[:price]].clone
|
|
157
|
+
|
|
158
|
+
if price.nil?
|
|
159
|
+
raise StripeMock::StripeMockError.new("Price not found for ID: #{line_item[:price]}")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
{
|
|
163
|
+
id: line_item[:id],
|
|
164
|
+
object: "item",
|
|
165
|
+
amount_subtotal: price[:unit_amount] * line_item[:quantity],
|
|
166
|
+
amount_total: price[:unit_amount] * line_item[:quantity],
|
|
167
|
+
currency: price[:currency],
|
|
168
|
+
price: price.clone,
|
|
169
|
+
quantity: line_item[:quantity]
|
|
170
|
+
}
|
|
171
|
+
end
|
|
172
|
+
else
|
|
173
|
+
throw Stripe::InvalidRequestError("Only payment and subscription sessions have line items")
|
|
174
|
+
end
|
|
175
|
+
end
|
|
19
176
|
end
|
|
20
177
|
end
|
|
21
178
|
end
|