create or replace function get_luhn_16_check_digit
( p_str in varchar)
return pls_integer
is
b16 sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll() ;
n16 sys.odcinumberlist := sys.odcinumberlist();
tot simple_integer := 0;
chk_digit pls_integer;
begin
-- step 1)
select to_number(tkn, 'X')
bulk collect into n16
from ( select substr(p_str, level, 1) as tkn
from dual
connect by level <= length(p_str));
b16.extend(n16.count());
for idx in 1..n16.count() loop
if mod(idx, 2) = 0
then
-- step 2) and 3)
b16(idx) := to_char( to_char(n16(idx)) * 2, 'fmXX');
-- step 4)
if length( b16(idx)) = 2 then
b16(idx) := to_number(substr(b16(idx),1,1)) + to_number(substr(b16(idx),2,1));
end if;
else
b16(idx) := trim(to_char(n16(idx)));
end if;
end loop;
-- step 5) and 6) [except for mysterious conversion see my comment]
for idx in 1..b16.count() loop
if b16(idx) not in ('A','B','C','D','E','F') then
tot := tot + to_number(b16(idx));
else
tot := tot + n16(idx);
end if;
end loop;
-- step 7) and 8)
chk_digit := (ceil(tot/16)*16) - tot;
return chk_digit;
end;