-
Notifications
You must be signed in to change notification settings - Fork 10
/
clicksend_pkg.pkb
1229 lines (973 loc) · 38.6 KB
/
clicksend_pkg.pkb
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
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
create or replace package body clicksend_pkg as
/* Clicksend API v0.3
https://github.com/jeffreykemp/clicksend-plsql-api
by Jeffrey Kemp
*/
queue_name constant varchar2(500) := sys_context('userenv','current_schema')||'.clicksend_queue';
queue_table constant varchar2(500) := sys_context('userenv','current_schema')||'.clicksend_queue_tab';
job_name constant varchar2(30) := 'clicksend_process_queue';
purge_job_name constant varchar2(30) := 'clicksend_purge_logs';
payload_type constant varchar2(500) := sys_context('userenv','current_schema')||'.t_clicksend_msg';
max_dequeue_count constant integer := 1000; -- max messages processed by push_queue in one go
-- defaults to use if init() not used to set these settings
default_country constant varchar2(10) := 'AU';
default_api_url constant varchar2(200) := 'https://rest.clicksend.com/v3/';
default_voice_lang constant varchar2(10) := 'en-au'; -- aussie
default_voice_gender constant varchar2(6) := 'female';
default_voice_preamble constant varchar2(500) := '.....'; -- add a pause at the start
default_log_retention_days constant number := 30;
default_queue_expiration constant integer := 24 * 60 * 60; -- failed messages expire from the queue after 24 hours
-- message types
message_type_sms constant varchar2(20) := 'sms';
message_type_mms constant varchar2(20) := 'mms';
message_type_voice constant varchar2(20) := 'voice';
-- setting names
setting_clicksend_username constant varchar2(100) := 'clicksend_username';
setting_clicksend_secret_key constant varchar2(100) := 'clicksend_secret_key';
setting_api_url constant varchar2(100) := 'api_url';
setting_wallet_path constant varchar2(100) := 'wallet_path';
setting_wallet_password constant varchar2(100) := 'wallet_password';
setting_log_retention_days constant varchar2(100) := 'log_retention_days';
setting_default_sender constant varchar2(100) := 'default_sender';
setting_default_country constant varchar2(100) := 'default_country';
setting_default_voice_lang constant varchar2(100) := 'default_voice_lang';
setting_default_voice_gender constant varchar2(100) := 'default_voice_gender';
setting_voice_preamble constant varchar2(100) := 'voice_preamble';
setting_queue_expiration constant varchar2(100) := 'queue_expiration';
setting_prod_instance_name constant varchar2(100) := 'prod_instance_name';
setting_non_prod_recipient constant varchar2(100) := 'non_prod_recipient';
type t_key_val_arr is table of varchar2(4000) index by varchar2(100);
g_setting t_key_val_arr;
e_no_queue_data exception;
pragma exception_init (e_no_queue_data, -25228);
--------------------------------------------------------------------------------
--------------------------------- PRIVATE METHODS ------------------------------
--------------------------------------------------------------------------------
procedure assert (cond in boolean, err in varchar2) is
begin
if not cond then
raise_application_error(-20000, $$plsql_unit || ' assertion failed: ' || err);
end if;
end assert;
-- set or update a setting
procedure set_setting
(p_name in varchar2
,p_value in varchar2
) is
begin
assert(p_name is not null, 'p_name cannot be null');
merge into clicksend_settings t
using (select p_name as setting_name
,p_value as setting_value
from dual) s
on (t.setting_name = s.setting_name)
when matched then
update set t.setting_value = s.setting_value
when not matched then
insert (setting_name, setting_value)
values (s.setting_name, s.setting_value);
commit;
end set_setting;
-- retrieve all the settings for a normal session
procedure load_settings is
begin
-- set defaults first
g_setting(setting_api_url) := default_api_url;
g_setting(setting_wallet_path) := '';
g_setting(setting_wallet_password) := '';
g_setting(setting_log_retention_days) := default_log_retention_days;
g_setting(setting_default_sender) := '';
g_setting(setting_default_country) := default_country;
g_setting(setting_default_voice_lang) := default_voice_lang;
g_setting(setting_default_voice_gender) := default_voice_gender;
g_setting(setting_voice_preamble) := default_voice_preamble;
g_setting(setting_queue_expiration) := default_queue_expiration;
g_setting(setting_prod_instance_name) := '';
g_setting(setting_non_prod_recipient) := '';
for r in (
select s.setting_name
,s.setting_value
from clicksend_settings s
) loop
g_setting(r.setting_name) := r.setting_value;
end loop;
end load_settings;
procedure reset is
begin
g_setting.delete;
end reset;
-- get a setting
-- if p_default is set, a null/not found will return the default value
-- if p_default is null, a not found will raise an exception
function setting (p_name in varchar2) return varchar2 is
p_value clicksend_settings.setting_value%type;
begin
assert(p_name is not null, 'p_name cannot be null');
-- prime the settings array for this session
if g_setting.count = 0 then
load_settings;
end if;
p_value := g_setting(p_name);
return p_value;
exception
when no_data_found then
raise_application_error(-20000, 'clicksend setting not set "' || p_name || '" - please setup using ' || $$plsql_unit || '.init()');
end setting;
function log_retention_days return number is
begin
return to_number(setting(setting_log_retention_days));
end log_retention_days;
procedure prod_check
(p_is_prod out boolean
,p_non_prod_recipient out varchar2
) is
prod_instance_name clicksend_settings.setting_value%type;
begin
prod_instance_name := setting(setting_prod_instance_name);
if prod_instance_name is not null then
p_is_prod := (prod_instance_name = sys_context('userenv','db_name'));
else
p_is_prod := true; -- if setting not set, we treat this as a prod env
end if;
if not p_is_prod then
p_non_prod_recipient := setting(setting_non_prod_recipient);
end if;
end prod_check;
procedure set_wallet is
wallet_path varchar2(4000);
wallet_password varchar2(4000);
begin
wallet_path := setting(setting_wallet_path);
wallet_password := setting(setting_wallet_password);
if wallet_path is not null or wallet_password is not null then
sys.utl_http.set_wallet(wallet_path, wallet_password);
end if;
end set_wallet;
function get_response (resp in out nocopy sys.utl_http.resp) return clob is
buf varchar2(32767);
ret clob := empty_clob;
begin
sys.dbms_lob.createtemporary(ret, true);
begin
loop
sys.utl_http.read_text(resp, buf, 32767);
sys.dbms_lob.writeappend(ret, length(buf), buf);
end loop;
exception
when sys.utl_http.end_of_body then
null;
end;
sys.utl_http.end_response(resp);
return ret;
end get_response;
function get_json
(p_url in varchar2
,p_method in varchar2
,p_params in varchar2 := null
,p_data in varchar2 := null
,p_user in varchar2 := null
,p_pwd in varchar2 := null
,p_accept in varchar2 := null
) return clob is
url varchar2(4000) := p_url;
req sys.utl_http.req;
resp sys.utl_http.resp;
ret clob;
begin
assert(p_url is not null, 'get_json: p_url cannot be null');
set_wallet;
if p_params is not null then
url := url || '?' || p_params;
end if;
req := sys.utl_http.begin_request(url => url, method => p_method);
if p_user is not null or p_pwd is not null then
sys.utl_http.set_authentication(req, p_user, p_pwd);
end if;
if p_data is not null then
sys.utl_http.set_header (req,'Content-Type','application/json');
sys.utl_http.set_header (req,'Content-Length',length(p_data));
sys.utl_http.write_text (req,p_data);
end if;
if p_accept is not null then
sys.utl_http.set_header (req,'Accept',p_accept);
end if;
resp := sys.utl_http.get_response(req);
if resp.status_code != '200' then
raise_application_error(-20000, 'get_json call failed ' || resp.status_code || ' ' || resp.reason_phrase || ' [' || url || ']');
end if;
ret := get_response(resp);
return ret;
end get_json;
function get_epoch (p_date in date) return number as
date_utc date;
begin
date_utc := sys_extract_utc(cast(p_date as timestamp));
return trunc((date_utc - date'1970-01-01') * 24 * 60 * 60);
end get_epoch;
function epoch_to_dt (p_epoch in number) return date as
begin
return date'1970-01-01' + (p_epoch / 24 / 60 / 60)
+ (systimestamp-sys_extract_utc(systimestamp));
end epoch_to_dt;
procedure url_param (buf in out varchar2, attr in varchar2, val in varchar2) is
begin
if val is not null then
if buf is not null then
buf := buf || '&';
end if;
buf := buf || attr || '=' || apex_util.url_encode(val);
end if;
end url_param;
procedure url_param (buf in out varchar2, attr in varchar2, dt in date) is
begin
if dt is not null then
if buf is not null then
buf := buf || '&';
end if;
buf := buf || attr || '=' || get_epoch(dt);
end if;
end url_param;
procedure send_msg (p_payload in out nocopy t_clicksend_msg) as
is_prod boolean;
non_prod_recipient varchar2(255);
recipient varchar2(255);
payload varchar2(32767);
resp_text varchar2(32767);
procedure log_response is
-- needs to commit the log entry independently of calling transaction
pragma autonomous_transaction;
log clicksend_msg_log%rowtype;
begin
log.sent_ts := systimestamp;
log.message_type := p_payload.message_type;
log.requested_ts := p_payload.requested_ts;
log.schedule_dt := p_payload.schedule_dt;
log.sender := p_payload.sender;
log.recipient := p_payload.recipient;
log.subject := p_payload.subject;
log.message := p_payload.message;
log.media_file := p_payload.media_file;
log.voice_lang := p_payload.voice_lang;
log.voice_gender := p_payload.voice_gender;
log.country := p_payload.country;
log.reply_email := p_payload.reply_email;
log.custom_string := p_payload.custom_string;
log.clicksend_response := substr(resp_text, 1, 4000);
begin
apex_json.parse(resp_text);
log.clicksend_messageid := apex_json.get_varchar2('data.messages[1].message_id');
log.clicksend_result := apex_json.get_number('http_code');
log.clicksend_errortext := apex_json.get_varchar2('response_code');
log.clicksend_cost := apex_json.get_number('data.total_price');
exception
when others then
-- log the parse problem but don't stop the logging
sys.dbms_output.put_line(SQLERRM);
sys.dbms_output.put_line(resp_text);
end;
insert into clicksend_msg_log values log;
commit;
end log_response;
begin
assert(p_payload.message_type in (message_type_sms, message_type_mms, message_type_voice)
,'message_type must be sms, mms or voice');
prod_check
(p_is_prod => is_prod
,p_non_prod_recipient => non_prod_recipient
);
if not is_prod and non_prod_recipient is not null then
-- replace recipient with the non-prod recipient
recipient := non_prod_recipient;
else
recipient := p_payload.recipient;
end if;
begin
apex_json.initialize_clob_output;
apex_json.open_object;
if p_payload.media_file is not null then
apex_json.write('media_file', p_payload.media_file);
end if;
apex_json.open_array('messages');
apex_json.open_object;
apex_json.write('source', 'oracle');
if p_payload.message_type in (message_type_sms, message_type_mms) then
apex_json.write('from', p_payload.sender);
end if;
if p_payload.message_type = message_type_mms then
apex_json.write('subject', p_payload.subject);
end if;
apex_json.write('body', p_payload.message);
apex_json.write('to', recipient);
if p_payload.message_type = message_type_voice then
apex_json.write('lang', p_payload.voice_lang);
apex_json.write('voice', p_payload.voice_gender);
end if;
if p_payload.schedule_dt is not null then
apex_json.write('schedule', get_epoch(p_payload.schedule_dt));
end if;
if p_payload.custom_string is not null then
apex_json.write('custom_string', p_payload.custom_string);
end if;
if p_payload.country is not null then
apex_json.write('country', p_payload.country);
end if;
if p_payload.reply_email is not null then
apex_json.write('from_email', p_payload.reply_email);
end if;
apex_json.close_all;
payload := apex_json.get_clob_output;
apex_json.free_output;
exception
when others then
apex_json.free_output;
raise;
end;
if is_prod or non_prod_recipient is not null then
resp_text := get_json
(p_url => setting(setting_api_url) || p_payload.message_type || '/send'
,p_method => 'POST'
,p_data => payload
,p_user => setting(setting_clicksend_username)
,p_pwd => setting(setting_clicksend_secret_key)
);
else
resp_text := 'message suppressed: ' || sys_context('userenv','db_name');
end if;
log_response;
end send_msg;
-- convert '0408123456' to '+61408123456'
function local_to_intnl_au
(p_mobile in varchar2
,p_country in varchar2
) return varchar2 is
ret varchar2(20) := substr(p_mobile, 1, 20);
begin
if substr(ret, 1, 1) != '+'
and p_country = 'AU' then
ret := '+61' || substr(ret, 2);
end if;
return ret;
end local_to_intnl_au;
-- comma-delimited list of attributes, plus values if required
function json_members_csv
(p_path in varchar2
,p0 in varchar2
,p_values in boolean
) return varchar2 is
arr wwv_flow_t_varchar2;
buf varchar2(32767);
begin
arr := apex_json.get_members(p_path, p0);
if arr.count > 0 then
for i in 1..arr.count loop
if buf is not null then
buf := buf || ',';
end if;
buf := buf || arr(i);
if p_values then
buf := buf || '=' || apex_json.get_varchar2(p_path || '.' || arr(i), p0);
end if;
end loop;
end if;
return buf;
exception
when value_error /*not an array or object*/ then
return null;
end json_members_csv;
--------------------------------------------------------------------------------
--------------------------------- PUBLIC METHODS ------------------------------
--------------------------------------------------------------------------------
procedure init
(p_clicksend_username in varchar2 := default_no_change
,p_clicksend_secret_key in varchar2 := default_no_change
,p_api_url in varchar2 := default_no_change
,p_wallet_path in varchar2 := default_no_change
,p_wallet_password in varchar2 := default_no_change
,p_default_sender in varchar2 := default_no_change
,p_default_country in varchar2 := default_no_change
,p_default_voice_lang in varchar2 := default_no_change
,p_default_voice_gender in varchar2 := default_no_change
,p_voice_preamble in varchar2 := default_no_change
,p_log_retention_days in number := null
,p_queue_expiration in number := null
,p_prod_instance_name in varchar2 := default_no_change
,p_non_prod_recipient in varchar2 := default_no_change
) is
begin
if nvl(p_clicksend_username,'*') != default_no_change then
set_setting(setting_clicksend_username, p_clicksend_username);
end if;
if nvl(p_clicksend_secret_key,'*') != default_no_change then
set_setting(setting_clicksend_secret_key, p_clicksend_secret_key);
end if;
if nvl(p_api_url,'*') != default_no_change then
-- make sure the url ends with a /
set_setting(setting_api_url, p_api_url
|| case when substr(p_api_url,-1,1) != '/' then '/' end);
end if;
if nvl(p_wallet_path,'*') != default_no_change then
set_setting(setting_wallet_path, p_wallet_path);
end if;
if nvl(p_wallet_password,'*') != default_no_change then
set_setting(setting_wallet_password, p_wallet_password);
end if;
if nvl(p_default_sender,'*') != default_no_change then
set_setting(setting_default_sender, p_default_sender);
end if;
if nvl(p_default_country,'*') != default_no_change then
set_setting(setting_default_country, p_default_country);
end if;
if nvl(p_default_voice_lang,'*') != default_no_change then
set_setting(setting_default_voice_lang, p_default_voice_lang);
end if;
if nvl(p_default_voice_gender,'*') != default_no_change then
set_setting(setting_default_voice_gender, p_default_voice_gender);
end if;
if nvl(p_voice_preamble,'*') != default_no_change then
set_setting(setting_voice_preamble, p_voice_preamble);
end if;
if p_log_retention_days is not null then
set_setting(setting_log_retention_days, p_log_retention_days);
end if;
if p_queue_expiration is not null then
set_setting(setting_queue_expiration, p_queue_expiration);
end if;
if nvl(p_prod_instance_name,'*') != default_no_change then
set_setting(setting_prod_instance_name, p_prod_instance_name);
end if;
if nvl(p_non_prod_recipient,'*') != default_no_change then
set_setting(setting_non_prod_recipient, p_non_prod_recipient);
end if;
end init;
procedure send_sms
(p_mobile in varchar2
,p_message in varchar2
,p_sender in varchar2 := null
,p_schedule_dt in date := null -- default is ASAP
,p_country in varchar2 := null
,p_reply_email in varchar2 := null
,p_custom_string in varchar2 := null
,p_priority in number := default_priority -- lower numbers are processed first
) is
enq_opts sys.dbms_aq.enqueue_options_t;
enq_msg_props sys.dbms_aq.message_properties_t;
payload t_clicksend_msg;
msgid raw(16);
sender varchar2(100);
country varchar2(10);
begin
reset;
assert(p_mobile is not null, 'p_mobile cannot be null');
if substr(p_mobile, 1, 1) = '+' then
assert(length(p_mobile) = 12, 'mobile starting with + must be 12 characters exactly (' || p_mobile || ')');
assert(replace(translate(substr(p_mobile,2),'0123456789','-'),'-','') is null, 'mobile starting with + must have 11 digits (' || p_mobile || ')');
else
assert(length(p_mobile) = 10, 'mobile must have 10 digits exactly (' || p_mobile || ') (unless it starts with a +)');
assert(replace(translate(p_mobile,'0123456789','-'),'-','') is null, 'mobile must be 10 digits (' || p_mobile || ') (unless it starts with a +)');
end if;
country := nvl(p_country, setting(setting_default_country));
if country = 'AU' then
assert(substr(p_mobile, 1, 2) in ('04', '05') or substr(p_mobile, 1, 4) in ('+614', '+615'), 'AU mobile must start with 04 or 05 (or +614 or +615)');
end if;
assert(p_message is not null, 'p_message cannot be null');
assert(length(p_message) <= 960, 'maximum message length is 960 (' || length(p_message) || ')');
sender := nvl(p_sender, setting(setting_default_sender));
assert(sender is not null, 'sender cannot be null');
assert(length(sender) <= 11, 'sender cannot be >11 characters (' || sender || ')');
assert(length(p_reply_email) <= 255, 'p_reply_email cannot be >255 characters');
assert(length(p_custom_string) <= 4000, 'p_custom_string cannot be >4000 characters');
payload := t_clicksend_msg
(message_type => message_type_sms
,requested_ts => systimestamp
,schedule_dt => p_schedule_dt
,sender => sender
,recipient => local_to_intnl_au(p_mobile, country)
,subject => ''
,message => p_message
,media_file => ''
,voice_lang => ''
,voice_gender => ''
,country => country
,reply_email => p_reply_email
,custom_string => p_custom_string
);
enq_msg_props.expiration := setting(setting_queue_expiration);
enq_msg_props.priority := p_priority;
sys.dbms_aq.enqueue
(queue_name => queue_name
,enqueue_options => enq_opts
,message_properties => enq_msg_props
,payload => payload
,msgid => msgid
);
end send_sms;
procedure send_mms
(p_mobile in varchar2
,p_subject in varchar2
,p_message in varchar2
,p_media_file_url in varchar2
,p_sender in varchar2 := null
,p_schedule_dt in date := null -- default is ASAP
,p_country in varchar2 := null
,p_reply_email in varchar2 := null
,p_custom_string in varchar2 := null
,p_priority in number := default_priority -- lower numbers are processed first
) is
enq_opts sys.dbms_aq.enqueue_options_t;
enq_msg_props sys.dbms_aq.message_properties_t;
payload t_clicksend_msg;
msgid raw(16);
sender varchar2(100);
country varchar2(10);
begin
reset;
assert(p_mobile is not null, 'p_mobile cannot be null');
assert(p_subject is not null, 'p_subject cannot be null');
assert(length(p_subject) <= 20, 'maximum subject length is 20 (' || length(p_subject) || ')');
assert(p_media_file_url is not null, 'p_media_file_url cannot be null');
if substr(p_mobile, 1, 1) = '+' then
assert(length(p_mobile) = 12, 'mobile starting with + must be 12 characters exactly (' || p_mobile || ')');
assert(replace(translate(substr(p_mobile,2),'0123456789','-'),'-','') is null, 'mobile starting with + must have 11 digits (' || p_mobile || ')');
else
assert(length(p_mobile) = 10, 'mobile must have 10 digits exactly (' || p_mobile || ') (unless it starts with a +)');
assert(replace(translate(p_mobile,'0123456789','-'),'-','') is null, 'mobile must be 10 digits (' || p_mobile || ') (unless it starts with a +)');
end if;
country := nvl(p_country, setting(setting_default_country));
if country = 'AU' then
assert(substr(p_mobile, 1, 2) in ('04', '05') or substr(p_mobile, 1, 4) in ('+614', '+615'), 'AU mobile must start with 04 or 05 (or +614 or +615)');
end if;
assert(p_message is not null, 'p_message cannot be null');
assert(length(p_message) <= 1500, 'maximum message length is 1500 (' || length(p_message) || ')');
sender := nvl(p_sender, setting(setting_default_sender));
assert(sender is not null, 'sender cannot be null');
assert(length(sender) <= 11, 'sender cannot be >11 characters (' || sender || ')');
assert(length(p_reply_email) <= 255, 'p_reply_email cannot be >255 characters');
assert(length(p_custom_string) <= 4000, 'p_custom_string cannot be >4000 characters');
payload := t_clicksend_msg
(message_type => message_type_mms
,requested_ts => systimestamp
,schedule_dt => p_schedule_dt
,sender => sender
,recipient => local_to_intnl_au(p_mobile, country)
,subject => p_subject
,message => p_message
,media_file => p_media_file_url
,voice_lang => ''
,voice_gender => ''
,country => country
,reply_email => p_reply_email
,custom_string => p_custom_string
);
enq_msg_props.expiration := setting(setting_queue_expiration);
enq_msg_props.priority := p_priority;
sys.dbms_aq.enqueue
(queue_name => queue_name
,enqueue_options => enq_opts
,message_properties => enq_msg_props
,payload => payload
,msgid => msgid
);
end send_mms;
procedure send_voice
(p_phone_no in varchar2
,p_message in varchar2
,p_voice_lang in varchar2 := null
,p_voice_gender in varchar2 := null
,p_schedule_dt in date := null -- default is ASAP
,p_country in varchar2 := null
,p_custom_string in varchar2 := null
,p_priority in number := default_priority -- lower numbers are processed first
) is
enq_opts sys.dbms_aq.enqueue_options_t;
enq_msg_props sys.dbms_aq.message_properties_t;
payload t_clicksend_msg;
msgid raw(16);
message varchar2(4000);
voice_lang varchar2(100);
voice_gender varchar2(6);
country varchar2(10);
begin
reset;
assert(p_phone_no is not null, 'p_phone_no cannot be null');
if substr(p_phone_no, 1, 1) = '+' then
assert(length(p_phone_no) = 12, 'phone_no starting with + must be 12 characters exactly (' || p_phone_no || ')');
assert(replace(translate(substr(p_phone_no,2),'0123456789','-'),'-','') is null, 'phone_no starting with + must have 11 digits (' || p_phone_no || ')');
else
assert(length(p_phone_no) = 10, 'phone_no must have 10 digits exactly (' || p_phone_no || ') (unless it starts with a +)');
assert(replace(translate(p_phone_no,'0123456789','-'),'-','') is null, 'phone_no must be 10 digits (' || p_phone_no || ') (unless it starts with a +)');
end if;
country := nvl(p_country, setting(setting_default_country));
assert(p_message is not null, 'p_message cannot be null');
message := substr(setting(setting_voice_preamble) || p_message, 1, 4000);
assert(length(message) <= 1200, 'maximum message length is 1200 (' || length(message) || ') including preamble');
voice_lang := nvl(p_voice_lang, setting(setting_default_voice_lang));
assert(voice_lang is not null, 'voice_lang cannot be null');
voice_gender := nvl(p_voice_gender, setting(setting_default_voice_gender));
assert(voice_gender in ('female','male'), 'voice_gender must be female or male');
assert(length(p_custom_string) <= 4000, 'p_custom_string cannot be >4000 characters');
payload := t_clicksend_msg
(message_type => message_type_voice
,requested_ts => systimestamp
,schedule_dt => p_schedule_dt
,sender => ''
,recipient => p_phone_no
,subject => ''
,message => message
,media_file => ''
,voice_lang => voice_lang
,voice_gender => voice_gender
,country => country
,reply_email => ''
,custom_string => p_custom_string
);
enq_msg_props.expiration := setting(setting_queue_expiration);
enq_msg_props.priority := p_priority;
sys.dbms_aq.enqueue
(queue_name => queue_name
,enqueue_options => enq_opts
,message_properties => enq_msg_props
,payload => payload
,msgid => msgid
);
end send_voice;
function get_account_details return varchar2 is
pragma autonomous_transaction;
v_json varchar2(32767);
begin
reset;
v_json := get_json
(p_url => setting(setting_api_url) || 'account'
,p_method => 'GET'
,p_user => setting(setting_clicksend_username)
,p_pwd => setting(setting_clicksend_secret_key)
,p_accept => 'application/json'
);
return v_json;
end get_account_details;
function get_credit_balance return number is
v_json varchar2(4000);
v_bal varchar2(4000);
begin
v_json := get_account_details;
apex_json.parse(v_json);
v_bal := apex_json.get_varchar2('data.balance');
return to_number(v_bal);
end get_credit_balance;
function get_languages return t_clicksend_lang_arr pipelined is
v_json varchar2(32767);
data_count number;
gender apex_json.t_value;
gender1 varchar2(10);
gender2 varchar2(10);
begin
v_json := get_json
(p_url => setting(setting_api_url) || 'voice/lang'
,p_method => 'GET'
,p_user => setting(setting_clicksend_username)
,p_pwd => setting(setting_clicksend_secret_key)
,p_accept => 'application/json'
);
apex_json.parse(v_json);
data_count := apex_json.get_count('data');
if data_count > 0 then
for i in 1..data_count loop
gender1 := null;
gender2 := null;
gender := apex_json.get_value('data[%d].gender', i);
-- perversely, the gender node might be a simple value (e.g. "gender":"female")
-- or it might be an array (e.g. "gender":["female","male"])
if gender.kind = apex_json.c_varchar2 then
gender1 := gender.varchar2_value;
elsif gender.kind = apex_json.c_array then
gender1 := apex_json.get_varchar2('data[%d].gender[1]', i);
gender2 := apex_json.get_varchar2('data[%d].gender[2]', i);
end if;
pipe row (t_clicksend_lang
(lang_code => substr(apex_json.get_varchar2('data[%d].code', i), 1, 10)
,country_desc => substr(apex_json.get_varchar2('data[%d].country', i), 1, 100)
,female => case when voice_female in (gender1,gender2) then 'Y' end
,male => case when voice_male in (gender1,gender2) then 'Y' end
));
end loop;
end if;
return;
end get_languages;
function get_countries return t_clicksend_country_arr pipelined is
v_json varchar2(32767);
data_count number;
begin
v_json := get_json
(p_url => setting(setting_api_url) || 'countries'
,p_method => 'GET'
,p_accept => 'application/json'
);
apex_json.parse(v_json);
data_count := apex_json.get_count('data');
if data_count > 0 then
for i in 1..data_count loop
pipe row (t_clicksend_country
(country_code => substr(apex_json.get_varchar2('data[%d].code', i), 1, 10)
,country_name => substr(apex_json.get_varchar2('data[%d].value', i), 1, 100)
));
end loop;
end if;
return;
end get_countries;
function get_sms_history
(p_from in date := null -- default is 7 days prior to p_until
,p_until in date := null -- default is sysdate
) return t_clicksend_sms_history_arr pipelined is
prm varchar2(4000);
url varchar2(4000);
v_json varchar2(32767);
data_count number;
page_count number := 1;
begin
url_param(prm, 'date_from', nvl(p_from, nvl(p_until, sysdate) - 7));
url_param(prm, 'date_to', nvl(p_until, sysdate));
v_json := get_json
(p_url => setting(setting_api_url) || 'sms/history'
,p_method => 'GET'
,p_params => prm
,p_user => setting(setting_clicksend_username)
,p_pwd => setting(setting_clicksend_secret_key)
,p_accept => 'application/json'
);
loop
apex_json.parse(v_json);
data_count := apex_json.get_count('data.data');
if data_count > 0 then
for i in 1..data_count loop
pipe row (t_clicksend_sms_history
(event_dt => epoch_to_dt(apex_json.get_varchar2('data.data[%d].date', i))
,mobile => substr(apex_json.get_varchar2('data.data[%d].to', i), 1, 20)
,message => substr(apex_json.get_varchar2('data.data[%d].body', i), 1, 4000)
,status => substr(apex_json.get_varchar2('data.data[%d].status', i), 1, 100)
,sender => substr(apex_json.get_varchar2('data.data[%d].from', i), 1, 100)
,schedule_dt => epoch_to_dt(apex_json.get_varchar2('data.data[%d].schedule', i))
,status_code => substr(apex_json.get_varchar2('data.data[%d].status_code', i), 1, 100)
,status_text => substr(apex_json.get_varchar2('data.data[%d].status_text', i), 1, 4000)
,error_code => substr(apex_json.get_varchar2('data.data[%d].error_code', i), 1, 100)
,error_text => substr(apex_json.get_varchar2('data.data[%d].error_text', i), 1, 4000)
,message_id => substr(apex_json.get_varchar2('data.data[%d].message_id', i), 1, 4000)
,message_parts => to_number(apex_json.get_varchar2('data.data[%d].message_parts', i))
,message_price => to_number(apex_json.get_varchar2('data.data[%d].message_price', i))
,reply_email => substr(apex_json.get_varchar2('data.data[%d].from_email', i), 1, 255)
,custom_string => substr(apex_json.get_varchar2('data.data[%d].custom_string', i), 1, 4000)
,subaccount_id => apex_json.get_number('data.data[%d].subaccount_id', i)
,country => substr(apex_json.get_varchar2('data.data[%d].country', i), 1, 10)
,carrier => substr(apex_json.get_varchar2('data.data[%d].carrier', i), 1, 100)
));
end loop;
end if;
url := apex_json.get_varchar2('data.next_page_url');
exit when url is null;
v_json := get_json
(p_url => setting(setting_api_url) || 'sms/history' || url
,p_method => 'GET'
,p_user => setting(setting_clicksend_username)
,p_pwd => setting(setting_clicksend_secret_key)
,p_accept => 'application/json'
);
page_count := page_count + 1;
exit when page_count > 10;
end loop;
return;
end get_sms_history;
procedure create_queue
(p_max_retries in number := default_max_retries
,p_retry_delay in number := default_retry_delay
) is
begin
sys.dbms_aqadm.create_queue_table
(queue_table => queue_table
,queue_payload_type => payload_type
,sort_list => 'priority,enq_time'
);
sys.dbms_aqadm.create_queue
(queue_name => queue_name
,queue_table => queue_table
,max_retries => p_max_retries
,retry_delay => p_retry_delay
);
sys.dbms_aqadm.start_queue (queue_name);
end create_queue;
procedure drop_queue is
begin
sys.dbms_aqadm.stop_queue (queue_name);
sys.dbms_aqadm.drop_queue (queue_name);