nfg-client 1.0.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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZDhhZGYxODA1OTNjMTcwNWJiMDVjMzUwOTE5NTgzNzEwOGQ3NDY5Mg==
5
+ data.tar.gz: !binary |-
6
+ ZmVjOTFiMWM5ZTA2NGQwMzc5NjNmYWZjNTBlYjllYjlkNDllNmNlMw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ OTU5ZjhlZTQ1NGNlNDNiNDY0MDI1OTI2ODA1N2JhZDk5NGJlNzgxYWQ0Njgy
10
+ YzRlZGRlOWY0MjA0MmMyYTY4MGE1NDg4ZTk3NmJmNjQ5ODA5MTg3M2U4OThl
11
+ ODYyODRlNGNjNjQ1ZGI3MjJkZTc0ZGEwNmExZjhiMzBhMzA4YmE=
12
+ data.tar.gz: !binary |-
13
+ ODc3MTViZmU1YzBmNzI2NjMwMGVhNWQ5OGRkNzg0MTliNzcyYjc2N2M1NzM2
14
+ ZTczOTMzYWE1YzQyNTgyNzA0MGE4ODMyMGFiNzc1ZjMxNGNhYmJmZTE1OGVm
15
+ NTM3NWY3MzAxMTFmZjNjOTA2ZDY2NGJjMDQ0Yjg4ZTk4ZWU2Mzc=
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ coverage
6
+ /.project
7
+ /nbproject/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in nfg-client.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Antonio Ruano Cuesta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,360 @@
1
+ # NFG Client
2
+
3
+ A client to interact with the NFG (Network for Good) API via SOAP requests.
4
+
5
+ ## Installation
6
+
7
+ Install the gem:
8
+
9
+ gem install nfg-client
10
+
11
+ Or add it to your Gemfile:
12
+
13
+ gem "nfg-client"
14
+
15
+ ## Usage
16
+
17
+ Create a NFG Client instance using your NFG credentials:
18
+
19
+ nfg_client = NFGClient.new(partner_id, partner_password, partner_source, partner_campaign, use_sandbox)
20
+
21
+ The fifth parameter is optional (defaults to false), and indicates the Client which API URLs to use (sandbox or production).
22
+
23
+ Use the Client to make calls to the NFG API, each call will take a hash with the parameters you want to send along with it (apart from the NFG credentials).
24
+
25
+ Example:
26
+
27
+ nfg_client.get_donor_cofs({"DonorToken" => donor_token})
28
+
29
+ All required parameters for the different calls should have the name specified by the NFG documentation.
30
+
31
+ For more information, refer to the official NFG documentation:
32
+
33
+ http://www.thenetworkforgood.org/t5/Developer-Resource-Center/ct-p/Developer
34
+
35
+ ## Supported Calls
36
+
37
+ Below, the list of supported calls and examples of usage. All functions return a hash with the response from NFG.
38
+
39
+ ### CreateCOF
40
+
41
+ Creates a Card on File without a transaction.
42
+
43
+ **_Call_**
44
+
45
+ response = nfg_client.create_cof({
46
+ "DonorToken" => "",
47
+ "DonorFirstName" => "",
48
+ "DonorLastName" => "",
49
+ "DonorEmail" => "",
50
+ "DonorAddress1" => "",
51
+ "DonorAddress2" => "",
52
+ "DonorCity" => "",
53
+ "DonorState" => "",
54
+ "DonorZip" => "",
55
+ "DonorCountry" => "",
56
+ "DonorPhone" => "",
57
+ "CardType" => "", # Unk, Visa, Mastercard, Amex or Discover
58
+ "NameOnCard" => "",
59
+ "CardNumber" => "",
60
+ "ExpMonth" => "",
61
+ "ExpYear" => "",
62
+ "CSC" => ""
63
+ })
64
+
65
+ **_Response on Success_**
66
+
67
+ {
68
+ "StatusCode" => "Success",
69
+ "Message" => "", # Empty
70
+ "ErrorDetails" => "", # Empty
71
+ "CallDuration" => "x.x",
72
+ "DonorToken" => "x",
73
+ "COFId" => "x" # Integer
74
+ }
75
+
76
+ ### GetDonorCOFs
77
+
78
+ Gets a list of donor Cards on File.
79
+
80
+ **_Call_**
81
+
82
+ response = nfg_client.get_donor_cofs({
83
+ "DonorToken" => ""
84
+ })
85
+
86
+ **_Response on Success_**
87
+
88
+ {
89
+ "StatusCode" => "Success",
90
+ "Message" => "", # Empty
91
+ "ErrorDetails" => "", # Empty
92
+ "CallDuration" => "x.x",
93
+ "DonorToken" => "x",
94
+ "Cards" => [
95
+ {
96
+ "COFId" => "x", # Integer
97
+ "CardType" => "x", # e.g. Mastercard
98
+ "CCSuffix" => "xxxx", # Last four digits
99
+ "CCExpMonth" => "", # 1 or 2 digits
100
+ "CCExpYear" => "", # 4 digits
101
+ "bInUseByLiveRD" => "", # true or false
102
+ "COFEmailAddress" => ""
103
+ },
104
+ ...
105
+ ]
106
+ }
107
+
108
+ ### DeleteDonorCOF
109
+
110
+ Removes an existing Card on File.
111
+
112
+ **_Call_**
113
+
114
+ response = nfg_client.delete_donor_cof({
115
+ "DonorToken" => "",
116
+ "COFId" => ""
117
+ })
118
+
119
+ **_Response on Success_**
120
+
121
+ {
122
+ "StatusCode" => "Success",
123
+ "Message" => "", # Empty
124
+ "ErrorDetails" => "", # Empty
125
+ "CallDuration" => "x.xx"
126
+ }
127
+
128
+ ### MakeCOFDonation
129
+
130
+ Triggers a one-time or recurring donation to one or more charities with an existing Card on File.
131
+
132
+ **_Call_**
133
+
134
+ response = nfg_client.make_cof_donation({
135
+ "DonationLineItems" => {
136
+ "DonationItem1" => {
137
+ "NpoEin" => "",
138
+ "donorVis" => "", # ProvideAll, ProvideNameAndEmailOnly or Anonymous
139
+ "ItemAmount" => "",
140
+ "RecurType" => "", # NotRecurring, Monthly, Quarterly or Annually
141
+ "Designation" => ""
142
+ "Dedication" => ""
143
+ },
144
+ "DonationItem2" => {
145
+ ...
146
+ },
147
+ ...
148
+ },
149
+ "TotalAmount" => "",
150
+ "TipAmount" => "",
151
+ "DonorIpAddress" => "",
152
+ "DonorToken" => "",
153
+ "COFId" => ""
154
+ })
155
+
156
+ **_Response on Success_**
157
+
158
+ {
159
+ "StatusCode" => "Success",
160
+ "Message" => "", # Empty
161
+ "ErrorDetails" => "", # Empty
162
+ "CallDuration" => "x.x",
163
+ "ChargeId" => "x", # Integer
164
+ "COFId" => "0"
165
+ }
166
+
167
+ ### MakeDonationAddCOF
168
+
169
+ Triggers a one-time or recurring donation to one or more charities with a new Card on File.
170
+
171
+ **_Call_**
172
+
173
+ response = nfg_client.make_donation_add_cof({
174
+ "DonationLineItems" => {
175
+ "DonationItem1" => {
176
+ "NpoEin" => "",
177
+ "donorVis" => "", # ProvideAll, ProvideNameAndEmailOnly or Anonymous
178
+ "ItemAmount" => "",
179
+ "RecurType" => "", # NotRecurring, Monthly, Quarterly or Annually
180
+ "Designation" => ""
181
+ "Dedication" => ""
182
+ },
183
+ "DonationItem2" => {
184
+ ...
185
+ },
186
+ ...
187
+ },
188
+ "TotalAmount" => "",
189
+ "TipAmount" => "",
190
+ "DonorIpAddress" => "",
191
+ "DonorToken" => "",
192
+ "COFId" => "",
193
+ "DonorFirstName" => "",
194
+ "DonorLastName" => "",
195
+ "DonorEmail" => "",
196
+ "DonorAddress1" => "",
197
+ "DonorAddress2" => "",
198
+ "DonorCity" => "",
199
+ "DonorState" => "",
200
+ "DonorZip" => "",
201
+ "DonorCountry" => "",
202
+ "DonorPhone" => "",
203
+ "CardType" => "", # Unk, Visa, Mastercard, Amex or Discover
204
+ "NameOnCard" => "",
205
+ "CardNumber" => "",
206
+ "ExpMonth" => "",
207
+ "ExpYear" => "",
208
+ "CSC" => ""
209
+ })
210
+
211
+ **_Response on Success_**
212
+
213
+ {
214
+ "StatusCode" => "Success",
215
+ "Message" => "", # Empty
216
+ "ErrorDetails" => "", # Empty
217
+ "CallDuration" => "x.x",
218
+ "ChargeId" => "x", # Integer
219
+ "COFId" => "0"
220
+ }
221
+
222
+ ### GetFee
223
+
224
+ Calculates the fee for a transaction that would ultimately be used with payment functions.
225
+
226
+ **_Call_**
227
+
228
+ response = nfg_client.get_fee({
229
+ "DonationLineItems" => {
230
+ "DonationItem1" => {
231
+ "NpoEin" => "",
232
+ "donorVis" => "", # ProvideAll, ProvideNameAndEmailOnly or Anonymous
233
+ "ItemAmount" => "",
234
+ "RecurType" => "", # NotRecurring, Monthly, Quarterly or Annually
235
+ "Designation" => ""
236
+ "Dedication" => ""
237
+ },
238
+ "DonationItem2" => {
239
+ ...
240
+ },
241
+ ...
242
+ },
243
+ "TipAmount" => "",
244
+ "CardType" => ""
245
+ })
246
+
247
+ **_Response on Success_**
248
+
249
+ {
250
+ "Message" => "", # Empty
251
+ "ErrorDetails" => "", # Empty
252
+ "CallDuration" => "x.x",
253
+ "TotalChargeAmount" => "x.x",
254
+ "TotalAddFee" => "x.x",
255
+ "TotalDeductFee" => "x.x",
256
+ "TipAmount" => "x.x"
257
+ }
258
+
259
+ ### GetDonorDonationHistory
260
+
261
+ Gets donor transactions.
262
+
263
+ **_Call_**
264
+
265
+ response = nfg_client.get_donor_donation_history({
266
+ "DonorToken" => ""
267
+ })
268
+
269
+ **_Response on Success_**
270
+
271
+ {
272
+ "StatusCode" => "Success",
273
+ "Message" => "", # Empty
274
+ "ErrorDetails" => "", # Empty
275
+ "CallDuration" => "x.x",
276
+ "DonorToken" => "x",
277
+ "Donations"=>[
278
+ {
279
+ "DonationDate" => "xxxx-xx-xxTxx:xx:xx.xx",
280
+ "RecurType" => "x", # e.g. NotRecurring
281
+ "IsTpcAddOnFee" => "", # true or false
282
+ "NpoName" => "", # e.g. Smithsonian Institution
283
+ "Designation" => "",
284
+ "Dedication" => "",
285
+ "Amount" => "x.x",
286
+ "ChargeId" => "x" # Integer
287
+ },
288
+ ...
289
+ ]
290
+ }
291
+
292
+ ### GetDonationReport
293
+
294
+ Returns a report of partner transactions during a specific period of time.
295
+
296
+ **_Call_**
297
+
298
+ response = nfg_client.get_donation_report({
299
+ "StartDate" => "",
300
+ "EndDate" => "",
301
+ "DonationReportType" => "" # All, OneTime or Recurring
302
+ })
303
+
304
+ **_Response on Success_**
305
+
306
+ {
307
+ "StatusCode" => "Success",
308
+ "Message" => "", # Empty
309
+ "ErrorDetails" => "", # Empty
310
+ "CallDuration" => "x.x",
311
+ "ReturnCount" => "x",
312
+ "ReportResults"=>[
313
+ {
314
+ "Source" => "x",
315
+ "Campaign" => "x",
316
+ "ChargeId" => "x", # Integer
317
+ "ShareWithCharity" => "x", # e.g. No Donor Info - Anonymous
318
+ "DonorEmail" => "x",
319
+ "DonorFirstName" => "x",
320
+ "DonorLastName" => "x",
321
+ "DonorAddress1" => "x",
322
+ "DonorAddress2" => "x",
323
+ "DonorCity" => "x",
324
+ "DonorState" => "x",
325
+ "DonorZip" => "x",
326
+ "DonorCountry" => "x",
327
+ "DonorPhone" => "x",
328
+ "Designation" => "",
329
+ "DonateInName" => "",
330
+ "RecurringPeriod" => "",
331
+ "EventDate" => "xxxx-xx-xxTxx:xx:xx.xxxZ",
332
+ "NpoEIN" => "x",
333
+ "NpoName" => "x",
334
+ "ChargeStatus" => "", # e.g. CS_SUCCESS
335
+ "ChargeAmount" => "x.x",
336
+ "NpoTPC" => "x.x",
337
+ "DonationItemAmount" => "x.x",
338
+ "TotalDonationAmount" => "x.x",
339
+ "HistoryRecordId" => "x"
340
+ },
341
+ ...
342
+ ]
343
+ }
344
+
345
+ ## Failed calls
346
+
347
+ When a call fails (i.e. the "StatusCode" is different than "Success"), it's usually possible to find out more about the error looking at the "Message" and "ErrorDetails" fields. "Message" is a string, while "ErrorDetails" is a hash with the following format:
348
+
349
+ {
350
+ "StatusCode" => "Other",
351
+ "Message" => "x",
352
+ "ErrorDetails" => {
353
+ "ErrorInfo"=>{
354
+ "ErrCode" => "x",
355
+ "ErrData" => "x"
356
+ }
357
+ }
358
+ "CallDuration" => "x.x",
359
+ ...
360
+ }
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Run specs'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ desc 'Default: run specs.'
8
+ task :default => :spec
@@ -0,0 +1,3 @@
1
+ require 'nfg-client/version'
2
+ require 'nfg-client/utils'
3
+ require 'nfg-client/client'
@@ -0,0 +1,321 @@
1
+ module NFGClient
2
+
3
+ def self.new(partner_id, partner_password, partner_source, partner_campaign, use_sandbox = false)
4
+ NFGClient::Client.new(partner_id, partner_password, partner_source, partner_campaign, use_sandbox)
5
+ end
6
+
7
+ class Client
8
+ include NFGClient::Utils
9
+
10
+ def initialize(partner_id, partner_password, partner_source, partner_campaign, use_sandbox)
11
+ @partner_id = partner_id
12
+ @partner_password = partner_password
13
+ @partner_source = partner_source
14
+ @partner_campaign = partner_campaign
15
+ @use_sandbox = use_sandbox
16
+ end
17
+
18
+ # Creates a card on file for the given donor token
19
+ #
20
+ # Arguments:
21
+ # params: (Hash)
22
+ def create_cof(params)
23
+ call_params = add_credentials_to_params(params)
24
+ response = nfg_soap_request('CreateCOF', call_params, @use_sandbox)
25
+ if response.is_a? REXML::Element
26
+ {
27
+ 'StatusCode' => response.elements['StatusCode'].get_text.to_s,
28
+ 'Message' => response.elements['Message'].get_text.to_s,
29
+ 'ErrorDetails' => response.elements['ErrorDetails'].get_text.to_s,
30
+ 'CallDuration' => response.elements['CallDuration'].get_text.to_s,
31
+ 'DonorToken' => response.elements['DonorToken'].get_text.to_s,
32
+ 'COFId' => response.elements['CofId'].get_text.to_s
33
+ }
34
+ else
35
+ response
36
+ end
37
+ end
38
+
39
+ # Deletes a card on file for the given donor token
40
+ #
41
+ # Arguments:
42
+ # params: (Hash)
43
+ def delete_donor_cof(params)
44
+ call_params = add_credentials_to_params(params)
45
+ response = nfg_soap_request('DeleteDonorCOF', call_params, @use_sandbox)
46
+ if response.is_a? REXML::Element
47
+ if response.elements['StatusCode'].get_text.to_s == 'Success'
48
+ {
49
+ 'StatusCode' => response.elements['StatusCode'].get_text.to_s,
50
+ 'Message' => response.elements['Message'].get_text.to_s,
51
+ 'ErrorDetails' => response.elements['ErrorDetails'].get_text.to_s,
52
+ 'CallDuration' => response.elements['CallDuration'].get_text.to_s
53
+ }
54
+ else
55
+ {
56
+ 'StatusCode' => response.elements['StatusCode'].get_text.to_s,
57
+ 'Message' => response.elements['Message'].get_text.to_s,
58
+ 'ErrorDetails' => {
59
+ 'ErrorInfo' => {
60
+ 'ErrCode' => response.elements['ErrorDetails'].andand.elements.andand['ErrorInfo'].andand.elements.andand['ErrCode'].andand.get_text.andand.to_s,
61
+ 'ErrData' => response.elements['ErrorDetails'].andand.elements.andand['ErrorInfo'].andand.elements.andand['ErrData'].andand.get_text.andand.to_s
62
+ }
63
+ },
64
+ 'CallDuration' => response.elements['CallDuration'].get_text.to_s
65
+ }
66
+ end
67
+ else
68
+ response
69
+ end
70
+ end
71
+
72
+ # Gets a list of the given donor token's cards on file
73
+ #
74
+ # Arguments:
75
+ # params: (Hash)
76
+ def get_donor_cofs(params)
77
+ call_params = add_credentials_to_params(params)
78
+ response = nfg_soap_request('GetDonorCOFs', call_params, @use_sandbox)
79
+ if response.is_a? REXML::Element
80
+ response_hash = {
81
+ 'StatusCode' => response.elements['StatusCode'].get_text.to_s,
82
+ 'Message' => response.elements['Message'].get_text.to_s,
83
+ 'ErrorDetails' => response.elements['ErrorDetails'].get_text.to_s,
84
+ 'CallDuration' => response.elements['CallDuration'].get_text.to_s,
85
+ 'DonorToken' => response.elements['DonorToken'].get_text.to_s
86
+ }
87
+ response_hash['Cards'] = Array.new
88
+ response.elements.each('Cards/COFRecord') do |card|
89
+ response_hash['Cards'] << {
90
+ 'COFId' => card.elements['COFId'].get_text.to_s,
91
+ 'CardType' => card.elements['CardType'].get_text.to_s,
92
+ 'CCSuffix' => card.elements['CCSuffix'].get_text.to_s,
93
+ 'CCExpMonth' => card.elements['CCExpMonth'].get_text.to_s,
94
+ 'CCExpYear' => card.elements['CCExpYear'].get_text.to_s,
95
+ 'bInUseByLiveRD' => card.elements['bInUseByLiveRD'].get_text.to_s,
96
+ 'COFEmailAddress' => card.elements['COFEmailAddress'].get_text.to_s
97
+ }
98
+ end
99
+ response_hash
100
+ else
101
+ response
102
+ end
103
+ end
104
+
105
+ # Calculates fee tied to transaction
106
+ #
107
+ # Arguments:
108
+ # params: (Hash)
109
+ def get_fee(params)
110
+ call_params = add_credentials_to_params(params)
111
+ response = nfg_soap_request('GetFee', call_params, @use_sandbox)
112
+ if response.is_a? REXML::Element
113
+ if response.elements['ErrorDetails'].elements['ErrorInfo'].nil?
114
+ {
115
+ 'Message' => response.elements['Message'].get_text.to_s,
116
+ 'ErrorDetails' => response.elements['ErrorDetails'].get_text.to_s,
117
+ 'CallDuration' => response.elements['CallDuration'].get_text.to_s,
118
+ 'TotalChargeAmount' => response.elements['TotalChargeAmount'].get_text.to_s,
119
+ 'TotalAddFee' => response.elements['TotalAddFee'].get_text.to_s,
120
+ 'TotalDeductFee' => response.elements['TotalChargeAmount'].get_text.to_s,
121
+ 'TipAmount' => response.elements['TotalAddFee'].get_text.to_s
122
+ }
123
+ else
124
+ {
125
+ 'Message' => response.elements['Message'].get_text.to_s,
126
+ 'ErrorDetails' => {
127
+ 'ErrorInfo' => {
128
+ 'ErrCode' => response.elements['ErrorDetails'].andand.elements.andand['ErrorInfo'].andand.elements.andand['ErrCode'].andand.get_text.andand.to_s,
129
+ 'ErrData' => response.elements['ErrorDetails'].andand.elements.andand['ErrorInfo'].andand.elements.andand['ErrData'].andand.get_text.andand.to_s
130
+ }
131
+ },
132
+ 'CallDuration' => response.elements['CallDuration'].get_text.to_s,
133
+ 'TotalChargeAmount' => response.elements['TotalChargeAmount'].get_text.to_s,
134
+ 'TotalAddFee' => response.elements['TotalAddFee'].get_text.to_s,
135
+ 'TotalDeductFee' => response.elements['TotalChargeAmount'].get_text.to_s,
136
+ 'TipAmount' => response.elements['TotalAddFee'].get_text.to_s
137
+ }
138
+ end
139
+ else
140
+ response
141
+ end
142
+ end
143
+
144
+ # Makes a donation using the given COF
145
+ #
146
+ # Arguments:
147
+ # params: (Hash)
148
+ def make_cof_donation(params)
149
+ call_params = add_credentials_to_params(params)
150
+ response = nfg_soap_request('MakeCOFDonation', call_params, @use_sandbox)
151
+ if response.is_a? REXML::Element
152
+ if response.elements['StatusCode'].get_text.to_s == 'Success'
153
+ {
154
+ 'StatusCode' => response.elements['StatusCode'].get_text.to_s,
155
+ 'Message' => response.elements['Message'].get_text.to_s,
156
+ 'ErrorDetails' => response.elements['ErrorDetails'].get_text.to_s,
157
+ 'CallDuration' => response.elements['CallDuration'].get_text.to_s,
158
+ 'ChargeId' => response.elements['ChargeId'].get_text.to_s,
159
+ 'COFId' => response.elements['CofId'].get_text.to_s
160
+ }
161
+ else
162
+ {
163
+ 'StatusCode' => response.elements['StatusCode'].get_text.to_s,
164
+ 'Message' => response.elements['Message'].get_text.to_s,
165
+ 'ErrorDetails' => {
166
+ 'ErrorInfo' => {
167
+ 'ErrCode' => response.elements['ErrorDetails'].andand.elements.andand['ErrorInfo'].andand.elements.andand['ErrCode'].andand.get_text.andand.to_s,
168
+ 'ErrData' => response.elements['ErrorDetails'].andand.elements.andand['ErrorInfo'].andand.elements.andand['ErrData'].andand.get_text.andand.to_s
169
+ }
170
+ },
171
+ 'CallDuration' => response.elements['CallDuration'].get_text.to_s,
172
+ 'ChargeId' => response.elements['ChargeId'].get_text.to_s,
173
+ 'COFId' => response.elements['CofId'].get_text.to_s
174
+ }
175
+ end
176
+ else
177
+ response
178
+ end
179
+ end
180
+
181
+ # Makes a donation and stores a COF for the given donor token
182
+ #
183
+ # Arguments:
184
+ # params: (Hash)
185
+ def make_donation_add_cof(params)
186
+ call_params = add_credentials_to_params(params)
187
+ response = nfg_soap_request('MakeDonationAddCOF', call_params, @use_sandbox)
188
+ if response.is_a? REXML::Element
189
+ if (response.elements['StatusCode'].get_text.to_s == 'Success') || ((response.elements['StatusCode'].get_text.to_s != 'Success') && response.elements['ErrorDetails'].elements['ErrorInfo'].nil?)
190
+ {
191
+ 'StatusCode' => response.elements['StatusCode'].get_text.to_s,
192
+ 'Message' => response.elements['Message'].get_text.to_s,
193
+ 'ErrorDetails' => response.elements['ErrorDetails'].get_text.to_s,
194
+ 'CallDuration' => response.elements['CallDuration'].get_text.to_s,
195
+ 'ChargeId' => response.elements['ChargeId'].get_text.to_s,
196
+ 'COFId' => response.elements['CofId'].get_text.to_s
197
+ }
198
+ else
199
+ {
200
+ 'StatusCode' => response.elements['StatusCode'].get_text.to_s,
201
+ 'Message' => response.elements['Message'].get_text.to_s,
202
+ 'ErrorDetails' => {
203
+ 'ErrorInfo' => {
204
+ 'ErrCode' => response.elements['ErrorDetails'].andand.elements.andand['ErrorInfo'].andand.elements.andand['ErrCode'].andand.get_text.andand.to_s,
205
+ 'ErrData' => response.elements['ErrorDetails'].andand.elements.andand['ErrorInfo'].andand.elements.andand['ErrData'].andand.get_text.andand.to_s
206
+ }
207
+ },
208
+ 'CallDuration' => response.elements['CallDuration'].get_text.to_s,
209
+ 'ChargeId' => response.elements['ChargeId'].get_text.to_s,
210
+ 'COFId' => response.elements['CofId'].get_text.to_s
211
+ }
212
+ end
213
+ else
214
+ response
215
+ end
216
+ end
217
+
218
+ # Retrieves the donation history for a specified donor token
219
+ #
220
+ # Arguments:
221
+ # params: (Hash)
222
+ def get_donor_donation_history(params)
223
+ call_params = add_credentials_to_params(params)
224
+ response = nfg_soap_request('GetDonorDonationHistory', call_params, @use_sandbox)
225
+ if response.is_a? REXML::Element
226
+ response_hash = {
227
+ 'StatusCode' => response.elements['StatusCode'].get_text.to_s,
228
+ 'Message' => response.elements['Message'].get_text.to_s,
229
+ 'ErrorDetails' => response.elements['ErrorDetails'].get_text.to_s,
230
+ 'CallDuration' => response.elements['CallDuration'].get_text.to_s,
231
+ 'DonorToken' => response.elements['DonorToken'].get_text.to_s
232
+ }
233
+ response_hash['Donations'] = Array.new
234
+ response.elements.each('Donations/DonationItemData') do |donation|
235
+ response_hash['Donations'] << {
236
+ 'DonationDate' => donation.elements['DonationDate'].get_text.to_s,
237
+ 'RecurType' => donation.elements['RecurType'].get_text.to_s,
238
+ 'IsTpcAddOnFee' => donation.elements['IsTpcAddOnFee'].get_text.to_s,
239
+ 'NpoName' => donation.elements['NpoName'].get_text.to_s,
240
+ 'Designation' => donation.elements['Designation'].get_text.to_s,
241
+ 'Dedication' => donation.elements['Dedication'].get_text.to_s,
242
+ 'Amount' => donation.elements['Amount'].get_text.to_s,
243
+ 'ChargeId' => donation.elements['ChargeId'].get_text.to_s,
244
+ }
245
+ end
246
+ response_hash
247
+ else
248
+ response
249
+ end
250
+ end
251
+
252
+ # Retrieves the donation history by date
253
+ #
254
+ # Arguments:
255
+ # params: (Hash)
256
+ def get_donation_report(params)
257
+ call_params = add_credentials_to_params(params)
258
+ response = nfg_soap_request('GetDonationReport', call_params, @use_sandbox)
259
+ if response.is_a? REXML::Element
260
+ response_hash = {
261
+ 'StatusCode' => response.elements['StatusCode'].get_text.to_s,
262
+ 'Message' => response.elements['Message'].get_text.to_s,
263
+ 'ErrorDetails' => response.elements['ErrorDetails'].get_text.to_s,
264
+ 'CallDuration' => response.elements['CallDuration'].get_text.to_s,
265
+ 'ReturnCount' => response.elements['ReturnCount'].get_text.to_s
266
+ }
267
+ response_hash['ReportResults'] = Array.new
268
+ response.elements.each('ReportResults/DonationReportResult') do |donation|
269
+ response_hash['ReportResults'] << {
270
+ 'Source' => donation.elements['Source'].get_text.to_s,
271
+ 'Campaign' => donation.elements['Campaign'].get_text.to_s,
272
+ 'ChargeId' => donation.elements['ChargeId'].get_text.to_s,
273
+ 'ShareWithCharity' => donation.elements['ShareWithCharity'].get_text.to_s,
274
+ 'DonorEmail' => donation.elements['DonorEmail'].get_text.to_s,
275
+ 'DonorFirstName' => donation.elements['DonorFirstName'].get_text.to_s,
276
+ 'DonorLastName' => donation.elements['DonorLastName'].get_text.to_s,
277
+ 'DonorAddress1' => donation.elements['DonorAddress1'].get_text.to_s,
278
+ 'DonorAddress2' => donation.elements['DonorAddress2'].get_text.to_s,
279
+ 'DonorCity' => donation.elements['DonorCity'].get_text.to_s,
280
+ 'DonorState' => donation.elements['DonorState'].get_text.to_s,
281
+ 'DonorZip' => donation.elements['DonorZip'].get_text.to_s,
282
+ 'DonorCountry' => donation.elements['DonorCountry'].get_text.to_s,
283
+ 'DonorPhone' => donation.elements['DonorPhone'].get_text.to_s,
284
+ 'Designation' => donation.elements['Designation'].get_text.to_s,
285
+ 'DonateInName' => donation.elements['DonateInName'].get_text.to_s,
286
+ 'RecurringPeriod' => donation.elements['RecurringPeriod'].get_text.to_s,
287
+ 'EventDate' => donation.elements['EventDate'].get_text.to_s,
288
+ 'NpoEIN' => donation.elements['NpoEIN'].get_text.to_s,
289
+ 'NpoName' => donation.elements['NpoName'].get_text.to_s,
290
+ 'ChargeStatus' => donation.elements['ChargeStatus'].get_text.to_s,
291
+ 'ChargeAmount' => donation.elements['ChargeAmount'].get_text.to_s,
292
+ 'NpoTPC' => donation.elements['NpoTPC'].get_text.to_s,
293
+ 'DonationItemAmount' => donation.elements['DonationItemAmount'].get_text.to_s,
294
+ 'TotalDonationAmount' => donation.elements['TotalDonationAmount'].get_text.to_s,
295
+ 'HistoryRecordId' => donation.elements['HistoryRecordId'].get_text.to_s
296
+ }
297
+ end
298
+ response_hash
299
+ else
300
+ response
301
+ end
302
+ end
303
+
304
+ private
305
+
306
+ # Adds client credentials to hash of parameters
307
+ #
308
+ # Arguments:
309
+ # params: (Hash)
310
+ def add_credentials_to_params(params)
311
+ return params unless params.is_a? Hash
312
+ credentials = {
313
+ 'PartnerID' => @partner_id,
314
+ 'PartnerPW' => @partner_password,
315
+ 'PartnerSource' => @partner_source,
316
+ 'PartnerCampaign' => @partner_campaign
317
+ }
318
+ credentials.merge(params)
319
+ end
320
+ end
321
+ end
@@ -0,0 +1,91 @@
1
+ require 'net/http'
2
+ require 'rexml/document'
3
+
4
+ module NFGClient
5
+ module Utils
6
+ @@nfg_urls = {
7
+ 'sandbox' => {
8
+ 'host' => 'api-sandbox.networkforgood.org',
9
+ 'url' => 'https://api-sandbox.networkforgood.org/PartnerDonationService/DonationServices.asmx',
10
+ 'wsdl' => 'https://api-sandbox.networkforgood.org/PartnerDonationService/DonationServices.asmx?wsdl'
11
+ },
12
+ 'production' => {
13
+ 'host' => 'api.networkforgood.org',
14
+ 'url' => 'https://api.networkforgood.org/PartnerDonationService/DonationServices.asmx',
15
+ 'wsdl' => 'https://api.networkforgood.org/PartnerDonationService/DonationServices.asmx?wsdl'
16
+ }
17
+ }
18
+
19
+ # Makes HTTP POST request to NFG server (sandbox or production) and returns parsed XML response.
20
+ #
21
+ # Arguments:
22
+ # nfg_method: (String)
23
+ # params: (Hash)
24
+ def nfg_soap_request(nfg_method, params, use_sandbox = false)
25
+ if (nfg_method.is_a? String) && (params.is_a? Hash)
26
+ # Build SOAP 1.2 request
27
+ soap_request = build_nfg_soap_request(nfg_method,params)
28
+
29
+ # Build request URL & header
30
+ host = use_sandbox ? @@nfg_urls['sandbox']['host'] : @@nfg_urls['production']['host']
31
+ url = use_sandbox ? @@nfg_urls['sandbox']['url'] : @@nfg_urls['production']['url']
32
+ headers = {
33
+ 'Host' => host,
34
+ 'Content-Type' => 'application/soap+xml; charset=utf-8',
35
+ 'Content-Length' => soap_request.length.to_s,
36
+ 'SOAPAction' => "#{url}/#{nfg_method}".gsub('.asmx','')
37
+ }
38
+
39
+ return_value = Hash.new
40
+
41
+ # Being HTTP Post
42
+ begin
43
+ uri = URI.parse(url)
44
+ https_conn = Net::HTTP.new(uri.host, uri.port)
45
+ https_conn.use_ssl = true
46
+ response = https_conn.post(uri.path, soap_request, headers)
47
+ if response.code == '200'
48
+ parsed = REXML::Document.new(response.body)
49
+ #return response.body
50
+ # Build return hash parsing XML response
51
+ if parsed.root.nil?
52
+ return_value['StatusCode'] = 'MissingParameter'
53
+ return_value['Message'] = response.body
54
+ return_value['ErrorDetails'] = nil
55
+ else
56
+ return_value = parsed.root.elements['soap:Body'].elements["#{nfg_method}Response"].elements["#{nfg_method}Result"]
57
+ end
58
+ else
59
+ return_value['StatusCode'] = 'UnexpectedError'
60
+ return_value['Message'] = response.message
61
+ return_value['ErrorDetails'] = response.body
62
+ end
63
+ rescue StandardError => e
64
+ return_value['StatusCode'] = 'UnexpectedError'
65
+ return_value['Message'] = e
66
+ return_value['ErrorDetails'] = e.backtrace.join(' ')
67
+ end
68
+
69
+ return_value
70
+ else
71
+ raise ArgumentError.new('http_post requires a nfg_method and a hash of params')
72
+ end
73
+ end
74
+
75
+ def build_nfg_soap_request(nfg_method, params)
76
+ get_nfg_soap_request_template.gsub('|body|',"<#{nfg_method} xmlns=\"http://api.networkforgood.org/partnerdonationservice\">#{hash_to_xml(params)}</#{nfg_method}>")
77
+ end
78
+
79
+ def get_nfg_soap_request_template
80
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap12:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\"><soap12:Body>|body|</soap12:Body></soap12:Envelope>"
81
+ end
82
+
83
+ def hash_to_xml(hash)
84
+ hash.map do |k, v|
85
+ text = (v.is_a? Hash) ? hash_to_xml(v) : v
86
+ xml_elem = (v.is_a? Hash) ? k.gsub(/(\d)/, "") : k
87
+ "<%s>%s</%s>" % [xml_elem, text, xml_elem]
88
+ end.join
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,3 @@
1
+ module NFGClient
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'nfg-client/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "nfg-client"
8
+ gem.version = NFGClient::VERSION
9
+ gem.authors = ["Antonio Ruano Cuesta"]
10
+ gem.email = ["ruanest@gmail.com"]
11
+ gem.description = %q{Client for the Network for Good SOAP API.}
12
+ gem.summary = gem.description
13
+ gem.licenses = %w(MIT)
14
+ gem.homepage = "https://github.com/aruanoc/nfg-client"
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+
21
+ gem.add_runtime_dependency 'andand', "~> 1.3.3"
22
+
23
+ gem.add_development_dependency 'rspec', '~> 2.13.0'
24
+ gem.add_development_dependency 'simplecov'
25
+ end
File without changes
@@ -0,0 +1,20 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ # This file was generated by the `rspec --init` command. Conventionally, all
5
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
6
+ # Require this file using `require "spec_helper"` to ensure that it is only
7
+ # loaded once.
8
+ #
9
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
10
+ RSpec.configure do |config|
11
+ config.treat_symbols_as_metadata_keys_with_true_values = true
12
+ config.run_all_when_everything_filtered = true
13
+ config.filter_run :focus
14
+
15
+ # Run specs in random order to surface order dependencies. If you find an
16
+ # order dependency and want to debug it, you can fix the order by providing
17
+ # the seed, which is printed after each run.
18
+ # --seed 1234
19
+ config.order = 'random'
20
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nfg-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Antonio Ruano Cuesta
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: andand
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 1.3.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 1.3.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 2.13.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 2.13.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: simplecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Client for the Network for Good SOAP API.
56
+ email:
57
+ - ruanest@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - .gitignore
63
+ - .rspec
64
+ - Gemfile
65
+ - LICENSE.md
66
+ - README.md
67
+ - Rakefile
68
+ - lib/nfg-client.rb
69
+ - lib/nfg-client/client.rb
70
+ - lib/nfg-client/utils.rb
71
+ - lib/nfg-client/version.rb
72
+ - nfg-client.gemspec
73
+ - spec/nfg_client_spec.rb
74
+ - spec/spec_helper.rb
75
+ homepage: https://github.com/aruanoc/nfg-client
76
+ licenses:
77
+ - MIT
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.2.2
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Client for the Network for Good SOAP API.
99
+ test_files:
100
+ - spec/nfg_client_spec.rb
101
+ - spec/spec_helper.rb
102
+ has_rdoc: