paygate-ruby 0.1.11 → 0.2.1

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.
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Paygate
4
+ module ActionView
5
+ module FormHelper
6
+ PAYGATE_FORM_TEXT_FIELDS = {
7
+ mid: {
8
+ placeholder: 'Member ID'
9
+ },
10
+
11
+ locale: {
12
+ name: 'langcode',
13
+ default: 'KR',
14
+ placeholder: 'Language'
15
+ },
16
+
17
+ charset: {
18
+ default: 'UTF-8',
19
+ placeholder: 'Charset'
20
+ },
21
+
22
+ title: {
23
+ name: 'goodname',
24
+ placeholder: 'Title'
25
+ },
26
+
27
+ currency: {
28
+ name: 'goodcurrency',
29
+ default: 'WON',
30
+ placeholder: 'Currency'
31
+ },
32
+
33
+ amount: {
34
+ name: 'unitprice',
35
+ placeholder: 'Amount'
36
+ },
37
+
38
+ meta1: {
39
+ name: 'goodoption1',
40
+ placeholder: 'Good Option 1'
41
+ },
42
+
43
+ meta2: {
44
+ name: 'goodoption2',
45
+ placeholder: 'Good Option 2'
46
+ },
47
+
48
+ meta3: {
49
+ name: 'goodoption3',
50
+ placeholder: 'Good Option 3'
51
+ },
52
+
53
+ meta4: {
54
+ name: 'goodoption4',
55
+ placeholder: 'Good Option 4'
56
+ },
57
+
58
+ meta5: {
59
+ name: 'goodoption5',
60
+ placeholder: 'Good Option 5'
61
+ },
62
+
63
+ pay_method: {
64
+ name: 'paymethod',
65
+ default: 'card',
66
+ placeholder: 'Pay Method'
67
+ },
68
+
69
+ customer_name: {
70
+ name: 'receipttoname',
71
+ placeholder: 'Customer Name'
72
+ },
73
+
74
+ customer_email: {
75
+ name: 'receipttoemail',
76
+ placeholder: 'Customer Email'
77
+ },
78
+
79
+ card_number: {
80
+ name: 'cardnumber',
81
+ placeholder: 'Card Number'
82
+ },
83
+
84
+ expiry_year: {
85
+ name: 'cardexpireyear',
86
+ placeholder: 'Expiry Year'
87
+ },
88
+
89
+ expiry_month: {
90
+ name: 'cardexpiremonth',
91
+ placeholder: 'Expiry Month'
92
+ },
93
+
94
+ cvv: {
95
+ name: 'cardsecretnumber',
96
+ placeholder: 'CVV'
97
+ },
98
+
99
+ card_auth_code: {
100
+ name: 'cardauthcode',
101
+ placeholder: 'Card Auth Code'
102
+ },
103
+
104
+ response_code: {
105
+ name: 'replycode',
106
+ placeholder: 'Response Code'
107
+ },
108
+
109
+ response_message: {
110
+ name: 'replyMsg',
111
+ placeholder: 'Response Message'
112
+ },
113
+
114
+ tid: {
115
+ placeholder: 'TID'
116
+ },
117
+
118
+ profile_no: {
119
+ placeholder: 'Profile No'
120
+ },
121
+
122
+ hash_result: {
123
+ name: 'hashresult',
124
+ placeholder: 'Hash Result'
125
+ }
126
+ }.freeze
127
+
128
+ def paygate_open_pay_api_js_url
129
+ if Paygate.configuration.mode == :live
130
+ 'https://api.paygate.net/ajax/common/OpenPayAPI.js'
131
+ else
132
+ 'https://stgapi.paygate.net/ajax/common/OpenPayAPI.js'
133
+ end
134
+ end
135
+
136
+ def paygate_open_pay_api_form(options = {})
137
+ form_tag({}, name: 'PGIOForm') do
138
+ fields = []
139
+
140
+ PAYGATE_FORM_TEXT_FIELDS.each do |key, opts|
141
+ arg_opts = options[key] || {}
142
+ fields << text_field_tag(
143
+ key,
144
+ arg_opts[:value] || opts[:default],
145
+ name: opts[:name] || key.to_s,
146
+ placeholder: arg_opts[:placeholder] || opts[:placeholder]
147
+ ).html_safe
148
+ end
149
+
150
+ fields.join.html_safe
151
+ end.html_safe
152
+ end
153
+
154
+ def paygate_open_pay_api_screen
155
+ content_tag(:div, nil, id: 'PGIOscreen')
156
+ end
157
+ end
158
+ end
159
+ end
data/lib/paygate/aes.rb CHANGED
@@ -1,22 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Paygate
2
4
  class Aes
3
5
  # Pre-computed multiplicative inverse in GF(2^8)
4
- S_BOX = [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
5
- 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
6
- 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
7
- 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
8
- 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
9
- 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
10
- 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
11
- 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
12
- 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
13
- 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
14
- 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
15
- 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
16
- 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
17
- 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
18
- 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
19
- 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16].freeze
6
+ S_BOX = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
7
+ 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
8
+ 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
9
+ 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
10
+ 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
11
+ 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
12
+ 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
13
+ 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
14
+ 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
15
+ 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
16
+ 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
17
+ 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
18
+ 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
19
+ 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
20
+ 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
21
+ 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16].freeze
20
22
 
21
23
  # Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)]
22
24
  R_CON = [[0x00, 0x00, 0x00, 0x00],
@@ -29,10 +31,10 @@ module Paygate
29
31
  [0x40, 0x00, 0x00, 0x00],
30
32
  [0x80, 0x00, 0x00, 0x00],
31
33
  [0x1b, 0x00, 0x00, 0x00],
32
- [0x36, 0x00, 0x00, 0x00] ].freeze
34
+ [0x36, 0x00, 0x00, 0x00]].freeze
33
35
 
34
36
  # Block size (in words): no of columns in state (fixed for AES)
35
- CIPHER_BLOCK_SIZE = 4.freeze
37
+ CIPHER_BLOCK_SIZE = 4
36
38
 
37
39
  # AES Cipher function: encrypt 'input' state with Rijndael algorithm
38
40
  # applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
@@ -40,8 +42,8 @@ module Paygate
40
42
  # @param int[][] w Key schedule as 2D byte-array (Nr+1 x Nb bytes)
41
43
  # @returns int[] Encrypted output state array
42
44
  def self.cipher(input, w)
43
- nr = w.length / CIPHER_BLOCK_SIZE - 1 # no of rounds: 10/12/14 for 128/192/256-bit keys
44
- state = [[],[],[],[]] # initialize 4x4 byte-array 'state' with input
45
+ nr = (w.length / CIPHER_BLOCK_SIZE) - 1 # no of rounds: 10/12/14 for 128/192/256-bit keys
46
+ state = [[], [], [], []] # initialize 4x4 byte-array 'state' with input
45
47
 
46
48
  (0...(4 * CIPHER_BLOCK_SIZE)).each do |i|
47
49
  state[i % 4][(i / 4.0).floor] = input[i]
@@ -74,17 +76,17 @@ module Paygate
74
76
 
75
77
  w = []
76
78
  temp = []
77
- 0.upto(nk - 1){ |i| w[i] = [key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]] }
79
+ 0.upto(nk - 1) { |i| w[i] = [key[4 * i], key[(4 * i) + 1], key[(4 * i) + 2], key[(4 * i) + 3]] }
78
80
  (nk...(CIPHER_BLOCK_SIZE * (nr + 1))).each do |i|
79
81
  w[i] = []
80
- 0.upto(3){ |t| temp[t] = w[i - 1][t] }
81
- if i % nk == 0
82
+ 0.upto(3) { |t| temp[t] = w[i - 1][t] }
83
+ if (i % nk).zero?
82
84
  temp = sub_word(rotate_word(temp))
83
- 0.upto(3){ |t| temp[t] ^= R_CON[i / nk][t]}
85
+ 0.upto(3) { |t| temp[t] ^= R_CON[i / nk][t] }
84
86
  elsif nk > 6 && i % nk == 4
85
87
  temp = sub_word(temp)
86
88
  end
87
- 0.upto(3){ |t| w[i][t] = w[i - nk][t] ^ temp[t] }
89
+ 0.upto(3) { |t| w[i][t] = w[i - nk][t] ^ temp[t] }
88
90
  end
89
91
  w
90
92
  end
@@ -92,7 +94,7 @@ module Paygate
92
94
  # apply SBox to state
93
95
  def self.sub_bytes(state)
94
96
  0.upto(3) do |r|
95
- 0.upto(CIPHER_BLOCK_SIZE - 1){ |c| state[r][c] = S_BOX[state[r][c]] }
97
+ 0.upto(CIPHER_BLOCK_SIZE - 1) { |c| state[r][c] = S_BOX[state[r][c]] }
96
98
  end
97
99
  state
98
100
  end
@@ -101,20 +103,20 @@ module Paygate
101
103
  def self.shift_rows(state)
102
104
  t = []
103
105
  1.upto(3) do |r|
104
- 0.upto(3){ |c| t[c] = state[r][(c + r) % CIPHER_BLOCK_SIZE] } # shift into temp copy
105
- 0.upto(3){ |c| state[r][c] = t[c] } # and copy back
106
- end # note that this will work for nb = 4,5,6, but not 7,8 (always 4 for AES):
106
+ 0.upto(3) { |c| t[c] = state[r][(c + r) % CIPHER_BLOCK_SIZE] } # shift into temp copy
107
+ 0.upto(3) { |c| state[r][c] = t[c] } # and copy back
108
+ end
107
109
  state # see asmaes.sourceforge.net/rijndael/rijndaelImplementation.pdf
108
110
  end
109
111
 
110
112
  # combine bytes of each col of state S
111
113
  def self.mix_columns(state)
112
114
  0.upto(3) do |c|
113
- a=[] # 'a' is a copy of the current column from 'state'
114
- b=[] # 'b' is a•{02} in GF(2^8)
115
+ a = [] # 'a' is a copy of the current column from 'state'
116
+ b = [] # 'b' is a•{02} in GF(2^8)
115
117
  0.upto(3) do |i|
116
118
  a[i] = state[i][c]
117
- b[i] = (state[i][c] & 0x80 == 0) ? (state[i][c] << 1) ^ 0x011b : state[i][c] << 1
119
+ b[i] = (state[i][c] & 0x80).zero? ? (state[i][c] << 1) ^ 0x011b : state[i][c] << 1
118
120
  end
119
121
  # a[n] ^ b[n] is a•{03} in GF(2^8)
120
122
  state[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3] # 2*a0 + 3*a1 + a2 + a3
@@ -128,21 +130,21 @@ module Paygate
128
130
  # xor Round Key into state S
129
131
  def self.add_round_key(state, w, rnd)
130
132
  0.upto(3) do |r|
131
- 0.upto(CIPHER_BLOCK_SIZE - 1){ |c| state[r][c] ^= w[rnd * 4 + c][r]}
133
+ 0.upto(CIPHER_BLOCK_SIZE - 1) { |c| state[r][c] ^= w[(rnd * 4) + c][r] }
132
134
  end
133
135
  state
134
136
  end
135
137
 
136
138
  # apply SBox to 4-byte word w
137
139
  def self.sub_word(word)
138
- 0.upto(3){ |i| word[i] = S_BOX[word[i]] }
140
+ 0.upto(3) { |i| word[i] = S_BOX[word[i]] }
139
141
  word
140
142
  end
141
143
 
142
144
  # rotate 4-byte word w left by one byte
143
145
  def self.rotate_word(word)
144
146
  tmp = word[0]
145
- 0.upto(2){ |i| word[i] = word[i + 1] }
147
+ 0.upto(2) { |i| word[i] = word[i + 1] }
146
148
  word[3] = tmp
147
149
  word
148
150
  end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module Paygate
4
6
  class AesCtr
5
-
6
7
  # Encrypt a text using AES encryption in Counter mode of operation
7
8
  #
8
9
  # Unicode multi-byte character safe
@@ -12,16 +13,19 @@ module Paygate
12
13
  # @param int num_bits Number of bits to be used in the key (128, 192, or 256)
13
14
  # @returns string Encrypted text
14
15
  def self.encrypt(plaintext, password, num_bits)
15
- block_size = 16 # block size fixed at 16 bytes / 128 bits (Nb=4) for AES
16
- return '' unless num_bits.in?([128, 192, 256])
16
+ block_size = 16 # block size fixed at 16 bytes / 128 bits (Nb=4) for AES
17
+ return '' unless [128, 192, 256].include?(num_bits)
17
18
 
18
19
  # use AES itself to encrypt password to get cipher key (using plain password as source for key
19
20
  # expansion) - gives us well encrypted key (though hashed key might be preferred for prod'n use)
20
- num_bytes = num_bits / 8 # no bytes in key (16/24/32)
21
+ num_bytes = num_bits / 8 # no bytes in key (16/24/32)
21
22
  pw_bytes = []
22
- 0.upto(num_bytes - 1){ |i| pw_bytes[i] = password.bytes.to_a[i] & 0xff || 0} # use 1st 16/24/32 chars of password for key #warn
23
- key = Aes.cipher(pw_bytes, Aes.key_expansion(pw_bytes)) # gives us 16-byte key
24
- key = key + key[0, num_bytes - 16] # expand key to 16/24/32 bytes long
23
+ # use 1st 16/24/32 chars of password for key #warn
24
+ 0.upto(num_bytes - 1) do |i|
25
+ pw_bytes[i] = (password.bytes.to_a[i] & 0xff) || 0
26
+ end
27
+ key = Aes.cipher(pw_bytes, Aes.key_expansion(pw_bytes)) # gives us 16-byte key
28
+ key += key[0, num_bytes - 16] # expand key to 16/24/32 bytes long
25
29
 
26
30
  # initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2): [0-1] = millisec,
27
31
  # [2-3] = random, [4-7] = seconds, together giving full sub-millisec uniqueness up to Feb 2106
@@ -29,14 +33,14 @@ module Paygate
29
33
  nonce = Time.now.to_i
30
34
  nonce_ms = nonce % 1000
31
35
  nonce_sec = (nonce / 1000.0).floor
32
- nonce_rand = (rand() * 0xffff).floor
33
- 0.upto(1){ |i| counter_block[i] = urs(nonce_ms, i * 8) & 0xff }
34
- 0.upto(1){ |i| counter_block[i + 2] = urs(nonce_rand, i * 8) & 0xff }
35
- 0.upto(3){ |i| counter_block[i + 4] = urs(nonce_sec, i * 8) & 0xff }
36
+ nonce_rand = (rand * 0xffff).floor
37
+ 0.upto(1) { |i| counter_block[i] = urs(nonce_ms, i * 8) & 0xff }
38
+ 0.upto(1) { |i| counter_block[i + 2] = urs(nonce_rand, i * 8) & 0xff }
39
+ 0.upto(3) { |i| counter_block[i + 4] = urs(nonce_sec, i * 8) & 0xff }
36
40
 
37
41
  # and convert it to a string to go on the front of the ciphertext
38
42
  ctr_text = ''
39
- 0.upto(7){ |i| ctr_text += counter_block[i].chr }
43
+ 0.upto(7) { |i| ctr_text += counter_block[i].chr }
40
44
 
41
45
  # generate key schedule - an expansion of the key into distinct Key Rounds for each round
42
46
  key_schedule = Aes.key_expansion(key)
@@ -46,85 +50,84 @@ module Paygate
46
50
  0.upto(block_count - 1) do |b|
47
51
  # set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
48
52
  # done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
49
- 0.upto(3){ |c| counter_block[15 - c] = urs(b, c * 8) & 0xff }
50
- 0.upto(3){ |c| counter_block[15 - c - 4] = urs(b / 0x100000000, c * 8) }
53
+ 0.upto(3) { |c| counter_block[15 - c] = urs(b, c * 8) & 0xff }
54
+ 0.upto(3) { |c| counter_block[15 - c - 4] = urs(b / 0x100000000, c * 8) }
51
55
 
52
56
  cipher_cntr = Aes.cipher(counter_block, key_schedule) # -- encrypt counter block --
53
57
  # block size is reduced on final block
54
- block_length = b < block_count - 1 ? block_size : (plaintext.length - 1) % block_size + 1
58
+ block_length = b < block_count - 1 ? block_size : ((plaintext.length - 1) % block_size) + 1
55
59
  cipher_char = []
56
60
  0.upto(block_length - 1) do |i|
57
- cipher_char[i] = (cipher_cntr[i] ^ plaintext.bytes.to_a[b * block_size + i]).chr
61
+ cipher_char[i] = (cipher_cntr[i] ^ plaintext.bytes.to_a[(b * block_size) + i]).chr
58
62
  end
59
63
  cipher_text[b] = cipher_char.join
60
64
  end
61
65
 
62
66
  cipher_text = ctr_text + cipher_text.join
63
- cipher_text = Base64.encode64(cipher_text).gsub(/\n/, '') + "\n"; # encode in base64
67
+ "#{Base64.encode64(cipher_text).delete("\n")}\n" # encode in base64
64
68
  end
65
69
 
66
70
  # Decrypt a text encrypted by AES in counter mode of operation
67
71
  #
68
72
  # @param string ciphertext Source text to be encrypted
69
73
  # @param string password The password to use to generate a key
70
- # @param int nBits Number of bits to be used in the key (128, 192, or 256)
74
+ # @param int n_bits Number of bits to be used in the key (128, 192, or 256)
71
75
  # @returns string
72
76
  # Decrypted text
73
- def self.decrypt(ciphertext, password, nBits)
74
- blockSize = 16 # block size fixed at 16 bytes / 128 bits (Nb=4) for AES
75
- return '' unless(nBits==128 || nBits==192 || nBits==256)
76
- ciphertext = Base64.decode64(ciphertext);
77
-
78
- nBytes = nBits/8 # no bytes in key (16/24/32)
79
- pwBytes = []
80
- 0.upto(nBytes-1){|i| pwBytes[i] = password.bytes.to_a[i] & 0xff || 0}
81
- key = Aes.cipher(pwBytes, Aes.key_expansion(pwBytes)) # gives us 16-byte key
82
- key = key.concat(key.slice(0, nBytes-16)) # expand key to 16/24/32 bytes long
77
+ def self.decrypt(ciphertext, password, n_bits)
78
+ block_size = 16 # block size fixed at 16 bytes / 128 bits (Nb=4) for AES
79
+ return '' unless [128, 192, 256].include?(n_bits)
80
+
81
+ ciphertext = Base64.decode64(ciphertext)
82
+
83
+ n_bytes = n_bits / 8 # no bytes in key (16/24/32)
84
+ pw_bytes = []
85
+ 0.upto(n_bytes - 1) { |i| pw_bytes[i] = (password.bytes.to_a[i] & 0xff) || 0 }
86
+ key = Aes.cipher(pw_bytes, Aes.key_expansion(pw_bytes)) # gives us 16-byte key
87
+ key.concat(key.slice(0, n_bytes - 16)) # expand key to 16/24/32 bytes long
83
88
  # recover nonce from 1st 8 bytes of ciphertext
84
- counterBlock = []
85
- ctrTxt = ciphertext[0,8]
86
- 0.upto(7){|i| counterBlock[i] = ctrTxt.bytes.to_a[i]}
89
+ counter_block = []
90
+ ctr_txt = ciphertext[0, 8]
91
+ 0.upto(7) { |i| counter_block[i] = ctr_txt.bytes.to_a[i] }
87
92
 
88
- #generate key Schedule
89
- keySchedule = Aes.key_expansion(key);
93
+ # generate key Schedule
94
+ key_schedule = Aes.key_expansion(key)
90
95
 
91
96
  # separate ciphertext into blocks (skipping past initial 8 bytes)
92
- nBlocks = ((ciphertext.length-8)/blockSize.to_f).ceil
93
- ct=[]
94
- 0.upto(nBlocks-1){|b|ct[b] = ciphertext[8+b*blockSize, 16]}
97
+ n_blocks = ((ciphertext.length - 8) / block_size.to_f).ceil
98
+ ct = []
99
+ 0.upto(n_blocks - 1) { |b| ct[b] = ciphertext[8 + (b * block_size), 16] }
95
100
 
96
- ciphertext = ct; # ciphertext is now array of block-length strings
101
+ ciphertext = ct; # ciphertext is now array of block-length strings
97
102
 
98
103
  # plaintext will get generated block-by-block into array of block-length strings
99
- plaintxt = [];
100
- 0.upto(nBlocks-1) do |b|
101
- 0.upto(3){|c| counterBlock[15-c] = urs(b,c*8) & 0xff}
102
- 0.upto(3){|c| counterBlock[15-c-4] = urs((b+1)/(0x100000000-1),c*8) & 0xff}
103
- cipherCntr = Aes.cipher(counterBlock, keySchedule) # encrypt counter block
104
- plaintxtByte = []
104
+ plaintxt = []
105
+ 0.upto(n_blocks - 1) do |b|
106
+ 0.upto(3) { |c| counter_block[15 - c] = urs(b, c * 8) & 0xff }
107
+ 0.upto(3) { |c| counter_block[15 - c - 4] = urs((b + 1) / (0x100000000 - 1), c * 8) & 0xff }
108
+ cipher_cntr = Aes.cipher(counter_block, key_schedule) # encrypt counter block
109
+ plaintxt_byte = []
105
110
  0.upto(ciphertext[b].length - 1) do |i|
106
111
  # -- xor plaintxt with ciphered counter byte-by-byte --
107
- plaintxtByte[i] = (cipherCntr[i] ^ ciphertext[b].bytes.to_a[i]).chr;
112
+ plaintxt_byte[i] = (cipher_cntr[i] ^ ciphertext[b].bytes.to_a[i]).chr
108
113
  end
109
- plaintxt[b] = plaintxtByte.join('')
114
+ plaintxt[b] = plaintxt_byte.join
110
115
  end
111
- plaintext = plaintxt.join('')
116
+ plaintxt.join
112
117
  end
113
118
 
114
- private
115
-
116
- # Unsigned right shift function, since Ruby has neither >>> operator nor unsigned ints
117
- #
118
- # @param a number to be shifted (32-bit integer)
119
- # @param b number of bits to shift a to the right (0..31)
120
- # @return a right-shifted and zero-filled by b bits
119
+ # Unsigned right shift function, since Ruby has neither >>> operator nor unsigned ints
120
+ #
121
+ # @param a number to be shifted (32-bit integer)
122
+ # @param b number of bits to shift a to the right (0..31)
123
+ # @return a right-shifted and zero-filled by b bits
121
124
  def self.urs(a, b)
122
125
  a &= 0xffffffff
123
126
  b &= 0x1f
124
- if a & 0x80000000 && b > 0 # if left-most bit set
127
+ if (a & 0x80000000) && b.positive? # if left-most bit set
125
128
  a = ((a >> 1) & 0x7fffffff) # right-shift one bit & clear left-most bit
126
129
  a = a >> (b - 1) # remaining right-shifts
127
- else # otherwise
130
+ else # otherwise
128
131
  a = (a >> b); # use normal right-shift
129
132
  end
130
133
  a
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Paygate
2
4
  class Configuration
3
- MODES = %i(live sandbox).freeze
5
+ MODES = %i[live sandbox].freeze
4
6
 
5
7
  attr_reader :mode
6
8
 
@@ -9,8 +11,10 @@ module Paygate
9
11
  end
10
12
 
11
13
  def mode=(value)
12
- fail 'Invalid mode. Value must be one of the following: :live, :sandbox' unless value && MODES.include?(value.to_sym)
13
- @mode = value.to_sym
14
+ value = value.to_sym
15
+ raise 'Invalid mode. Value must be one of the following: :live, :sandbox' unless value && MODES.include?(value)
16
+
17
+ @mode = value
14
18
  end
15
19
  end
16
20
  end
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Paygate
2
4
  class Member
3
5
  attr_reader :mid, :secret
4
6
 
5
7
  def initialize(mid, secret)
6
- @mid, @secret = mid, secret
8
+ @mid = mid
9
+ @secret = secret
7
10
  end
8
11
 
9
12
  def refund_transaction(txn_id, options = {})
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require 'net/http'
3
5
 
4
6
  module Paygate
5
7
  class Profile
6
- PURCHASE_URL = 'https://service.paygate.net/INTL/pgtlProcess3.jsp'.freeze
8
+ PURCHASE_URL = 'https://service.paygate.net/INTL/pgtlProcess3.jsp'
7
9
 
8
10
  attr_reader :profile_no
9
11
  attr_accessor :member
@@ -17,7 +19,8 @@ module Paygate
17
19
  params = { profile_no: profile_no,
18
20
  mid: member.mid,
19
21
  goodcurrency: currency,
20
- unitprice: amount }.delete_if { |_, v| v.blank? }
22
+ unitprice: amount }
23
+ params.compact!
21
24
 
22
25
  # Make request
23
26
  uri = URI(PURCHASE_URL)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Paygate
2
4
  class Response
3
5
  attr_accessor :transaction_type, :http_code, :message, :body, :raw_info, :json
@@ -10,14 +12,14 @@ module Paygate
10
12
  r.body = response.body
11
13
 
12
14
  case txn_type
13
- when :refund
14
- r.json = JSON.parse response.body.gsub(/^callback\((.*)\)$/, '\1') if response.code.to_i == 200
15
- when :profile_pay
16
- r.json = {}
17
- response.body.split('&').each do |key_value_pair|
18
- key_value_ary = key_value_pair.split('=')
19
- r.json[key_value_ary[0]] = key_value_ary[1]
20
- end
15
+ when :refund
16
+ r.json = JSON.parse response.body.gsub(/^callback\((.*)\)$/, '\1') if response.code.to_i == 200
17
+ when :profile_pay
18
+ r.json = {}
19
+ response.body.split('&').each do |key_value_pair|
20
+ key_value_ary = key_value_pair.split('=')
21
+ r.json[key_value_ary[0]] = key_value_ary[1]
22
+ end
21
23
  end
22
24
  r
23
25
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'digest'
2
4
  require 'uri'
3
5
  require 'net/http'
4
6
 
5
7
  module Paygate
6
8
  class Transaction
7
- FULL_AMOUNT_IDENTIFIER = 'F'.freeze
9
+ FULL_AMOUNT_IDENTIFIER = 'F'
8
10
 
9
11
  attr_reader :tid
10
12
  attr_accessor :member
@@ -15,8 +17,8 @@ module Paygate
15
17
 
16
18
  def refund(options = {})
17
19
  # Encrypt data
18
- api_key_256 = ::Digest::SHA256.hexdigest(member.secret)
19
- aes_ctr = AesCtr.encrypt(tid, api_key_256, 256)
20
+ api_key256 = ::Digest::SHA256.hexdigest(member.secret)
21
+ aes_ctr = AesCtr.encrypt(tid, api_key256, 256)
20
22
  tid_enc = "AES256#{aes_ctr}"
21
23
 
22
24
  # Prepare params
@@ -24,7 +26,7 @@ module Paygate
24
26
  params.merge!(options.slice(:amount))
25
27
  params[:amount] ||= FULL_AMOUNT_IDENTIFIER
26
28
  params[:mb_serial_no] = options[:order_id]
27
- params.delete_if { |_, v| v.blank? }
29
+ params.compact!
28
30
 
29
31
  # Make request
30
32
  uri = URI(self.class.refund_api_url)
@@ -32,7 +34,7 @@ module Paygate
32
34
  response = ::Net::HTTP.get_response(uri)
33
35
 
34
36
  r = Response.build_from_net_http_response(:refund, response)
35
- r.raw_info = OpenStruct.new(tid: tid, tid_enc: tid_enc, request_url: uri.to_s)
37
+ r.raw_info = OpenStruct.new(tid: tid, tid_enc: tid_enc, request_url: uri.to_s) # rubocop:disable Style/OpenStructUse
36
38
  r
37
39
  end
38
40
 
@@ -47,12 +49,12 @@ module Paygate
47
49
  Response.build_from_net_http_response(:verify, response)
48
50
  end
49
51
 
50
- private
51
-
52
52
  def self.refund_api_url
53
- (Paygate.configuration.mode == :live) ?
54
- 'https://service.paygate.net/service/cancelAPI.json' :
53
+ if Paygate.configuration.mode == :live
54
+ 'https://service.paygate.net/service/cancelAPI.json'
55
+ else
55
56
  'https://stgsvc.paygate.net/service/cancelAPI.json'
57
+ end
56
58
  end
57
59
  end
58
60
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Paygate
2
- VERSION = '0.1.11'
4
+ VERSION = '0.2.1'
3
5
  end