amex 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +2 -2
- data/README.md +11 -81
- data/lib/amex/card_account.rb +72 -8
- data/lib/amex/client.rb +31 -11
- data/lib/amex/loyalty_programme.rb +8 -0
- data/lib/amex/transaction.rb +13 -0
- data/lib/amex/utils.rb +7 -7
- data/lib/amex/version.rb +1 -1
- metadata +3 -7
- data/amex-0.1.0.gem +0 -0
- data/amex-0.2.0.gem +0 -0
- data/amex-0.3.0.gem +0 -0
- data/amex-0.3.1.gem +0 -0
- data/amex-0.3.2.gem +0 -0
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
## Changelog
|
2
|
+
|
3
|
+
__v0.1.0__ - Original version
|
4
|
+
|
5
|
+
__v0.2.0__ - Support for multiple American Express cards, parsing using
|
6
|
+
Nokogiri
|
7
|
+
|
8
|
+
__v0.3.0__ - Adds support for loading the transactions from the most recent statement
|
9
|
+
(but it's broken because I forgot to change something from testing :( )
|
10
|
+
|
11
|
+
__v0.3.1__ - Working version of v0.3.0 that will successfully load transactions
|
12
|
+
from the most recent statement
|
13
|
+
|
14
|
+
__v0.3.2__ - Generates a fake HardwareId in the first request, since I'm
|
15
|
+
paranoid about American Express blocking 'dummy_device_id'
|
16
|
+
|
17
|
+
__v0.4.0__ - Improves transactions - adds support for lazy-loading and
|
18
|
+
pagination from Amex::CardAccount#transactions
|
19
|
+
|
20
|
+
__v0.4.1__ - Adds YARD documentation
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
amex (0.4.
|
4
|
+
amex (0.4.1)
|
5
5
|
httparty
|
6
6
|
nokogiri
|
7
7
|
httparty (0.9.0)
|
@@ -15,6 +15,6 @@ PLATFORMS
|
|
15
15
|
ruby
|
16
16
|
|
17
17
|
DEPENDENCIES
|
18
|
-
amex (= 0.4.
|
18
|
+
amex (= 0.4.1)
|
19
19
|
httparty (= 0.9.0)
|
20
20
|
nokogiri (= 1.5.6)
|
data/README.md
CHANGED
@@ -9,26 +9,9 @@ the previous unknown internal API used by American Express for their
|
|
9
9
|
"Amex UK" iPhone app. I suspect it works elsewhere, but I haven't tested.
|
10
10
|
|
11
11
|
This allows you to fetch the details of all the cards on your American
|
12
|
-
Express login, as well as
|
12
|
+
Express login, as well as transactions on each card.
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
__v0.1.0__ - Original version
|
17
|
-
|
18
|
-
__v0.2.0__ - Support for multiple American Express cards, parsing using
|
19
|
-
Nokogiri
|
20
|
-
|
21
|
-
__v0.3.0__ - Adds support for loading the transactions from the most recent statement
|
22
|
-
(but it's broken because I forgot to change something from testing :( )
|
23
|
-
|
24
|
-
__v0.3.1__ - Working version of v0.3.0 that will successfully load transactions
|
25
|
-
from the most recent statement
|
26
|
-
|
27
|
-
__v0.3.2__ - Generates a fake HardwareId in the first request, since I'm
|
28
|
-
paranoid about American Express blocking 'dummy_device_id'
|
29
|
-
|
30
|
-
__v0.4.0__ - Improves transactions - adds support for lazy-loading and
|
31
|
-
pagination from Amex::CardAccount#transactions
|
14
|
+
*See CHANGELOG.md for a changelog.*
|
32
15
|
|
33
16
|
|
34
17
|
### Usage
|
@@ -67,68 +50,15 @@ puts my_account.type
|
|
67
50
|
puts my_account.transactions.inspect
|
68
51
|
```
|
69
52
|
|
70
|
-
###
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
* __card_index (integer)__ - the internal number of the card on your account
|
80
|
-
in the Amex system, starting from 0 *(used internally by the gem)*
|
81
|
-
* __lending_type (string)__ - either "Charge" or "Credit", depending on the type of credit arrangement you have
|
82
|
-
* __card_member_name (string)__ - your name, as recorded as the account holder
|
83
|
-
* __past_due (boolean)__ - is the card past its due payment date?
|
84
|
-
* __cancelled?__ (boolean)__ - has the account been cancelled?
|
85
|
-
* __is_basic/centurion/platinum/premium] (boolean)__ - which type of account does this conform to?
|
86
|
-
* __market (string)__ - the market that your card is registered too, e.g. "en_GB"
|
87
|
-
* __card__art (string)__ - a URL for an image of your card
|
88
|
-
* __loyalty_indicator (boolean)__ - does this card have some
|
89
|
-
kind of loyalty scheme active? (e.g. Membership Rewards)
|
90
|
-
* __loyalty_programmes (array of Amex::LoyaltyProgramme objects)__ - the loyalty programmes this card belongs to
|
91
|
-
* __loyalty_balances (hash)__ - the loyalty balances on this card - the key is the name of the programme (string), and the balance is the value, as an integer
|
92
|
-
* __statement_balance (float)__ - the amount of the last statement on your account
|
93
|
-
* __payment_credits (float)__ - the combination of current payments and credits on your account
|
94
|
-
* __recent_charges (float)__ - charges since your last statement was issued (I believe)
|
95
|
-
* __total_balance (float)__ - what American Express refer to as your total balance, whatever that is!
|
96
|
-
* __payment_due (float)__ - the amount of money you need to pay before `payment_due_date`
|
97
|
-
* __payment_due_date (DateTime)__ - when the `payment_due` needs to be paid by
|
98
|
-
|
99
|
-
The transactions for a given account can be found by calling the `transaction`
|
100
|
-
method on a CardAccount. This takes a parameter, `billing_period`, which can
|
101
|
-
be an integer referring to a particular statement, or a range of integers.
|
102
|
-
|
103
|
-
To get transactions since the most recent statement, simply call
|
104
|
-
`account.transactions(0)` or `account.transactions`, since this is the default.
|
105
|
-
|
106
|
-
To get the last statement, call `account.transactions(1)`. To get from now up
|
107
|
-
to 5 statements ago, call `account.transactions(0..5)`. You get the idea.
|
108
|
-
|
109
|
-
There are lots of extra useful methods here to make accessing
|
110
|
-
some of the various properties easier. They're very self-explanatory - check `lib/amex/card_account.rb`.
|
111
|
-
|
112
|
-
A __Amex::LoyaltyProgramme has just two attributes:
|
113
|
-
|
114
|
-
* __name (string)__ - The name of the programme, most often "Membership Rewards"
|
115
|
-
* __balance (integer)__ - The balance of the programme
|
116
|
-
|
117
|
-
An __Amex::Transaction represents a transaction in your most recent American
|
118
|
-
Express statement. It has four attributes:
|
119
|
-
|
120
|
-
* __amount (float)__ - The amount of the transaction
|
121
|
-
* __date (Date)__ - The date that the transaction was recorded
|
122
|
-
* __narrative (string)__ - The description shown on your statement, usually
|
123
|
-
contaning the name of the merchant and their location
|
124
|
-
* __extra_details (hash)__ - This hash contains any extra pieces of information
|
125
|
-
provided by American Express...for instance, foreign transactions have their
|
126
|
-
exchange rate and commission. The name of the attribute will be the key, and
|
127
|
-
its formatted value in the value in the hash.
|
128
|
-
|
129
|
-
There's one helper method currently available here, `is_foreign_transaction?`,
|
130
|
-
which returns a boolean representing whether the transaction was foreign (i.e.
|
131
|
-
in a non-native currency).
|
53
|
+
### Documentation
|
54
|
+
|
55
|
+
You can view the full YARD documentation [here](http://rubydoc.info/github/timrogers/amex/master/frames).
|
56
|
+
|
57
|
+
### Examples
|
58
|
+
|
59
|
+
* __[amex_alerts](https://github.com/timrogers/amex-alerts)__, a script
|
60
|
+
designed to be run as a cron which tracks and alerts you on your rewards
|
61
|
+
balances
|
132
62
|
|
133
63
|
### License
|
134
64
|
|
data/lib/amex/card_account.rb
CHANGED
@@ -7,6 +7,13 @@ module Amex
|
|
7
7
|
:total_balance, :payment_due, :payment_due_date, :loyalty_programmes,
|
8
8
|
:client
|
9
9
|
|
10
|
+
# Generates a CardAccount object from XML properties grabbed by the client
|
11
|
+
#
|
12
|
+
# @param [Hash] options A Hash containing XML properties pulled directly
|
13
|
+
# from the API XML
|
14
|
+
# @return [Amex::CardAccount] an object representing an American Express
|
15
|
+
# card
|
16
|
+
#
|
10
17
|
def initialize(options)
|
11
18
|
options.each do |key, value|
|
12
19
|
method = key.to_s.underscore + "="
|
@@ -15,15 +22,20 @@ module Amex
|
|
15
22
|
@loyalty_programmes = []
|
16
23
|
end
|
17
24
|
|
25
|
+
# Fetches transactions on an American Express card
|
26
|
+
#
|
27
|
+
# @param [Fixnum, Range] billing_period The billing period(s) to fetch
|
28
|
+
# transactions for, as either a single billing period (e.g. 0 or 1) or
|
29
|
+
# a range of periods to fetch (e.g. 0..3)
|
30
|
+
# @return [Array<Amex::Transaction>] an array of `Amex::Transaction` objects
|
31
|
+
# @note This can fetch either a single billing period or a range
|
32
|
+
# of billing periods, e.g:
|
33
|
+
# => account.transaction(0)
|
34
|
+
# fetches transactions since the last statement (default)
|
35
|
+
# => account.transaction(0..5)
|
36
|
+
# fetches transactions between now and five statements ago
|
37
|
+
#
|
18
38
|
def transactions(billing_period=0)
|
19
|
-
# Fetch the transactions for this account based upon the passed in
|
20
|
-
# options - this can fetch either a single billing period or a range
|
21
|
-
# of billing periods, e.g:
|
22
|
-
#
|
23
|
-
# => account.transaction(0) fetches transactions since the last statement
|
24
|
-
# (default)
|
25
|
-
# => account.transaction(0..5) fetches transactions between now and
|
26
|
-
# five statements ago
|
27
39
|
result = []
|
28
40
|
|
29
41
|
# Build an array of billing periods we need to fetch - this is almost
|
@@ -55,23 +67,46 @@ module Amex
|
|
55
67
|
result
|
56
68
|
end
|
57
69
|
|
70
|
+
# Returns the balance of the most recent statement
|
71
|
+
#
|
72
|
+
# @return [Float] the balance of the most recent statement
|
73
|
+
#
|
58
74
|
def statement_balance
|
59
75
|
@stmt_balance
|
60
76
|
end
|
61
77
|
|
78
|
+
# Returns the name of the card product that your card conforms to (e.g.
|
79
|
+
# "American Express Preferred Rewards Gold")
|
80
|
+
#
|
81
|
+
# @return [String] the name of the card product that your card conforms to
|
82
|
+
#
|
62
83
|
def product
|
63
84
|
@card_product
|
64
85
|
end
|
65
86
|
|
87
|
+
# Returns whether the card account is cancelled
|
88
|
+
#
|
89
|
+
# @return [Boolean] the cancellation status of this card, either true or
|
90
|
+
# false
|
66
91
|
def cancelled?
|
67
92
|
@cancelled
|
68
93
|
end
|
69
94
|
|
95
|
+
# Returns the date that the next payment on the card is due
|
96
|
+
#
|
97
|
+
# @return [DateTime] the date when the next payment against this account
|
98
|
+
# is due
|
70
99
|
def payment_due_date
|
71
100
|
# Overrides attr_accessor so it actually returns a DateTime, not String
|
72
101
|
DateTime.parse(@payment_due_date)
|
73
102
|
end
|
74
103
|
|
104
|
+
# Returns the type of account this card conforms to (generally not useful,
|
105
|
+
# probably largely used internally by American Express
|
106
|
+
#
|
107
|
+
# @return [:basic, :platinum, :centurion, :premium, :unknown] a symbol
|
108
|
+
# representing the 'type' of your card
|
109
|
+
#
|
75
110
|
def type
|
76
111
|
return :basic if @is_basic
|
77
112
|
return :platinum if @is_platinum
|
@@ -80,30 +115,59 @@ module Amex
|
|
80
115
|
:unknown
|
81
116
|
end
|
82
117
|
|
118
|
+
# Returns whether this account is a credit card
|
119
|
+
#
|
120
|
+
# @return [Boolean] true if the account is a credit card, false otherwise
|
121
|
+
#
|
83
122
|
def is_credit_card?
|
84
123
|
return true if @lending_type == "Credit"
|
85
124
|
false
|
86
125
|
end
|
87
126
|
|
127
|
+
# Returns whether this account is a charge card
|
128
|
+
#
|
129
|
+
# @return [Boolean] true if the account is a charge card, false otherwise
|
130
|
+
#
|
88
131
|
def is_charge_card?
|
89
132
|
return true if @lending_type == "Charge"
|
90
133
|
false
|
91
134
|
end
|
92
135
|
|
136
|
+
# Returns whether payment on this account is overdue
|
137
|
+
#
|
138
|
+
# @return [Boolean] true if the account is overdue, false otherwise
|
139
|
+
#
|
93
140
|
def overdue?
|
94
141
|
return true if @past_due
|
95
142
|
false
|
96
143
|
end
|
97
144
|
|
145
|
+
# Returns whether this account has a due payment (i.e. whether you need
|
146
|
+
# to pay American Express anything) (see #overdue?)
|
147
|
+
#
|
148
|
+
# @return [Boolean] true if the account has a due payment, false otherwise
|
149
|
+
#
|
98
150
|
def due?
|
99
151
|
return true if @payment_due.to_f > 0
|
100
152
|
false
|
101
153
|
end
|
102
154
|
|
155
|
+
# Returns whether this account has any kind of loyalty scheme attached
|
156
|
+
#
|
157
|
+
# @return [Boolean] true if the account has a loyalty scheme, false
|
158
|
+
# otherwise
|
159
|
+
#
|
103
160
|
def loyalty_enabled?
|
104
161
|
@loyalty_indicator
|
105
162
|
end
|
106
163
|
|
164
|
+
|
165
|
+
# Returns a hash of loyalty scheme balances for this account
|
166
|
+
#
|
167
|
+
# @return [Hash{String => String}] the loyalty balances for this account,
|
168
|
+
# with the key being the name of the loyalty scheme, and the value its
|
169
|
+
# balance
|
170
|
+
#
|
107
171
|
def loyalty_balances
|
108
172
|
result = {}
|
109
173
|
@loyalty_programmes.each do |programme|
|
data/lib/amex/client.rb
CHANGED
@@ -8,14 +8,23 @@ module Amex
|
|
8
8
|
include HTTParty
|
9
9
|
base_uri 'https://global.americanexpress.com/'
|
10
10
|
|
11
|
+
# Generates an Amex::Client object from a username and password
|
12
|
+
#
|
13
|
+
# @param [String] username Your American Express online services username
|
14
|
+
# @param [String] password Your American Express online services password
|
15
|
+
# @return [Amex::Client] an object representing an American Express online
|
16
|
+
# account
|
17
|
+
#
|
11
18
|
def initialize(username, password)
|
12
19
|
@username = username
|
13
20
|
@password = password
|
14
21
|
end
|
15
22
|
|
23
|
+
# Fetches the cards on an American Express online services account
|
24
|
+
#
|
25
|
+
# @return [Array<Amex::CardAccount>] an array of `Amex::CardAccount` objects
|
26
|
+
#
|
16
27
|
def accounts
|
17
|
-
# This only supports one account for now, because I'm lazy and I
|
18
|
-
# hate traversing XML...
|
19
28
|
options = { :body => { "PayLoadText" => request_xml }}
|
20
29
|
response = self.class.post(
|
21
30
|
'/myca/intl/moblclient/emea/ws.do?Face=en_GB', options
|
@@ -80,10 +89,17 @@ module Amex
|
|
80
89
|
|
81
90
|
end
|
82
91
|
|
92
|
+
# Generates the XML to send in a request to fetch transactions for a card
|
93
|
+
#
|
94
|
+
# @param [Integer] card_index The index of the card you're looking up
|
95
|
+
# in your account (see Amex::CardAccount#card_index)
|
96
|
+
# @param [Integer] billing_period The billing period to look at, with "0"
|
97
|
+
# being transactions since your last statement, "1" being your last
|
98
|
+
# statement, "2" the statement before that and so on....
|
99
|
+
#
|
100
|
+
# @return [String] XML to be sent in the request
|
101
|
+
#
|
83
102
|
def statement_request_xml(card_index, billing_period=0)
|
84
|
-
# Generates XML for grabbing the last statement's transactions for a
|
85
|
-
# card, using the card_index attribute from an account's XML
|
86
|
-
|
87
103
|
xml = File.read(
|
88
104
|
File.expand_path(File.dirname(__FILE__) + '/data/statement_request.xml')
|
89
105
|
)
|
@@ -95,10 +111,11 @@ module Amex
|
|
95
111
|
|
96
112
|
private
|
97
113
|
|
114
|
+
# Generates the XML to send in a request to fetch cards for an account
|
115
|
+
#
|
116
|
+
# @return [String] XML to be sent in the request
|
117
|
+
#
|
98
118
|
def request_xml
|
99
|
-
# Generates XML for the first request for account information, taking
|
100
|
-
# an XML template and interpolating some parts with ERB
|
101
|
-
|
102
119
|
xml = File.read(
|
103
120
|
File.expand_path(File.dirname(__FILE__) + '/data/request.xml')
|
104
121
|
)
|
@@ -110,10 +127,13 @@ module Amex
|
|
110
127
|
ERB.new(xml).result(binding)
|
111
128
|
end
|
112
129
|
|
130
|
+
# Generates a fake HardwareId to be sent in requests, in an attempt to
|
131
|
+
# hide what requests to the API are coming from this gem
|
132
|
+
#
|
133
|
+
# @return [String] a 40 character alphanumeric lower-case string, which
|
134
|
+
# is passed in with the original API request
|
135
|
+
#
|
113
136
|
def hardware_id
|
114
|
-
# Generates a fake HardwareId - a 40 character alphanumeric lower-case
|
115
|
-
# string, which is passed in with the original API request
|
116
|
-
|
117
137
|
chars = 'abcdefghjkmnpqrstuvwxyz1234567890'
|
118
138
|
id = ''
|
119
139
|
40.times { id << chars[rand(chars.size)] }
|
@@ -2,6 +2,14 @@ module Amex
|
|
2
2
|
class LoyaltyProgramme
|
3
3
|
attr_accessor :name, :balance
|
4
4
|
|
5
|
+
# Generates an Amex::LoyaltyProgramme object from a programme name and
|
6
|
+
# balance, grabbed from the XML
|
7
|
+
#
|
8
|
+
# @param [String] name The name of the reward programme
|
9
|
+
# @param [Fixnum] balance The balance of the reward programme
|
10
|
+
# @return [Amex::LoyaltyProgramme] an object representing a loyalty
|
11
|
+
# programme
|
12
|
+
#
|
5
13
|
def initialize(name, balance)
|
6
14
|
@name = name
|
7
15
|
@balance = balance
|
data/lib/amex/transaction.rb
CHANGED
@@ -4,6 +4,14 @@ module Amex
|
|
4
4
|
class Transaction
|
5
5
|
attr_reader :date, :narrative, :amount, :extra_details
|
6
6
|
|
7
|
+
# Generates an Amex::LoyaltyProgramme object from a Nokogiri object
|
8
|
+
# representing <Transaction> element
|
9
|
+
#
|
10
|
+
# @param [Nokogiri::XML::Element] transaction A <Transaction> node taken
|
11
|
+
# the API XML request, parsed by Nokogiri
|
12
|
+
# @return [Amex::Transaction] an object representing an individual
|
13
|
+
# transaction
|
14
|
+
#
|
7
15
|
def initialize(transaction)
|
8
16
|
# Pass this a <Transaction> element, and it'll parse it
|
9
17
|
@date = Date.strptime(transaction.css('TransChargeDate').text, '%m/%d/%y')
|
@@ -16,6 +24,11 @@ module Amex
|
|
16
24
|
end
|
17
25
|
end
|
18
26
|
|
27
|
+
# Returns whether the transaction was made abroad/in a foreign currency
|
28
|
+
#
|
29
|
+
# @return [Boolean] true if the transaction was made abroad/in a foreign
|
30
|
+
# currency, false otherwise
|
31
|
+
#
|
19
32
|
def is_foreign_transaction?
|
20
33
|
return true if @extra_details.has_key?('currencyRate')
|
21
34
|
false
|
data/lib/amex/utils.rb
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
class String
|
2
|
+
|
3
|
+
# Converts a String object into a simple as used on an Amex::CardAccount
|
4
|
+
# object - the XML uses camelCase attributes, we want underscored ones
|
5
|
+
# which we can convert to symbol (e.g. cardProduct to card_product)
|
6
|
+
#
|
7
|
+
# @return [String] the reformatted string
|
8
|
+
#
|
2
9
|
def underscore
|
3
10
|
self.gsub(/::/, '/').
|
4
11
|
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
@@ -6,11 +13,4 @@ class String
|
|
6
13
|
tr("-", "_").
|
7
14
|
downcase
|
8
15
|
end
|
9
|
-
|
10
|
-
def to_bool
|
11
|
-
return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
|
12
|
-
return false if self == false || self.blank? || self =~ (/(false|f|no|n|0)$/i)
|
13
|
-
raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
|
14
|
-
end
|
15
|
-
|
16
16
|
end
|
data/lib/amex/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amex
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: httparty
|
@@ -51,14 +51,10 @@ extensions: []
|
|
51
51
|
extra_rdoc_files: []
|
52
52
|
files:
|
53
53
|
- .gitignore
|
54
|
+
- CHANGELOG.md
|
54
55
|
- Gemfile
|
55
56
|
- Gemfile.lock
|
56
57
|
- README.md
|
57
|
-
- amex-0.1.0.gem
|
58
|
-
- amex-0.2.0.gem
|
59
|
-
- amex-0.3.0.gem
|
60
|
-
- amex-0.3.1.gem
|
61
|
-
- amex-0.3.2.gem
|
62
58
|
- amex.gemspec
|
63
59
|
- example.rb
|
64
60
|
- lib/amex.rb
|
data/amex-0.1.0.gem
DELETED
Binary file
|
data/amex-0.2.0.gem
DELETED
Binary file
|
data/amex-0.3.0.gem
DELETED
Binary file
|
data/amex-0.3.1.gem
DELETED
Binary file
|
data/amex-0.3.2.gem
DELETED
Binary file
|