amex 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gem 'httparty', '0.9.0'
4
- gem 'nokogiri', '1.5.6'
4
+ gem 'nokogiri', '1.5.6'
5
+ gem 'amex', '0.3.1'
@@ -1,6 +1,9 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ amex (0.3.0)
5
+ httparty
6
+ nokogiri
4
7
  httparty (0.9.0)
5
8
  multi_json (~> 1.0)
6
9
  multi_xml
@@ -12,5 +15,6 @@ PLATFORMS
12
15
  ruby
13
16
 
14
17
  DEPENDENCIES
18
+ amex (= 0.3.0)
15
19
  httparty (= 0.9.0)
16
20
  nokogiri (= 1.5.6)
data/README.md CHANGED
@@ -8,11 +8,18 @@ However, this one doesn't rely on nasty screen-scraping. Instead, it uses
8
8
  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
+ This allows you to fetch the details of all the cards on your American
12
+ Express login, as well as the most recent statement's transactions.
13
+
11
14
  ### Changelog
12
15
 
13
16
  __v0.1.0__ - Original version
14
17
  __v0.2.0__ - Support for multiple American Express cards, parsing using
15
18
  Nokogiri
19
+ __v0.3.0__ - Adds support for loading the transactions from the most recent statement
20
+ (but it's broken because I forgot to change something from testing :( )
21
+ __v0.3.1__ - Working version of v0.3.0 that will successfully load transactions
22
+ from the most recent statement
16
23
 
17
24
  ### Usage
18
25
 
@@ -42,8 +49,10 @@ only supports one card at a time right now.
42
49
 
43
50
  ```
44
51
  accounts = client.accounts
45
- puts account.first.product
46
- puts account.first.type
52
+ my_account = accounts.first
53
+ puts my_account.product
54
+ puts my_account.type
55
+ puts my_account.transactions.inspect
47
56
  ```
48
57
 
49
58
  ### Data models
@@ -55,6 +64,8 @@ An __Amex::CardAccount__ instance has the following attributes:
55
64
 
56
65
  * __product (string)__ - the type of card (e.g. "The Preferred Rewards Gold Card®")
57
66
  * __card_number_suffix (string)__ - the last five digits of your card number
67
+ * __card_index (integer)__ - the internal number of the card on your account
68
+ in the Amex system, starting from 0 *(used internally by the gem)*
58
69
  * __lending_type (string)__ - either "Charge" or "Credit", depending on the type of credit arrangement you have
59
70
  * __card_member_name (string)__ - your name, as recorded as the account holder
60
71
  * __past_due (boolean)__ - is the card past its due payment date?
@@ -72,18 +83,36 @@ kind of loyalty scheme active? (e.g. Membership Rewards)
72
83
  * __total_balance (float)__ - what American Express refer to as your total balance, whatever that is!
73
84
  * __payment_due (float)__ - the amount of money you need to pay before `payment_due_date`
74
85
  * __payment_due_date (DateTime)__ - when the `payment_due` needs to be paid by
86
+ * __transactions (array)__ - an array of Amex::Transaction objects)
75
87
 
76
88
  There are lots of extra useful methods here to make accessing
77
89
  some of the various properties easier. They're very self-explanatory - check `lib/amex/card_account.rb`.
78
90
 
79
- A __LloydsTSB::LoyaltyProgramme has just two attributes:
91
+ A __Amex::LoyaltyProgramme has just two attributes:
80
92
 
81
93
  * __name (string)__ - The name of the programme, most often "Membership Rewards"
82
94
  * __balance (integer)__ - The balance of the programme
83
95
 
96
+ An __Amex::Transaction represents a transaction in your most recent American
97
+ Express statement. It has four attributes:
98
+
99
+ * __amount (float)__ - The amount of the transaction
100
+ * __date (Date)__ - The date that the transaction was recorded
101
+ * __narrative (string)__ - The description shown on your statement, usually
102
+ contaning the name of the merchant and their location
103
+ * __extra_details (hash)__ - This hash contains any extra pieces of information
104
+ provided by American Express...for instance, foreign transactions have their
105
+ exchange rate and commission. The name of the attribute will be the key, and
106
+ its formatted value in the value in the hash.
107
+
108
+ There's one helper method currently available here, `is_foreign_transaction?`,
109
+ which returns a boolean representing whether the transaction was foreign (i.e.
110
+ in a non-native currency).
111
+
84
112
  ### Limitations
85
113
 
86
- * There's no support for viewing transactions yet - watch this space!
114
+ * You can only view transactions from the most recent statement. I intend
115
+ to change this in due course to support pagination of some description.
87
116
 
88
117
  ### License
89
118
 
Binary file
Binary file
@@ -1,4 +1,5 @@
1
1
  require 'amex/card_account'
2
+ require 'amex/transaction'
2
3
  require 'amex/client'
3
4
  require 'amex/loyalty_programme'
4
5
  require 'amex/utils'
@@ -1,10 +1,11 @@
1
1
  module Amex
2
2
  class CardAccount
3
- attr_accessor :card_product, :card_number_suffix,
3
+ attr_accessor :card_product, :card_number_suffix, :card_index,
4
4
  :lending_type, :card_member_name, :past_due, :cancelled, :is_basic,
5
5
  :is_centurion, :is_platinum, :is_premium, :market, :card_art,
6
6
  :loyalty_indicator, :stmt_balance, :payment_credits, :recent_charges,
7
- :total_balance, :payment_due, :payment_due_date, :loyalty_programmes
7
+ :total_balance, :payment_due, :payment_due_date, :loyalty_programmes,
8
+ :transactions
8
9
 
9
10
  def initialize(options)
10
11
  options.each do |key, value|
@@ -12,6 +13,7 @@ module Amex
12
13
  send(key.underscore + "=", value) if respond_to? method.to_sym
13
14
  end
14
15
  @loyalty_programmes = []
16
+ @transactions = []
15
17
  end
16
18
 
17
19
  def statement_balance
@@ -1,6 +1,7 @@
1
1
  require 'erb'
2
2
  require 'httparty'
3
3
  require 'nokogiri'
4
+ require 'date'
4
5
 
5
6
  module Amex
6
7
  class Client
@@ -24,6 +25,15 @@ module Amex
24
25
  ERB.new(xml).result(binding)
25
26
  end
26
27
 
28
+ def statement_request_xml(card_index)
29
+ xml = File.read(
30
+ File.expand_path(File.dirname(__FILE__) + '/data/statement_request.xml')
31
+ )
32
+
33
+ security_token = @security_token
34
+ ERB.new(xml).result(binding)
35
+ end
36
+
27
37
  def accounts
28
38
  # This only supports one account for now, because I'm lazy and I
29
39
  # hate traversing XML...
@@ -38,6 +48,9 @@ module Amex
38
48
  if xml.css('ServiceResponse Status').text != "success"
39
49
  raise "There was a problem logging in to American Express."
40
50
  else
51
+ # Store the security token - we need this for further requests
52
+ @security_token = xml.css('ClientSecurityToken').text
53
+
41
54
  accounts = [] # We'll store all the accounts in here!
42
55
 
43
56
  xml.css('CardAccounts CardAccount').each do |item|
@@ -64,6 +77,21 @@ module Amex
64
77
  element.attr('label'), element.attr('formattedValue').gsub(",", "").to_i
65
78
  )
66
79
  end
80
+
81
+ # Now we can fetch the transactions...
82
+ options = { :body => {
83
+ "PayLoadText" => statement_request_xml(account.card_index)
84
+ }}
85
+ response = self.class.post(
86
+ '/myca/intl/moblclient/emea/ws.do?Face=en_GB', options
87
+ )
88
+ xml = Nokogiri::XML(response.body)
89
+ xml = xml.css("XMLResponse")
90
+
91
+ xml.css('Transaction').each do |transaction|
92
+ account.transactions << Amex::Transaction.new(transaction)
93
+ end
94
+
67
95
  accounts << account
68
96
 
69
97
  end
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <XMLRequest>
3
+ <ClientType>iPhone</ClientType>
4
+ <ClientVersion>1.8</ClientVersion>
5
+ <ServiceVersion>1.0</ServiceVersion>
6
+ <Locale>en_GB</Locale>
7
+ <ServiceName>StatementService</ServiceName>
8
+ <ClientSecurityToken><%= security_token %></ClientSecurityToken>
9
+ <StatementRequestData>
10
+ <BillingPeriod>0</BillingPeriod>
11
+ <CardIndex><%= card_index %></CardIndex>
12
+ </StatementRequestData>
13
+ </XMLRequest>
@@ -0,0 +1,25 @@
1
+ require 'nokogiri'
2
+
3
+ module Amex
4
+ class Transaction
5
+ attr_reader :date, :narrative, :amount, :extra_details
6
+
7
+ def initialize(transaction)
8
+ # Pass this a <Transaction> element, and it'll parse it
9
+ @date = Date.strptime(transaction.css('TransChargeDate').text, '%m/%d/%y')
10
+ @narrative = transaction.css('TransDesc').text
11
+ @amount = transaction.css('TransAmount').text.to_f
12
+
13
+ @extra_details = {}
14
+ transaction.css('TransExtDetail ExtDetailElement').each do |element|
15
+ @extra_details[element.attr('name')] = element.attr('formattedValue')
16
+ end
17
+ end
18
+
19
+ def is_foreign_transaction?
20
+ return true if @extra_details.has_key?('currencyRate')
21
+ false
22
+ end
23
+
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
1
  module Amex
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.1'
3
3
  end
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.2.0
4
+ version: 0.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -56,13 +56,16 @@ files:
56
56
  - README.md
57
57
  - amex-0.1.0.gem
58
58
  - amex-0.2.0.gem
59
+ - amex-0.3.0.gem
59
60
  - amex.gemspec
60
61
  - example.rb
61
62
  - lib/amex.rb
62
63
  - lib/amex/card_account.rb
63
64
  - lib/amex/client.rb
64
65
  - lib/amex/data/request.xml
66
+ - lib/amex/data/statement_request.xml
65
67
  - lib/amex/loyalty_programme.rb
68
+ - lib/amex/transaction.rb
66
69
  - lib/amex/utils.rb
67
70
  - lib/amex/version.rb
68
71
  homepage: https://github.com/timrogers/amex