wirecard 0.0.1 → 0.0.2

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/README.md +2 -0
  4. data/README.rdoc +3 -0
  5. data/circle.yml +7 -0
  6. data/lib/wirecard.rb +22 -0
  7. data/lib/wirecard/backend/approve_reversal.rb +9 -0
  8. data/lib/wirecard/backend/base.rb +20 -0
  9. data/lib/wirecard/backend/deposit.rb +9 -0
  10. data/lib/wirecard/backend/deposit_reversal.rb +9 -0
  11. data/lib/wirecard/backend/generate_order_number.rb +12 -0
  12. data/lib/wirecard/backend/get_order_details.rb +9 -0
  13. data/lib/wirecard/backend/recur_payment.rb +9 -0
  14. data/lib/wirecard/backend/refund.rb +9 -0
  15. data/lib/wirecard/backend/refund_reversal.rb +9 -0
  16. data/lib/wirecard/backend/transfer_fund/base.rb +15 -0
  17. data/lib/wirecard/backend/transfer_fund/existing_order.rb +11 -0
  18. data/lib/wirecard/base.rb +26 -11
  19. data/lib/wirecard/callback.rb +3 -8
  20. data/lib/wirecard/configuration.rb +4 -0
  21. data/lib/wirecard/data_storage/init.rb +1 -3
  22. data/lib/wirecard/fingerprint/base.rb +13 -2
  23. data/lib/wirecard/payment_process/init.rb +11 -0
  24. data/lib/wirecard/request.rb +2 -6
  25. data/lib/wirecard/response.rb +0 -4
  26. data/lib/wirecard/version.rb +1 -1
  27. data/spec/spec_helper.rb +10 -1
  28. data/spec/unit_tests/backend/approve_reversal_spec.rb +54 -0
  29. data/spec/unit_tests/backend/deposit_reversal_spec.rb +91 -0
  30. data/spec/unit_tests/backend/deposit_spec.rb +79 -0
  31. data/spec/unit_tests/backend/generate_order_number_spec.rb +37 -0
  32. data/spec/unit_tests/backend/get_order_details_spec.rb +233 -0
  33. data/spec/unit_tests/backend/recur_payment_spec.rb +86 -0
  34. data/spec/unit_tests/backend/refund_reversal.rb +60 -0
  35. data/spec/unit_tests/backend/refund_spec.rb +66 -0
  36. data/spec/unit_tests/backend/transfer_fund/existing_order_spec.rb +88 -0
  37. data/spec/unit_tests/callback_spec.rb +101 -0
  38. data/spec/unit_tests/data_storage/init_spec.rb +39 -25
  39. data/spec/unit_tests/data_storage/read_spec.rb +47 -0
  40. data/spec/unit_tests/fingerprint/sha_512_spec.rb +69 -0
  41. data/spec/unit_tests/payment_process/init_spec.rb +94 -0
  42. data/spec/unit_tests/support/shared_context.rb +235 -0
  43. data/spec/unit_tests/support/shared_examples.rb +7 -33
  44. data/spec/unit_tests/support/webserver.rb +29 -0
  45. data/spec/wirecard_spec.rb +133 -133
  46. data/wirecard.gemspec +5 -0
  47. metadata +115 -4
  48. data/spec/unit_tests/base_shared.rb +0 -34
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Wirecard::Backend::RecurPayment do
4
+
5
+ let(:recur_payment) { Wirecard::Backend::RecurPayment.new(options) }
6
+ let(:options) { Hash.new.merge(amount).merge(currency).merge(order_description).merge(source_order_number).merge(auto_deposit).merge(customer_statement).merge(order_number).merge(order_reference) }
7
+ let(:amount) { { amount: '1000'} }
8
+ let(:currency) { { currency: 'EUR' } }
9
+ let(:order_description) { { order_description: 'some description'} }
10
+ let(:source_order_number) { { source_order_number: '23473341'} }
11
+ let(:auto_deposit) { { auto_deposit: 'Yes' } }
12
+ let(:customer_statement) { { customer_statement: 'some statement' } }
13
+ let(:order_number) { { order_number: '23473341' } }
14
+ let(:order_reference) { { order_reference: 'MercantID_X345456' } }
15
+
16
+ include_context 'configuration'
17
+
18
+ it { is_expected.to be_a_kind_of(Wirecard::Backend::RecurPayment) }
19
+
20
+ describe '#implicit_fingerprint_order' do
21
+ subject { recur_payment.implicit_fingerprint_order }
22
+
23
+ it { is_expected.to eq([:customer_id, :shop_id, :password, :secret, :language, :order_number, :source_order_number, :auto_deposit, :order_description, :amount, :currency, :order_reference, :customer_statement]) }
24
+ end
25
+
26
+ describe '#defaults' do
27
+ subject { recur_payment.defaults }
28
+
29
+ include_examples 'Wirecard::Base#defaults'
30
+ include_examples 'Wirecard::Backend::Base#defaults'
31
+ end
32
+
33
+ describe '#url' do
34
+ subject { recur_payment.url }
35
+
36
+ it { is_expected.to eq('https://checkout.wirecard.com/seamless/backend/recurpayment') }
37
+ end
38
+
39
+ describe '#post' do
40
+ subject { recur_payment.post.response }
41
+ include_context 'stub requests'
42
+
43
+ context 'when required parameters are given' do
44
+ let(:amount) { { amount: '1000'} }
45
+ let(:currency) { { currency: 'EUR' } }
46
+ let(:order_description) { { order_description: 'some description'} }
47
+ let(:source_order_number) { { source_order_number: '23473341'} }
48
+
49
+ it { is_expected.to eq({ order_number: "23473341", status: "0" }) }
50
+
51
+ context 'when required parameters are not given' do
52
+ let(:amount) { Hash.new }
53
+ let(:currency) { Hash.new }
54
+ let(:order_description) { Hash.new }
55
+ let(:source_order_number) { Hash.new }
56
+
57
+ it { is_expected.to eq({
58
+ :"error.1.consumer_message" => "Source ORDERNUMBER is missing.",
59
+ :"error.1.error_code" => "11159",
60
+ :"error.1.message" => "Source ORDERNUMBER is missing.",
61
+ :"error.2.consumer_message" => "Order description is missing.",
62
+ :"error.2.error_code" => "11020",
63
+ :"error.2.message" => "Order description is missing.",
64
+ :"error.3.consumer_message" => "Amount is missing.",
65
+ :"error.3.error_code" => "11017",
66
+ :"error.3.message" => "Amount is missing.",
67
+ :"error.4.consumer_message" => "Currency is missing.",
68
+ :"error.4.error_code" => "11019",
69
+ :"error.4.message" => "Currency is missing.",
70
+ errors: "4",
71
+ status: "1"
72
+ }) }
73
+ end
74
+
75
+ context 'when optional parameters are not given' do
76
+ let(:auto_deposit) { Hash.new }
77
+ let(:customer_statement) { Hash.new }
78
+ let(:order_number) { Hash.new }
79
+ let(:order_reference) { Hash.new }
80
+
81
+ it { is_expected.to eq({ order_number: "23473341", status: "0" }) }
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Wirecard::Backend::RefundReversal do
4
+
5
+ let(:refund_reversal) { Wirecard::Backend::RefundReversal.new(options) }
6
+ let(:options) { Hash.new.merge(order_number).merge(credit_number) }
7
+ let(:order_number) { { order_number: '23473341' } }
8
+ let(:credit_number) { { credit_number: '14949449'} }
9
+
10
+ include_context 'configuration'
11
+
12
+ it { is_expected.to be_a_kind_of(Wirecard::Backend::RefundReversal) }
13
+
14
+ describe '#implicit_fingerprint_order' do
15
+ subject { refund_reversal.implicit_fingerprint_order }
16
+
17
+ it { is_expected.to eq([:customer_id, :shop_id, :password, :secret, :language, :order_number, :credit_number]) }
18
+ end
19
+
20
+ describe '#defaults' do
21
+ subject { refund_reversal.defaults }
22
+
23
+ include_examples 'Wirecard::Base#defaults'
24
+ include_examples 'Wirecard::Backend::Base#defaults'
25
+ end
26
+
27
+ describe '#url' do
28
+ subject { refund_reversal.url }
29
+
30
+ it { is_expected.to eq('https://checkout.wirecard.com/seamless/backend/refundreversal') }
31
+ end
32
+
33
+ describe '#post' do
34
+ subject { refund_reversal.post.response }
35
+ include_context 'stub requests'
36
+
37
+ context 'when required parameters are given' do
38
+ let(:order_number) { { order_number: '23473341' } }
39
+ let(:credit_number) { { credit_number: '14949449'} }
40
+
41
+ it { is_expected.to eq({ status: "0" }) }
42
+
43
+ context 'when required parameters are not given' do
44
+ let(:order_number) { Hash.new }
45
+ let(:credit_number) { Hash.new }
46
+
47
+ it { is_expected.to eq({
48
+ :"error.1.consumer_message" => "Order number is missing.",
49
+ :"error.1.error_code" => "11011",
50
+ :"error.1.message" => "Order number is missing.",
51
+ :"error.2.consumer_message" => "Credit number is missing.",
52
+ :"error.2.error_code" => "11013",
53
+ :"error.2.message" => "Credit number is missing.",
54
+ errors: "2",
55
+ status: "1"
56
+ }) }
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Wirecard::Backend::Refund do
4
+
5
+ let(:refund) { Wirecard::Backend::Refund.new(options) }
6
+ let(:options) { Hash.new.merge(order_number).merge(amount).merge(currency) }
7
+ let(:order_number) { { order_number: '23473341' } }
8
+ let(:amount) { { amount: '1000'} }
9
+ let(:currency) { { currency: 'EUR' } }
10
+
11
+ include_context 'configuration'
12
+
13
+ it { is_expected.to be_a_kind_of(Wirecard::Backend::Refund) }
14
+
15
+ describe '#implicit_fingerprint_order' do
16
+ subject { refund.implicit_fingerprint_order }
17
+
18
+ it { is_expected.to eq([:customer_id, :shop_id, :password, :secret, :language, :order_number, :amount, :currency]) }
19
+ end
20
+
21
+ describe '#defaults' do
22
+ subject { refund.defaults }
23
+
24
+ include_examples 'Wirecard::Base#defaults'
25
+ include_examples 'Wirecard::Backend::Base#defaults'
26
+ end
27
+
28
+ describe '#url' do
29
+ subject { refund.url }
30
+
31
+ it { is_expected.to eq('https://checkout.wirecard.com/seamless/backend/refund') }
32
+ end
33
+
34
+ describe '#post' do
35
+ subject { refund.post.response }
36
+ include_context 'stub requests'
37
+
38
+ context 'when required parameters are given' do
39
+ let(:order_number) { { order_number: '23473341' } }
40
+ let(:amount) { { amount: '1000'} }
41
+ let(:currency) { { currency: 'EUR' } }
42
+
43
+ it { is_expected.to eq({ credit_number: "14949449", status: "0" }) }
44
+
45
+ context 'when required parameters are not given' do
46
+ let(:order_number) { Hash.new }
47
+ let(:amount) { Hash.new }
48
+ let(:currency) { Hash.new }
49
+
50
+ it { is_expected.to eq({
51
+ :"error.1.consumer_message" => "Order number is missing.",
52
+ :"error.1.error_code" => "11011",
53
+ :"error.1.message" => "Order number is missing.",
54
+ :"error.2.consumer_message" => "Currency is missing.",
55
+ :"error.2.error_code" => "11019",
56
+ :"error.2.message" => "Currency is missing.",
57
+ :"error.3.consumer_message" => "Amount is missing.",
58
+ :"error.3.error_code" => "11017",
59
+ :"error.3.message" => "Amount is missing.",
60
+ errors: "3",
61
+ status: "1"
62
+ }) }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Wirecard::Backend::TransferFund::ExistingOrder do
4
+
5
+ let(:transfer_fund_existing_order) { Wirecard::Backend::TransferFund::ExistingOrder.new(options) }
6
+ let(:options) { Hash.new.merge(amount).merge(currency).merge(fund_transfer_type).merge(order_description).merge(source_order_number).merge(credit_number).merge(customer_statement).merge(order_number).merge(order_reference) }
7
+ let(:amount) { { amount: '1000' } }
8
+ let(:currency) { { currency: 'EUR'} }
9
+ let(:fund_transfer_type) { { fund_transfer_type: 'EXISTINGORDER'} }
10
+ let(:order_description) { { order_description: 'some description'} }
11
+ let(:source_order_number) { { source_order_number: '23473341'} }
12
+ let(:credit_number) { { credit_number: '14949449'} }
13
+ let(:customer_statement) { { customer_statement: 'invoice text'} }
14
+ let(:order_number) { { order_number: '56453412'} }
15
+ let(:order_reference) { { order_reference: 'MercantID F34545'} }
16
+
17
+ include_context 'configuration'
18
+
19
+ it { is_expected.to be_a_kind_of(Wirecard::Backend::TransferFund::ExistingOrder) }
20
+
21
+ describe '#implicit_fingerprint_order' do
22
+ subject { transfer_fund_existing_order.implicit_fingerprint_order }
23
+
24
+ it { is_expected.to eq([:customer_id, :shop_id, :password, :secret, :language, :order_number, :credit_number, :order_description, :amount, :currency, :order_reference, :customer_statement, :fund_transfer_type, :source_order_number]) }
25
+ end
26
+
27
+ describe '#defaults' do
28
+ subject { transfer_fund_existing_order.defaults }
29
+
30
+ include_examples 'Wirecard::Base#defaults'
31
+ include_examples 'Wirecard::Backend::Base#defaults'
32
+ end
33
+
34
+ describe '#url' do
35
+ subject { transfer_fund_existing_order.url }
36
+
37
+ it { is_expected.to eq('https://checkout.wirecard.com/seamless/backend/transferfund') }
38
+ end
39
+
40
+ describe '#post' do
41
+ subject { transfer_fund_existing_order.post.response }
42
+ include_context 'stub requests'
43
+
44
+ context 'when required parameters are given' do
45
+ let(:amount) { { amount: '1000' } }
46
+ let(:currency) { { currency: 'EUR'} }
47
+ let(:fund_transfer_type) { { fund_transfer_type: 'EXISTINGORDER'} }
48
+ let(:order_description) { { order_description: 'some description'} }
49
+ let(:source_order_number) { { source_order_number: '23473341'} }
50
+
51
+ it { is_expected.to eq({ credit_number: '14949449', status: '0' }) }
52
+
53
+ context 'when optional parameters not are given' do
54
+ let(:credit_number) { Hash.new }
55
+ let(:customer_statement) { Hash.new }
56
+ let(:order_number) { Hash.new }
57
+ let(:order_reference) { Hash.new }
58
+
59
+ it { is_expected.to eq({ credit_number: '14949449', status: '0' }) }
60
+ end
61
+ end
62
+
63
+ context 'required parameters are not given' do
64
+ let(:amount) { Hash.new }
65
+ let(:currency) { Hash.new }
66
+ let(:fund_transfer_type) { Hash.new }
67
+ let(:order_description) { Hash.new }
68
+ let(:source_order_number) { Hash.new }
69
+
70
+ it { is_expected.to eq({
71
+ :"error.1.consumer_message" => "Amount is missing.",
72
+ :"error.1.error_code" => "11017",
73
+ :"error.1.message" => "Amount is missing.",
74
+ :"error.2.consumer_message" => "Currency is missing.",
75
+ :"error.2.error_code" => "11019",
76
+ :"error.2.message" => "Currency is missing.",
77
+ :"error.3.consumer_message" => "Order description is missing.",
78
+ :"error.3.error_code" => "11020",
79
+ :"error.3.message" => "Order description is missing.",
80
+ :"error.4.consumer_message" => "FUNDTRANSFERTYPE is missing.",
81
+ :"error.4.error_code" => "11216",
82
+ :"error.4.message" => "FUNDTRANSFERTYPE is missing.",
83
+ errors: "4",
84
+ status: "1"
85
+ }) }
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Wirecard::Callback do
4
+ let(:callback) { Wirecard::Callback.new(response_params.merge(additional_params)) }
5
+ let(:additional_params) { Hash.new }
6
+
7
+ it { expect(true).to eq(true) }
8
+
9
+ include_examples 'configuration'
10
+
11
+ let(:response_params) do
12
+ {
13
+ "amount" => "1000",
14
+ "currency" => "EUR",
15
+ "paymentType" => "CCARD",
16
+ "financialInstitution" => "Visa",
17
+ "language" => "de",
18
+ "orderNumber" => "5028575",
19
+ "paymentState" => "SUCCESS",
20
+ "authenticated" => "No",
21
+ "anonymousPan" => "0004",
22
+ "expiry" => "03/2018",
23
+ "cardholder" => "sdg",
24
+ "maskedPan" => "940000******0004",
25
+ "gatewayReferenceNumber" => "DGW_5028575_RN",
26
+ "gatewayContractNumber" => "DemoContractNumber123",
27
+ "avsResponseCode" => "X",
28
+ "avsResponseMessage" => "Demo AVS ResultMessage",
29
+ "avsProviderResultCode" => "X",
30
+ "avsProviderResultMessage" => "Demo AVS ProviderResultMessage",
31
+ "responseFingerprintOrder" => "amount,currency,paymentType,financialInstitution,language,orderNumber,paymentState,authenticated,anonymousPan,expiry,cardholder,maskedPan,gatewayReferenceNumber,gatewayContractNumber,avsResponseCode,avsResponseMessage,avsProviderResultCode,avsProviderResultMessage,secret,responseFingerprintOrder",
32
+ "responseFingerprint" => response_fingerprint
33
+ }
34
+ end
35
+ let(:response_fingerprint) { "42c937f7712b69210839c8d149bb17a352e04761eb08d67d28b2319b4a254c923b55cd6270c5d03f32cc9613dc53924c52e7a0dd7ad2139a5334a15cb4763e97" }
36
+
37
+ describe '#to_hash' do
38
+ subject { callback.to_hash }
39
+
40
+ let(:parsed_response_params) do
41
+ {
42
+ amount: "1000",
43
+ currency: "EUR",
44
+ payment_type: "CCARD",
45
+ financial_institution: "Visa",
46
+ language: "de",
47
+ order_number: "5028575",
48
+ payment_state: "SUCCESS",
49
+ authenticated: "No",
50
+ anonymous_pan: "0004",
51
+ expiry: "03/2018",
52
+ cardholder: "sdg",
53
+ masked_pan: "940000******0004",
54
+ gateway_reference_number: "DGW_5028575_RN",
55
+ gateway_contract_number: "DemoContractNumber123",
56
+ avs_response_code: "X",
57
+ avs_response_message: "Demo AVS ResultMessage",
58
+ avs_provider_result_code: "X",
59
+ avs_provider_result_message: "Demo AVS ProviderResultMessage",
60
+ response_fingerprint_order: "amount,currency,paymentType,financialInstitution,language,orderNumber,paymentState,authenticated,anonymousPan,expiry,cardholder,maskedPan,gatewayReferenceNumber,gatewayContractNumber,avsResponseCode,avsResponseMessage,avsProviderResultCode,avsProviderResultMessage,secret,responseFingerprintOrder",
61
+ response_fingerprint: response_fingerprint
62
+ }
63
+ end
64
+
65
+ it { is_expected.to eq(parsed_response_params) }
66
+ end
67
+
68
+ describe '#fingerprint_valid?' do
69
+ subject { callback.fingerprint_valid? }
70
+
71
+ context 'when fingerprint is valid' do
72
+ let(:response_fingerprint) { "42c937f7712b69210839c8d149bb17a352e04761eb08d67d28b2319b4a254c923b55cd6270c5d03f32cc9613dc53924c52e7a0dd7ad2139a5334a15cb4763e97" }
73
+
74
+ it { is_expected.to be true }
75
+
76
+ context 'when response contains unfingerprinted params' do
77
+ let(:additional_params) { { "parameter_key" => "parameter_value" } }
78
+
79
+ it { is_expected.to be false }
80
+ end
81
+ end
82
+
83
+ context 'when fingerprint is invalid' do
84
+ let(:response_fingerprint) { "invalid fingerprint" }
85
+
86
+ it { is_expected.to be false }
87
+ end
88
+
89
+ context 'when response contains no fingerprint' do
90
+ let(:additional_params) { { "responseFingerprint" => nil } }
91
+
92
+ it { is_expected.to be false }
93
+ end
94
+
95
+ context 'when response contains no fingerprint order' do
96
+ let(:additional_params) { { "responseFingerprintOrder" => nil } }
97
+
98
+ it { is_expected.to be false }
99
+ end
100
+ end
101
+ end
@@ -3,35 +3,49 @@ require 'spec_helper'
3
3
  RSpec.describe Wirecard::DataStorage::Init do
4
4
 
5
5
  let(:init) { Wirecard::DataStorage::Init.new(options) }
6
- let(:options) { Hash.new }
6
+ let(:options) { { order_ident: 'order123' } }
7
7
 
8
- include_examples 'configuration'
8
+ include_context 'configuration'
9
9
 
10
10
  it { is_expected.to be_a_kind_of(Wirecard::DataStorage::Init) }
11
11
 
12
- it { binding.pry }
12
+ describe '#implicit_fingerprint_order' do
13
+ subject { init.implicit_fingerprint_order }
14
+
15
+ it { is_expected.to eq([:customer_id, :shop_id, :order_ident, :return_url, :language, :javascript_script_version, :secret]) }
16
+ end
17
+
18
+ describe '#defaults' do
19
+ subject { init.defaults }
20
+
21
+ include_examples 'Wirecard::Base#defaults'
22
+ it { expect(subject[:javascript_script_version]).to eq('pci3') }
23
+ it { expect(subject[:language]).to eq(config[:language]) }
24
+ it { expect(subject[:return_url]).to eq(config[:return_url]) }
25
+ end
13
26
 
14
- # describe '#implicit_fingerprint_order' do
15
- # subject { init.implicit_fingerprint_order }
16
- #
17
- # it { is_expected.to eq([:customer_id, :shop_id, :order_ident, :return_url, :language, :javascript_script_version, :secret]) }
18
- # end
19
- #
20
- # describe '#defaults' do
21
- # subject { init.defaults }
22
- #
23
- # include_examples 'Wirecard::Base#defaults'
24
- # it { expect(subject[:javascript_script_version]).to eq('pci3') }
25
- #
26
- # context 'when defaults are overwritten' do
27
- # let(:options) { { customer_id: 'special_customer_id', shop_id: 'special_shop_id' } }
28
- #
29
- # it { expect(subject[:customer_id]).to eq(options[:customer_id]) }
30
- # it { expect(subject[:customer_id]).not_to eq(config[:customer_id]) }
31
- #
32
- # it { expect(subject[:shop_id]).to eq(options[:shop_id]) }
33
- # it { expect(subject[:shop_id]).not_to eq(config[:shop_id]) }
34
- # end
35
- # end
27
+ describe '#url' do
28
+ subject { init.url }
29
+
30
+ it { is_expected.to eq('https://checkout.wirecard.com/seamless/dataStorage/init') }
31
+ end
36
32
 
33
+ describe '#post' do
34
+ subject { init.post.response }
35
+ include_context 'stub requests'
36
+
37
+ context 'when order_ident is given' do
38
+ it { is_expected.to eq ({javascript_url: "https://checkout.wirecard.com/seamless/dataStorage/js/D200001/qmore/b2737b746627482e0b024097cadb1b41/dataStorage.js",
39
+ storage_id: "b2737b746627482e0b024097cadb1b41"}) }
40
+ end
41
+
42
+ context 'when order_ident is missing' do
43
+ let(:options) { Hash.new }
44
+
45
+ it { is_expected.to eq ({:"error.1.consumer_message" => "ORDERIDENT has an invalid length.",
46
+ :"error.1.error_code" => "15300",
47
+ :"error.1.message" => "ORDERIDENT has an invalid length.",
48
+ errors: "1",}) }
49
+ end
50
+ end
37
51
  end