pagseguro-oficial 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -1
  3. data/CHANGELOG.md +17 -1
  4. data/Gemfile +0 -2
  5. data/README.md +1 -1
  6. data/docs/transparent_checkout.md +3 -3
  7. data/examples/authorization/create_authorization.rb +32 -0
  8. data/examples/authorization/create_authorization_with_account.rb +56 -0
  9. data/examples/authorization/{authorization_by_code.rb → search/search_authorization_by_code.rb} +4 -4
  10. data/examples/authorization/search/search_authorization_by_date.rb +35 -0
  11. data/examples/authorization/{authorization_by_notification_code.rb → search/search_authorization_by_notification_code.rb} +4 -5
  12. data/examples/authorization/search/search_authorization_by_reference.rb +31 -0
  13. data/examples/{payment_request.rb → checkout/create_payment_request.rb} +5 -7
  14. data/examples/{transaction/abandoned_transactions.rb → checkout/search/search_abandoned_transactions.rb} +6 -3
  15. data/examples/{transaction/transaction_by_code.rb → checkout/search/search_transaction_by_code.rb} +3 -3
  16. data/examples/{transaction/transaction_by_notification_code.rb → checkout/search/search_transaction_by_notification_code.rb} +3 -3
  17. data/examples/{transaction/transaction_by_reference.rb → checkout/search/search_transaction_by_reference.rb} +3 -3
  18. data/examples/{transaction/transaction_status_history.rb → checkout/search/search_transaction_status_history.rb} +2 -2
  19. data/examples/{transaction/transactions_by_date.rb → checkout/search/search_transactions_by_date.rb} +3 -3
  20. data/examples/{transaction/boleto_transaction_request.rb → direct/create_transaction_using_boleto.rb} +1 -1
  21. data/examples/{transaction/credit_card_transaction_request.rb → direct/create_transaction_using_credit_card.rb} +1 -1
  22. data/examples/{transaction/online_debit_transaction.rb → direct/create_transaction_using_online_debit.rb} +1 -1
  23. data/examples/{installment.rb → get_installments.rb} +3 -3
  24. data/examples/{transaction/transaction_cancellation.rb → request_transaction_cancellation.rb} +2 -2
  25. data/examples/{authorization/authorization.rb → split_payment/create_authorization.rb} +5 -4
  26. data/examples/split_payment/create_payment_request.rb +71 -0
  27. data/lib/pagseguro.rb +14 -8
  28. data/lib/pagseguro/account.rb +29 -0
  29. data/lib/pagseguro/authorization.rb +10 -4
  30. data/lib/pagseguro/authorization/collection.rb +3 -1
  31. data/lib/pagseguro/authorization/request_serializer.rb +6 -6
  32. data/lib/pagseguro/authorization/response.rb +2 -2
  33. data/lib/pagseguro/authorization_request.rb +16 -4
  34. data/lib/pagseguro/authorization_request/request_serializer.rb +127 -15
  35. data/lib/pagseguro/authorization_request/response.rb +2 -2
  36. data/lib/pagseguro/company.rb +41 -0
  37. data/lib/pagseguro/document.rb +4 -0
  38. data/lib/pagseguro/documents.rb +5 -0
  39. data/lib/pagseguro/errors.rb +27 -5
  40. data/lib/pagseguro/extensions/collection_object.rb +34 -0
  41. data/lib/pagseguro/item.rb +4 -0
  42. data/lib/pagseguro/items.rb +10 -17
  43. data/lib/pagseguro/partner.rb +20 -0
  44. data/lib/pagseguro/payment_request.rb +30 -2
  45. data/lib/pagseguro/payment_request/{serializer.rb → request_serializer.rb} +86 -1
  46. data/lib/pagseguro/person.rb +40 -0
  47. data/lib/pagseguro/phone.rb +8 -0
  48. data/lib/pagseguro/phones.rb +5 -0
  49. data/lib/pagseguro/receiver.rb +17 -0
  50. data/lib/pagseguro/receiver_split.rb +15 -0
  51. data/lib/pagseguro/request.rb +25 -1
  52. data/lib/pagseguro/transaction.rb +7 -5
  53. data/lib/pagseguro/transaction_cancellation.rb +3 -6
  54. data/lib/pagseguro/transaction_cancellation/response.rb +7 -6
  55. data/lib/pagseguro/{refund.rb → transaction_refund.rb} +2 -2
  56. data/lib/pagseguro/{refund → transaction_refund}/request_serializer.rb +1 -1
  57. data/lib/pagseguro/{refund → transaction_refund}/response.rb +9 -9
  58. data/lib/pagseguro/{refund → transaction_refund}/response_serializer.rb +1 -1
  59. data/lib/pagseguro/transaction_request.rb +1 -4
  60. data/lib/pagseguro/transaction_request/response.rb +8 -7
  61. data/lib/pagseguro/version.rb +1 -1
  62. data/locales/pt-BR.yml +1 -0
  63. data/pagseguro-oficial.gemspec +13 -13
  64. data/spec/fixtures/authorization/search_authorization.xml +47 -0
  65. data/spec/fixtures/authorization_request/authorization_request.xml +11 -0
  66. data/spec/fixtures/authorization_request/authorization_request_with_account.xml +47 -0
  67. data/spec/pagseguro/account_spec.rb +27 -0
  68. data/spec/pagseguro/authorization/collection_spec.rb +3 -3
  69. data/spec/pagseguro/authorization/request_serializer_spec.rb +1 -2
  70. data/spec/pagseguro/authorization/response_spec.rb +70 -15
  71. data/spec/pagseguro/authorization_request/request_serializer_spec.rb +314 -14
  72. data/spec/pagseguro/authorization_request_spec.rb +96 -31
  73. data/spec/pagseguro/authorization_spec.rb +52 -10
  74. data/spec/pagseguro/company_spec.rb +12 -0
  75. data/spec/pagseguro/documents_spec.rb +37 -0
  76. data/spec/pagseguro/errors_spec.rb +45 -6
  77. data/spec/pagseguro/extensions/collection_object_spec.rb +77 -0
  78. data/spec/pagseguro/features/create_transaction_request_spec.rb +2 -2
  79. data/spec/pagseguro/installment/response_spec.rb +5 -3
  80. data/spec/pagseguro/installment_spec.rb +5 -2
  81. data/spec/pagseguro/items_spec.rb +104 -12
  82. data/spec/pagseguro/pagseguro_spec.rb +8 -8
  83. data/spec/pagseguro/partner_spec.rb +8 -0
  84. data/spec/pagseguro/payment_request/request_serializer_spec.rb +251 -0
  85. data/spec/pagseguro/payment_request_spec.rb +67 -11
  86. data/spec/pagseguro/person_spec.rb +10 -0
  87. data/spec/pagseguro/phones_spec.rb +38 -0
  88. data/spec/pagseguro/receiver_spec.rb +6 -0
  89. data/spec/pagseguro/receiver_split_spec.rb +7 -0
  90. data/spec/pagseguro/request_spec.rb +27 -4
  91. data/spec/pagseguro/session/response_spec.rb +6 -2
  92. data/spec/pagseguro/session_spec.rb +5 -2
  93. data/spec/pagseguro/transaction/response_spec.rb +14 -8
  94. data/spec/pagseguro/transaction/search_spec.rb +15 -8
  95. data/spec/pagseguro/transaction_cancellation/response_spec.rb +6 -3
  96. data/spec/pagseguro/transaction_cancellation_spec.rb +7 -5
  97. data/spec/pagseguro/{refund → transaction_refund}/request_serializer_spec.rb +3 -3
  98. data/spec/pagseguro/{refund → transaction_refund}/response_serializer_spec.rb +1 -1
  99. data/spec/pagseguro/{refund → transaction_refund}/response_spec.rb +3 -3
  100. data/spec/pagseguro/{refund_spec.rb → transaction_refund_spec.rb} +15 -7
  101. data/spec/pagseguro/transaction_request/request_serializer_spec.rb +9 -9
  102. data/spec/pagseguro/transaction_request/response_spec.rb +5 -2
  103. data/spec/pagseguro/transaction_request_spec.rb +14 -9
  104. data/spec/pagseguro/transaction_spec.rb +37 -30
  105. data/spec/spec_helper.rb +0 -3
  106. data/spec/support/ensure_type_macro.rb +20 -2
  107. data/{examples/refund.rb → transaction/transaction_refund.rb} +1 -1
  108. metadata +112 -96
  109. data/examples/transaction/invalid_transaction_by_notification_code.rb +0 -22
  110. data/lib/pagseguro/refund/serializer.rb +0 -24
  111. data/lib/pagseguro/transaction_cancellation/serializer.rb +0 -18
  112. data/spec/pagseguro/payment_request/serializer_spec.rb +0 -166
  113. data/spec/pagseguro/refund/serializer_spec.rb +0 -15
  114. data/spec/pagseguro/transaction_cancellation/serializer_spec.rb +0 -13
@@ -9,53 +9,118 @@ describe PagSeguro::AuthorizationRequest do
9
9
  describe "#create" do
10
10
  let(:request) { double(:request) }
11
11
  let(:response) { double(:response) }
12
- let(:options) do
13
- {
14
- notification_url: "foo",
15
- redirect_url: "bar",
16
- permissions: [:notifications, :searches]
17
- }
18
- end
19
-
20
- let(:params) do
21
- {
22
- notificationURL: "foo",
23
- redirectURL: "bar",
24
- permissions: "RECEIVE_TRANSACTION_NOTIFICATIONS,SEARCH_TRANSACTIONS"
25
- }
26
- end
27
-
28
- subject { described_class.new(options) }
12
+ let(:credentials) { PagSeguro::ApplicationCredentials.new('app123', 'key123') }
29
13
 
30
14
  before do
31
- expect(PagSeguro::Request).to receive(:post)
32
- .with("authorizations/request", 'v2', params)
15
+ expect(PagSeguro::Request).to receive(:post_xml)
16
+ .with('authorizations/request', 'v2', credentials, data)
33
17
  .and_return(request)
18
+
34
19
  expect(PagSeguro::AuthorizationRequest::Response).to receive(:new)
35
20
  .with(request)
36
21
  .and_return(response)
22
+
37
23
  expect(response).to receive(:serialize).and_return(serialized_data)
38
24
  end
39
25
 
40
- context "when request succeeds" do
41
- let(:serialized_data) { {code: "123"} }
26
+ context "when an account is not given" do
27
+ let(:options) do
28
+ {
29
+ credentials: credentials,
30
+ notification_url: 'http://seusite.com.br/notification',
31
+ redirect_url: 'http://seusite.com.br/redirect',
32
+ permissions: [:checkouts, :searches, :notifications],
33
+ reference: '123'
34
+ }
35
+ end
36
+
37
+ let(:data) { File.read('./spec/fixtures/authorization_request/authorization_request.xml') }
38
+
39
+ subject { described_class.new(options) }
40
+
41
+ context "when request succeeds" do
42
+ let(:serialized_data) { {code: "123"} }
43
+
44
+ it "creates a transaction request" do
45
+ expect(response).to receive(:success?).and_return(true)
42
46
 
43
- it "creates a transaction request" do
44
- expect(response).to receive(:success?).and_return(true)
47
+ expect(subject.create).to be_truthy
48
+ expect(subject.code).to eq(serialized_data[:code])
49
+ end
50
+ end
51
+
52
+
53
+ context "when request fails" do
54
+ let(:serialized_data) { {errors: PagSeguro::Errors.new} }
45
55
 
46
- expect(subject.create).to be_truthy
47
- expect(subject.code).to eq(serialized_data[:code])
56
+ it "does not create a transaction request" do
57
+ expect(response).to receive(:success?).and_return(false)
58
+
59
+ expect(subject.create).to be_falsey
60
+ expect(subject.code).to be_nil
61
+ end
48
62
  end
49
63
  end
50
64
 
51
- context "when request fails" do
52
- let(:serialized_data) { {errors: PagSeguro::Errors.new} }
65
+ context "when an account is given" do
66
+ let(:options) do
67
+ {
68
+ credentials: credentials,
69
+ permissions: [:checkouts, :searches, :notifications],
70
+ notification_url: 'http://seusite.com.br/notification',
71
+ redirect_url: 'http://seusite.com.br/redirect',
72
+ reference: '123',
73
+ account: {
74
+ email: 'usuario@seusite.com.br',
75
+ type: 'SELLER',
76
+ person: {
77
+ name: 'Antonio Carlos',
78
+ birth_date: '05/02/1982',
79
+ address: {
80
+ postal_code: 01452002,
81
+ street: 'Av. Brig. Faria Lima',
82
+ number: 1384,
83
+ complement: '5o andar',
84
+ district: 'Jardim Paulistano',
85
+ city: 'Sao Paulo',
86
+ state: 'SP',
87
+ country: 'BRA'
88
+ },
89
+ documents: [{type: 'CPF', value: 99988877766}],
90
+ phones: [
91
+ {type: 'HOME', area_code: 11, number: 30302323},
92
+ {type: 'MOBILE', area_code: 11, number: 976302323},
93
+ ]
94
+ }
95
+ }
96
+ }
97
+ end
98
+ let(:data) { File.read('./spec/fixtures/authorization_request/authorization_request_with_account.xml') }
99
+ let(:request) { double(:request) }
100
+ let(:response) { double(:response) }
101
+
102
+ subject { described_class.new(options) }
103
+
104
+ context "when request succeeds" do
105
+ let(:serialized_data) { {code: "123"} }
106
+
107
+ it "creates a transaction request send xml" do
108
+ expect(response).to receive(:success?).and_return(true)
109
+
110
+ expect(subject.create).to be_truthy
111
+ expect(subject.code).to eq(serialized_data[:code])
112
+ end
113
+ end
114
+
115
+ context "when request fails" do
116
+ let(:serialized_data) { {errors: PagSeguro::Errors.new} }
53
117
 
54
- it "does not create a transaction request" do
55
- expect(response).to receive(:success?).and_return(false)
118
+ it "does not create a transaction request" do
119
+ expect(response).to receive(:success?).and_return(false)
56
120
 
57
- expect(subject.create).to be_falsey
58
- expect(subject.code).to be_nil
121
+ expect(subject.create).to be_falsey
122
+ expect(subject.code).to be_nil
123
+ end
59
124
  end
60
125
  end
61
126
  end
@@ -48,22 +48,64 @@ describe PagSeguro::Authorization do
48
48
  end
49
49
  end
50
50
 
51
- describe "find_by_date" do
52
- let(:date_options) { { initial_date: Date.today, final_date: Date.today + 1 } }
51
+ describe "find_by" do
53
52
  before do
54
- expect(PagSeguro::Request).to receive(:get)
55
- .with("authorizations", 'v2', {})
56
- .and_return(request)
57
- expect(PagSeguro::Authorization::Collection).to receive(:new)
53
+ allow(PagSeguro::Authorization::Collection).to receive(:new)
58
54
  .and_return(collection)
59
- expect(PagSeguro::Authorization::Response).to receive(:new)
55
+ allow(PagSeguro::Authorization::Response).to receive(:new)
60
56
  .with(request, collection)
61
57
  .and_return(response)
62
- expect(response).to receive(:serialize_collection)
58
+ allow(response).to receive(:serialize_collection)
63
59
  end
64
60
 
65
- it 'finds authorizations by a date range' do
66
- expect(PagSeguro::Authorization.find_by_date(date_options)).to eq (collection)
61
+ context "with reference" do
62
+ let(:reference) { 'REF1111' }
63
+
64
+ it 'finds authorizations' do
65
+ expect(PagSeguro::Request).to receive(:get)
66
+ .with("authorizations", 'v2', { reference: 'REF1111' })
67
+ .and_return(request)
68
+
69
+ PagSeguro::Authorization.find_by(reference: 'REF1111')
70
+ end
71
+ end
72
+
73
+ context "with initial_date" do
74
+ let(:initial_date) { Time.new(2015, 11, 1, 12, 0) }
75
+
76
+ it 'finds authorizations' do
77
+ expect(PagSeguro::Request).to receive(:get)
78
+ .with("authorizations", 'v2', { initialDate: initial_date.xmlschema })
79
+ .and_return(request)
80
+
81
+ PagSeguro::Authorization.find_by(initial_date: initial_date)
82
+ end
83
+ end
84
+
85
+ context "with final_date" do
86
+ let(:final_date) { Time.new(2015, 11, 1, 12, 0) }
87
+
88
+ it 'finds authorizations' do
89
+ expect(PagSeguro::Request).to receive(:get)
90
+ .with("authorizations", 'v2', { finalDate: final_date.xmlschema })
91
+ .and_return(request)
92
+
93
+ PagSeguro::Authorization.find_by(final_date: final_date)
94
+ end
95
+ end
96
+
97
+ context "with credentials" do
98
+ let(:credentials) do
99
+ PagSeguro::ApplicationCredentials.new('APP_ID', 'APP_KEY')
100
+ end
101
+
102
+ it 'passes' do
103
+ expect(PagSeguro::Request).to receive(:get)
104
+ .with("authorizations", 'v2', { credentials: credentials })
105
+ .and_return(request)
106
+
107
+ PagSeguro::Authorization.find_by(credentials: credentials)
108
+ end
67
109
  end
68
110
  end
69
111
  end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe PagSeguro::Company do
4
+ it_assigns_attribute :name
5
+ it_assigns_attribute :display_name
6
+ it_assigns_attribute :website_url
7
+
8
+ it_ensures_type PagSeguro::Address, :address
9
+ it_ensures_type PagSeguro::Partner, :partner
10
+ it_ensures_collection_type PagSeguro::Document, :documents, [{type: 'CPF', value: 12312312312}]
11
+ it_ensures_collection_type PagSeguro::Phone, :phones, [{type: 'HOME', area_code: 11, number: 12312312312}]
12
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe PagSeguro::Documents do
4
+ [
5
+ :size,
6
+ :clear,
7
+ :empty?,
8
+ :any?,
9
+ :each,
10
+ :map
11
+ ].each do |method|
12
+ it { is_expected.to respond_to(method) }
13
+ end
14
+
15
+ it 'should initialize empty' do
16
+ expect(subject).to be_empty
17
+ end
18
+
19
+ context 'adding a new document' do
20
+ let(:document) { { type: 'CPF', value: 11122233344 } }
21
+
22
+ it "shouldn't add the same document" do
23
+ subject << document
24
+ expect{ subject << document }.to_not change{ subject.size }
25
+ end
26
+
27
+ context 'ensures type the new document' do
28
+ it 'can be a hash' do
29
+ expect{ subject << document }.to change{ subject.size }.to(1)
30
+ end
31
+
32
+ it 'can be a PagSeguro::Document' do
33
+ expect{ subject << PagSeguro::Document.new(type: 'CPF', value: 11122233344) }.to change{ subject.size }.to(1)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -3,7 +3,13 @@ require "spec_helper"
3
3
 
4
4
  describe PagSeguro::Errors do
5
5
  let(:response) { double }
6
- let(:http_response) { double(:http_response, unauthorized?: true, bad_request?: false, not_found?: false) }
6
+ let(:http_response) do
7
+ double(
8
+ :http_response,
9
+ error?: true,
10
+ error: Aitch::UnauthorizedError
11
+ )
12
+ end
7
13
 
8
14
  context "when have no response" do
9
15
  it "returns errors" do
@@ -16,7 +22,10 @@ describe PagSeguro::Errors do
16
22
  subject(:errors) { PagSeguro::Errors.new(response) }
17
23
 
18
24
  before do
19
- response.stub unauthorized?: true, bad_request?: false, not_found?: false
25
+ allow(response).to receive_messages(
26
+ error?: true,
27
+ error: Aitch::UnauthorizedError
28
+ )
20
29
  errors.add(http_response)
21
30
  end
22
31
 
@@ -28,7 +37,10 @@ describe PagSeguro::Errors do
28
37
  subject(:errors) { PagSeguro::Errors.new(response) }
29
38
 
30
39
  before do
31
- response.stub unauthorized?: true, bad_request?: false, not_found?: true
40
+ allow(response).to receive_messages(
41
+ error?: true,
42
+ error: Aitch::NotFoundError
43
+ )
32
44
  errors.add(http_response)
33
45
  end
34
46
 
@@ -36,6 +48,21 @@ describe PagSeguro::Errors do
36
48
  it { expect(errors).to include(I18n.t("pagseguro.errors.not_found")) }
37
49
  end
38
50
 
51
+ context 'when forbidden' do
52
+ subject(:errors) { PagSeguro::Errors.new(response) }
53
+
54
+ before do
55
+ allow(response).to receive_messages(
56
+ error?: true,
57
+ error: Aitch::ForbiddenError
58
+ )
59
+ errors.add(http_response)
60
+ end
61
+
62
+ it { expect(errors).not_to be_empty }
63
+ it { expect(errors).to include(I18n.t("pagseguro.errors.forbidden")) }
64
+ end
65
+
39
66
  context "when message can't be translated" do
40
67
  let(:error) {
41
68
  <<-XML
@@ -52,7 +79,11 @@ describe PagSeguro::Errors do
52
79
  subject(:errors) { PagSeguro::Errors.new(response) }
53
80
 
54
81
  before do
55
- response.stub data: xml, unauthorized?: false, bad_request?: true, not_found?: true
82
+ allow(response).to receive_messages(
83
+ data: xml,
84
+ error?: true,
85
+ error: Aitch::BadRequestError
86
+ )
56
87
  end
57
88
 
58
89
  it { expect(errors).to include("Sample message") }
@@ -74,7 +105,11 @@ describe PagSeguro::Errors do
74
105
  subject(:errors) { PagSeguro::Errors.new(response) }
75
106
 
76
107
  before do
77
- response.stub data: xml, unauthorized?: false, bad_request?: true, not_found?: false
108
+ allow(response).to receive_messages(
109
+ data: xml,
110
+ error?: true,
111
+ error: Aitch::BadRequestError
112
+ )
78
113
  end
79
114
 
80
115
  it { expect(errors).to include("O parâmetro email deve ser informado.") }
@@ -97,7 +132,11 @@ describe PagSeguro::Errors do
97
132
  subject(:errors) { PagSeguro::Errors.new(response) }
98
133
 
99
134
  before do
100
- response.stub data: xml, unauthorized?: false, bad_request?: true, not_found?: true
135
+ allow(response).to receive_messages(
136
+ data: xml,
137
+ error?: true,
138
+ error: Aitch::BadRequestError
139
+ )
101
140
  errors.add(http_response)
102
141
  end
103
142
 
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe PagSeguro::Extensions::CollectionObject do
4
+ before(:all) do
5
+ # Collection
6
+ class MyItems
7
+ include PagSeguro::Extensions::CollectionObject
8
+ end
9
+
10
+ # Item
11
+ class MyItem
12
+ attr_accessor :name
13
+
14
+ def ==(other)
15
+ name.eql?(other.name)
16
+ end
17
+ end
18
+ end
19
+
20
+ let(:collection) do
21
+ MyItems.new
22
+ end
23
+
24
+ let(:object) do
25
+ item = MyItem.new
26
+ item.name = 'Item1'
27
+ item
28
+ end
29
+
30
+ context "when was included in a new class" do
31
+ it "new class must be include PagSeguro::Extensions::CollectionObject" do
32
+ expect(MyItems.included_modules).to include(PagSeguro::Extensions::CollectionObject)
33
+ end
34
+
35
+ context "and when this class is instantiated" do
36
+ it "must be initialized empty" do
37
+ expect(collection).to be_empty
38
+ end
39
+
40
+ context "must respond to" do
41
+ [
42
+ :size,
43
+ :clear,
44
+ :empty?,
45
+ :any?,
46
+ :each,
47
+ :include?,
48
+ :<<
49
+ ].each do |method|
50
+ it "#{method}" do
51
+ expect(collection).to respond_to(method)
52
+ end
53
+ end
54
+ end
55
+
56
+ it "object type must be the class name singularized" do
57
+ expect(collection.collection_type).to eq MyItem
58
+ end
59
+ end
60
+ end
61
+
62
+ context 'collection operations' do
63
+ it "when add a new object it must be incremented" do
64
+ expect{ collection << object }.to change{ collection.size }.by(1)
65
+ end
66
+
67
+ it "when add an object already added" do
68
+ collection << object
69
+ expect{ collection << object }.to_not change{ collection.size }
70
+ end
71
+
72
+ it "must to include an object before added" do
73
+ collection << object
74
+ expect(collection).to include(object)
75
+ end
76
+ end
77
+ end