rails-shopee-qris 0.1.0 → 0.2.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/CHANGELOG.md +37 -0
- data/README.md +20 -8
- data/lib/rails/shopee/qris/client.rb +67 -12
- data/lib/rails/shopee/qris/response.rb +13 -1
- data/lib/rails/shopee/qris/setup.rb +57 -30
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5f301fa2107316c8aa94a3ab8946fc318bbbb73d0306c9d0b08290648437e2c1
|
|
4
|
+
data.tar.gz: d35eb2223fdad00dfc593ec82eb4c926652aa97a15ccea640177f1d9b6c31b3b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e4a3757eb7bd2e386a1774bafdb50b8486a51dd3265c6513b75c2039bac9d3e4ec6a8c0760794de5cbdf6fdba45b0cefee97e16aeb988dcb1e2ae8b11180f443
|
|
7
|
+
data.tar.gz: 84a529743925b2494c02b9cd9c5a322f7efda75dc83c630e258a7833a6c59fb9a7dbfc1f04b31f45d743f1d265032da7f39c3c5d0d28fd960c903dcb636b11b3
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
Verified against a live ShopeePay Partner merchant account (store listing, transaction
|
|
6
|
+
feed, transaction detail, device-risk, OTP request).
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- `Client` accepts either the raw `B:...` merchant token or the whole
|
|
10
|
+
`__shopee_partner_website_x_token_live` JWT cookie, extracting the inner token,
|
|
11
|
+
`userid` and `exp`.
|
|
12
|
+
- `Client#refreshable?` tells callers whether a session can silently renew.
|
|
13
|
+
- Provider error codes are translated into actionable text
|
|
14
|
+
(`10002`, `200020`, `2010000`, `48401003`, `48401102`, `48401103`, `48500102`).
|
|
15
|
+
- `Setup` exposes `CHANNEL_NAMES`; unavailable OTP channels are reported with the
|
|
16
|
+
available list.
|
|
17
|
+
- `test/response_test.rb` coverage for the error mapping.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **Payment envelope shape.** Request fields were nested under `data.metadata`; Shopee
|
|
21
|
+
expects them as siblings of `metadata`. Store listing and the transaction feed never
|
|
22
|
+
worked before this.
|
|
23
|
+
- **`10002` aborted every non-password account.** It means "no password set" — the OTP
|
|
24
|
+
flow now continues instead of raising.
|
|
25
|
+
- **Wrong refresh error.** A rejected manual token raised "session cannot be refreshed";
|
|
26
|
+
it now reports the real rejection and its code. Proactive refresh only runs when the
|
|
27
|
+
session actually has renewal credentials.
|
|
28
|
+
- **Duplicate-key crash.** `Client` symbolized sessions via `JSON.generate`, which raised
|
|
29
|
+
on hashes carrying both `"token"` and `:token`.
|
|
30
|
+
- **`48401003`** is now reported as a wrong/expired OTP rather than a bare code.
|
|
31
|
+
- QRIS generation rejects fractional and oversized amounts instead of truncating them,
|
|
32
|
+
and parsing rejects non-decimal lengths and duplicate tags.
|
|
33
|
+
|
|
34
|
+
## 0.1.0
|
|
35
|
+
|
|
36
|
+
Initial release: dynamic QRIS generation, static QRIS validation, CRC-16, store listing,
|
|
37
|
+
transaction feed, payment matching, OTP login flow.
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Unofficial Ruby/Rails ShopeePay Partner client, based on `QrisMerchantID` Shopee API logic and `rails-gopay-unofficial` gem structure. Ruby >= 3.1; no runtime dependencies beyond stdlib.
|
|
4
4
|
|
|
5
|
-
Use only with merchant authorization. Internal Shopee APIs can change.
|
|
5
|
+
Use only with merchant authorization. Internal Shopee APIs can change. Read paths (`get-store-list`, `get-transaction-list`, `get-transaction-detail`, device-risk, OTP request/verify) have been exercised against a live merchant account; actual money settlement has not. Never log tokens, passwords, device reports, session cookies, or raw payment data.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -24,7 +24,9 @@ end
|
|
|
24
24
|
|
|
25
25
|
## Direct merchant token
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Pass either the inner `B:...` token, or the whole `__shopee_partner_website_x_token_live` cookie value — the client decodes that JWT and pulls out the inner `token` (plus `userid` and `exp`). It does not verify the JWT signature; Shopee validates credentials on each request.
|
|
28
|
+
|
|
29
|
+
Get a fresh token: log in at `partner.shopee.co.id` → DevTools → Network → open the ShopeePay transaction history page → click `get-transaction-list` → Request Payload → `data.metadata.token`.
|
|
28
30
|
|
|
29
31
|
```ruby
|
|
30
32
|
client = Rails::Shopee::Qris::Client.new(
|
|
@@ -33,7 +35,7 @@ client = Rails::Shopee::Qris::Client.new(
|
|
|
33
35
|
stores = client.list_stores
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
A manually supplied token cannot silently renew
|
|
38
|
+
Tokens rotate when the portal session refreshes; `200020` means paste a new one. A manually supplied token cannot silently renew — only an OTP session carries `switch_credential` and can call `refresh!`.
|
|
37
39
|
|
|
38
40
|
## OTP login
|
|
39
41
|
|
|
@@ -58,7 +60,7 @@ else
|
|
|
58
60
|
end
|
|
59
61
|
```
|
|
60
62
|
|
|
61
|
-
Channels: `1` SMS, `2` voice, `3` WhatsApp, `4` email, `5` Zalo;
|
|
63
|
+
Channels: `1` SMS, `2` voice, `3` WhatsApp, `4` email, `5` Zalo. Omit `channel:` to use the channel Shopee recommends for that account; asking for an unavailable one raises with the available list. `verify_otp(challenge, otp)` also returns verification separately, reusable by `complete_login(verification, merchant_id:, store_id:)`. Requested store must belong to selected merchant; multiple stores can leave `store_id` unset, requiring explicit selection before transaction reconciliation.
|
|
62
64
|
|
|
63
65
|
Persist **entire** session encrypted, including `cookies`, `merchant`, `merchants`, and `switch_credential`. These are credentials, not safe client-side session data.
|
|
64
66
|
|
|
@@ -130,7 +132,17 @@ rescue Rails::Shopee::Qris::Error => error
|
|
|
130
132
|
end
|
|
131
133
|
```
|
|
132
134
|
|
|
133
|
-
Invalid provider envelopes, HTTP failures, expired credentials, malformed inputs, and incomplete pagination fail explicitly.
|
|
135
|
+
Invalid provider envelopes, HTTP failures, expired credentials, malformed inputs, and incomplete pagination fail explicitly. Shopee often answers with a bare numeric code and no message; observed codes are translated:
|
|
136
|
+
|
|
137
|
+
| Code | Meaning |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `10002` | Account has no password set (not an error — the OTP flow continues) |
|
|
140
|
+
| `200020` | Token invalid or expired — paste a fresh one |
|
|
141
|
+
| `2010000` | Request carried no token |
|
|
142
|
+
| `48401003` | OTP code wrong or expired |
|
|
143
|
+
| `48401102` | Password required before an OTP is sent |
|
|
144
|
+
| `48401103` | OTP channel unavailable for this account |
|
|
145
|
+
| `48500102` | Account session expired; log in again with an OTP |
|
|
134
146
|
|
|
135
147
|
## Verification
|
|
136
148
|
|
|
@@ -141,12 +153,12 @@ gem build rails-shopee-qris.gemspec
|
|
|
141
153
|
|
|
142
154
|
## RubyGems release
|
|
143
155
|
|
|
144
|
-
Package
|
|
156
|
+
Package with `gem build rails-shopee-qris.gemspec`. Authenticate via `gem signin` (or configure a scoped `GEM_HOST_API_KEY` with push permission), then:
|
|
145
157
|
|
|
146
158
|
```sh
|
|
147
|
-
gem push rails-shopee-qris-0.
|
|
159
|
+
gem push rails-shopee-qris-0.2.0.gem --host https://rubygems.org
|
|
148
160
|
```
|
|
149
161
|
|
|
150
|
-
RubyGems versions are immutable. Bump gemspec version before subsequent releases. Built `.gem` files and credentials must not be committed.
|
|
162
|
+
RubyGems versions are immutable. Bump the gemspec version before subsequent releases. Built `.gem` files and credentials must not be committed.
|
|
151
163
|
|
|
152
164
|
MIT license; see [LICENSE.txt](LICENSE.txt).
|
|
@@ -25,16 +25,22 @@ module Rails
|
|
|
25
25
|
)
|
|
26
26
|
config = Rails::Shopee::Qris.configuration
|
|
27
27
|
raise Error, "Shopee session must be an object" if session && !session.is_a?(Hash)
|
|
28
|
-
@session = session &&
|
|
29
|
-
|
|
28
|
+
@session = session && deep_symbolize(session)
|
|
29
|
+
raw_token = token || @session&.dig(:token) || config.token
|
|
30
|
+
parsed_token, token_meta = parse_token_input(raw_token)
|
|
31
|
+
@token = parsed_token
|
|
30
32
|
@store_id = (store_id || @session&.dig(:store_id) || config.store_id)&.to_s
|
|
31
|
-
@merchant_id = (merchant_id || @session&.dig(:merchant, :id) || @session&.dig(:merchant_id) || config.merchant_id)&.to_s
|
|
33
|
+
@merchant_id = (merchant_id || @session&.dig(:merchant, :id) || @session&.dig(:merchant_id) || token_meta[:account_id] || config.merchant_id)&.to_s
|
|
32
34
|
@phone_number = phone_number || @session&.dig(:phone_number) || config.phone_number
|
|
33
35
|
@device_id = device_id || @session&.dig(:switch_credential, :spc_clientid)
|
|
34
|
-
expiry = expires_at || @session&.dig(:expires_at)
|
|
36
|
+
expiry = expires_at || @session&.dig(:expires_at) || token_meta[:expires_at]
|
|
35
37
|
@expires_at = extract_time(expiry)
|
|
36
38
|
raise Error, "Invalid Shopee session expiry" if expiry && !@expires_at
|
|
37
|
-
|
|
39
|
+
if @session
|
|
40
|
+
@session[:token] = @token if @token
|
|
41
|
+
@session[:merchant_id] ||= @merchant_id if @merchant_id
|
|
42
|
+
@session[:expires_at] = @expires_at if expiry
|
|
43
|
+
end
|
|
38
44
|
end
|
|
39
45
|
|
|
40
46
|
attr_reader :token, :store_id, :merchant_id, :session, :phone_number, :device_id, :expires_at
|
|
@@ -53,9 +59,14 @@ module Rails
|
|
|
53
59
|
raise Error, "Invalid payment amount"
|
|
54
60
|
end
|
|
55
61
|
|
|
62
|
+
def refreshable?
|
|
63
|
+
session.is_a?(Hash) && session[:switch_credential].is_a?(Hash)
|
|
64
|
+
end
|
|
65
|
+
|
|
56
66
|
def refresh!
|
|
57
|
-
unless
|
|
58
|
-
raise Error, "Shopee
|
|
67
|
+
unless refreshable?
|
|
68
|
+
raise Error, "Shopee merchant token is invalid or expired and this session has no renewal " \
|
|
69
|
+
"credentials; log in again with OTP or paste a fresh B:... token"
|
|
59
70
|
end
|
|
60
71
|
|
|
61
72
|
updated = Setup.new.refresh_session(session)
|
|
@@ -66,7 +77,7 @@ module Rails
|
|
|
66
77
|
end
|
|
67
78
|
|
|
68
79
|
def transactions_between(start_time:, end_time:, store_id: @store_id, page_size: 10, max_pages: 20)
|
|
69
|
-
refresh! if expires_at && expires_at <= Time.now + 900
|
|
80
|
+
refresh! if refreshable? && expires_at && expires_at <= Time.now + 900
|
|
70
81
|
|
|
71
82
|
start_ts = start_time.respond_to?(:to_i) ? start_time.to_i : Integer(start_time)
|
|
72
83
|
end_ts = end_time.respond_to?(:to_i) ? end_time.to_i : Integer(end_time)
|
|
@@ -307,6 +318,43 @@ module Rails
|
|
|
307
318
|
rescue ArgumentError, TypeError, RangeError
|
|
308
319
|
nil
|
|
309
320
|
end
|
|
321
|
+
def deep_symbolize(obj)
|
|
322
|
+
case obj
|
|
323
|
+
when Hash
|
|
324
|
+
obj.each_with_object({}) do |(k, v), acc|
|
|
325
|
+
acc[k.to_sym] = deep_symbolize(v)
|
|
326
|
+
end
|
|
327
|
+
when Array
|
|
328
|
+
obj.map { |item| deep_symbolize(item) }
|
|
329
|
+
else
|
|
330
|
+
obj
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def parse_token_input(val)
|
|
335
|
+
return [nil, {}] if val.nil?
|
|
336
|
+
|
|
337
|
+
str = val.to_s.strip.gsub(/[\r\n\t ]/, "")
|
|
338
|
+
return [str, {}] if str.start_with?("B:")
|
|
339
|
+
|
|
340
|
+
if str.count(".") == 2
|
|
341
|
+
segments = str.split(".")
|
|
342
|
+
padded = segments[1] + ("=" * (-segments[1].bytesize % 4))
|
|
343
|
+
payload = JSON.parse(Base64.urlsafe_decode64(padded), symbolize_names: true)
|
|
344
|
+
if payload.is_a?(Hash) && payload[:token].to_s.start_with?("B:")
|
|
345
|
+
meta = {
|
|
346
|
+
account_id: payload[:userid]&.to_s,
|
|
347
|
+
expires_at: payload[:exp] ? Time.at(payload[:exp]) : nil
|
|
348
|
+
}
|
|
349
|
+
return [payload[:token].to_s, meta]
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
[str, {}]
|
|
354
|
+
rescue StandardError
|
|
355
|
+
[str, {}]
|
|
356
|
+
end
|
|
357
|
+
|
|
310
358
|
|
|
311
359
|
def request_payment(url, inner_data, retried: false)
|
|
312
360
|
raise Error, "Shopee merchant token is missing; configure token or login with OTP" if token.to_s.empty?
|
|
@@ -326,14 +374,21 @@ module Rails
|
|
|
326
374
|
raise Error, "Shopee returned a non-object response" unless resp.is_a?(Hash)
|
|
327
375
|
code = resp[:code].to_s
|
|
328
376
|
|
|
329
|
-
if
|
|
330
|
-
|
|
331
|
-
|
|
377
|
+
if code != "0" && INVALID_TOKEN_CODES.include?(code) && !retried
|
|
378
|
+
if refreshable?
|
|
379
|
+
refresh!
|
|
380
|
+
return request_payment(url, inner_data, retried: true)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
raise Error.new(
|
|
384
|
+
"Shopee rejected the merchant token (code #{code}); it is invalid or expired. " \
|
|
385
|
+
"Log in again with OTP or paste a fresh B:... token.",
|
|
386
|
+
nil, resp[:code], resp
|
|
387
|
+
)
|
|
332
388
|
end
|
|
333
389
|
|
|
334
390
|
err = Response.error(resp)
|
|
335
391
|
raise Error.new(err || "ShopeePay request failed", nil, resp[:code], resp) if err
|
|
336
|
-
Response.data(resp)
|
|
337
392
|
|
|
338
393
|
resp
|
|
339
394
|
end
|
|
@@ -6,6 +6,18 @@ module Rails
|
|
|
6
6
|
module Response
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
|
+
# Shopee answers many failures with a bare numeric code and no message.
|
|
10
|
+
# Map the ones we have observed so callers get something actionable.
|
|
11
|
+
CODE_HINTS = {
|
|
12
|
+
10002 => "The Shopee account has no password set",
|
|
13
|
+
200020 => "Shopee rejected the token as invalid or expired",
|
|
14
|
+
2010000 => "Shopee request carried no token",
|
|
15
|
+
48401003 => "Shopee rejected the OTP code as wrong or expired",
|
|
16
|
+
48401102 => "Shopee requires the account password before sending an OTP",
|
|
17
|
+
48401103 => "Shopee refused to send the OTP on the requested channel",
|
|
18
|
+
48500102 => "The Shopee account session has expired; log in again with an OTP"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
9
21
|
def data(payload)
|
|
10
22
|
code = payload[:code] if payload.is_a?(Hash)
|
|
11
23
|
success = (code.is_a?(Integer) && code.zero?) || code == "0"
|
|
@@ -38,7 +50,7 @@ module Rails
|
|
|
38
50
|
|
|
39
51
|
messages << msg.to_s unless msg.to_s.empty?
|
|
40
52
|
messages << description.to_s unless description.to_s.empty?
|
|
41
|
-
messages << "Shopee error (code #{code})" if messages.empty? && !code.nil?
|
|
53
|
+
messages << (CODE_HINTS[code_i] || "Shopee error (code #{code})") if messages.empty? && !code.nil?
|
|
42
54
|
end
|
|
43
55
|
return nil if messages.empty?
|
|
44
56
|
|
|
@@ -18,8 +18,13 @@ module Rails
|
|
|
18
18
|
OTP_CHANNELS = [1, 2, 3, 5].freeze
|
|
19
19
|
SEND_OTP_CHANNELS = [1, 2, 3, 5, 4].freeze
|
|
20
20
|
DEFAULT_OTP_CHANNEL = 3
|
|
21
|
+
CHANNEL_NAMES = { 1 => "SMS", 2 => "voice", 3 => "WhatsApp", 4 => "email", 5 => "Zalo" }.freeze
|
|
21
22
|
NEED_OTP_CODE = 48401102
|
|
22
23
|
NOT_LOGIN_CODE = 48500102
|
|
24
|
+
NO_PASSWORD_CODE = 10002
|
|
25
|
+
OTP_CHANNEL_UNAVAILABLE_CODE = 48401103
|
|
26
|
+
WRONG_OTP_CODE = 48401003
|
|
27
|
+
PASSWORD_REQUIRED = "This Shopee account is password-protected; supply the password to receive an OTP"
|
|
23
28
|
|
|
24
29
|
LIVE_TOKEN_COOKIE = "__shopee_partner_website_x_token_live"
|
|
25
30
|
CLIENT_ID_COOKIE = "SPC_CLIENTID"
|
|
@@ -60,22 +65,32 @@ module Rails
|
|
|
60
65
|
default_ch = settings[:default_channel]
|
|
61
66
|
resolved = channel || default_ch || DEFAULT_OTP_CHANNEL
|
|
62
67
|
if !available.empty? && !available.include?(resolved)
|
|
63
|
-
|
|
68
|
+
names = available.map { |c| "#{c} (#{CHANNEL_NAMES[c] || 'unknown'})" }.join(", ")
|
|
69
|
+
raise Error, "Shopee OTP channel #{resolved} is unavailable for this account; available: #{names}"
|
|
64
70
|
end
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
begin
|
|
73
|
+
account_request(
|
|
74
|
+
"/api/v4/account/business/send_otp",
|
|
75
|
+
{
|
|
76
|
+
operation: OTP_OPERATION,
|
|
77
|
+
phone: phone,
|
|
78
|
+
security_device_fingerprint: fingerprint,
|
|
79
|
+
support_session: false,
|
|
80
|
+
supported_channels: SEND_OTP_CHANNELS,
|
|
81
|
+
channel: resolved,
|
|
82
|
+
captcha_signature: ""
|
|
83
|
+
},
|
|
84
|
+
fingerprint
|
|
85
|
+
)
|
|
86
|
+
rescue Error => e
|
|
87
|
+
raise Error.new(
|
|
88
|
+
e.code == OTP_CHANNEL_UNAVAILABLE_CODE ?
|
|
89
|
+
"Shopee refused to send the OTP on channel #{resolved}; choose another available channel" :
|
|
90
|
+
e.message,
|
|
91
|
+
e.status, e.code, e.payload
|
|
92
|
+
)
|
|
93
|
+
end
|
|
79
94
|
|
|
80
95
|
{
|
|
81
96
|
version: 1,
|
|
@@ -105,17 +120,26 @@ module Rails
|
|
|
105
120
|
fingerprint = challenge[:device_fingerprint].to_s
|
|
106
121
|
|
|
107
122
|
formatted_phone = format_phone_for_verification(phone)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
begin
|
|
124
|
+
verified = account_request(
|
|
125
|
+
"/api/v4/account/business/verify_otp",
|
|
126
|
+
{
|
|
127
|
+
operation: OTP_OPERATION,
|
|
128
|
+
otp: code.to_s,
|
|
129
|
+
phone: formatted_phone,
|
|
130
|
+
security_device_fingerprint: fingerprint,
|
|
131
|
+
support_session: false
|
|
132
|
+
},
|
|
133
|
+
fingerprint
|
|
134
|
+
)
|
|
135
|
+
rescue Error => e
|
|
136
|
+
raise e unless e.code.to_s == WRONG_OTP_CODE.to_s
|
|
137
|
+
|
|
138
|
+
raise Error.new(
|
|
139
|
+
"Shopee rejected the OTP code (wrong or expired); request a new one and try again",
|
|
140
|
+
e.status, e.code, e.payload
|
|
141
|
+
)
|
|
142
|
+
end
|
|
119
143
|
|
|
120
144
|
token = verified[:otp_token]
|
|
121
145
|
raise Error, "Shopee OTP verification returned no token" unless token.is_a?(String) && !token.empty?
|
|
@@ -374,20 +398,23 @@ module Rails
|
|
|
374
398
|
parsed = parse_json(resp)
|
|
375
399
|
|
|
376
400
|
if parsed[:error] == NEED_OTP_CODE
|
|
377
|
-
raise Error,
|
|
401
|
+
raise Error, PASSWORD_REQUIRED if password.to_s.empty?
|
|
378
402
|
|
|
379
403
|
return true
|
|
380
404
|
end
|
|
381
405
|
|
|
382
|
-
return false if parsed[:error] == 0
|
|
406
|
+
return false if parsed[:error] == 0
|
|
407
|
+
# 10002: the account exists but has no password set — nothing to verify.
|
|
408
|
+
return false if parsed[:error] == NO_PASSWORD_CODE
|
|
383
409
|
|
|
384
410
|
account = parsed[:data].is_a?(Hash) ? parsed[:data][:toc_account] : nil
|
|
385
411
|
if account.is_a?(Hash) && account[:has_password] == true
|
|
386
|
-
raise Error,
|
|
412
|
+
raise Error, PASSWORD_REQUIRED
|
|
387
413
|
end
|
|
388
414
|
|
|
389
|
-
|
|
390
|
-
|
|
415
|
+
# Any other code means this account does not authenticate by password;
|
|
416
|
+
# OTP delivery is validated by send_otp afterwards.
|
|
417
|
+
false
|
|
391
418
|
end
|
|
392
419
|
|
|
393
420
|
def login_status
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-shopee-qris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Azmi
|
|
@@ -14,6 +14,7 @@ executables: []
|
|
|
14
14
|
extensions: []
|
|
15
15
|
extra_rdoc_files: []
|
|
16
16
|
files:
|
|
17
|
+
- CHANGELOG.md
|
|
17
18
|
- LICENSE.txt
|
|
18
19
|
- README.md
|
|
19
20
|
- lib/rails-shopee-qris.rb
|