cover/Elixir.DaProductApp.QRCode.Generator.html

1 defmodule DaProductApp.QRCode.Generator do
2 @moduledoc """
3 QR Code generation utilities for UPI payment QR codes.
4
5 Generates PNG QR code images and returns them as base64 encoded strings
6 for easy transmission in JSON responses.
7 """
8
9 @doc """
10 Generate base64 encoded PNG QR code image from a QR string.
11
12 ## Parameters
13 - `qr_string`: The UPI QR string to encode
14 - `options`: Optional map with configuration:
15 - `:size`: QR code size in pixels (default: 300)
16 - `:margin`: Margin size in pixels (default: 4)
17 - `:background_color`: Background color (default: :white)
18 - `:foreground_color`: Foreground/data color (default: :black)
19
20 ## Returns
21 - `{:ok, base64_string}` on success
22 - `{:error, reason}` on failure
23
24 ## Examples
25 iex> DaProductApp.QRCode.Generator.generate_base64("upi://pay?pa=test@upi&am=100")
26 {:ok, "iVBORw0KGgoAAAANSUhEUgAA..."}
27
28 iex> DaProductApp.QRCode.Generator.generate_base64("invalid", %{size: 200})
29 {:error, "QR generation failed: invalid_input"}
30 """
31 @spec generate_base64(String.t(), map()) :: {:ok, String.t()} | {:error, String.t()}
32
:-(
def generate_base64(qr_string, options \\ %{}) when is_binary(qr_string) do
33
:-(
try do
34 # Default options
35
:-(
opts = Map.merge(%{
36 size: 300,
37 margin: 4,
38 background_color: :white,
39 foreground_color: :black
40 }, options)
41
42 # Generate QR code data matrix
43 # QRCode.create/1 returns {:ok, %QRCode.QR{}} or {:error, reason}
44
:-(
case QRCode.create(qr_string) do
45 {:ok, qr_struct} ->
46 # Render to PNG binary. QRCode.render/2 accepts the {:ok, qr_struct} shape
47
:-(
case QRCode.render({:ok, qr_struct}, :png) do
48 {:ok, png_binary} ->
49 # The deps' QRCode.Render.to_base64 expects {:ok, binary}
50
:-(
case QRCode.to_base64({:ok, png_binary}) do
51
:-(
{:ok, base64} -> {:ok, base64}
52
:-(
err -> {:error, "QR base64 encoding failed: #{inspect(err)}"}
53 end
54
55
:-(
{:error, reason} -> {:error, "QR render failed: #{reason}"}
56
:-(
other -> {:error, "QR render unexpected result: #{inspect(other)}"}
57 end
58
59
:-(
{:error, reason} -> {:error, "QR generation failed: #{reason}"}
60
:-(
other -> {:error, "QR generation unexpected result: #{inspect(other)}"}
61 end
62 rescue
63
:-(
exception -> {:error, "QR generation exception: #{Exception.message(exception)}"}
64 end
65 end
66
67 @doc """
68 Generate base64 encoded PNG QR code with data URI prefix.
69 Returns a data URI that can be directly used in HTML img tags.
70
71 ## Examples
72 iex> DaProductApp.QRCode.Generator.generate_data_uri("upi://pay?pa=test@upi&am=100")
73 {:ok, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."}
74 """
75 @spec generate_data_uri(String.t(), map()) :: {:ok, String.t()} | {:error, String.t()}
76
:-(
def generate_data_uri(qr_string, options \\ %{}) do
77
:-(
case generate_base64(qr_string, options) do
78
:-(
{:ok, base64_data} -> {:ok, "data:image/png;base64,#{base64_data}"}
79
:-(
{:error, reason} -> {:error, reason}
80 end
81 end
82
83 @doc """
84 Validate if a string can be encoded as QR code.
85 Checks string length and character compatibility.
86
87 ## Returns
88 - `:ok` if valid
89 - `{:error, reason}` if invalid
90 """
91 @spec validate_qr_string(String.t()) :: :ok | {:error, String.t()}
92 def validate_qr_string(qr_string) when is_binary(qr_string) do
93
:-(
cond do
94
:-(
String.length(qr_string) == 0 ->
95 {:error, "QR string cannot be empty"}
96
97
:-(
String.length(qr_string) > 4296 ->
98 {:error, "QR string too long (max 4296 characters)"}
99
100
:-(
not String.valid?(qr_string) ->
101 {:error, "QR string contains invalid UTF-8 characters"}
102
103
:-(
true ->
104 :ok
105 end
106 end
107
108
:-(
def validate_qr_string(_), do: {:error, "QR string must be a binary string"}
109
110 @doc """
111 Generate QR code with UPI-specific validation.
112 Performs additional validation for UPI payment QR codes.
113 """
114 @spec generate_upi_qr_base64(String.t(), map()) :: {:ok, String.t()} | {:error, String.t()}
115
:-(
def generate_upi_qr_base64(upi_string, options \\ %{}) do
116
:-(
with :ok <- validate_qr_string(upi_string),
117
:-(
:ok <- validate_upi_format(upi_string),
118
:-(
{:ok, base64_image} <- generate_base64(upi_string, options) do
119 {:ok, base64_image}
120 else
121
:-(
{:error, reason} -> {:error, reason}
122 end
123 end
124
125 # Private helper to validate UPI QR format
126 defp validate_upi_format(upi_string) do
127 # Accept both standard 'upi://' and partner-specific 'upiGlobal://' prefixes
128
:-(
normalized = upi_string |> String.trim()
129
130
:-(
if String.downcase(normalized) |> String.starts_with?("upi://") or
131
:-(
String.downcase(normalized) |> String.starts_with?("upiglobal://") do
132 :ok
133 else
134 {:error, "Invalid UPI QR format: must start with 'upi://' or 'upiGlobal://'"}
135 end
136 end
137 end
Line Hits Source