This repository has been archived by the owner on Jun 10, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PostalMethods.cfc
599 lines (403 loc) · 17.9 KB
/
PostalMethods.cfc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
component displayname="PostalMethods" hint="PostalMethods Web-to-Postal Web Service v2009-02-26 wrapper" {
/*
* Version 1.0 — Dec 28, 2011
* Home page: https://github.com/sgalashyn/postalmethods
* API docs: http://www.postalmethods.com/postal-api
*/
variables.apiurl.post = "https://api.postalmethods.com/2009-02-26/PostalWS.asmx/";
variables.apiurl.soap = "https://api.postalmethods.com/2009-02-26/PostalWS.asmx?WSDL";
variables.username = "";
variables.password = "";
variables.successCode = "-3000";
variables.statusCodes = {};
variables.useragent = "";
variables.verbose = false;
/*
* @username PostalMethods user name
* @password Password for the user
* @statuscodes Full path to status codes JSON file
* @useragent Custom useragent for HTTP requests
* @verbose Append extended info to the output
*/
public any function init(
required string username,
required string password,
required string statuscodes,
string useragent = server.ColdFusion.ProductName,
boolean verbose = false
)
hint="Component initialization" {
setUsername(arguments.username);
setPassword(arguments.password);
setUserAgent(arguments.useragent);
setVerbose(arguments.verbose);
variables.statusCodes = DeserializeJSON(FileRead(arguments.statuscodes));
return this;
}
/*
* INTERACTION WITH API
*/
/*
* @method API method name to invoke
* @defaults Required default request dataset
* @params Actual arguments of the invokation
* @acesspoint API access point: soap or post
* @xpath XPath query to search through returned XML
*/
private struct function invokeMethod(
required string method,
required struct defaults,
required struct params,
required string acesspoint,
string xpath = ""
)
hint="Perform request to the API: invoke remote method and handle response" {
var local = {};
local.output = {};
try {
// prepare the request arguments collection
local.args = StructCopy(arguments.defaults);
local.args.Username = getUsername();
local.args.Password = getPassword();
// override the values with actual arguments
for (local.key in arguments.params) {
if (StructKeyExists(local.args, local.key)) {
local.args[local.key] = arguments.params[local.key];
}
}
if (arguments.acesspoint EQ "soap") {
// use SOAP API for the 'action' methods
variables.service = CreateObject("webservice", variables.apiurl.soap);
// evaluate has acceptable performance for web-service invokation which is pretty slow
// possible hacking with cfinvoke wrapping would affect the library code badly
// hopefully ACF 10 will support following syntax, Railo does this already:
// local.result = variables.service[arguments.method](argumentCollection = local.args);
local.result = Evaluate("variables.service.#arguments.method#(argumentCollection = local.args)");
if (getVerbose()) {
local.output.result = local.result;
}
if (isNumeric(local.result)) {
if (local.result GT 0 OR local.result EQ variables.successCode) {
local.output.data = local.result;
local.output.fault = false;
}
else if (StructKeyExists(getStatusCodes(), local.result)) {
throw(message=getStatusMessage(local.result), detail=getStatusDetail(local.result));
}
else {
throw(message="Unknown error code: #local.result#");
}
}
else {
local.output.data = local.result;
local.output.fault = false;
}
}
else {
// use HTTP POST API for GetXXX methods
// Note: Railo (specifically, its Apache Axis) does not process
// some requests properly, plus XML response is easier to handle
local.service = new http(
url = "#variables.apiurl.post##arguments.method#",
method = "post",
useragent = getUserAgent()
);
for (local.key in local.args) {
local.service.addParam(type="formfield", name=local.key, value=local.args[local.key]);
}
local.result = local.service.send().getPrefix();
if (getVerbose()) {
local.output.result = local.result;
}
if (local.result.responseheader.status_code EQ 200) {
// response can be XML or plain text
if (isXML(local.result.filecontent)) {
local.parsedXml = XMLParse(local.result.filecontent);
// ResultCode is only common node, other depend on method (duh!)
// codes are in status-codes.json or http://www.postalmethods.com/statuscodes
local.resultCode = XMLSearch(local.parsedXml, "string(//:ResultCode)");
if (local.resultCode EQ variables.successCode) {
// xpath comes from parent method
local.nodes = XMLSearch(local.parsedXml, arguments.xpath);
if (ListFind("GetUploadedFileDetails,GetStatus,GetDetails,GetDetailsExtended", arguments.method)) {
// XML to array of structures
local.result = [];
for (local.idx=1; local.idx LTE ArrayLen(local.nodes); local.idx++) {
local.children = XMLSearch(local.nodes[local.idx], "*");
local.values = {};
for (local.indx=1; local.indx LTE ArrayLen(local.children); local.indx++) {
local.values[ local.children[local.indx].xmlName ] = local.children[local.indx].XmlText;
}
ArrayAppend(local.result, local.values);
}
}
else {
// XML to structure
local.result = {};
for (local.idx=1; local.idx LTE ArrayLen(local.nodes); local.idx++) {
local.result[ local.nodes[local.idx].XmlName ] = local.nodes[local.idx].XmlText;
}
}
local.output.data = local.result;
local.output.fault = false;
}
else if (StructKeyExists(getStatusCodes(), local.resultCode)) {
throw(message=getStatusMessage(local.resultCode), detail=getStatusDetail(local.resultCode));
}
else {
throw(message="Unknown error code: #local.resultCode#");
}
}
else {
local.output.data = local.result.filecontent;
local.output.fault = false;
}
}
else {
throw(message=local.result.filecontent, detail=local.result.errordetail);
}
}
}
catch (any exception) {
local.output.fault = true;
local.output.data = exception.Message;
if (getVerbose()) {
local.output.exception = exception;
}
}
return local.output;
}
/*
* API METHODS
*/
/*
* @FilePath Optional path to the document/template to upload with letter [custom helper]
*/
public struct function SendLetter(string FilePath = "")
hint="The SendLetter() method is the simplest way to send a letter through the PostalMethods service." {
var defaults = {
MyDescription = "",
FileExtension = "",
FileBinaryData = "",
WorkMode = "Default"
};
if (FileExists(arguments.FilePath)) {
defaults.FileExtension = ListLast(arguments.FilePath, '.');
defaults.FileBinaryData = BinaryEncode(FileReadBinary(arguments.FilePath), "Base64");
}
return invokeMethod("SendLetter", defaults, arguments, "soap");
}
/*
* @FilePath Optional path to the document/template to upload with letter [custom helper]
*/
public struct function SendLetterAndAddress(string FilePath = "")
hint="The SendLetterAndAddress() method is the simplest way to send a letter through the PostalMethods service." {
var defaults = {
MyDescription = "",
FileExtension = "",
FileBinaryData = "",
WorkMode = "Default",
AttentionLine1 = "",
AttentionLine2 = "",
Company = "",
Address1 = "",
Address2 = "",
City = "",
State = "",
PostalCode = "",
Country = ""
};
if (FileExists(arguments.FilePath)) {
defaults.FileExtension = ListLast(arguments.FilePath, '.');
defaults.FileBinaryData = BinaryEncode(FileReadBinary(arguments.FilePath), "Base64");
}
return invokeMethod("SendLetterAndAddress", defaults, arguments, "soap");
}
/*
* @ImageSideFilePath Optional path to the file to use for image side [custom helper]
* @AddressSideFilePath Optional path to the document/template to use for address side [custom helper]
*/
public struct function SendPostcardAndAddress(
string ImageSideFilePath = "",
string AddressSideFilePath = ""
)
hint="The SendPostcardAndAddress() method is the simplest way to send postcards." {
var defaults = {
MyDescription = "",
ImageSideFileType = "",
ImageSideBinaryData = "",
ImageSideScaling = "Default",
AddressSideFileType = "",
AddressSideBinaryData = "",
WorkMode = "Default",
PrintColor = "Default",
PostcardSize = "Default",
MailingPriority = "Default",
AttentionLine1 = "",
AttentionLine2 = "",
Company = "",
Address1 = "",
Address2 = "",
City = "",
State = "",
PostalCode = "",
Country = ""
};
if (FileExists(arguments.ImageSideFilePath)) {
defaults.ImageSideFileType = ListLast(arguments.ImageSideFilePath, '.');
defaults.ImageSideBinaryData = BinaryEncode(FileReadBinary(arguments.ImageSideFilePath), "Base64");
}
if (FileExists(arguments.AddressSideFilePath)) {
defaults.AddressSideFileType = ListLast(arguments.AddressSideFilePath, '.');
defaults.AddressSideBinaryData = BinaryEncode(FileReadBinary(arguments.AddressSideFilePath), "Base64");
}
return invokeMethod("SendPostcardAndAddress", defaults, arguments, "soap");
}
/*
* @ID See below ↓
* Single Item: Matches the ID provided as the response for the original Web Service request.
* Multiple Items: ID1,ID2,ID3. Response is provided for up to 1000 letters per query.
Additional items are ignored. Items not assigned to the account or which the user
has no permission to access are returned with a "No Permissions" status code.
* Range Of Items: LowerID-HigherID. Response is provided for up to 1000 letters per query.
Additional items are ignored. Items not assigned to the account or which the user
has no permission to access will not be returned.
*/
public struct function GetStatus(required string ID)
hint="The GetStatus() method is the way to get status mailer reports." {
var defaults = {
ID = arguments.ID
};
return invokeMethod("GetStatus", defaults, arguments, "post", "//:LetterStatusAndDesc");
}
/*
* @ID See GetStatus
*/
public struct function GetDetails(required string ID)
hint="The GetDetails() method is a way to get detailed mailer reports." {
var defaults = {
ID = arguments.ID
};
return invokeMethod("GetDetails", defaults, arguments, "post", "//:Details/:Details");
}
/*
* @ID See GetStatus
*/
public struct function GetDetailsExtended(required string ID)
hint="The GetDetailsExtended() method is a way to get the full mailer details." {
var defaults = {
ID = arguments.ID
};
return invokeMethod("GetDetailsExtended", defaults, arguments, "post", "//:ExtendedDetails");
}
/*
* @ID Matches the ID provided as the response to the original Web Service request.
* @FilePath Optional path to the save the fetched PDF instead of returing binary data [custom helper]
*/
public struct function GetPDF(required numeric ID, string FilePath = "")
hint="The GetPDF() method is a way to get the PDF file used for printing the mailer as a binary file." {
var defaults = {
ID = arguments.ID
};
var res = invokeMethod("GetPDF", defaults, arguments, "post", "//:FileData");
if (arguments.FilePath NEQ "") {
FileWrite(arguments.FilePath, BinaryDecode(res.data.FileData, "Base64"));
StructDelete(res.data, "FileData");
return res;
}
else {
return res;
}
}
/*
* @ID Matches the ID provided as the response to the original Web Service request.
*/
public struct function CancelDelivery(required numeric ID)
hint="The CancelDelivery() method is a way to cancel fulfillment of a mailer still not delivered to the postal service." {
var defaults = {
ID = arguments.ID
};
return invokeMethod("CancelDelivery", defaults, arguments, "soap");
}
/*
* @FilePath Optional path to the document/template to upload [custom helper]
*/
public struct function UploadFile(string FilePath = "")
hint="The UploadFile() method is used to upload files for later usage." {
var defaults = {
MyFileName = "",
FileBinaryData = "",
Permissions = "Account",
Description = "",
Overwrite = false
};
if (FileExists(arguments.FilePath)) {
defaults.MyFileName = GetFileFromPath(arguments.FilePath);
defaults.FileBinaryData = BinaryEncode(FileReadBinary(arguments.FilePath), "Base64");
}
return invokeMethod("UploadFile", defaults, arguments, "soap");
}
/*
* @MyFileName File name to delete, as provided in the UploadFile request
*/
public struct function DeleteUploadedFile(required string MyFileName)
hint="The DeleteUploadedFile() method is used to delete files from your storage." {
var defaults = {
MyFileName = arguments.MyFileName
};
return invokeMethod("DeleteUploadedFile", defaults, arguments, "soap");
}
public struct function GetUploadedFileDetails()
hint="The GetUploadedFileDetails() method is used to show account files in storage." {
var defaults = {};
return invokeMethod("GetUploadedFileDetails", defaults, arguments, "post", "//:UploadedFiles/:FileDetails");
}
/*
* HELPERS
*/
public void function setUsername(required string username) hint="Set current username setting" {
variables.username = arguments.username;
}
public string function getUsername() hint="Get current username setting" {
return variables.username;
}
public void function setPassword(required string password) hint="Set current password setting" {
variables.password = arguments.password;
}
public string function getPassword() hint="Get current password setting" {
return variables.password;
}
public void function setUserAgent(required string useragent) hint="Set current useragent setting" {
variables.useragent = arguments.useragent;
}
public string function getUserAgent() hint="Get current useragent setting" {
return variables.useragent;
}
/*
* @acesspoint API access point: soap or post
*/
public string function getApiUrl(required string acesspoint) hint="Get target API URL by access point" {
return variables.apiurl[arguments.acesspoint];
}
public void function setVerbose(required boolean verbose) hint="Set current verbose setting" {
variables.verbose = arguments.verbose;
}
public boolean function getVerbose() hint="Get current verbose setting" {
return variables.verbose;
}
public struct function getStatusCodes() hint="Get current statusCodes setting" {
return variables.statusCodes;
}
/*
* @code Result code for this Web Service request.
*/
public string function getStatusMessage(required string code) hint="Get message string for given status code" {
return variables.statusCodes[arguments.code].message;
}
/*
* @code Result code for this Web Service request.
*/
public string function getStatusDetail(required string code) hint="Get detail string for given status code" {
return variables.statusCodes[arguments.code].detail;
}
}