CouponCodeEx v0.1.0 CouponCode View Source
A module for generating coupon codes in Elixir. Inspired by the Perl
library CouponCode,
a coupon code is a group of letters and numbers known as part
and
separated by a hyphen(-
) that is meant for a receipient types in. For
example, a 3 part code with 4 characters per part looks like this:
H6YV-UDPL-383N
Like with the original module, it has the same features:
Codes are validated regardless of case (upper or lower)
Codes use the upper cased letters and numbers; however, it does not use the letters
"O"
,"I"
,"Z"
and"S"
since they are visually similar to"0"
,"1"
,"2"
and"5"
. Still, the receipient can enter those ambigious letters and be considered valid or corrected.The last character of every part is a checkdigit that helps in determining which specifc part(s) has been entered correctly.
Generated parts are also rejected against a list of bad words since they are manually entered.
Generated parts that can be transposed (or for example with a 4 character part
ABCD
, the partsBACD
.ACBD
andABDC
generate a valid checkdigit) are also rejected.
Config
While this library has good defaults, it can be configured like so:
config :coupon_code_ex,
parts: 3
part_length: 4
bad_words: ~w(SHPX PHAG JNAX JNAT CVFF PBPX FUVG GJNG GVGF SNEG URYY ZHSS QVPX XABO NEFR FUNT GBFF FYHG GHEQ FYNT PENC CBBC OHGG SRPX OBBO WVFZ WVMM CUNG)
While part_length
can have multiple values, it is discouraged to
change it from the default since the bad_words
is built for that
length. If you still want to change it, remember to update the
bad_words
lists to the maximum length that is supported.
When customizing the bad_words
list, remember it is encoded with
rot13
to avoid profanity in the code or configuraton. So right
before adding a new word to that list, you can encode it with
CouponCode.rot13/1
or an online rot13 encoder.
Link to this section Summary
Link to this section Functions
Generate the regex used in detecting bad words from the generated codes.
Aside from detecting all words in the decoded list, it should detect
similar characters (0
to O
, 1
to I
, 2
to S
and 5
to Z
).
Examples
iex> "P00P" =~ CouponCode.bad_word_regex()
true
iex> "POOP" =~ CouponCode.bad_word_regex()
true
iex> "P00P1E" =~ CouponCode.bad_word_regex()
false
iex> "F0RD" =~ CouponCode.bad_word_regex()
false
Generate a random coupon code.
Algorithm
Like with the original module, each generated code uses a plaintext
as a source of bytes. It is then hashed with sha1
and consumed for
each random character that is needed (which excludes checkdigits) to
generate a part. When the bytes are insufficient to generate a new
part or is rejected by having a filtered word or is transposable, it
is rehashed to generate and used as the new source of bytes. This
process is repeated until every part is generated.
Options
This function takes a keyword options as the first argument:
plaintext
- The plaintext to use for generating the coupon code. Useful in generating the same or deterministic code with the same options but usually not filled in. If none is given, a random 8 byte plaintext is generated.parts
- The number of delimited segments to generate. Defaults to 3 orApplication.get_env(:coupon_code_ex, :parts)
and must be a positive integer.part_length
- The number of characters per each part which includes the checkdigit. Defaults to 4 orApplication.get_env(:coupon_code_ex, :part_length)
and must be a positive integer between 2 - 20 inclusively. (The limitation stems fromsha1
generating exactly 20 bytes.)
Examples
iex> CouponCode.generate(plaintext: "1234567890")
"1K7Q-CTFM-LMTC"
iex> CouponCode.generate(plaintext: "123456789A")
"X730-KCV1-MA2G"
iex> CouponCode.generate()
"5UMN-WBKJ-2MCA"
iex> CouponCode.generate(parts: 1)
"YUVN"
iex> CouponCode.generate(parts: 5)
"D51P-H52K-9VMD-UT5H-XE3A"
iex> CouponCode.generate(part_length: 3)
"GVB-KDB-ADF"
iex> CouponCode.generate(part: 2, part_length: 7)
"86NMUDX-GFEJHVR"
rot13 utility function to encode/decode bad words.
This encoding is a character substitution algorithm by rotating a letter 13 places after it. Lower case characters are equally converted while anything else is passedthrough.
Examples
iex> CouponCode.rot13("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
"NOPQRSTUVWXYZABCDEFGHIJKLM"
iex> CouponCode.rot13("abcdefghijklmnopqrstuvwxyz")
"nopqrstuvwxyzabcdefghijklm"
iex> CouponCode.rot13("1234567890!@#$%^&*()")
"1234567890!@#$%^&*()"
iex> CouponCode.rot13("Hello World!")
"Uryyb Jbeyq!"
validate(code, opts \\ [])
View Sourcevalidate(charlist(), Keyword.t()) :: {:ok, charlist()} | {:error, :parts_invalid, pos_integer()} | {:error, :part_invalid, pos_integer()}
Validates a receipient entered code based on the required parts and length it should have.
With an entered code, it is normalized with the following steps:
- It is uppercased
- All non-word (
0-9A-Z
) characters are removed - Similar letters (
OIZS
) are corrected (0125
)
If the normalized code is valid, this returns {:ok, corrected_code}
.
If the number of computed parts are invalid, it returns {:error, :parts_invalid, actual_parts}
. Lastly it returns {:error, :part_invalid, part_with_error}
for the first parsed part that has an
checkdigit error.
Options
This function takes a keyword options as the second argument:
parts
- The number of expected delimited segments to generate. Defaults to 3 orApplication.get_env(:coupon_code_ex, :parts)
and must be a positive integer.part_length
- The number of expected characters per each part. Defaults to 4 orApplication.get_env(:coupon_code_ex, :part_length)
and must be a positive integer between 2 - 20 inclusively.
Examples
iex> CouponCode.validate("7B5M-LJ4J-D5FN")
{:ok, "7B5M-LJ4J-D5FN"}
iex> CouponCode.validate("7B5MLJ4JD5FN")
{:ok, "7B5M-LJ4J-D5FN"}
iex> CouponCode.validate("7B5mlJ4jd5fn")
{:ok, "7B5M-LJ4J-D5FN"}
iex> CouponCode.validate("7B5mlJ4jd5fn", parts: 4)
{:error, :parts_invalid, 3}
iex> CouponCode.validate("7B5mlJ4jd5fn", part_length: 5)
{:error, :parts_invalid, 2}
iex> CouponCode.validate("7B5mlJ4jd5fM")
{:error, :part_invalid, 2}
iex> CouponCode.validate("i9oD-V467-8Dsz")
{:ok, "190D-V467-8D52"}