diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee9671e..422fb6e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,9 @@ Book](https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field)
## [Unreleased]
+### Added
+- A batteries-included payment gateway built around the core library.
+
### Changed
- Use webpki CA roots instead of native for better portability.
diff --git a/Cargo.toml b/Cargo.toml
index 3f97c03..9747351 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,74 +1,6 @@
-[package]
-name = "acceptxmr"
-version = "0.12.0"
-edition = "2021"
-rust-version = "1.65"
-license = "MIT OR Apache-2.0"
-description = "Accept monero in your application."
-repository = "https://github.com/busyboredom/acceptxmr"
-readme = "README.md"
-keywords = ["crypto", "gateway", "monero", "payment", "xmr"]
-categories = ["cryptography::cryptocurrencies"]
+[workspace]
-[lib]
-name = "acceptxmr"
-path = "src/lib.rs"
-
-[dependencies]
-bincode = { version = "^2.0.0-rc.3", optional = true }
-hex = "0.4"
-http = "0.2"
-hyper = { version = "0.14", features = ["client", "http1", "http2", "tcp"] }
-hyper-rustls = { version = "0.24", features = ["logging", "http1", "http2", "tls12", "webpki-tokio"], default-features = false }
-indexmap = "1"
-log = "0.4"
-md-5 = "0.10"
-monero = "0.18"
-rand = "0.8"
-rand_chacha = "0.3"
-serde = {version = "1", features = ["derive"], optional = true }
-serde_json = "1"
-sled = { version = "0.34", optional = true }
-sqlite = { version = "0.30", optional = true }
-strum = { version = "0.24", features = ["derive"] }
-thiserror = "1"
-tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
-
-[features]
-bincode = ["dep:bincode"]
-in-memory = []
-serde = ["dep:serde"]
-sled = ["bincode", "dep:sled"]
-sqlite = ["bincode", "dep:sqlite"]
-
-[dev-dependencies]
-actix = "0.13"
-actix-files = "0.6"
-actix-session = { version = "0.7", features = ["cookie-session"] }
-actix-web = "4"
-actix-web-actors = "4"
-bytestring = "1"
-env_logger = "0.10"
-handlebars = { version = "4", features = ["dir_source"] }
-httpmock = "0.6"
-qrcode = "0.12"
-serde = "1"
-tempfile = "3"
-test-case = "3"
-# This is a workaround to enable features in tests.
-acceptxmr = { path = ".", features = ["sled", "in-memory", "sqlite"] }
-
-[[example]]
-name = "custom_storage"
-
-[[example]]
-name = "nojs"
-required-features = ["serde", "in-memory"]
-
-[[example]]
-name = "persistence"
-required-features = ["sqlite"]
-
-[[example]]
-name = "websockets"
-required-features = ["serde", "in-memory"]
+members = [
+ "server",
+ "library"
+]
diff --git a/README.md b/README.md
index e3df1e4..63242b0 100644
--- a/README.md
+++ b/README.md
@@ -1,100 +1,84 @@
[![BuildStatus](https://github.com/busyboredom/acceptxmr/workflows/CI/badge.svg)](https://img.shields.io/github/actions/workflow/status/busyboredom/acceptxmr/ci.yml?branch=main)
-[![Crates.io](https://img.shields.io/crates/v/acceptxmr.svg)](https://crates.io/crates/acceptxmr)
-[![Documentation](https://docs.rs/acceptxmr/badge.svg)](https://docs.rs/acceptxmr)
-[![MSRV](https://img.shields.io/badge/MSRV-1.65.0-blue)](https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html)
# `AcceptXMR`: Accept Monero in Your Application
+`AcceptXMR` aims to provide a simple, reliable, and efficient means to track
+monero payments.
-This library aims to provide a simple, reliable, and efficient means to track monero payments.
+To track payments, `AcceptXMR` generates subaddresses using your private view
+key and primary address. It then watches for monero sent to that subaddress
+using a monero daemon of your choosing, updating the UI in realtime and
+optionally performing a configurable callback once payment is confirmed.
-To track payments, the `PaymentGateway` generates subaddresses using your private view key and
-primary address. It then watches for monero sent to that subaddress using a monero daemon of your
-choosing, your private view key and your primary address.
+For a batteries-included payment gateway, see
+[`AcceptXMR-Server`](./server/).
-Use this library at your own risk, it is young and unproven.
+For a slim & performant library to use in your rust applications, see
+[`AcceptXMR`](./library/).
## Key Advantages
* View pair only, no hot wallet.
* Subaddress based.
-* Pending invoices can be stored persistently, enabling recovery from power loss.
+* Pending invoices can be stored persistently, enabling recovery from power
+ loss.
* Number of confirmations is configurable per-invoice.
* Ignores transactions with timelocks.
* Payment can occur over multiple transactions.
## Security
+`AcceptXMR` is non-custodial, and does not require a hot wallet. However, it
+does require your private view key and primary address for scanning outputs. If
+keeping these private is important to you, please take appropriate precautions
+to secure the platform you run your application on.
-`AcceptXMR` is non-custodial, and does not require a hot wallet. However, it does require your
-private view key and primary address for scanning outputs. If keeping these private is important
-to you, please take appropriate precautions to secure the platform you run your application on.
-
-Also note that anonymity networks like TOR are not currently supported for RPC calls. This
-means that your network traffic will reveal that you are interacting with the monero network.
+Also note that anonymity networks like TOR are not currently supported for RPC
+calls. This means that your network traffic will reveal that you are interacting
+with the monero network.
## Reliability
+`AcceptXMR` strives for reliability, but that attempt may not be successful. It
+is young and unproven, and relies on several crates which are undergoing rapid
+changes themselves. For example, one of the built-in storage layer
+implementations ([`Sled`](https://docs.rs/sled)) is still in beta.
-This library strives for reliability, but that attempt may not be successful. `AcceptXMR` is young
-and unproven, and relies on several crates which are undergoing rapid changes themselves For
-example, the primary storage layer implementation ([`Sled`](https://docs.rs/sled)) is still in beta.
-
-That said, this payment gateway should survive unexpected power loss thanks to the ability to flush
-pending invoices to disk each time new blocks/transactions are scanned. A best effort is made to
-keep the scanning thread free any of potential panics, and RPC calls in the scanning thread are
-logged on failure and repeated next scan. In the event that an error does occur, the liberal use of
-logging within this library will hopefully facilitate a speedy diagnosis an correction.
+That said, `AcceptXMR` can survive unexpected power loss thanks to the ability
+to flush pending invoices to disk each time new blocks/transactions are scanned.
+A best effort is made to keep the scanning thread free any of potential panics,
+and RPC calls in the scanning thread are logged on failure and repeated next
+scan. In the event that an error does occur, the liberal use of logging within
+`AcceptXMR` will hopefully facilitate a speedy diagnosis an correction.
-Use this library at your own risk.
+Use `AcceptXMR` at your own risk.
## Performance
+It is recommended that you host your own monero daemon on the same local
+network. Network and daemon slowness are the primary cause of high invoice
+update latency in the majority of use cases.
-It is strongly recommended that you host your own monero daemon on the same local network. Network
-and daemon slowness are the primary cause of high invoice update latency in the majority of use
-cases.
-
-To reduce the average latency before receiving invoice updates, you may also consider lowering
-the `PaymentGateway`'s `scan_interval` below the default of 1 second:
-```rust
-use acceptxmr::PaymentGateway;
-use std::time::Duration;
-
-let private_view_key =
- "ad2093a5705b9f33e6f0f0c1bc1f5f639c756cdfc168c8f2ac6127ccbdab3a03";
-let primary_address =
- "4613YiHLM6JMH4zejMB2zJY5TwQCxL8p65ufw8kBP5yxX9itmuGLqp1dS4tkVoTxjyH3aYhYNrtGHbQzJQP5bFus3KHVdmf";
-
-let store = InMemory::new();
+To reduce the average latency before receiving invoice updates, you may also
+consider lowering the gateway's scanning interval below the default of 1 second.
+If using the `AcceptXMR` library, this can be done using the `scan_interval`
+method of the `PaymentGatewayBuilder`. If using the standalone
+`AcceptXMR-Server`, the scanning interval can be set in config.
-let payment_gateway = PaymentGateway::builder(
- private_view_key.to_string(),
- primary_address.to_string(),
- store
-)
-.scan_interval(Duration::from_millis(100)) // Scan for updates every 100 ms.
-.build()?;
-```
-
-Please note that `scan_interval` is the minimum time between scanning for updates. If your
-daemon's response time is already greater than your `scan_interval` or if your CPU is unable to
-scan new transactions fast enough, reducing your `scan_interval` will do nothing.
+Note that lowering the gateway's scanning interval will do nothing if latency to
+your chosen node is slower than the scan interval.
## License
-
Licensed under either of
- * Apache License, Version 2.0
- ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- * MIT license
- ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+ * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
+ http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or
+ http://opensource.org/licenses/MIT)
at your option.
## Contribution
-
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
### Donations
-
AcceptXMR is a hobby project which generates no revenue for the developer(s).
Donations from generous users and community members help keep it economically
viable to work on.
diff --git a/library/Cargo.toml b/library/Cargo.toml
new file mode 100644
index 0000000..3f97c03
--- /dev/null
+++ b/library/Cargo.toml
@@ -0,0 +1,74 @@
+[package]
+name = "acceptxmr"
+version = "0.12.0"
+edition = "2021"
+rust-version = "1.65"
+license = "MIT OR Apache-2.0"
+description = "Accept monero in your application."
+repository = "https://github.com/busyboredom/acceptxmr"
+readme = "README.md"
+keywords = ["crypto", "gateway", "monero", "payment", "xmr"]
+categories = ["cryptography::cryptocurrencies"]
+
+[lib]
+name = "acceptxmr"
+path = "src/lib.rs"
+
+[dependencies]
+bincode = { version = "^2.0.0-rc.3", optional = true }
+hex = "0.4"
+http = "0.2"
+hyper = { version = "0.14", features = ["client", "http1", "http2", "tcp"] }
+hyper-rustls = { version = "0.24", features = ["logging", "http1", "http2", "tls12", "webpki-tokio"], default-features = false }
+indexmap = "1"
+log = "0.4"
+md-5 = "0.10"
+monero = "0.18"
+rand = "0.8"
+rand_chacha = "0.3"
+serde = {version = "1", features = ["derive"], optional = true }
+serde_json = "1"
+sled = { version = "0.34", optional = true }
+sqlite = { version = "0.30", optional = true }
+strum = { version = "0.24", features = ["derive"] }
+thiserror = "1"
+tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
+
+[features]
+bincode = ["dep:bincode"]
+in-memory = []
+serde = ["dep:serde"]
+sled = ["bincode", "dep:sled"]
+sqlite = ["bincode", "dep:sqlite"]
+
+[dev-dependencies]
+actix = "0.13"
+actix-files = "0.6"
+actix-session = { version = "0.7", features = ["cookie-session"] }
+actix-web = "4"
+actix-web-actors = "4"
+bytestring = "1"
+env_logger = "0.10"
+handlebars = { version = "4", features = ["dir_source"] }
+httpmock = "0.6"
+qrcode = "0.12"
+serde = "1"
+tempfile = "3"
+test-case = "3"
+# This is a workaround to enable features in tests.
+acceptxmr = { path = ".", features = ["sled", "in-memory", "sqlite"] }
+
+[[example]]
+name = "custom_storage"
+
+[[example]]
+name = "nojs"
+required-features = ["serde", "in-memory"]
+
+[[example]]
+name = "persistence"
+required-features = ["sqlite"]
+
+[[example]]
+name = "websockets"
+required-features = ["serde", "in-memory"]
diff --git a/library/README.md b/library/README.md
new file mode 100644
index 0000000..ba0a578
--- /dev/null
+++ b/library/README.md
@@ -0,0 +1,69 @@
+[![BuildStatus](https://github.com/busyboredom/acceptxmr/workflows/CI/badge.svg)](https://img.shields.io/github/actions/workflow/status/busyboredom/acceptxmr/ci.yml?branch=main)
+[![Crates.io](https://img.shields.io/crates/v/acceptxmr.svg)](https://crates.io/crates/acceptxmr)
+[![Documentation](https://docs.rs/acceptxmr/badge.svg)](https://docs.rs/acceptxmr)
+[![MSRV](https://img.shields.io/badge/MSRV-1.65.0-blue)](https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html)
+
+# `AcceptXMR`: Accept Monero in Your Application
+`AcceptXMR` is a library for building payment gateways.
+
+For a batteries-included gateway, please see
+[`AcceptXMR-Server`](../server/).
+
+## Getting Started
+
+To use `AcceptXMR` in your rust project, first add it to your `Cargo.toml`. For
+example if you intend to use the `Sqlite` storage backend and need `serde`
+support, you should add this to your `Cargo.toml`:
+```toml
+[dependencies]
+acceptxmr = { version = "0.12", features = ["serde", "sqlite"] }
+```
+You can then create and run a `PaymentGateway`:
+```rust
+use acceptxmr::{PaymentGateway, storage::stores::Sqlite};
+use std::time::Duration;
+
+let private_view_key =
+ "ad2093a5705b9f33e6f0f0c1bc1f5f639c756cdfc168c8f2ac6127ccbdab3a03";
+let primary_address =
+ "4613YiHLM6JMH4zejMB2zJY5TwQCxL8p65ufw8kBP5yxX9itmuGLqp1dS4tkVoTxjyH3aYhYNrtGHbQzJQP5bFus3KHVdmf";
+
+let store = Sqlite::new("AcceptXMR_DB", "invoices")?;
+
+let payment_gateway = PaymentGateway::builder(
+ private_view_key.to_string(),
+ primary_address.to_string(),
+ store
+)
+.daemon_url("https://node.example.com") // Specify a node.
+.scan_interval(Duration::from_millis(500)) // Scan for updates every 500 ms.
+.build()?;
+
+payment_gateway.run()?;
+```
+Finally, you can create invoices and subscribe to them so you know when they get
+paid:
+```rust
+// Oh hey, a customer is checking out!
+let invoice_id = payment_gateway.new_invoice(
+ 100 * 10 ** 9, // We'll charge 100 millineros,
+ 0, // require 0 confirmations,
+ 10, // expire in 10 blocks,
+ "Large Cheese Pizza".to_string() // and get the order right.
+)?;
+
+// We can now subscribe to updates to the pizza invoice.
+let subscriber = payment_gateway.subscribe(invoice_id)?
+ .expect("invoice doesn't exist");
+
+// Have we been paid yet?
+let update = subscriber.recv().await.expect("channel closed");
+
+if update.is_confirmed() {
+ // Great, ship the pizza and stop tracking the invoice.
+ println!("Invoice for \"{}\" paid", update.description());
+ payment_gateway.remove_invoice(invoice_id)?;
+}
+```
+For more detailed documentation, see [docs.rs](https://docs.rs/acceptxmr) or the
+[examples](./examples/).
\ No newline at end of file
diff --git a/examples/custom_storage/main.rs b/library/examples/custom_storage/main.rs
similarity index 100%
rename from examples/custom_storage/main.rs
rename to library/examples/custom_storage/main.rs
diff --git a/examples/nojs/main.rs b/library/examples/nojs/main.rs
similarity index 96%
rename from examples/nojs/main.rs
rename to library/examples/nojs/main.rs
index e0c2673..6139952 100644
--- a/examples/nojs/main.rs
+++ b/library/examples/nojs/main.rs
@@ -44,7 +44,7 @@ async fn main() -> std::io::Result<()> {
primary_address.to_string(),
InMemory::new(),
)
- .daemon_url("http://node.sethforprivacy.com:18089".to_string())
+ .daemon_url("http://xmr-node.cakewallet.com:18081".to_string())
.build()
.expect("failed to build payment gateway");
info!("Payment gateway created.");
@@ -87,7 +87,7 @@ async fn main() -> std::io::Result<()> {
// Templating setup.
let mut handlebars = Handlebars::new();
handlebars
- .register_templates_directory(".html", "./examples/nojs/static/templates")
+ .register_templates_directory(".html", "./library/examples/nojs/static/templates")
.expect("failed to register template directory");
handlebars.register_escape_fn(no_escape);
@@ -107,7 +107,7 @@ async fn main() -> std::io::Result<()> {
.app_data(handlebars_ref.clone())
.service(start_checkout)
.service(checkout)
- .service(Files::new("", "./examples/nojs/static").index_file("index.html"))
+ .service(Files::new("", "./library/examples/nojs/static").index_file("index.html"))
})
.bind("0.0.0.0:8080")?
.run()
diff --git a/examples/nojs/static/acceptxmr.css b/library/examples/nojs/static/acceptxmr.css
similarity index 100%
rename from examples/nojs/static/acceptxmr.css
rename to library/examples/nojs/static/acceptxmr.css
diff --git a/examples/nojs/static/expired.html b/library/examples/nojs/static/expired.html
similarity index 100%
rename from examples/nojs/static/expired.html
rename to library/examples/nojs/static/expired.html
diff --git a/examples/nojs/static/favicon.ico b/library/examples/nojs/static/favicon.ico
similarity index 100%
rename from examples/nojs/static/favicon.ico
rename to library/examples/nojs/static/favicon.ico
diff --git a/examples/nojs/static/index.html b/library/examples/nojs/static/index.html
similarity index 100%
rename from examples/nojs/static/index.html
rename to library/examples/nojs/static/index.html
diff --git a/examples/nojs/static/refresh.png b/library/examples/nojs/static/refresh.png
similarity index 100%
rename from examples/nojs/static/refresh.png
rename to library/examples/nojs/static/refresh.png
diff --git a/examples/nojs/static/templates/checkout.html b/library/examples/nojs/static/templates/checkout.html
similarity index 100%
rename from examples/nojs/static/templates/checkout.html
rename to library/examples/nojs/static/templates/checkout.html
diff --git a/examples/persistence/main.rs b/library/examples/persistence/main.rs
similarity index 92%
rename from examples/persistence/main.rs
rename to library/examples/persistence/main.rs
index 6d0b016..3a1dbe2 100644
--- a/examples/persistence/main.rs
+++ b/library/examples/persistence/main.rs
@@ -18,7 +18,7 @@ async fn main() {
let primary_address = "4613YiHLM6JMH4zejMB2zJY5TwQCxL8p65ufw8kBP5yxX9itmuGLqp1dS4tkVoTxjyH3aYhYNrtGHbQzJQP5bFus3KHVdmf";
// Use an Sqlite database for persistent storage of pending invoices.
- let store = Sqlite::new("examples/persistence/AcceptXMR_DB", "invoices").unwrap();
+ let store = Sqlite::new("library/examples/persistence/AcceptXMR_DB", "invoices").unwrap();
let payment_gateway = PaymentGatewayBuilder::new(
private_view_key.to_string(),
@@ -50,7 +50,7 @@ async fn main() {
power_failure(payment_gateway);
// Reconstruct the gateway...
- let store = Sqlite::new("examples/persistence/AcceptXMR_DB", "invoices").unwrap();
+ let store = Sqlite::new("library/examples/persistence/AcceptXMR_DB", "invoices").unwrap();
let payment_gateway = PaymentGatewayBuilder::new(
private_view_key.to_string(),
primary_address.to_string(),
diff --git a/examples/websockets/main.rs b/library/examples/websockets/main.rs
similarity index 98%
rename from examples/websockets/main.rs
rename to library/examples/websockets/main.rs
index a333dfa..a3d6d35 100644
--- a/examples/websockets/main.rs
+++ b/library/examples/websockets/main.rs
@@ -57,7 +57,7 @@ async fn main() -> std::io::Result<()> {
primary_address.to_string(),
invoice_store,
)
- .daemon_url("http://node.sethforprivacy.com:18089".to_string())
+ .daemon_url("http://xmr-node.cakewallet.com:18081".to_string())
.build()
.expect("failed to build payment gateway");
info!("Payment gateway created.");
@@ -111,7 +111,9 @@ async fn main() -> std::io::Result<()> {
.service(update)
.service(checkout)
.service(websocket)
- .service(Files::new("", "./examples/websockets/static").index_file("index.html"))
+ .service(
+ Files::new("", "./library/examples/websockets/static").index_file("index.html"),
+ )
})
.bind("0.0.0.0:8080")?
.run()
diff --git a/examples/websockets/static/acceptxmr.css b/library/examples/websockets/static/acceptxmr.css
similarity index 100%
rename from examples/websockets/static/acceptxmr.css
rename to library/examples/websockets/static/acceptxmr.css
diff --git a/examples/websockets/static/acceptxmr.js b/library/examples/websockets/static/acceptxmr.js
similarity index 100%
rename from examples/websockets/static/acceptxmr.js
rename to library/examples/websockets/static/acceptxmr.js
diff --git a/examples/websockets/static/favicon.ico b/library/examples/websockets/static/favicon.ico
similarity index 100%
rename from examples/websockets/static/favicon.ico
rename to library/examples/websockets/static/favicon.ico
diff --git a/examples/websockets/static/index.html b/library/examples/websockets/static/index.html
similarity index 100%
rename from examples/websockets/static/index.html
rename to library/examples/websockets/static/index.html
diff --git a/examples/websockets/static/vendor/qrcode.js b/library/examples/websockets/static/vendor/qrcode.js
similarity index 100%
rename from examples/websockets/static/vendor/qrcode.js
rename to library/examples/websockets/static/vendor/qrcode.js
diff --git a/src/caching/block_cache.rs b/library/src/caching/block_cache.rs
similarity index 100%
rename from src/caching/block_cache.rs
rename to library/src/caching/block_cache.rs
diff --git a/src/caching/mod.rs b/library/src/caching/mod.rs
similarity index 100%
rename from src/caching/mod.rs
rename to library/src/caching/mod.rs
diff --git a/src/caching/subaddress_cache.rs b/library/src/caching/subaddress_cache.rs
similarity index 100%
rename from src/caching/subaddress_cache.rs
rename to library/src/caching/subaddress_cache.rs
diff --git a/src/caching/txpool_cache.rs b/library/src/caching/txpool_cache.rs
similarity index 100%
rename from src/caching/txpool_cache.rs
rename to library/src/caching/txpool_cache.rs
diff --git a/src/invoice.rs b/library/src/invoice.rs
similarity index 100%
rename from src/invoice.rs
rename to library/src/invoice.rs
diff --git a/src/lib.rs b/library/src/lib.rs
similarity index 100%
rename from src/lib.rs
rename to library/src/lib.rs
diff --git a/src/payment_gateway.rs b/library/src/payment_gateway.rs
similarity index 100%
rename from src/payment_gateway.rs
rename to library/src/payment_gateway.rs
diff --git a/src/pubsub.rs b/library/src/pubsub.rs
similarity index 100%
rename from src/pubsub.rs
rename to library/src/pubsub.rs
diff --git a/src/rpc/authentication.rs b/library/src/rpc/authentication.rs
similarity index 100%
rename from src/rpc/authentication.rs
rename to library/src/rpc/authentication.rs
diff --git a/src/rpc/mod.rs b/library/src/rpc/mod.rs
similarity index 100%
rename from src/rpc/mod.rs
rename to library/src/rpc/mod.rs
diff --git a/src/scanner.rs b/library/src/scanner.rs
similarity index 100%
rename from src/scanner.rs
rename to library/src/scanner.rs
diff --git a/library/src/static/acceptxmr.css b/library/src/static/acceptxmr.css
new file mode 100644
index 0000000..0880322
--- /dev/null
+++ b/library/src/static/acceptxmr.css
@@ -0,0 +1,151 @@
+body {
+ background-color: #4d4d4d;
+}
+
+.acceptxmr {
+ max-width: 80ch;
+ margin: auto;
+ background-color: #f26822;
+ border-radius: 1em;
+ display: flex;
+ flex-flow: column wrap;
+ align-items: center;
+}
+
+.acceptxmr button {
+ background-color: #0088ff;
+ border-radius: 0.5em;
+ border: 0;
+ margin: 0;
+ font-size: medium;
+}
+
+.acceptxmr button:hover {
+ background-color: #005fb3;
+ cursor: pointer;
+}
+
+.acceptxmr button:disabled {
+ background-color: #979797;
+}
+
+.acceptxmr #message-container {
+ margin: 1em;
+ flex: 2 1 20em;
+}
+
+.acceptxmr #payment-content {
+ display: none;
+}
+
+.acceptxmr .content button {
+ margin: 1em;
+ flex: 1 2 10ch;
+ padding: 12px 0.5em;
+ align-self: flex-end;
+}
+
+.acceptxmr textarea {
+ border-radius: 0.5em;
+ border: 0;
+ padding: 0.5em;
+ width: 100%;
+ margin: 0;
+ background-color: #4d4d4d;
+ color: #ffffff;
+ resize: vertical;
+}
+
+.acceptxmr .instruction-container {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
+ justify-content: center;
+}
+
+.acceptxmr .instruction-container p {
+ margin: 0.1em;
+ font-size: large;
+}
+
+.acceptxmr .instruction-container button {
+ margin: 0.5em;
+ padding: 0.25em 0.5em;
+}
+
+.acceptxmr .warning {
+ animation: acceptxmr-fade 2s linear;
+ animation-iteration-count: infinite;
+}
+
+@keyframes acceptxmr-fade {
+ 0%,100% { opacity: 0.2 }
+ 50% { opacity: 1 }
+}
+
+.acceptxmr hr {
+ width: 90%;
+ border-style: solid;
+ border-color: #000000;
+ margin: 0;
+}
+
+.content {
+ width: 100%;
+ display: flex;
+ flex-flow: row wrap;
+}
+
+.acceptxmr .qrcode-container {
+ flex: 1 1 10ch;
+ margin: 1em;
+ display: flex;
+ justify-content: center;
+}
+
+.acceptxmr .qrcode-container svg {
+ border-radius: 1em;
+ min-width: 18ch;
+ align-self: center;
+ background-color: #dddddd;
+}
+
+.acceptxmr .details {
+ flex: 2 2 30ch;
+ margin: 0 1em 1em 1em;
+ overflow: hidden;
+ align-self: center;
+}
+
+.acceptxmr #address-container {
+ display: flex;
+ height: 3em;
+ border-radius: 0.5em;
+ margin-bottom: 0.5em;
+ background-color: #ffffff;
+ justify-content: space-between;
+}
+
+.acceptxmr #address-container p {
+ overflow-x: scroll;
+ overflow-y: hidden;
+ margin: 0 0.5em 0 0.5em;
+ height: 100%;
+ width: 100%;
+ display: flex;
+ align-items: center;
+}
+
+.acceptxmr #address-container button {
+ border-radius: 0 0.5em 0.5em 0;
+ height: 100%;
+ padding: 0 1em;
+ margin: 0;
+}
+
+.acceptxmr .status {
+ margin: 0 0 0 0;
+ border-radius: 0.5em;
+ padding: 0.5em;
+ background-color: #ffffff;
+}
diff --git a/library/src/static/acceptxmr.js b/library/src/static/acceptxmr.js
new file mode 100644
index 0000000..ff24d34
--- /dev/null
+++ b/library/src/static/acceptxmr.js
@@ -0,0 +1,152 @@
+// Try to load existing invoice on page load.
+async function init() {
+ let response = await fetch("/update");
+ if (response.status !== 410 ) {
+ let invoiceUpdate = await response.json();
+ displayInvoiceUpdate(invoiceUpdate);
+ await next(true);
+ }
+}
+init()
+
+async function next(hasAddress) {
+ // Hide prep stuff, show payment stuff.
+ document.getElementById("preperation-content").style.display = "None";
+ document.getElementById("payment-content").style.display = "inherit";
+
+ // Create invoice.
+ if (!hasAddress) {
+ document.getElementById("instruction").innerHTML = "Loading...";
+ await newAddress();
+ } else {
+ newWebsocket();
+ }
+}
+
+async function newAddress() {
+ const message = document.getElementById("message").value;
+ const checkOutInfo = {
+ method: "POST",
+ body: JSON.stringify({
+ "message": message
+ }),
+ headers: {
+ 'content-type': 'application/json'
+ }
+ };
+
+ await fetch("/checkout", checkOutInfo);
+ newWebsocket();
+
+ let response = await fetch("/update");
+ let invoiceUpdate = await response.json();
+ displayInvoiceUpdate(invoiceUpdate);
+}
+
+function displayInvoiceUpdate(invoiceUpdate) {
+ console.log(invoiceUpdate);
+
+ // Show paid/due.
+ document.getElementById("paid").innerHTML = picoToXMR(invoiceUpdate.amount_paid);
+ document.getElementById("due").innerHTML = picoToXMR(invoiceUpdate.amount_requested);
+
+ // Show confirmations/required.
+ var confirmations = invoiceUpdate.confirmations;
+ document.getElementById("confirmations").innerHTML = Math.max(0, confirmations);
+ document.getElementById("confirmations-required").innerHTML = invoiceUpdate.confirmations_required;
+
+ // Show instructive text depending on invoice state.
+ var instructionString = "Loading...";
+ var instructionClass = "acceptxmr-instruction";
+ var newAddressBtnHidden = true;
+ var closeReason = null;
+ if (confirmations !== null && confirmations >= invoiceUpdate.confirmations_required) {
+ instructionString = "Paid! Thank you"
+ closeReason = "Confirmed";
+ } else if (invoiceUpdate.amount_paid >= invoiceUpdate.amount_requested) {
+ instructionString = "Paid! Waiting for Confirmation..."
+ } else if (invoiceUpdate.expiration_in > 2) {
+ instructionString = "Send Monero to Address Below"
+ } else if (invoiceUpdate.expiration_in > 0) {
+ instructionString = "Address Expiring Soon";
+ instructionClass += " warning";
+ newAddressBtnHidden = false;
+ } else {
+ instructionString = "Address Expired!";
+ newAddressBtnHidden = false;
+ closeReason = "Expired";
+ }
+ document.getElementById("instruction").innerHTML = instructionString;
+ document.getElementById("instruction").classList = instructionClass;
+
+ // Hide address if nearing expiration.
+ document.getElementById("new-address-btn").hidden = newAddressBtnHidden;
+ document.getElementById("address-copy-btn").disabled = !newAddressBtnHidden;
+ if (newAddressBtnHidden) {
+ document.getElementById("address").innerHTML = invoiceUpdate.address;
+
+ const qr = qrcode(0, "M");
+ qr.addData(invoiceUpdate.uri);
+ qr.make();
+ document.getElementById('qrcode-container').innerHTML = qr.createSvgTag({ scalable: true });
+ } else {
+ document.getElementById("address").innerHTML = "Expiring or expired...";
+ document.getElementById('qrcode-container').innerHTML = " ";
+ }
+
+ return closeReason;
+}
+
+function newWebsocket() {
+ // Close websocket if it already exists.
+ if (typeof window.acceptxmrSocket != 'undefined') {
+ window.acceptxmrSocket.close(1000, "New Address");
+ }
+
+ // Open websocket.
+ window.acceptxmrSocket = new WebSocket("ws://localhost:8080/ws/");
+
+ window.acceptxmrSocket.onmessage = function (event) {
+ let closeReason = displayInvoiceUpdate(JSON.parse(event.data));
+ if (closeReason != null) {
+ window.acceptxmrSocket.close(1000, closeReason);
+ }
+ }
+
+ // If the websocket closes cleanly, log it. Otherwise, alert the user.
+ window.acceptxmrSocket.onclose = function (event) {
+ if (event.code === 1000) {
+ console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
+ } else {
+ // Server process killed or network down.
+ // Event.code is usually 1006 in this case.
+ alert('Connection died. If you have paid already, rest assured that it will still be processed.');
+ }
+ };
+
+ window.acceptxmrSocket.onerror = function (error) {
+ alert(`[error] ${error.message}`);
+ };
+}
+
+// Convert from piconeros to monero.
+function picoToXMR(amount) {
+ const divisor = 1_000_000_000_000;
+ const xmr = Math.floor(amount / divisor) + amount % divisor / divisor;
+ return new Intl.NumberFormat(undefined, { maximumSignificantDigits: 20 }).format(xmr);
+}
+
+// Make the copy button work.
+function copyInvoiceAddress() {
+ // Get the text field
+ const copyText = document.getElementById("address");
+
+ // Copy the text inside the text field
+ navigator.clipboard.writeText(copyText.innerHTML);
+
+ // Provide feedback
+ document.getElementById("address-copy-btn").innerHTML = "Copied!";
+ setTimeout(function () {
+ document.getElementById("address-copy-btn").innerHTML = "Copy";
+ }, 1000);
+}
diff --git a/library/src/static/favicon.ico b/library/src/static/favicon.ico
new file mode 100644
index 0000000..37a6dae
Binary files /dev/null and b/library/src/static/favicon.ico differ
diff --git a/library/src/static/index.html b/library/src/static/index.html
new file mode 100644
index 0000000..b9511dd
--- /dev/null
+++ b/library/src/static/index.html
@@ -0,0 +1,40 @@
+
+
+
AcceptXMR Websocket Demo
+
+
+
+
AcceptXMR Websocket Demo
+
New Address
+
+
+
+
+ Message (Optional)
+
+
+
Next
+
+
+
+
+
+
+
Address
+
+
Status
+
+ Paid: 0.00000000 / 0.00000000 XMR
+ Confirmations:
+ 0 / 0
+
+
+
+
+
+
+
diff --git a/library/src/static/vendor/qrcode.js b/library/src/static/vendor/qrcode.js
new file mode 100644
index 0000000..fde008b
--- /dev/null
+++ b/library/src/static/vendor/qrcode.js
@@ -0,0 +1,2297 @@
+//---------------------------------------------------------------------
+//
+// QR Code Generator for JavaScript
+//
+// Copyright (c) 2009 Kazuhiko Arase
+//
+// URL: http://www.d-project.com/
+//
+// Licensed under the MIT license:
+// http://www.opensource.org/licenses/mit-license.php
+//
+// The word 'QR Code' is registered trademark of
+// DENSO WAVE INCORPORATED
+// http://www.denso-wave.com/qrcode/faqpatent-e.html
+//
+//---------------------------------------------------------------------
+
+var qrcode = function() {
+
+ //---------------------------------------------------------------------
+ // qrcode
+ //---------------------------------------------------------------------
+
+ /**
+ * qrcode
+ * @param typeNumber 1 to 40
+ * @param errorCorrectionLevel 'L','M','Q','H'
+ */
+ var qrcode = function(typeNumber, errorCorrectionLevel) {
+
+ var PAD0 = 0xEC;
+ var PAD1 = 0x11;
+
+ var _typeNumber = typeNumber;
+ var _errorCorrectionLevel = QRErrorCorrectionLevel[errorCorrectionLevel];
+ var _modules = null;
+ var _moduleCount = 0;
+ var _dataCache = null;
+ var _dataList = [];
+
+ var _this = {};
+
+ var makeImpl = function(test, maskPattern) {
+
+ _moduleCount = _typeNumber * 4 + 17;
+ _modules = function(moduleCount) {
+ var modules = new Array(moduleCount);
+ for (var row = 0; row < moduleCount; row += 1) {
+ modules[row] = new Array(moduleCount);
+ for (var col = 0; col < moduleCount; col += 1) {
+ modules[row][col] = null;
+ }
+ }
+ return modules;
+ }(_moduleCount);
+
+ setupPositionProbePattern(0, 0);
+ setupPositionProbePattern(_moduleCount - 7, 0);
+ setupPositionProbePattern(0, _moduleCount - 7);
+ setupPositionAdjustPattern();
+ setupTimingPattern();
+ setupTypeInfo(test, maskPattern);
+
+ if (_typeNumber >= 7) {
+ setupTypeNumber(test);
+ }
+
+ if (_dataCache == null) {
+ _dataCache = createData(_typeNumber, _errorCorrectionLevel, _dataList);
+ }
+
+ mapData(_dataCache, maskPattern);
+ };
+
+ var setupPositionProbePattern = function(row, col) {
+
+ for (var r = -1; r <= 7; r += 1) {
+
+ if (row + r <= -1 || _moduleCount <= row + r) continue;
+
+ for (var c = -1; c <= 7; c += 1) {
+
+ if (col + c <= -1 || _moduleCount <= col + c) continue;
+
+ if ( (0 <= r && r <= 6 && (c == 0 || c == 6) )
+ || (0 <= c && c <= 6 && (r == 0 || r == 6) )
+ || (2 <= r && r <= 4 && 2 <= c && c <= 4) ) {
+ _modules[row + r][col + c] = true;
+ } else {
+ _modules[row + r][col + c] = false;
+ }
+ }
+ }
+ };
+
+ var getBestMaskPattern = function() {
+
+ var minLostPoint = 0;
+ var pattern = 0;
+
+ for (var i = 0; i < 8; i += 1) {
+
+ makeImpl(true, i);
+
+ var lostPoint = QRUtil.getLostPoint(_this);
+
+ if (i == 0 || minLostPoint > lostPoint) {
+ minLostPoint = lostPoint;
+ pattern = i;
+ }
+ }
+
+ return pattern;
+ };
+
+ var setupTimingPattern = function() {
+
+ for (var r = 8; r < _moduleCount - 8; r += 1) {
+ if (_modules[r][6] != null) {
+ continue;
+ }
+ _modules[r][6] = (r % 2 == 0);
+ }
+
+ for (var c = 8; c < _moduleCount - 8; c += 1) {
+ if (_modules[6][c] != null) {
+ continue;
+ }
+ _modules[6][c] = (c % 2 == 0);
+ }
+ };
+
+ var setupPositionAdjustPattern = function() {
+
+ var pos = QRUtil.getPatternPosition(_typeNumber);
+
+ for (var i = 0; i < pos.length; i += 1) {
+
+ for (var j = 0; j < pos.length; j += 1) {
+
+ var row = pos[i];
+ var col = pos[j];
+
+ if (_modules[row][col] != null) {
+ continue;
+ }
+
+ for (var r = -2; r <= 2; r += 1) {
+
+ for (var c = -2; c <= 2; c += 1) {
+
+ if (r == -2 || r == 2 || c == -2 || c == 2
+ || (r == 0 && c == 0) ) {
+ _modules[row + r][col + c] = true;
+ } else {
+ _modules[row + r][col + c] = false;
+ }
+ }
+ }
+ }
+ }
+ };
+
+ var setupTypeNumber = function(test) {
+
+ var bits = QRUtil.getBCHTypeNumber(_typeNumber);
+
+ for (var i = 0; i < 18; i += 1) {
+ var mod = (!test && ( (bits >> i) & 1) == 1);
+ _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod;
+ }
+
+ for (var i = 0; i < 18; i += 1) {
+ var mod = (!test && ( (bits >> i) & 1) == 1);
+ _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
+ }
+ };
+
+ var setupTypeInfo = function(test, maskPattern) {
+
+ var data = (_errorCorrectionLevel << 3) | maskPattern;
+ var bits = QRUtil.getBCHTypeInfo(data);
+
+ // vertical
+ for (var i = 0; i < 15; i += 1) {
+
+ var mod = (!test && ( (bits >> i) & 1) == 1);
+
+ if (i < 6) {
+ _modules[i][8] = mod;
+ } else if (i < 8) {
+ _modules[i + 1][8] = mod;
+ } else {
+ _modules[_moduleCount - 15 + i][8] = mod;
+ }
+ }
+
+ // horizontal
+ for (var i = 0; i < 15; i += 1) {
+
+ var mod = (!test && ( (bits >> i) & 1) == 1);
+
+ if (i < 8) {
+ _modules[8][_moduleCount - i - 1] = mod;
+ } else if (i < 9) {
+ _modules[8][15 - i - 1 + 1] = mod;
+ } else {
+ _modules[8][15 - i - 1] = mod;
+ }
+ }
+
+ // fixed module
+ _modules[_moduleCount - 8][8] = (!test);
+ };
+
+ var mapData = function(data, maskPattern) {
+
+ var inc = -1;
+ var row = _moduleCount - 1;
+ var bitIndex = 7;
+ var byteIndex = 0;
+ var maskFunc = QRUtil.getMaskFunction(maskPattern);
+
+ for (var col = _moduleCount - 1; col > 0; col -= 2) {
+
+ if (col == 6) col -= 1;
+
+ while (true) {
+
+ for (var c = 0; c < 2; c += 1) {
+
+ if (_modules[row][col - c] == null) {
+
+ var dark = false;
+
+ if (byteIndex < data.length) {
+ dark = ( ( (data[byteIndex] >>> bitIndex) & 1) == 1);
+ }
+
+ var mask = maskFunc(row, col - c);
+
+ if (mask) {
+ dark = !dark;
+ }
+
+ _modules[row][col - c] = dark;
+ bitIndex -= 1;
+
+ if (bitIndex == -1) {
+ byteIndex += 1;
+ bitIndex = 7;
+ }
+ }
+ }
+
+ row += inc;
+
+ if (row < 0 || _moduleCount <= row) {
+ row -= inc;
+ inc = -inc;
+ break;
+ }
+ }
+ }
+ };
+
+ var createBytes = function(buffer, rsBlocks) {
+
+ var offset = 0;
+
+ var maxDcCount = 0;
+ var maxEcCount = 0;
+
+ var dcdata = new Array(rsBlocks.length);
+ var ecdata = new Array(rsBlocks.length);
+
+ for (var r = 0; r < rsBlocks.length; r += 1) {
+
+ var dcCount = rsBlocks[r].dataCount;
+ var ecCount = rsBlocks[r].totalCount - dcCount;
+
+ maxDcCount = Math.max(maxDcCount, dcCount);
+ maxEcCount = Math.max(maxEcCount, ecCount);
+
+ dcdata[r] = new Array(dcCount);
+
+ for (var i = 0; i < dcdata[r].length; i += 1) {
+ dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset];
+ }
+ offset += dcCount;
+
+ var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
+ var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1);
+
+ var modPoly = rawPoly.mod(rsPoly);
+ ecdata[r] = new Array(rsPoly.getLength() - 1);
+ for (var i = 0; i < ecdata[r].length; i += 1) {
+ var modIndex = i + modPoly.getLength() - ecdata[r].length;
+ ecdata[r][i] = (modIndex >= 0)? modPoly.getAt(modIndex) : 0;
+ }
+ }
+
+ var totalCodeCount = 0;
+ for (var i = 0; i < rsBlocks.length; i += 1) {
+ totalCodeCount += rsBlocks[i].totalCount;
+ }
+
+ var data = new Array(totalCodeCount);
+ var index = 0;
+
+ for (var i = 0; i < maxDcCount; i += 1) {
+ for (var r = 0; r < rsBlocks.length; r += 1) {
+ if (i < dcdata[r].length) {
+ data[index] = dcdata[r][i];
+ index += 1;
+ }
+ }
+ }
+
+ for (var i = 0; i < maxEcCount; i += 1) {
+ for (var r = 0; r < rsBlocks.length; r += 1) {
+ if (i < ecdata[r].length) {
+ data[index] = ecdata[r][i];
+ index += 1;
+ }
+ }
+ }
+
+ return data;
+ };
+
+ var createData = function(typeNumber, errorCorrectionLevel, dataList) {
+
+ var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectionLevel);
+
+ var buffer = qrBitBuffer();
+
+ for (var i = 0; i < dataList.length; i += 1) {
+ var data = dataList[i];
+ buffer.put(data.getMode(), 4);
+ buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) );
+ data.write(buffer);
+ }
+
+ // calc num max data.
+ var totalDataCount = 0;
+ for (var i = 0; i < rsBlocks.length; i += 1) {
+ totalDataCount += rsBlocks[i].dataCount;
+ }
+
+ if (buffer.getLengthInBits() > totalDataCount * 8) {
+ throw 'code length overflow. ('
+ + buffer.getLengthInBits()
+ + '>'
+ + totalDataCount * 8
+ + ')';
+ }
+
+ // end code
+ if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
+ buffer.put(0, 4);
+ }
+
+ // padding
+ while (buffer.getLengthInBits() % 8 != 0) {
+ buffer.putBit(false);
+ }
+
+ // padding
+ while (true) {
+
+ if (buffer.getLengthInBits() >= totalDataCount * 8) {
+ break;
+ }
+ buffer.put(PAD0, 8);
+
+ if (buffer.getLengthInBits() >= totalDataCount * 8) {
+ break;
+ }
+ buffer.put(PAD1, 8);
+ }
+
+ return createBytes(buffer, rsBlocks);
+ };
+
+ _this.addData = function(data, mode) {
+
+ mode = mode || 'Byte';
+
+ var newData = null;
+
+ switch(mode) {
+ case 'Numeric' :
+ newData = qrNumber(data);
+ break;
+ case 'Alphanumeric' :
+ newData = qrAlphaNum(data);
+ break;
+ case 'Byte' :
+ newData = qr8BitByte(data);
+ break;
+ case 'Kanji' :
+ newData = qrKanji(data);
+ break;
+ default :
+ throw 'mode:' + mode;
+ }
+
+ _dataList.push(newData);
+ _dataCache = null;
+ };
+
+ _this.isDark = function(row, col) {
+ if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) {
+ throw row + ',' + col;
+ }
+ return _modules[row][col];
+ };
+
+ _this.getModuleCount = function() {
+ return _moduleCount;
+ };
+
+ _this.make = function() {
+ if (_typeNumber < 1) {
+ var typeNumber = 1;
+
+ for (; typeNumber < 40; typeNumber++) {
+ var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, _errorCorrectionLevel);
+ var buffer = qrBitBuffer();
+
+ for (var i = 0; i < _dataList.length; i++) {
+ var data = _dataList[i];
+ buffer.put(data.getMode(), 4);
+ buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) );
+ data.write(buffer);
+ }
+
+ var totalDataCount = 0;
+ for (var i = 0; i < rsBlocks.length; i++) {
+ totalDataCount += rsBlocks[i].dataCount;
+ }
+
+ if (buffer.getLengthInBits() <= totalDataCount * 8) {
+ break;
+ }
+ }
+
+ _typeNumber = typeNumber;
+ }
+
+ makeImpl(false, getBestMaskPattern() );
+ };
+
+ _this.createTableTag = function(cellSize, margin) {
+
+ cellSize = cellSize || 2;
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
+
+ var qrHtml = '';
+
+ qrHtml += '';
+ qrHtml += '';
+
+ for (var r = 0; r < _this.getModuleCount(); r += 1) {
+
+ qrHtml += '';
+
+ for (var c = 0; c < _this.getModuleCount(); c += 1) {
+ qrHtml += ' ';
+ }
+
+ qrHtml += ' ';
+ }
+
+ qrHtml += ' ';
+ qrHtml += '
';
+
+ return qrHtml;
+ };
+
+ _this.createSvgTag = function(cellSize, margin, alt, title) {
+
+ var opts = {};
+ if (typeof arguments[0] == 'object') {
+ // Called by options.
+ opts = arguments[0];
+ // overwrite cellSize and margin.
+ cellSize = opts.cellSize;
+ margin = opts.margin;
+ alt = opts.alt;
+ title = opts.title;
+ }
+
+ cellSize = cellSize || 2;
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
+
+ // Compose alt property surrogate
+ alt = (typeof alt === 'string') ? {text: alt} : alt || {};
+ alt.text = alt.text || null;
+ alt.id = (alt.text) ? alt.id || 'qrcode-description' : null;
+
+ // Compose title property surrogate
+ title = (typeof title === 'string') ? {text: title} : title || {};
+ title.text = title.text || null;
+ title.id = (title.text) ? title.id || 'qrcode-title' : null;
+
+ var size = _this.getModuleCount() * cellSize + margin * 2;
+ var c, mc, r, mr, qrSvg='', rect;
+
+ rect = 'l' + cellSize + ',0 0,' + cellSize +
+ ' -' + cellSize + ',0 0,-' + cellSize + 'z ';
+
+ qrSvg += '';
+ qrSvg += (title.text) ? '' +
+ escapeXml(title.text) + ' ' : '';
+ qrSvg += (alt.text) ? '' +
+ escapeXml(alt.text) + ' ' : '';
+ qrSvg += ' ';
+ qrSvg += ' ';
+ qrSvg += ' ';
+
+ return qrSvg;
+ };
+
+ _this.createDataURL = function(cellSize, margin) {
+
+ cellSize = cellSize || 2;
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
+
+ var size = _this.getModuleCount() * cellSize + margin * 2;
+ var min = margin;
+ var max = size - margin;
+
+ return createDataURL(size, size, function(x, y) {
+ if (min <= x && x < max && min <= y && y < max) {
+ var c = Math.floor( (x - min) / cellSize);
+ var r = Math.floor( (y - min) / cellSize);
+ return _this.isDark(r, c)? 0 : 1;
+ } else {
+ return 1;
+ }
+ } );
+ };
+
+ _this.createImgTag = function(cellSize, margin, alt) {
+
+ cellSize = cellSize || 2;
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
+
+ var size = _this.getModuleCount() * cellSize + margin * 2;
+
+ var img = '';
+ img += ' ';
+
+ return img;
+ };
+
+ var escapeXml = function(s) {
+ var escaped = '';
+ for (var i = 0; i < s.length; i += 1) {
+ var c = s.charAt(i);
+ switch(c) {
+ case '<': escaped += '<'; break;
+ case '>': escaped += '>'; break;
+ case '&': escaped += '&'; break;
+ case '"': escaped += '"'; break;
+ default : escaped += c; break;
+ }
+ }
+ return escaped;
+ };
+
+ var _createHalfASCII = function(margin) {
+ var cellSize = 1;
+ margin = (typeof margin == 'undefined')? cellSize * 2 : margin;
+
+ var size = _this.getModuleCount() * cellSize + margin * 2;
+ var min = margin;
+ var max = size - margin;
+
+ var y, x, r1, r2, p;
+
+ var blocks = {
+ '██': '█',
+ '█ ': '▀',
+ ' █': '▄',
+ ' ': ' '
+ };
+
+ var blocksLastLineNoMargin = {
+ '██': '▀',
+ '█ ': '▀',
+ ' █': ' ',
+ ' ': ' '
+ };
+
+ var ascii = '';
+ for (y = 0; y < size; y += 2) {
+ r1 = Math.floor((y - min) / cellSize);
+ r2 = Math.floor((y + 1 - min) / cellSize);
+ for (x = 0; x < size; x += 1) {
+ p = '█';
+
+ if (min <= x && x < max && min <= y && y < max && _this.isDark(r1, Math.floor((x - min) / cellSize))) {
+ p = ' ';
+ }
+
+ if (min <= x && x < max && min <= y+1 && y+1 < max && _this.isDark(r2, Math.floor((x - min) / cellSize))) {
+ p += ' ';
+ }
+ else {
+ p += '█';
+ }
+
+ // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square.
+ ascii += (margin < 1 && y+1 >= max) ? blocksLastLineNoMargin[p] : blocks[p];
+ }
+
+ ascii += '\n';
+ }
+
+ if (size % 2 && margin > 0) {
+ return ascii.substring(0, ascii.length - size - 1) + Array(size+1).join('▀');
+ }
+
+ return ascii.substring(0, ascii.length-1);
+ };
+
+ _this.createASCII = function(cellSize, margin) {
+ cellSize = cellSize || 1;
+
+ if (cellSize < 2) {
+ return _createHalfASCII(margin);
+ }
+
+ cellSize -= 1;
+ margin = (typeof margin == 'undefined')? cellSize * 2 : margin;
+
+ var size = _this.getModuleCount() * cellSize + margin * 2;
+ var min = margin;
+ var max = size - margin;
+
+ var y, x, r, p;
+
+ var white = Array(cellSize+1).join('██');
+ var black = Array(cellSize+1).join(' ');
+
+ var ascii = '';
+ var line = '';
+ for (y = 0; y < size; y += 1) {
+ r = Math.floor( (y - min) / cellSize);
+ line = '';
+ for (x = 0; x < size; x += 1) {
+ p = 1;
+
+ if (min <= x && x < max && min <= y && y < max && _this.isDark(r, Math.floor((x - min) / cellSize))) {
+ p = 0;
+ }
+
+ // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square.
+ line += p ? white : black;
+ }
+
+ for (r = 0; r < cellSize; r += 1) {
+ ascii += line + '\n';
+ }
+ }
+
+ return ascii.substring(0, ascii.length-1);
+ };
+
+ _this.renderTo2dContext = function(context, cellSize) {
+ cellSize = cellSize || 2;
+ var length = _this.getModuleCount();
+ for (var row = 0; row < length; row++) {
+ for (var col = 0; col < length; col++) {
+ context.fillStyle = _this.isDark(row, col) ? 'black' : 'white';
+ context.fillRect(row * cellSize, col * cellSize, cellSize, cellSize);
+ }
+ }
+ }
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // qrcode.stringToBytes
+ //---------------------------------------------------------------------
+
+ qrcode.stringToBytesFuncs = {
+ 'default' : function(s) {
+ var bytes = [];
+ for (var i = 0; i < s.length; i += 1) {
+ var c = s.charCodeAt(i);
+ bytes.push(c & 0xff);
+ }
+ return bytes;
+ }
+ };
+
+ qrcode.stringToBytes = qrcode.stringToBytesFuncs['default'];
+
+ //---------------------------------------------------------------------
+ // qrcode.createStringToBytes
+ //---------------------------------------------------------------------
+
+ /**
+ * @param unicodeData base64 string of byte array.
+ * [16bit Unicode],[16bit Bytes], ...
+ * @param numChars
+ */
+ qrcode.createStringToBytes = function(unicodeData, numChars) {
+
+ // create conversion map.
+
+ var unicodeMap = function() {
+
+ var bin = base64DecodeInputStream(unicodeData);
+ var read = function() {
+ var b = bin.read();
+ if (b == -1) throw 'eof';
+ return b;
+ };
+
+ var count = 0;
+ var unicodeMap = {};
+ while (true) {
+ var b0 = bin.read();
+ if (b0 == -1) break;
+ var b1 = read();
+ var b2 = read();
+ var b3 = read();
+ var k = String.fromCharCode( (b0 << 8) | b1);
+ var v = (b2 << 8) | b3;
+ unicodeMap[k] = v;
+ count += 1;
+ }
+ if (count != numChars) {
+ throw count + ' != ' + numChars;
+ }
+
+ return unicodeMap;
+ }();
+
+ var unknownChar = '?'.charCodeAt(0);
+
+ return function(s) {
+ var bytes = [];
+ for (var i = 0; i < s.length; i += 1) {
+ var c = s.charCodeAt(i);
+ if (c < 128) {
+ bytes.push(c);
+ } else {
+ var b = unicodeMap[s.charAt(i)];
+ if (typeof b == 'number') {
+ if ( (b & 0xff) == b) {
+ // 1byte
+ bytes.push(b);
+ } else {
+ // 2bytes
+ bytes.push(b >>> 8);
+ bytes.push(b & 0xff);
+ }
+ } else {
+ bytes.push(unknownChar);
+ }
+ }
+ }
+ return bytes;
+ };
+ };
+
+ //---------------------------------------------------------------------
+ // QRMode
+ //---------------------------------------------------------------------
+
+ var QRMode = {
+ MODE_NUMBER : 1 << 0,
+ MODE_ALPHA_NUM : 1 << 1,
+ MODE_8BIT_BYTE : 1 << 2,
+ MODE_KANJI : 1 << 3
+ };
+
+ //---------------------------------------------------------------------
+ // QRErrorCorrectionLevel
+ //---------------------------------------------------------------------
+
+ var QRErrorCorrectionLevel = {
+ L : 1,
+ M : 0,
+ Q : 3,
+ H : 2
+ };
+
+ //---------------------------------------------------------------------
+ // QRMaskPattern
+ //---------------------------------------------------------------------
+
+ var QRMaskPattern = {
+ PATTERN000 : 0,
+ PATTERN001 : 1,
+ PATTERN010 : 2,
+ PATTERN011 : 3,
+ PATTERN100 : 4,
+ PATTERN101 : 5,
+ PATTERN110 : 6,
+ PATTERN111 : 7
+ };
+
+ //---------------------------------------------------------------------
+ // QRUtil
+ //---------------------------------------------------------------------
+
+ var QRUtil = function() {
+
+ var PATTERN_POSITION_TABLE = [
+ [],
+ [6, 18],
+ [6, 22],
+ [6, 26],
+ [6, 30],
+ [6, 34],
+ [6, 22, 38],
+ [6, 24, 42],
+ [6, 26, 46],
+ [6, 28, 50],
+ [6, 30, 54],
+ [6, 32, 58],
+ [6, 34, 62],
+ [6, 26, 46, 66],
+ [6, 26, 48, 70],
+ [6, 26, 50, 74],
+ [6, 30, 54, 78],
+ [6, 30, 56, 82],
+ [6, 30, 58, 86],
+ [6, 34, 62, 90],
+ [6, 28, 50, 72, 94],
+ [6, 26, 50, 74, 98],
+ [6, 30, 54, 78, 102],
+ [6, 28, 54, 80, 106],
+ [6, 32, 58, 84, 110],
+ [6, 30, 58, 86, 114],
+ [6, 34, 62, 90, 118],
+ [6, 26, 50, 74, 98, 122],
+ [6, 30, 54, 78, 102, 126],
+ [6, 26, 52, 78, 104, 130],
+ [6, 30, 56, 82, 108, 134],
+ [6, 34, 60, 86, 112, 138],
+ [6, 30, 58, 86, 114, 142],
+ [6, 34, 62, 90, 118, 146],
+ [6, 30, 54, 78, 102, 126, 150],
+ [6, 24, 50, 76, 102, 128, 154],
+ [6, 28, 54, 80, 106, 132, 158],
+ [6, 32, 58, 84, 110, 136, 162],
+ [6, 26, 54, 82, 110, 138, 166],
+ [6, 30, 58, 86, 114, 142, 170]
+ ];
+ var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0);
+ var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0);
+ var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1);
+
+ var _this = {};
+
+ var getBCHDigit = function(data) {
+ var digit = 0;
+ while (data != 0) {
+ digit += 1;
+ data >>>= 1;
+ }
+ return digit;
+ };
+
+ _this.getBCHTypeInfo = function(data) {
+ var d = data << 10;
+ while (getBCHDigit(d) - getBCHDigit(G15) >= 0) {
+ d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15) ) );
+ }
+ return ( (data << 10) | d) ^ G15_MASK;
+ };
+
+ _this.getBCHTypeNumber = function(data) {
+ var d = data << 12;
+ while (getBCHDigit(d) - getBCHDigit(G18) >= 0) {
+ d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18) ) );
+ }
+ return (data << 12) | d;
+ };
+
+ _this.getPatternPosition = function(typeNumber) {
+ return PATTERN_POSITION_TABLE[typeNumber - 1];
+ };
+
+ _this.getMaskFunction = function(maskPattern) {
+
+ switch (maskPattern) {
+
+ case QRMaskPattern.PATTERN000 :
+ return function(i, j) { return (i + j) % 2 == 0; };
+ case QRMaskPattern.PATTERN001 :
+ return function(i, j) { return i % 2 == 0; };
+ case QRMaskPattern.PATTERN010 :
+ return function(i, j) { return j % 3 == 0; };
+ case QRMaskPattern.PATTERN011 :
+ return function(i, j) { return (i + j) % 3 == 0; };
+ case QRMaskPattern.PATTERN100 :
+ return function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0; };
+ case QRMaskPattern.PATTERN101 :
+ return function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0; };
+ case QRMaskPattern.PATTERN110 :
+ return function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0; };
+ case QRMaskPattern.PATTERN111 :
+ return function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0; };
+
+ default :
+ throw 'bad maskPattern:' + maskPattern;
+ }
+ };
+
+ _this.getErrorCorrectPolynomial = function(errorCorrectLength) {
+ var a = qrPolynomial([1], 0);
+ for (var i = 0; i < errorCorrectLength; i += 1) {
+ a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0) );
+ }
+ return a;
+ };
+
+ _this.getLengthInBits = function(mode, type) {
+
+ if (1 <= type && type < 10) {
+
+ // 1 - 9
+
+ switch(mode) {
+ case QRMode.MODE_NUMBER : return 10;
+ case QRMode.MODE_ALPHA_NUM : return 9;
+ case QRMode.MODE_8BIT_BYTE : return 8;
+ case QRMode.MODE_KANJI : return 8;
+ default :
+ throw 'mode:' + mode;
+ }
+
+ } else if (type < 27) {
+
+ // 10 - 26
+
+ switch(mode) {
+ case QRMode.MODE_NUMBER : return 12;
+ case QRMode.MODE_ALPHA_NUM : return 11;
+ case QRMode.MODE_8BIT_BYTE : return 16;
+ case QRMode.MODE_KANJI : return 10;
+ default :
+ throw 'mode:' + mode;
+ }
+
+ } else if (type < 41) {
+
+ // 27 - 40
+
+ switch(mode) {
+ case QRMode.MODE_NUMBER : return 14;
+ case QRMode.MODE_ALPHA_NUM : return 13;
+ case QRMode.MODE_8BIT_BYTE : return 16;
+ case QRMode.MODE_KANJI : return 12;
+ default :
+ throw 'mode:' + mode;
+ }
+
+ } else {
+ throw 'type:' + type;
+ }
+ };
+
+ _this.getLostPoint = function(qrcode) {
+
+ var moduleCount = qrcode.getModuleCount();
+
+ var lostPoint = 0;
+
+ // LEVEL1
+
+ for (var row = 0; row < moduleCount; row += 1) {
+ for (var col = 0; col < moduleCount; col += 1) {
+
+ var sameCount = 0;
+ var dark = qrcode.isDark(row, col);
+
+ for (var r = -1; r <= 1; r += 1) {
+
+ if (row + r < 0 || moduleCount <= row + r) {
+ continue;
+ }
+
+ for (var c = -1; c <= 1; c += 1) {
+
+ if (col + c < 0 || moduleCount <= col + c) {
+ continue;
+ }
+
+ if (r == 0 && c == 0) {
+ continue;
+ }
+
+ if (dark == qrcode.isDark(row + r, col + c) ) {
+ sameCount += 1;
+ }
+ }
+ }
+
+ if (sameCount > 5) {
+ lostPoint += (3 + sameCount - 5);
+ }
+ }
+ };
+
+ // LEVEL2
+
+ for (var row = 0; row < moduleCount - 1; row += 1) {
+ for (var col = 0; col < moduleCount - 1; col += 1) {
+ var count = 0;
+ if (qrcode.isDark(row, col) ) count += 1;
+ if (qrcode.isDark(row + 1, col) ) count += 1;
+ if (qrcode.isDark(row, col + 1) ) count += 1;
+ if (qrcode.isDark(row + 1, col + 1) ) count += 1;
+ if (count == 0 || count == 4) {
+ lostPoint += 3;
+ }
+ }
+ }
+
+ // LEVEL3
+
+ for (var row = 0; row < moduleCount; row += 1) {
+ for (var col = 0; col < moduleCount - 6; col += 1) {
+ if (qrcode.isDark(row, col)
+ && !qrcode.isDark(row, col + 1)
+ && qrcode.isDark(row, col + 2)
+ && qrcode.isDark(row, col + 3)
+ && qrcode.isDark(row, col + 4)
+ && !qrcode.isDark(row, col + 5)
+ && qrcode.isDark(row, col + 6) ) {
+ lostPoint += 40;
+ }
+ }
+ }
+
+ for (var col = 0; col < moduleCount; col += 1) {
+ for (var row = 0; row < moduleCount - 6; row += 1) {
+ if (qrcode.isDark(row, col)
+ && !qrcode.isDark(row + 1, col)
+ && qrcode.isDark(row + 2, col)
+ && qrcode.isDark(row + 3, col)
+ && qrcode.isDark(row + 4, col)
+ && !qrcode.isDark(row + 5, col)
+ && qrcode.isDark(row + 6, col) ) {
+ lostPoint += 40;
+ }
+ }
+ }
+
+ // LEVEL4
+
+ var darkCount = 0;
+
+ for (var col = 0; col < moduleCount; col += 1) {
+ for (var row = 0; row < moduleCount; row += 1) {
+ if (qrcode.isDark(row, col) ) {
+ darkCount += 1;
+ }
+ }
+ }
+
+ var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
+ lostPoint += ratio * 10;
+
+ return lostPoint;
+ };
+
+ return _this;
+ }();
+
+ //---------------------------------------------------------------------
+ // QRMath
+ //---------------------------------------------------------------------
+
+ var QRMath = function() {
+
+ var EXP_TABLE = new Array(256);
+ var LOG_TABLE = new Array(256);
+
+ // initialize tables
+ for (var i = 0; i < 8; i += 1) {
+ EXP_TABLE[i] = 1 << i;
+ }
+ for (var i = 8; i < 256; i += 1) {
+ EXP_TABLE[i] = EXP_TABLE[i - 4]
+ ^ EXP_TABLE[i - 5]
+ ^ EXP_TABLE[i - 6]
+ ^ EXP_TABLE[i - 8];
+ }
+ for (var i = 0; i < 255; i += 1) {
+ LOG_TABLE[EXP_TABLE[i] ] = i;
+ }
+
+ var _this = {};
+
+ _this.glog = function(n) {
+
+ if (n < 1) {
+ throw 'glog(' + n + ')';
+ }
+
+ return LOG_TABLE[n];
+ };
+
+ _this.gexp = function(n) {
+
+ while (n < 0) {
+ n += 255;
+ }
+
+ while (n >= 256) {
+ n -= 255;
+ }
+
+ return EXP_TABLE[n];
+ };
+
+ return _this;
+ }();
+
+ //---------------------------------------------------------------------
+ // qrPolynomial
+ //---------------------------------------------------------------------
+
+ function qrPolynomial(num, shift) {
+
+ if (typeof num.length == 'undefined') {
+ throw num.length + '/' + shift;
+ }
+
+ var _num = function() {
+ var offset = 0;
+ while (offset < num.length && num[offset] == 0) {
+ offset += 1;
+ }
+ var _num = new Array(num.length - offset + shift);
+ for (var i = 0; i < num.length - offset; i += 1) {
+ _num[i] = num[i + offset];
+ }
+ return _num;
+ }();
+
+ var _this = {};
+
+ _this.getAt = function(index) {
+ return _num[index];
+ };
+
+ _this.getLength = function() {
+ return _num.length;
+ };
+
+ _this.multiply = function(e) {
+
+ var num = new Array(_this.getLength() + e.getLength() - 1);
+
+ for (var i = 0; i < _this.getLength(); i += 1) {
+ for (var j = 0; j < e.getLength(); j += 1) {
+ num[i + j] ^= QRMath.gexp(QRMath.glog(_this.getAt(i) ) + QRMath.glog(e.getAt(j) ) );
+ }
+ }
+
+ return qrPolynomial(num, 0);
+ };
+
+ _this.mod = function(e) {
+
+ if (_this.getLength() - e.getLength() < 0) {
+ return _this;
+ }
+
+ var ratio = QRMath.glog(_this.getAt(0) ) - QRMath.glog(e.getAt(0) );
+
+ var num = new Array(_this.getLength() );
+ for (var i = 0; i < _this.getLength(); i += 1) {
+ num[i] = _this.getAt(i);
+ }
+
+ for (var i = 0; i < e.getLength(); i += 1) {
+ num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i) ) + ratio);
+ }
+
+ // recursive call
+ return qrPolynomial(num, 0).mod(e);
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // QRRSBlock
+ //---------------------------------------------------------------------
+
+ var QRRSBlock = function() {
+
+ var RS_BLOCK_TABLE = [
+
+ // L
+ // M
+ // Q
+ // H
+
+ // 1
+ [1, 26, 19],
+ [1, 26, 16],
+ [1, 26, 13],
+ [1, 26, 9],
+
+ // 2
+ [1, 44, 34],
+ [1, 44, 28],
+ [1, 44, 22],
+ [1, 44, 16],
+
+ // 3
+ [1, 70, 55],
+ [1, 70, 44],
+ [2, 35, 17],
+ [2, 35, 13],
+
+ // 4
+ [1, 100, 80],
+ [2, 50, 32],
+ [2, 50, 24],
+ [4, 25, 9],
+
+ // 5
+ [1, 134, 108],
+ [2, 67, 43],
+ [2, 33, 15, 2, 34, 16],
+ [2, 33, 11, 2, 34, 12],
+
+ // 6
+ [2, 86, 68],
+ [4, 43, 27],
+ [4, 43, 19],
+ [4, 43, 15],
+
+ // 7
+ [2, 98, 78],
+ [4, 49, 31],
+ [2, 32, 14, 4, 33, 15],
+ [4, 39, 13, 1, 40, 14],
+
+ // 8
+ [2, 121, 97],
+ [2, 60, 38, 2, 61, 39],
+ [4, 40, 18, 2, 41, 19],
+ [4, 40, 14, 2, 41, 15],
+
+ // 9
+ [2, 146, 116],
+ [3, 58, 36, 2, 59, 37],
+ [4, 36, 16, 4, 37, 17],
+ [4, 36, 12, 4, 37, 13],
+
+ // 10
+ [2, 86, 68, 2, 87, 69],
+ [4, 69, 43, 1, 70, 44],
+ [6, 43, 19, 2, 44, 20],
+ [6, 43, 15, 2, 44, 16],
+
+ // 11
+ [4, 101, 81],
+ [1, 80, 50, 4, 81, 51],
+ [4, 50, 22, 4, 51, 23],
+ [3, 36, 12, 8, 37, 13],
+
+ // 12
+ [2, 116, 92, 2, 117, 93],
+ [6, 58, 36, 2, 59, 37],
+ [4, 46, 20, 6, 47, 21],
+ [7, 42, 14, 4, 43, 15],
+
+ // 13
+ [4, 133, 107],
+ [8, 59, 37, 1, 60, 38],
+ [8, 44, 20, 4, 45, 21],
+ [12, 33, 11, 4, 34, 12],
+
+ // 14
+ [3, 145, 115, 1, 146, 116],
+ [4, 64, 40, 5, 65, 41],
+ [11, 36, 16, 5, 37, 17],
+ [11, 36, 12, 5, 37, 13],
+
+ // 15
+ [5, 109, 87, 1, 110, 88],
+ [5, 65, 41, 5, 66, 42],
+ [5, 54, 24, 7, 55, 25],
+ [11, 36, 12, 7, 37, 13],
+
+ // 16
+ [5, 122, 98, 1, 123, 99],
+ [7, 73, 45, 3, 74, 46],
+ [15, 43, 19, 2, 44, 20],
+ [3, 45, 15, 13, 46, 16],
+
+ // 17
+ [1, 135, 107, 5, 136, 108],
+ [10, 74, 46, 1, 75, 47],
+ [1, 50, 22, 15, 51, 23],
+ [2, 42, 14, 17, 43, 15],
+
+ // 18
+ [5, 150, 120, 1, 151, 121],
+ [9, 69, 43, 4, 70, 44],
+ [17, 50, 22, 1, 51, 23],
+ [2, 42, 14, 19, 43, 15],
+
+ // 19
+ [3, 141, 113, 4, 142, 114],
+ [3, 70, 44, 11, 71, 45],
+ [17, 47, 21, 4, 48, 22],
+ [9, 39, 13, 16, 40, 14],
+
+ // 20
+ [3, 135, 107, 5, 136, 108],
+ [3, 67, 41, 13, 68, 42],
+ [15, 54, 24, 5, 55, 25],
+ [15, 43, 15, 10, 44, 16],
+
+ // 21
+ [4, 144, 116, 4, 145, 117],
+ [17, 68, 42],
+ [17, 50, 22, 6, 51, 23],
+ [19, 46, 16, 6, 47, 17],
+
+ // 22
+ [2, 139, 111, 7, 140, 112],
+ [17, 74, 46],
+ [7, 54, 24, 16, 55, 25],
+ [34, 37, 13],
+
+ // 23
+ [4, 151, 121, 5, 152, 122],
+ [4, 75, 47, 14, 76, 48],
+ [11, 54, 24, 14, 55, 25],
+ [16, 45, 15, 14, 46, 16],
+
+ // 24
+ [6, 147, 117, 4, 148, 118],
+ [6, 73, 45, 14, 74, 46],
+ [11, 54, 24, 16, 55, 25],
+ [30, 46, 16, 2, 47, 17],
+
+ // 25
+ [8, 132, 106, 4, 133, 107],
+ [8, 75, 47, 13, 76, 48],
+ [7, 54, 24, 22, 55, 25],
+ [22, 45, 15, 13, 46, 16],
+
+ // 26
+ [10, 142, 114, 2, 143, 115],
+ [19, 74, 46, 4, 75, 47],
+ [28, 50, 22, 6, 51, 23],
+ [33, 46, 16, 4, 47, 17],
+
+ // 27
+ [8, 152, 122, 4, 153, 123],
+ [22, 73, 45, 3, 74, 46],
+ [8, 53, 23, 26, 54, 24],
+ [12, 45, 15, 28, 46, 16],
+
+ // 28
+ [3, 147, 117, 10, 148, 118],
+ [3, 73, 45, 23, 74, 46],
+ [4, 54, 24, 31, 55, 25],
+ [11, 45, 15, 31, 46, 16],
+
+ // 29
+ [7, 146, 116, 7, 147, 117],
+ [21, 73, 45, 7, 74, 46],
+ [1, 53, 23, 37, 54, 24],
+ [19, 45, 15, 26, 46, 16],
+
+ // 30
+ [5, 145, 115, 10, 146, 116],
+ [19, 75, 47, 10, 76, 48],
+ [15, 54, 24, 25, 55, 25],
+ [23, 45, 15, 25, 46, 16],
+
+ // 31
+ [13, 145, 115, 3, 146, 116],
+ [2, 74, 46, 29, 75, 47],
+ [42, 54, 24, 1, 55, 25],
+ [23, 45, 15, 28, 46, 16],
+
+ // 32
+ [17, 145, 115],
+ [10, 74, 46, 23, 75, 47],
+ [10, 54, 24, 35, 55, 25],
+ [19, 45, 15, 35, 46, 16],
+
+ // 33
+ [17, 145, 115, 1, 146, 116],
+ [14, 74, 46, 21, 75, 47],
+ [29, 54, 24, 19, 55, 25],
+ [11, 45, 15, 46, 46, 16],
+
+ // 34
+ [13, 145, 115, 6, 146, 116],
+ [14, 74, 46, 23, 75, 47],
+ [44, 54, 24, 7, 55, 25],
+ [59, 46, 16, 1, 47, 17],
+
+ // 35
+ [12, 151, 121, 7, 152, 122],
+ [12, 75, 47, 26, 76, 48],
+ [39, 54, 24, 14, 55, 25],
+ [22, 45, 15, 41, 46, 16],
+
+ // 36
+ [6, 151, 121, 14, 152, 122],
+ [6, 75, 47, 34, 76, 48],
+ [46, 54, 24, 10, 55, 25],
+ [2, 45, 15, 64, 46, 16],
+
+ // 37
+ [17, 152, 122, 4, 153, 123],
+ [29, 74, 46, 14, 75, 47],
+ [49, 54, 24, 10, 55, 25],
+ [24, 45, 15, 46, 46, 16],
+
+ // 38
+ [4, 152, 122, 18, 153, 123],
+ [13, 74, 46, 32, 75, 47],
+ [48, 54, 24, 14, 55, 25],
+ [42, 45, 15, 32, 46, 16],
+
+ // 39
+ [20, 147, 117, 4, 148, 118],
+ [40, 75, 47, 7, 76, 48],
+ [43, 54, 24, 22, 55, 25],
+ [10, 45, 15, 67, 46, 16],
+
+ // 40
+ [19, 148, 118, 6, 149, 119],
+ [18, 75, 47, 31, 76, 48],
+ [34, 54, 24, 34, 55, 25],
+ [20, 45, 15, 61, 46, 16]
+ ];
+
+ var qrRSBlock = function(totalCount, dataCount) {
+ var _this = {};
+ _this.totalCount = totalCount;
+ _this.dataCount = dataCount;
+ return _this;
+ };
+
+ var _this = {};
+
+ var getRsBlockTable = function(typeNumber, errorCorrectionLevel) {
+
+ switch(errorCorrectionLevel) {
+ case QRErrorCorrectionLevel.L :
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
+ case QRErrorCorrectionLevel.M :
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
+ case QRErrorCorrectionLevel.Q :
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
+ case QRErrorCorrectionLevel.H :
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
+ default :
+ return undefined;
+ }
+ };
+
+ _this.getRSBlocks = function(typeNumber, errorCorrectionLevel) {
+
+ var rsBlock = getRsBlockTable(typeNumber, errorCorrectionLevel);
+
+ if (typeof rsBlock == 'undefined') {
+ throw 'bad rs block @ typeNumber:' + typeNumber +
+ '/errorCorrectionLevel:' + errorCorrectionLevel;
+ }
+
+ var length = rsBlock.length / 3;
+
+ var list = [];
+
+ for (var i = 0; i < length; i += 1) {
+
+ var count = rsBlock[i * 3 + 0];
+ var totalCount = rsBlock[i * 3 + 1];
+ var dataCount = rsBlock[i * 3 + 2];
+
+ for (var j = 0; j < count; j += 1) {
+ list.push(qrRSBlock(totalCount, dataCount) );
+ }
+ }
+
+ return list;
+ };
+
+ return _this;
+ }();
+
+ //---------------------------------------------------------------------
+ // qrBitBuffer
+ //---------------------------------------------------------------------
+
+ var qrBitBuffer = function() {
+
+ var _buffer = [];
+ var _length = 0;
+
+ var _this = {};
+
+ _this.getBuffer = function() {
+ return _buffer;
+ };
+
+ _this.getAt = function(index) {
+ var bufIndex = Math.floor(index / 8);
+ return ( (_buffer[bufIndex] >>> (7 - index % 8) ) & 1) == 1;
+ };
+
+ _this.put = function(num, length) {
+ for (var i = 0; i < length; i += 1) {
+ _this.putBit( ( (num >>> (length - i - 1) ) & 1) == 1);
+ }
+ };
+
+ _this.getLengthInBits = function() {
+ return _length;
+ };
+
+ _this.putBit = function(bit) {
+
+ var bufIndex = Math.floor(_length / 8);
+ if (_buffer.length <= bufIndex) {
+ _buffer.push(0);
+ }
+
+ if (bit) {
+ _buffer[bufIndex] |= (0x80 >>> (_length % 8) );
+ }
+
+ _length += 1;
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // qrNumber
+ //---------------------------------------------------------------------
+
+ var qrNumber = function(data) {
+
+ var _mode = QRMode.MODE_NUMBER;
+ var _data = data;
+
+ var _this = {};
+
+ _this.getMode = function() {
+ return _mode;
+ };
+
+ _this.getLength = function(buffer) {
+ return _data.length;
+ };
+
+ _this.write = function(buffer) {
+
+ var data = _data;
+
+ var i = 0;
+
+ while (i + 2 < data.length) {
+ buffer.put(strToNum(data.substring(i, i + 3) ), 10);
+ i += 3;
+ }
+
+ if (i < data.length) {
+ if (data.length - i == 1) {
+ buffer.put(strToNum(data.substring(i, i + 1) ), 4);
+ } else if (data.length - i == 2) {
+ buffer.put(strToNum(data.substring(i, i + 2) ), 7);
+ }
+ }
+ };
+
+ var strToNum = function(s) {
+ var num = 0;
+ for (var i = 0; i < s.length; i += 1) {
+ num = num * 10 + chatToNum(s.charAt(i) );
+ }
+ return num;
+ };
+
+ var chatToNum = function(c) {
+ if ('0' <= c && c <= '9') {
+ return c.charCodeAt(0) - '0'.charCodeAt(0);
+ }
+ throw 'illegal char :' + c;
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // qrAlphaNum
+ //---------------------------------------------------------------------
+
+ var qrAlphaNum = function(data) {
+
+ var _mode = QRMode.MODE_ALPHA_NUM;
+ var _data = data;
+
+ var _this = {};
+
+ _this.getMode = function() {
+ return _mode;
+ };
+
+ _this.getLength = function(buffer) {
+ return _data.length;
+ };
+
+ _this.write = function(buffer) {
+
+ var s = _data;
+
+ var i = 0;
+
+ while (i + 1 < s.length) {
+ buffer.put(
+ getCode(s.charAt(i) ) * 45 +
+ getCode(s.charAt(i + 1) ), 11);
+ i += 2;
+ }
+
+ if (i < s.length) {
+ buffer.put(getCode(s.charAt(i) ), 6);
+ }
+ };
+
+ var getCode = function(c) {
+
+ if ('0' <= c && c <= '9') {
+ return c.charCodeAt(0) - '0'.charCodeAt(0);
+ } else if ('A' <= c && c <= 'Z') {
+ return c.charCodeAt(0) - 'A'.charCodeAt(0) + 10;
+ } else {
+ switch (c) {
+ case ' ' : return 36;
+ case '$' : return 37;
+ case '%' : return 38;
+ case '*' : return 39;
+ case '+' : return 40;
+ case '-' : return 41;
+ case '.' : return 42;
+ case '/' : return 43;
+ case ':' : return 44;
+ default :
+ throw 'illegal char :' + c;
+ }
+ }
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // qr8BitByte
+ //---------------------------------------------------------------------
+
+ var qr8BitByte = function(data) {
+
+ var _mode = QRMode.MODE_8BIT_BYTE;
+ var _data = data;
+ var _bytes = qrcode.stringToBytes(data);
+
+ var _this = {};
+
+ _this.getMode = function() {
+ return _mode;
+ };
+
+ _this.getLength = function(buffer) {
+ return _bytes.length;
+ };
+
+ _this.write = function(buffer) {
+ for (var i = 0; i < _bytes.length; i += 1) {
+ buffer.put(_bytes[i], 8);
+ }
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // qrKanji
+ //---------------------------------------------------------------------
+
+ var qrKanji = function(data) {
+
+ var _mode = QRMode.MODE_KANJI;
+ var _data = data;
+
+ var stringToBytes = qrcode.stringToBytesFuncs['SJIS'];
+ if (!stringToBytes) {
+ throw 'sjis not supported.';
+ }
+ !function(c, code) {
+ // self test for sjis support.
+ var test = stringToBytes(c);
+ if (test.length != 2 || ( (test[0] << 8) | test[1]) != code) {
+ throw 'sjis not supported.';
+ }
+ }('\u53cb', 0x9746);
+
+ var _bytes = stringToBytes(data);
+
+ var _this = {};
+
+ _this.getMode = function() {
+ return _mode;
+ };
+
+ _this.getLength = function(buffer) {
+ return ~~(_bytes.length / 2);
+ };
+
+ _this.write = function(buffer) {
+
+ var data = _bytes;
+
+ var i = 0;
+
+ while (i + 1 < data.length) {
+
+ var c = ( (0xff & data[i]) << 8) | (0xff & data[i + 1]);
+
+ if (0x8140 <= c && c <= 0x9FFC) {
+ c -= 0x8140;
+ } else if (0xE040 <= c && c <= 0xEBBF) {
+ c -= 0xC140;
+ } else {
+ throw 'illegal char at ' + (i + 1) + '/' + c;
+ }
+
+ c = ( (c >>> 8) & 0xff) * 0xC0 + (c & 0xff);
+
+ buffer.put(c, 13);
+
+ i += 2;
+ }
+
+ if (i < data.length) {
+ throw 'illegal char at ' + (i + 1);
+ }
+ };
+
+ return _this;
+ };
+
+ //=====================================================================
+ // GIF Support etc.
+ //
+
+ //---------------------------------------------------------------------
+ // byteArrayOutputStream
+ //---------------------------------------------------------------------
+
+ var byteArrayOutputStream = function() {
+
+ var _bytes = [];
+
+ var _this = {};
+
+ _this.writeByte = function(b) {
+ _bytes.push(b & 0xff);
+ };
+
+ _this.writeShort = function(i) {
+ _this.writeByte(i);
+ _this.writeByte(i >>> 8);
+ };
+
+ _this.writeBytes = function(b, off, len) {
+ off = off || 0;
+ len = len || b.length;
+ for (var i = 0; i < len; i += 1) {
+ _this.writeByte(b[i + off]);
+ }
+ };
+
+ _this.writeString = function(s) {
+ for (var i = 0; i < s.length; i += 1) {
+ _this.writeByte(s.charCodeAt(i) );
+ }
+ };
+
+ _this.toByteArray = function() {
+ return _bytes;
+ };
+
+ _this.toString = function() {
+ var s = '';
+ s += '[';
+ for (var i = 0; i < _bytes.length; i += 1) {
+ if (i > 0) {
+ s += ',';
+ }
+ s += _bytes[i];
+ }
+ s += ']';
+ return s;
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // base64EncodeOutputStream
+ //---------------------------------------------------------------------
+
+ var base64EncodeOutputStream = function() {
+
+ var _buffer = 0;
+ var _buflen = 0;
+ var _length = 0;
+ var _base64 = '';
+
+ var _this = {};
+
+ var writeEncoded = function(b) {
+ _base64 += String.fromCharCode(encode(b & 0x3f) );
+ };
+
+ var encode = function(n) {
+ if (n < 0) {
+ // error.
+ } else if (n < 26) {
+ return 0x41 + n;
+ } else if (n < 52) {
+ return 0x61 + (n - 26);
+ } else if (n < 62) {
+ return 0x30 + (n - 52);
+ } else if (n == 62) {
+ return 0x2b;
+ } else if (n == 63) {
+ return 0x2f;
+ }
+ throw 'n:' + n;
+ };
+
+ _this.writeByte = function(n) {
+
+ _buffer = (_buffer << 8) | (n & 0xff);
+ _buflen += 8;
+ _length += 1;
+
+ while (_buflen >= 6) {
+ writeEncoded(_buffer >>> (_buflen - 6) );
+ _buflen -= 6;
+ }
+ };
+
+ _this.flush = function() {
+
+ if (_buflen > 0) {
+ writeEncoded(_buffer << (6 - _buflen) );
+ _buffer = 0;
+ _buflen = 0;
+ }
+
+ if (_length % 3 != 0) {
+ // padding
+ var padlen = 3 - _length % 3;
+ for (var i = 0; i < padlen; i += 1) {
+ _base64 += '=';
+ }
+ }
+ };
+
+ _this.toString = function() {
+ return _base64;
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // base64DecodeInputStream
+ //---------------------------------------------------------------------
+
+ var base64DecodeInputStream = function(str) {
+
+ var _str = str;
+ var _pos = 0;
+ var _buffer = 0;
+ var _buflen = 0;
+
+ var _this = {};
+
+ _this.read = function() {
+
+ while (_buflen < 8) {
+
+ if (_pos >= _str.length) {
+ if (_buflen == 0) {
+ return -1;
+ }
+ throw 'unexpected end of file./' + _buflen;
+ }
+
+ var c = _str.charAt(_pos);
+ _pos += 1;
+
+ if (c == '=') {
+ _buflen = 0;
+ return -1;
+ } else if (c.match(/^\s$/) ) {
+ // ignore if whitespace.
+ continue;
+ }
+
+ _buffer = (_buffer << 6) | decode(c.charCodeAt(0) );
+ _buflen += 6;
+ }
+
+ var n = (_buffer >>> (_buflen - 8) ) & 0xff;
+ _buflen -= 8;
+ return n;
+ };
+
+ var decode = function(c) {
+ if (0x41 <= c && c <= 0x5a) {
+ return c - 0x41;
+ } else if (0x61 <= c && c <= 0x7a) {
+ return c - 0x61 + 26;
+ } else if (0x30 <= c && c <= 0x39) {
+ return c - 0x30 + 52;
+ } else if (c == 0x2b) {
+ return 62;
+ } else if (c == 0x2f) {
+ return 63;
+ } else {
+ throw 'c:' + c;
+ }
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // gifImage (B/W)
+ //---------------------------------------------------------------------
+
+ var gifImage = function(width, height) {
+
+ var _width = width;
+ var _height = height;
+ var _data = new Array(width * height);
+
+ var _this = {};
+
+ _this.setPixel = function(x, y, pixel) {
+ _data[y * _width + x] = pixel;
+ };
+
+ _this.write = function(out) {
+
+ //---------------------------------
+ // GIF Signature
+
+ out.writeString('GIF87a');
+
+ //---------------------------------
+ // Screen Descriptor
+
+ out.writeShort(_width);
+ out.writeShort(_height);
+
+ out.writeByte(0x80); // 2bit
+ out.writeByte(0);
+ out.writeByte(0);
+
+ //---------------------------------
+ // Global Color Map
+
+ // black
+ out.writeByte(0x00);
+ out.writeByte(0x00);
+ out.writeByte(0x00);
+
+ // white
+ out.writeByte(0xff);
+ out.writeByte(0xff);
+ out.writeByte(0xff);
+
+ //---------------------------------
+ // Image Descriptor
+
+ out.writeString(',');
+ out.writeShort(0);
+ out.writeShort(0);
+ out.writeShort(_width);
+ out.writeShort(_height);
+ out.writeByte(0);
+
+ //---------------------------------
+ // Local Color Map
+
+ //---------------------------------
+ // Raster Data
+
+ var lzwMinCodeSize = 2;
+ var raster = getLZWRaster(lzwMinCodeSize);
+
+ out.writeByte(lzwMinCodeSize);
+
+ var offset = 0;
+
+ while (raster.length - offset > 255) {
+ out.writeByte(255);
+ out.writeBytes(raster, offset, 255);
+ offset += 255;
+ }
+
+ out.writeByte(raster.length - offset);
+ out.writeBytes(raster, offset, raster.length - offset);
+ out.writeByte(0x00);
+
+ //---------------------------------
+ // GIF Terminator
+ out.writeString(';');
+ };
+
+ var bitOutputStream = function(out) {
+
+ var _out = out;
+ var _bitLength = 0;
+ var _bitBuffer = 0;
+
+ var _this = {};
+
+ _this.write = function(data, length) {
+
+ if ( (data >>> length) != 0) {
+ throw 'length over';
+ }
+
+ while (_bitLength + length >= 8) {
+ _out.writeByte(0xff & ( (data << _bitLength) | _bitBuffer) );
+ length -= (8 - _bitLength);
+ data >>>= (8 - _bitLength);
+ _bitBuffer = 0;
+ _bitLength = 0;
+ }
+
+ _bitBuffer = (data << _bitLength) | _bitBuffer;
+ _bitLength = _bitLength + length;
+ };
+
+ _this.flush = function() {
+ if (_bitLength > 0) {
+ _out.writeByte(_bitBuffer);
+ }
+ };
+
+ return _this;
+ };
+
+ var getLZWRaster = function(lzwMinCodeSize) {
+
+ var clearCode = 1 << lzwMinCodeSize;
+ var endCode = (1 << lzwMinCodeSize) + 1;
+ var bitLength = lzwMinCodeSize + 1;
+
+ // Setup LZWTable
+ var table = lzwTable();
+
+ for (var i = 0; i < clearCode; i += 1) {
+ table.add(String.fromCharCode(i) );
+ }
+ table.add(String.fromCharCode(clearCode) );
+ table.add(String.fromCharCode(endCode) );
+
+ var byteOut = byteArrayOutputStream();
+ var bitOut = bitOutputStream(byteOut);
+
+ // clear code
+ bitOut.write(clearCode, bitLength);
+
+ var dataIndex = 0;
+
+ var s = String.fromCharCode(_data[dataIndex]);
+ dataIndex += 1;
+
+ while (dataIndex < _data.length) {
+
+ var c = String.fromCharCode(_data[dataIndex]);
+ dataIndex += 1;
+
+ if (table.contains(s + c) ) {
+
+ s = s + c;
+
+ } else {
+
+ bitOut.write(table.indexOf(s), bitLength);
+
+ if (table.size() < 0xfff) {
+
+ if (table.size() == (1 << bitLength) ) {
+ bitLength += 1;
+ }
+
+ table.add(s + c);
+ }
+
+ s = c;
+ }
+ }
+
+ bitOut.write(table.indexOf(s), bitLength);
+
+ // end code
+ bitOut.write(endCode, bitLength);
+
+ bitOut.flush();
+
+ return byteOut.toByteArray();
+ };
+
+ var lzwTable = function() {
+
+ var _map = {};
+ var _size = 0;
+
+ var _this = {};
+
+ _this.add = function(key) {
+ if (_this.contains(key) ) {
+ throw 'dup key:' + key;
+ }
+ _map[key] = _size;
+ _size += 1;
+ };
+
+ _this.size = function() {
+ return _size;
+ };
+
+ _this.indexOf = function(key) {
+ return _map[key];
+ };
+
+ _this.contains = function(key) {
+ return typeof _map[key] != 'undefined';
+ };
+
+ return _this;
+ };
+
+ return _this;
+ };
+
+ var createDataURL = function(width, height, getPixel) {
+ var gif = gifImage(width, height);
+ for (var y = 0; y < height; y += 1) {
+ for (var x = 0; x < width; x += 1) {
+ gif.setPixel(x, y, getPixel(x, y) );
+ }
+ }
+
+ var b = byteArrayOutputStream();
+ gif.write(b);
+
+ var base64 = base64EncodeOutputStream();
+ var bytes = b.toByteArray();
+ for (var i = 0; i < bytes.length; i += 1) {
+ base64.writeByte(bytes[i]);
+ }
+ base64.flush();
+
+ return 'data:image/gif;base64,' + base64;
+ };
+
+ //---------------------------------------------------------------------
+ // returns qrcode function.
+
+ return qrcode;
+ }();
+
+ // multibyte support
+ !function() {
+
+ qrcode.stringToBytesFuncs['UTF-8'] = function(s) {
+ // http://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array
+ function toUTF8Array(str) {
+ var utf8 = [];
+ for (var i=0; i < str.length; i++) {
+ var charcode = str.charCodeAt(i);
+ if (charcode < 0x80) utf8.push(charcode);
+ else if (charcode < 0x800) {
+ utf8.push(0xc0 | (charcode >> 6),
+ 0x80 | (charcode & 0x3f));
+ }
+ else if (charcode < 0xd800 || charcode >= 0xe000) {
+ utf8.push(0xe0 | (charcode >> 12),
+ 0x80 | ((charcode>>6) & 0x3f),
+ 0x80 | (charcode & 0x3f));
+ }
+ // surrogate pair
+ else {
+ i++;
+ // UTF-16 encodes 0x10000-0x10FFFF by
+ // subtracting 0x10000 and splitting the
+ // 20 bits of 0x0-0xFFFFF into two halves
+ charcode = 0x10000 + (((charcode & 0x3ff)<<10)
+ | (str.charCodeAt(i) & 0x3ff));
+ utf8.push(0xf0 | (charcode >>18),
+ 0x80 | ((charcode>>12) & 0x3f),
+ 0x80 | ((charcode>>6) & 0x3f),
+ 0x80 | (charcode & 0x3f));
+ }
+ }
+ return utf8;
+ }
+ return toUTF8Array(s);
+ };
+
+ }();
+
+ (function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ define([], factory);
+ } else if (typeof exports === 'object') {
+ module.exports = factory();
+ }
+ }(function () {
+ return qrcode;
+ }));
\ No newline at end of file
diff --git a/src/storage/mod.rs b/library/src/storage/mod.rs
similarity index 100%
rename from src/storage/mod.rs
rename to library/src/storage/mod.rs
diff --git a/src/storage/stores/in_memory.rs b/library/src/storage/stores/in_memory.rs
similarity index 100%
rename from src/storage/stores/in_memory.rs
rename to library/src/storage/stores/in_memory.rs
diff --git a/src/storage/stores/mod.rs b/library/src/storage/stores/mod.rs
similarity index 100%
rename from src/storage/stores/mod.rs
rename to library/src/storage/stores/mod.rs
diff --git a/src/storage/stores/sled.rs b/library/src/storage/stores/sled.rs
similarity index 100%
rename from src/storage/stores/sled.rs
rename to library/src/storage/stores/sled.rs
diff --git a/src/storage/stores/sqlite.rs b/library/src/storage/stores/sqlite.rs
similarity index 100%
rename from src/storage/stores/sqlite.rs
rename to library/src/storage/stores/sqlite.rs
diff --git a/tests/common/mod.rs b/library/tests/common/mod.rs
similarity index 100%
rename from tests/common/mod.rs
rename to library/tests/common/mod.rs
diff --git a/tests/integration_tests/block_cache.rs b/library/tests/integration_tests/block_cache.rs
similarity index 100%
rename from tests/integration_tests/block_cache.rs
rename to library/tests/integration_tests/block_cache.rs
diff --git a/tests/integration_tests/invoice_tracking.rs b/library/tests/integration_tests/invoice_tracking.rs
similarity index 100%
rename from tests/integration_tests/invoice_tracking.rs
rename to library/tests/integration_tests/invoice_tracking.rs
diff --git a/tests/integration_tests/mod.rs b/library/tests/integration_tests/mod.rs
similarity index 100%
rename from tests/integration_tests/mod.rs
rename to library/tests/integration_tests/mod.rs
diff --git a/tests/integration_tests/scanning_thread_management.rs b/library/tests/integration_tests/scanning_thread_management.rs
similarity index 100%
rename from tests/integration_tests/scanning_thread_management.rs
rename to library/tests/integration_tests/scanning_thread_management.rs
diff --git a/tests/main.rs b/library/tests/main.rs
similarity index 100%
rename from tests/main.rs
rename to library/tests/main.rs
diff --git a/tests/rpc_resources/blocks/2477647/block.json b/library/tests/rpc_resources/blocks/2477647/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477647/block.json
rename to library/tests/rpc_resources/blocks/2477647/block.json
diff --git a/tests/rpc_resources/blocks/2477647/transactions_0.json b/library/tests/rpc_resources/blocks/2477647/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477647/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477647/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477647/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477647/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477647/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477647/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477648/block.json b/library/tests/rpc_resources/blocks/2477648/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477648/block.json
rename to library/tests/rpc_resources/blocks/2477648/block.json
diff --git a/tests/rpc_resources/blocks/2477648/transactions_0.json b/library/tests/rpc_resources/blocks/2477648/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477648/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477648/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477648/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477648/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477648/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477648/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477649/block.json b/library/tests/rpc_resources/blocks/2477649/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477649/block.json
rename to library/tests/rpc_resources/blocks/2477649/block.json
diff --git a/tests/rpc_resources/blocks/2477649/transactions_0.json b/library/tests/rpc_resources/blocks/2477649/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477649/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477649/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477649/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477649/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477649/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477649/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477650/block.json b/library/tests/rpc_resources/blocks/2477650/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477650/block.json
rename to library/tests/rpc_resources/blocks/2477650/block.json
diff --git a/tests/rpc_resources/blocks/2477650/transactions_0.json b/library/tests/rpc_resources/blocks/2477650/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477650/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477650/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477650/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477650/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477650/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477650/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477651/block.json b/library/tests/rpc_resources/blocks/2477651/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477651/block.json
rename to library/tests/rpc_resources/blocks/2477651/block.json
diff --git a/tests/rpc_resources/blocks/2477651/transactions_0.json b/library/tests/rpc_resources/blocks/2477651/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477651/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477651/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477651/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477651/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477651/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477651/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477652/block.json b/library/tests/rpc_resources/blocks/2477652/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477652/block.json
rename to library/tests/rpc_resources/blocks/2477652/block.json
diff --git a/tests/rpc_resources/blocks/2477652/transactions_0.json b/library/tests/rpc_resources/blocks/2477652/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477652/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477652/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477652/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477652/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477652/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477652/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477653/block.json b/library/tests/rpc_resources/blocks/2477653/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477653/block.json
rename to library/tests/rpc_resources/blocks/2477653/block.json
diff --git a/tests/rpc_resources/blocks/2477653/transactions_0.json b/library/tests/rpc_resources/blocks/2477653/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477653/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477653/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477653/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477653/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477653/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477653/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477654/block.json b/library/tests/rpc_resources/blocks/2477654/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477654/block.json
rename to library/tests/rpc_resources/blocks/2477654/block.json
diff --git a/tests/rpc_resources/blocks/2477654/transactions_0.json b/library/tests/rpc_resources/blocks/2477654/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477654/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477654/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477654/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477654/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477654/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477654/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477655/block.json b/library/tests/rpc_resources/blocks/2477655/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477655/block.json
rename to library/tests/rpc_resources/blocks/2477655/block.json
diff --git a/tests/rpc_resources/blocks/2477655/transactions_0.json b/library/tests/rpc_resources/blocks/2477655/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477655/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477655/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477655/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477655/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477655/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477655/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477656/block.json b/library/tests/rpc_resources/blocks/2477656/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477656/block.json
rename to library/tests/rpc_resources/blocks/2477656/block.json
diff --git a/tests/rpc_resources/blocks/2477656/daemon_height.json b/library/tests/rpc_resources/blocks/2477656/daemon_height.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477656/daemon_height.json
rename to library/tests/rpc_resources/blocks/2477656/daemon_height.json
diff --git a/tests/rpc_resources/blocks/2477656/transactions_0.json b/library/tests/rpc_resources/blocks/2477656/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477656/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477656/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477656/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477656/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477656/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477656/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477657/block.json b/library/tests/rpc_resources/blocks/2477657/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477657/block.json
rename to library/tests/rpc_resources/blocks/2477657/block.json
diff --git a/tests/rpc_resources/blocks/2477657/daemon_height.json b/library/tests/rpc_resources/blocks/2477657/daemon_height.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477657/daemon_height.json
rename to library/tests/rpc_resources/blocks/2477657/daemon_height.json
diff --git a/tests/rpc_resources/blocks/2477657/transactions_0.json b/library/tests/rpc_resources/blocks/2477657/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477657/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477657/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477657/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477657/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477657/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477657/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477657_alt/block.json b/library/tests/rpc_resources/blocks/2477657_alt/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477657_alt/block.json
rename to library/tests/rpc_resources/blocks/2477657_alt/block.json
diff --git a/tests/rpc_resources/blocks/2477657_alt/daemon_height.json b/library/tests/rpc_resources/blocks/2477657_alt/daemon_height.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477657_alt/daemon_height.json
rename to library/tests/rpc_resources/blocks/2477657_alt/daemon_height.json
diff --git a/tests/rpc_resources/blocks/2477657_alt/transactions_0.json b/library/tests/rpc_resources/blocks/2477657_alt/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477657_alt/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477657_alt/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477657_alt/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477657_alt/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477657_alt/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477657_alt/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477658/block.json b/library/tests/rpc_resources/blocks/2477658/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477658/block.json
rename to library/tests/rpc_resources/blocks/2477658/block.json
diff --git a/tests/rpc_resources/blocks/2477658/daemon_height.json b/library/tests/rpc_resources/blocks/2477658/daemon_height.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477658/daemon_height.json
rename to library/tests/rpc_resources/blocks/2477658/daemon_height.json
diff --git a/tests/rpc_resources/blocks/2477658/transactions_0.json b/library/tests/rpc_resources/blocks/2477658/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477658/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477658/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477658/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477658/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477658/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477658/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477658_alt/block.json b/library/tests/rpc_resources/blocks/2477658_alt/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477658_alt/block.json
rename to library/tests/rpc_resources/blocks/2477658_alt/block.json
diff --git a/tests/rpc_resources/blocks/2477658_alt/daemon_height.json b/library/tests/rpc_resources/blocks/2477658_alt/daemon_height.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477658_alt/daemon_height.json
rename to library/tests/rpc_resources/blocks/2477658_alt/daemon_height.json
diff --git a/tests/rpc_resources/blocks/2477658_alt/transactions_0.json b/library/tests/rpc_resources/blocks/2477658_alt/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477658_alt/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477658_alt/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477658_alt/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477658_alt/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477658_alt/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477658_alt/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477659/block.json b/library/tests/rpc_resources/blocks/2477659/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477659/block.json
rename to library/tests/rpc_resources/blocks/2477659/block.json
diff --git a/tests/rpc_resources/blocks/2477659/daemon_height.json b/library/tests/rpc_resources/blocks/2477659/daemon_height.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477659/daemon_height.json
rename to library/tests/rpc_resources/blocks/2477659/daemon_height.json
diff --git a/tests/rpc_resources/blocks/2477659/transactions_0.json b/library/tests/rpc_resources/blocks/2477659/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477659/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477659/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477659/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477659/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477659/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477659/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477660/block.json b/library/tests/rpc_resources/blocks/2477660/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477660/block.json
rename to library/tests/rpc_resources/blocks/2477660/block.json
diff --git a/tests/rpc_resources/blocks/2477660/daemon_height.json b/library/tests/rpc_resources/blocks/2477660/daemon_height.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477660/daemon_height.json
rename to library/tests/rpc_resources/blocks/2477660/daemon_height.json
diff --git a/tests/rpc_resources/blocks/2477660/transactions_0.json b/library/tests/rpc_resources/blocks/2477660/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477660/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477660/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477660/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477660/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477660/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477660/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477661/block.json b/library/tests/rpc_resources/blocks/2477661/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477661/block.json
rename to library/tests/rpc_resources/blocks/2477661/block.json
diff --git a/tests/rpc_resources/blocks/2477661/daemon_height.json b/library/tests/rpc_resources/blocks/2477661/daemon_height.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477661/daemon_height.json
rename to library/tests/rpc_resources/blocks/2477661/daemon_height.json
diff --git a/tests/rpc_resources/blocks/2477661/transactions_0.json b/library/tests/rpc_resources/blocks/2477661/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477661/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477661/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477661/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477661/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477661/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477661/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477662/block.json b/library/tests/rpc_resources/blocks/2477662/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477662/block.json
rename to library/tests/rpc_resources/blocks/2477662/block.json
diff --git a/tests/rpc_resources/blocks/2477662/daemon_height.json b/library/tests/rpc_resources/blocks/2477662/daemon_height.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477662/daemon_height.json
rename to library/tests/rpc_resources/blocks/2477662/daemon_height.json
diff --git a/tests/rpc_resources/blocks/2477662/transactions_0.json b/library/tests/rpc_resources/blocks/2477662/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477662/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477662/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477662/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477662/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477662/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477662/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477663/block.json b/library/tests/rpc_resources/blocks/2477663/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477663/block.json
rename to library/tests/rpc_resources/blocks/2477663/block.json
diff --git a/tests/rpc_resources/blocks/2477663/daemon_height.json b/library/tests/rpc_resources/blocks/2477663/daemon_height.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477663/daemon_height.json
rename to library/tests/rpc_resources/blocks/2477663/daemon_height.json
diff --git a/tests/rpc_resources/blocks/2477663/transactions_0.json b/library/tests/rpc_resources/blocks/2477663/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477663/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477663/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477663/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477663/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477663/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477663/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477664/block.json b/library/tests/rpc_resources/blocks/2477664/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477664/block.json
rename to library/tests/rpc_resources/blocks/2477664/block.json
diff --git a/tests/rpc_resources/blocks/2477664/daemon_height.json b/library/tests/rpc_resources/blocks/2477664/daemon_height.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477664/daemon_height.json
rename to library/tests/rpc_resources/blocks/2477664/daemon_height.json
diff --git a/tests/rpc_resources/blocks/2477664/transactions_0.json b/library/tests/rpc_resources/blocks/2477664/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477664/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477664/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477664/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477664/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477664/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477664/txs_hashes_0.json
diff --git a/tests/rpc_resources/blocks/2477665/block.json b/library/tests/rpc_resources/blocks/2477665/block.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477665/block.json
rename to library/tests/rpc_resources/blocks/2477665/block.json
diff --git a/tests/rpc_resources/blocks/2477665/daemon_height.json b/library/tests/rpc_resources/blocks/2477665/daemon_height.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477665/daemon_height.json
rename to library/tests/rpc_resources/blocks/2477665/daemon_height.json
diff --git a/tests/rpc_resources/blocks/2477665/transactions_0.json b/library/tests/rpc_resources/blocks/2477665/transactions_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477665/transactions_0.json
rename to library/tests/rpc_resources/blocks/2477665/transactions_0.json
diff --git a/tests/rpc_resources/blocks/2477665/txs_hashes_0.json b/library/tests/rpc_resources/blocks/2477665/txs_hashes_0.json
similarity index 100%
rename from tests/rpc_resources/blocks/2477665/txs_hashes_0.json
rename to library/tests/rpc_resources/blocks/2477665/txs_hashes_0.json
diff --git a/tests/rpc_resources/transactions/hashes_with_payment.json b/library/tests/rpc_resources/transactions/hashes_with_payment.json
similarity index 100%
rename from tests/rpc_resources/transactions/hashes_with_payment.json
rename to library/tests/rpc_resources/transactions/hashes_with_payment.json
diff --git a/tests/rpc_resources/transactions/hashes_with_payment_2.json b/library/tests/rpc_resources/transactions/hashes_with_payment_2.json
similarity index 100%
rename from tests/rpc_resources/transactions/hashes_with_payment_2.json
rename to library/tests/rpc_resources/transactions/hashes_with_payment_2.json
diff --git a/tests/rpc_resources/transactions/hashes_with_payment_account_0.json b/library/tests/rpc_resources/transactions/hashes_with_payment_account_0.json
similarity index 100%
rename from tests/rpc_resources/transactions/hashes_with_payment_account_0.json
rename to library/tests/rpc_resources/transactions/hashes_with_payment_account_0.json
diff --git a/tests/rpc_resources/transactions/hashes_with_payment_timelock.json b/library/tests/rpc_resources/transactions/hashes_with_payment_timelock.json
similarity index 100%
rename from tests/rpc_resources/transactions/hashes_with_payment_timelock.json
rename to library/tests/rpc_resources/transactions/hashes_with_payment_timelock.json
diff --git a/tests/rpc_resources/transactions/txs_with_payment.json b/library/tests/rpc_resources/transactions/txs_with_payment.json
similarity index 100%
rename from tests/rpc_resources/transactions/txs_with_payment.json
rename to library/tests/rpc_resources/transactions/txs_with_payment.json
diff --git a/tests/rpc_resources/transactions/txs_with_payment_2.json b/library/tests/rpc_resources/transactions/txs_with_payment_2.json
similarity index 100%
rename from tests/rpc_resources/transactions/txs_with_payment_2.json
rename to library/tests/rpc_resources/transactions/txs_with_payment_2.json
diff --git a/tests/rpc_resources/transactions/txs_with_payment_account_0.json b/library/tests/rpc_resources/transactions/txs_with_payment_account_0.json
similarity index 100%
rename from tests/rpc_resources/transactions/txs_with_payment_account_0.json
rename to library/tests/rpc_resources/transactions/txs_with_payment_account_0.json
diff --git a/tests/rpc_resources/transactions/txs_with_payment_timelock.json b/library/tests/rpc_resources/transactions/txs_with_payment_timelock.json
similarity index 100%
rename from tests/rpc_resources/transactions/txs_with_payment_timelock.json
rename to library/tests/rpc_resources/transactions/txs_with_payment_timelock.json
diff --git a/tests/rpc_resources/txpools/hashes.json b/library/tests/rpc_resources/txpools/hashes.json
similarity index 100%
rename from tests/rpc_resources/txpools/hashes.json
rename to library/tests/rpc_resources/txpools/hashes.json
diff --git a/tests/rpc_resources/txpools/hashes_with_payment.json b/library/tests/rpc_resources/txpools/hashes_with_payment.json
similarity index 100%
rename from tests/rpc_resources/txpools/hashes_with_payment.json
rename to library/tests/rpc_resources/txpools/hashes_with_payment.json
diff --git a/tests/rpc_resources/txpools/hashes_with_payment_2.json b/library/tests/rpc_resources/txpools/hashes_with_payment_2.json
similarity index 100%
rename from tests/rpc_resources/txpools/hashes_with_payment_2.json
rename to library/tests/rpc_resources/txpools/hashes_with_payment_2.json
diff --git a/tests/rpc_resources/txpools/hashes_with_payment_account_0.json b/library/tests/rpc_resources/txpools/hashes_with_payment_account_0.json
similarity index 100%
rename from tests/rpc_resources/txpools/hashes_with_payment_account_0.json
rename to library/tests/rpc_resources/txpools/hashes_with_payment_account_0.json
diff --git a/tests/rpc_resources/txpools/hashes_with_payment_timelock.json b/library/tests/rpc_resources/txpools/hashes_with_payment_timelock.json
similarity index 100%
rename from tests/rpc_resources/txpools/hashes_with_payment_timelock.json
rename to library/tests/rpc_resources/txpools/hashes_with_payment_timelock.json
diff --git a/tests/rpc_resources/txpools/txpool.json b/library/tests/rpc_resources/txpools/txpool.json
similarity index 100%
rename from tests/rpc_resources/txpools/txpool.json
rename to library/tests/rpc_resources/txpools/txpool.json
diff --git a/tests/rpc_resources/txpools/txpool_with_payment.json b/library/tests/rpc_resources/txpools/txpool_with_payment.json
similarity index 100%
rename from tests/rpc_resources/txpools/txpool_with_payment.json
rename to library/tests/rpc_resources/txpools/txpool_with_payment.json
diff --git a/tests/rpc_resources/txpools/txpool_with_payment_2.json b/library/tests/rpc_resources/txpools/txpool_with_payment_2.json
similarity index 100%
rename from tests/rpc_resources/txpools/txpool_with_payment_2.json
rename to library/tests/rpc_resources/txpools/txpool_with_payment_2.json
diff --git a/tests/rpc_resources/txpools/txpool_with_payment_account_0.json b/library/tests/rpc_resources/txpools/txpool_with_payment_account_0.json
similarity index 100%
rename from tests/rpc_resources/txpools/txpool_with_payment_account_0.json
rename to library/tests/rpc_resources/txpools/txpool_with_payment_account_0.json
diff --git a/tests/rpc_resources/txpools/txpool_with_payment_timelock.json b/library/tests/rpc_resources/txpools/txpool_with_payment_timelock.json
similarity index 100%
rename from tests/rpc_resources/txpools/txpool_with_payment_timelock.json
rename to library/tests/rpc_resources/txpools/txpool_with_payment_timelock.json
diff --git a/server/Cargo.toml b/server/Cargo.toml
new file mode 100644
index 0000000..1aeada9
--- /dev/null
+++ b/server/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "acceptxmr-server"
+publish = false
+version = "0.1.0"
+edition = "2021"
+rust-version = "1.65"
+license = "MIT OR Apache-2.0"
+description = "A monero payment gateway."
+repository = "https://github.com/busyboredom/acceptxmr"
+readme = "README.md"
+keywords = ["crypto", "gateway", "monero", "payment", "xmr"]
+categories = ["cryptography::cryptocurrencies"]
+
+[[bin]]
+name = "acceptxmr-server"
+path = "src/main.rs"
+
+[dependencies]
+acceptxmr = { path = "../library", features = ["serde", "sqlite"] }
+actix = "0.13"
+actix-files = "0.6"
+actix-session = { version = "0.7", features = ["cookie-session"] }
+actix-web = "4"
+actix-web-actors = "4"
+bytestring = "1"
+env_logger = "0.10"
+log = "0.4"
+rand = "0.8"
+rand_chacha = "0.3"
+serde = {version = "1", features = ["derive"] }
+serde_json = "1"
diff --git a/server/README.md b/server/README.md
new file mode 100644
index 0000000..3cdbd60
--- /dev/null
+++ b/server/README.md
@@ -0,0 +1,18 @@
+[![BuildStatus](https://github.com/busyboredom/acceptxmr/workflows/CI/badge.svg)](https://img.shields.io/github/actions/workflow/status/busyboredom/acceptxmr/ci.yml?branch=main)
+
+# `AcceptXMR-Server`: A monero payment gateway.
+`AcceptXMR-Server` is a batteries-included monero payment gateway built around
+the AcceptXMR library.
+
+If your application requires more flexibility than `AcceptXMR-Server` offers,
+please see the [`AcceptXMR`](../library/) library instead.
+
+## Getting Started
+### Running with Docker
+TODO
+
+### Configuration
+TODO
+
+### API
+TODO
diff --git a/server/src/main.rs b/server/src/main.rs
new file mode 100644
index 0000000..f8d491c
--- /dev/null
+++ b/server/src/main.rs
@@ -0,0 +1,312 @@
+use std::{
+ future::Future,
+ pin::Pin,
+ task::Poll,
+ time::{Duration, Instant},
+};
+
+use acceptxmr::{
+ storage::stores::Sqlite, Invoice, InvoiceId, PaymentGateway, PaymentGatewayBuilder, Subscriber,
+};
+use actix::{prelude::Stream, Actor, ActorContext, AsyncContext, StreamHandler};
+use actix_files::Files;
+use actix_session::{
+ config::CookieContentSecurity, storage::CookieSessionStore, Session, SessionMiddleware,
+};
+use actix_web::{
+ cookie, get,
+ http::header::{CacheControl, CacheDirective},
+ post, web,
+ web::Data,
+ App, HttpRequest, HttpResponse, HttpServer,
+};
+use actix_web_actors::ws;
+use bytestring::ByteString;
+use log::{debug, error, info, warn, LevelFilter};
+use rand::{thread_rng, Rng};
+use serde::Deserialize;
+use serde_json::json;
+
+/// Time before lack of client response causes a timeout.
+const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
+/// Time between sending heartbeat pings.
+const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(1);
+/// Length of secure session key for cookies.
+const SESSION_KEY_LEN: usize = 64;
+
+#[actix_web::main]
+async fn main() -> std::io::Result<()> {
+ env_logger::builder()
+ .filter_level(LevelFilter::Warn)
+ .filter_module("acceptxmr", log::LevelFilter::Debug)
+ .filter_module("websockets", log::LevelFilter::Trace)
+ .init();
+
+ // The private view key should be stored securely outside of the git repository.
+ // It is hardcoded here for demonstration purposes only.
+ let private_view_key = "ad2093a5705b9f33e6f0f0c1bc1f5f639c756cdfc168c8f2ac6127ccbdab3a03";
+ // No need to keep the primary address secret.
+ let primary_address = "4613YiHLM6JMH4zejMB2zJY5TwQCxL8p65ufw8kBP5yxX9itmuGLqp1dS4tkVoTxjyH3aYhYNrtGHbQzJQP5bFus3KHVdmf";
+
+ let invoice_store =
+ Sqlite::new("AcceptXMR_DB", "invoices").expect("failed to open invoice store");
+ let payment_gateway = PaymentGatewayBuilder::new(
+ private_view_key.to_string(),
+ primary_address.to_string(),
+ invoice_store,
+ )
+ .daemon_url("http://xmr-node.cakewallet.com:18081".to_string())
+ .build()
+ .expect("failed to build payment gateway");
+ info!("Payment gateway created.");
+
+ payment_gateway
+ .run()
+ .await
+ .expect("failed to run payment gateway");
+ info!("Payment gateway running.");
+
+ // Watch for invoice updates and deal with them accordingly.
+ let gateway_copy = payment_gateway.clone();
+ std::thread::spawn(move || {
+ // Watch all invoice updates.
+ let mut subscriber = gateway_copy.subscribe_all();
+ loop {
+ let invoice = match subscriber.blocking_recv() {
+ Some(p) => p,
+ // Global subscriptions should not close.
+ None => panic!("Blockchain scanner crashed!"),
+ };
+ // If it's confirmed or expired, we probably shouldn't bother tracking it
+ // anymore.
+ if (invoice.is_confirmed() && invoice.creation_height() < invoice.current_height())
+ || invoice.is_expired()
+ {
+ debug!(
+ "Invoice to index {} is either confirmed or expired. Removing invoice now",
+ invoice.index()
+ );
+ if let Err(e) = gateway_copy.remove_invoice(invoice.id()) {
+ error!("Failed to remove fully confirmed invoice: {}", e);
+ };
+ }
+ }
+ });
+
+ // Create secure session key for cookies.
+ let mut key_arr = [0u8; SESSION_KEY_LEN];
+ thread_rng().fill(&mut key_arr[..]);
+ let session_key = cookie::Key::generate();
+
+ // Run the demo webpage.
+ let shared_payment_gateway = Data::new(payment_gateway);
+ HttpServer::new(move || {
+ App::new()
+ .wrap(
+ SessionMiddleware::builder(CookieSessionStore::default(), session_key.clone())
+ .cookie_name("acceptxmr_session".to_string())
+ .cookie_secure(false)
+ .cookie_content_security(CookieContentSecurity::Private)
+ .build(),
+ )
+ .app_data(shared_payment_gateway.clone())
+ .service(update)
+ .service(checkout)
+ .service(websocket)
+ .service(Files::new("", "./server/static").index_file("index.html"))
+ })
+ .bind("0.0.0.0:8080")?
+ .run()
+ .await
+}
+
+#[derive(Deserialize)]
+struct CheckoutInfo {
+ message: String,
+}
+
+/// Create new invoice and place cookie.
+#[post("/checkout")]
+async fn checkout(
+ session: Session,
+ checkout_info: web::Json,
+ payment_gateway: web::Data>,
+) -> Result {
+ let invoice_id = payment_gateway
+ .new_invoice(1_000_000_000, 2, 5, checkout_info.message.clone())
+ .unwrap();
+ session.insert("id", invoice_id)?;
+ Ok(HttpResponse::Ok()
+ .append_header(CacheControl(vec![CacheDirective::NoStore]))
+ .finish())
+}
+
+// Get invoice update without waiting for websocket.
+#[get("/update")]
+async fn update(
+ session: Session,
+ payment_gateway: web::Data>,
+) -> Result {
+ if let Ok(Some(invoice_id)) = session.get::("id") {
+ if let Ok(Some(invoice)) = payment_gateway.get_invoice(invoice_id) {
+ return Ok(HttpResponse::Ok()
+ .append_header(CacheControl(vec![CacheDirective::NoStore]))
+ .json(json!(
+ {
+ "address": invoice.address(),
+ "amount_paid": invoice.amount_paid(),
+ "amount_requested": invoice.amount_requested(),
+ "uri": invoice.uri(),
+ "confirmations": invoice.confirmations(),
+ "confirmations_required": invoice.confirmations_required(),
+ "expiration_in": invoice.expiration_in(),
+ }
+ )));
+ };
+ }
+ Ok(HttpResponse::Gone()
+ .append_header(CacheControl(vec![CacheDirective::NoStore]))
+ .finish())
+}
+
+/// WebSocket rout.
+#[get("/ws/")]
+async fn websocket(
+ session: Session,
+ req: HttpRequest,
+ stream: web::Payload,
+ payment_gateway: web::Data>,
+) -> Result {
+ let invoice_id = match session.get::("id") {
+ Ok(Some(i)) => i,
+ _ => {
+ return Ok(HttpResponse::NotFound()
+ .append_header(CacheControl(vec![CacheDirective::NoStore]))
+ .finish())
+ }
+ };
+ let subscriber = match payment_gateway.subscribe(invoice_id) {
+ Some(s) => s,
+ _ => {
+ return Ok(HttpResponse::NotFound()
+ .append_header(CacheControl(vec![CacheDirective::NoStore]))
+ .finish())
+ }
+ };
+ let websocket = WebSocket::new(subscriber);
+ ws::start(websocket, &req, stream)
+}
+
+/// Define websocket HTTP actor
+struct WebSocket {
+ last_heartbeat: Instant,
+ invoice_subscriber: Option,
+}
+
+impl WebSocket {
+ fn new(invoice_subscriber: Subscriber) -> Self {
+ Self {
+ last_heartbeat: Instant::now(),
+ invoice_subscriber: Some(invoice_subscriber),
+ }
+ }
+
+ /// Sends ping to client every `HEARTBEAT_INTERVAL` and checks for responses
+ /// from client
+ fn heartbeat(&self, ctx: &mut ::Context) {
+ ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
+ // check client heartbeats
+ if Instant::now().duration_since(act.last_heartbeat) > CLIENT_TIMEOUT {
+ // heartbeat timed out
+ warn!("Websocket Client heartbeat failed, disconnecting!");
+ ctx.stop();
+ return;
+ }
+ ctx.ping(b"");
+ });
+ }
+}
+
+impl Actor for WebSocket {
+ type Context = ws::WebsocketContext;
+
+ /// This method is called on actor start. We add the invoice subscriber as a
+ /// stream here, and start heartbeat checks as well.
+ fn started(&mut self, ctx: &mut Self::Context) {
+ if let Some(subscriber) = self.invoice_subscriber.take() {
+ >::add_stream(InvoiceStream(subscriber), ctx);
+ }
+ self.heartbeat(ctx);
+ }
+}
+
+/// Handle incoming websocket messages.
+impl StreamHandler> for WebSocket {
+ fn handle(&mut self, msg: Result, ctx: &mut Self::Context) {
+ match msg {
+ Ok(ws::Message::Pong(_)) => {
+ self.last_heartbeat = Instant::now();
+ }
+ Ok(ws::Message::Close(reason)) => {
+ match &reason {
+ Some(r) => debug!("Websocket client closing: {:#?}", r.description),
+ None => debug!("Websocket client closing"),
+ }
+ ctx.close(reason);
+ ctx.stop();
+ }
+ Ok(m) => debug!("Received unexpected message from websocket client: {:?}", m),
+ Err(e) => warn!("Received error from websocket client: {:?}", e),
+ }
+ }
+}
+
+/// Handle incoming invoice updates.
+impl StreamHandler for WebSocket {
+ fn handle(&mut self, invoice_update: Invoice, ctx: &mut Self::Context) {
+ // Send the update to the user.
+ ctx.text(ByteString::from(
+ json!(
+ {
+ "address": invoice_update.address(),
+ "amount_paid": invoice_update.amount_paid(),
+ "amount_requested": invoice_update.amount_requested(),
+ "uri": invoice_update.uri(),
+ "confirmations": invoice_update.confirmations(),
+ "confirmations_required": invoice_update.confirmations_required(),
+ "expiration_in": invoice_update.expiration_in(),
+ }
+ )
+ .to_string(),
+ ));
+ // If the invoice is confirmed or expired, stop checking for updates.
+ if invoice_update.is_confirmed() {
+ ctx.close(Some(ws::CloseReason::from((
+ ws::CloseCode::Normal,
+ "Invoice Complete",
+ ))));
+ ctx.stop();
+ } else if invoice_update.is_expired() {
+ ctx.close(Some(ws::CloseReason::from((
+ ws::CloseCode::Normal,
+ "Invoice Expired",
+ ))));
+ ctx.stop();
+ }
+ }
+}
+
+// Wrapping `Subscriber` and implementing `Stream` on the wrapper allows us to
+// use it as an efficient asynchronous stream for the Actix websocket.
+struct InvoiceStream(Subscriber);
+
+impl Stream for InvoiceStream {
+ type Item = Invoice;
+
+ fn poll_next(
+ mut self: Pin<&mut Self>,
+ cx: &mut std::task::Context<'_>,
+ ) -> Poll> {
+ Pin::new(&mut self.0).poll(cx)
+ }
+}
diff --git a/server/static/acceptxmr.css b/server/static/acceptxmr.css
new file mode 100644
index 0000000..0880322
--- /dev/null
+++ b/server/static/acceptxmr.css
@@ -0,0 +1,151 @@
+body {
+ background-color: #4d4d4d;
+}
+
+.acceptxmr {
+ max-width: 80ch;
+ margin: auto;
+ background-color: #f26822;
+ border-radius: 1em;
+ display: flex;
+ flex-flow: column wrap;
+ align-items: center;
+}
+
+.acceptxmr button {
+ background-color: #0088ff;
+ border-radius: 0.5em;
+ border: 0;
+ margin: 0;
+ font-size: medium;
+}
+
+.acceptxmr button:hover {
+ background-color: #005fb3;
+ cursor: pointer;
+}
+
+.acceptxmr button:disabled {
+ background-color: #979797;
+}
+
+.acceptxmr #message-container {
+ margin: 1em;
+ flex: 2 1 20em;
+}
+
+.acceptxmr #payment-content {
+ display: none;
+}
+
+.acceptxmr .content button {
+ margin: 1em;
+ flex: 1 2 10ch;
+ padding: 12px 0.5em;
+ align-self: flex-end;
+}
+
+.acceptxmr textarea {
+ border-radius: 0.5em;
+ border: 0;
+ padding: 0.5em;
+ width: 100%;
+ margin: 0;
+ background-color: #4d4d4d;
+ color: #ffffff;
+ resize: vertical;
+}
+
+.acceptxmr .instruction-container {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
+ justify-content: center;
+}
+
+.acceptxmr .instruction-container p {
+ margin: 0.1em;
+ font-size: large;
+}
+
+.acceptxmr .instruction-container button {
+ margin: 0.5em;
+ padding: 0.25em 0.5em;
+}
+
+.acceptxmr .warning {
+ animation: acceptxmr-fade 2s linear;
+ animation-iteration-count: infinite;
+}
+
+@keyframes acceptxmr-fade {
+ 0%,100% { opacity: 0.2 }
+ 50% { opacity: 1 }
+}
+
+.acceptxmr hr {
+ width: 90%;
+ border-style: solid;
+ border-color: #000000;
+ margin: 0;
+}
+
+.content {
+ width: 100%;
+ display: flex;
+ flex-flow: row wrap;
+}
+
+.acceptxmr .qrcode-container {
+ flex: 1 1 10ch;
+ margin: 1em;
+ display: flex;
+ justify-content: center;
+}
+
+.acceptxmr .qrcode-container svg {
+ border-radius: 1em;
+ min-width: 18ch;
+ align-self: center;
+ background-color: #dddddd;
+}
+
+.acceptxmr .details {
+ flex: 2 2 30ch;
+ margin: 0 1em 1em 1em;
+ overflow: hidden;
+ align-self: center;
+}
+
+.acceptxmr #address-container {
+ display: flex;
+ height: 3em;
+ border-radius: 0.5em;
+ margin-bottom: 0.5em;
+ background-color: #ffffff;
+ justify-content: space-between;
+}
+
+.acceptxmr #address-container p {
+ overflow-x: scroll;
+ overflow-y: hidden;
+ margin: 0 0.5em 0 0.5em;
+ height: 100%;
+ width: 100%;
+ display: flex;
+ align-items: center;
+}
+
+.acceptxmr #address-container button {
+ border-radius: 0 0.5em 0.5em 0;
+ height: 100%;
+ padding: 0 1em;
+ margin: 0;
+}
+
+.acceptxmr .status {
+ margin: 0 0 0 0;
+ border-radius: 0.5em;
+ padding: 0.5em;
+ background-color: #ffffff;
+}
diff --git a/server/static/acceptxmr.js b/server/static/acceptxmr.js
new file mode 100644
index 0000000..ff24d34
--- /dev/null
+++ b/server/static/acceptxmr.js
@@ -0,0 +1,152 @@
+// Try to load existing invoice on page load.
+async function init() {
+ let response = await fetch("/update");
+ if (response.status !== 410 ) {
+ let invoiceUpdate = await response.json();
+ displayInvoiceUpdate(invoiceUpdate);
+ await next(true);
+ }
+}
+init()
+
+async function next(hasAddress) {
+ // Hide prep stuff, show payment stuff.
+ document.getElementById("preperation-content").style.display = "None";
+ document.getElementById("payment-content").style.display = "inherit";
+
+ // Create invoice.
+ if (!hasAddress) {
+ document.getElementById("instruction").innerHTML = "Loading...";
+ await newAddress();
+ } else {
+ newWebsocket();
+ }
+}
+
+async function newAddress() {
+ const message = document.getElementById("message").value;
+ const checkOutInfo = {
+ method: "POST",
+ body: JSON.stringify({
+ "message": message
+ }),
+ headers: {
+ 'content-type': 'application/json'
+ }
+ };
+
+ await fetch("/checkout", checkOutInfo);
+ newWebsocket();
+
+ let response = await fetch("/update");
+ let invoiceUpdate = await response.json();
+ displayInvoiceUpdate(invoiceUpdate);
+}
+
+function displayInvoiceUpdate(invoiceUpdate) {
+ console.log(invoiceUpdate);
+
+ // Show paid/due.
+ document.getElementById("paid").innerHTML = picoToXMR(invoiceUpdate.amount_paid);
+ document.getElementById("due").innerHTML = picoToXMR(invoiceUpdate.amount_requested);
+
+ // Show confirmations/required.
+ var confirmations = invoiceUpdate.confirmations;
+ document.getElementById("confirmations").innerHTML = Math.max(0, confirmations);
+ document.getElementById("confirmations-required").innerHTML = invoiceUpdate.confirmations_required;
+
+ // Show instructive text depending on invoice state.
+ var instructionString = "Loading...";
+ var instructionClass = "acceptxmr-instruction";
+ var newAddressBtnHidden = true;
+ var closeReason = null;
+ if (confirmations !== null && confirmations >= invoiceUpdate.confirmations_required) {
+ instructionString = "Paid! Thank you"
+ closeReason = "Confirmed";
+ } else if (invoiceUpdate.amount_paid >= invoiceUpdate.amount_requested) {
+ instructionString = "Paid! Waiting for Confirmation..."
+ } else if (invoiceUpdate.expiration_in > 2) {
+ instructionString = "Send Monero to Address Below"
+ } else if (invoiceUpdate.expiration_in > 0) {
+ instructionString = "Address Expiring Soon";
+ instructionClass += " warning";
+ newAddressBtnHidden = false;
+ } else {
+ instructionString = "Address Expired!";
+ newAddressBtnHidden = false;
+ closeReason = "Expired";
+ }
+ document.getElementById("instruction").innerHTML = instructionString;
+ document.getElementById("instruction").classList = instructionClass;
+
+ // Hide address if nearing expiration.
+ document.getElementById("new-address-btn").hidden = newAddressBtnHidden;
+ document.getElementById("address-copy-btn").disabled = !newAddressBtnHidden;
+ if (newAddressBtnHidden) {
+ document.getElementById("address").innerHTML = invoiceUpdate.address;
+
+ const qr = qrcode(0, "M");
+ qr.addData(invoiceUpdate.uri);
+ qr.make();
+ document.getElementById('qrcode-container').innerHTML = qr.createSvgTag({ scalable: true });
+ } else {
+ document.getElementById("address").innerHTML = "Expiring or expired...";
+ document.getElementById('qrcode-container').innerHTML = " ";
+ }
+
+ return closeReason;
+}
+
+function newWebsocket() {
+ // Close websocket if it already exists.
+ if (typeof window.acceptxmrSocket != 'undefined') {
+ window.acceptxmrSocket.close(1000, "New Address");
+ }
+
+ // Open websocket.
+ window.acceptxmrSocket = new WebSocket("ws://localhost:8080/ws/");
+
+ window.acceptxmrSocket.onmessage = function (event) {
+ let closeReason = displayInvoiceUpdate(JSON.parse(event.data));
+ if (closeReason != null) {
+ window.acceptxmrSocket.close(1000, closeReason);
+ }
+ }
+
+ // If the websocket closes cleanly, log it. Otherwise, alert the user.
+ window.acceptxmrSocket.onclose = function (event) {
+ if (event.code === 1000) {
+ console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
+ } else {
+ // Server process killed or network down.
+ // Event.code is usually 1006 in this case.
+ alert('Connection died. If you have paid already, rest assured that it will still be processed.');
+ }
+ };
+
+ window.acceptxmrSocket.onerror = function (error) {
+ alert(`[error] ${error.message}`);
+ };
+}
+
+// Convert from piconeros to monero.
+function picoToXMR(amount) {
+ const divisor = 1_000_000_000_000;
+ const xmr = Math.floor(amount / divisor) + amount % divisor / divisor;
+ return new Intl.NumberFormat(undefined, { maximumSignificantDigits: 20 }).format(xmr);
+}
+
+// Make the copy button work.
+function copyInvoiceAddress() {
+ // Get the text field
+ const copyText = document.getElementById("address");
+
+ // Copy the text inside the text field
+ navigator.clipboard.writeText(copyText.innerHTML);
+
+ // Provide feedback
+ document.getElementById("address-copy-btn").innerHTML = "Copied!";
+ setTimeout(function () {
+ document.getElementById("address-copy-btn").innerHTML = "Copy";
+ }, 1000);
+}
diff --git a/server/static/favicon.ico b/server/static/favicon.ico
new file mode 100644
index 0000000..37a6dae
Binary files /dev/null and b/server/static/favicon.ico differ
diff --git a/server/static/index.html b/server/static/index.html
new file mode 100644
index 0000000..b9511dd
--- /dev/null
+++ b/server/static/index.html
@@ -0,0 +1,40 @@
+
+
+AcceptXMR Websocket Demo
+
+
+
+
AcceptXMR Websocket Demo
+
New Address
+
+
+
+
+ Message (Optional)
+
+
+
Next
+
+
+
+
+
+
+
Address
+
+
Status
+
+ Paid: 0.00000000 / 0.00000000 XMR
+ Confirmations:
+ 0 / 0
+
+
+
+
+
+
+
diff --git a/server/static/vendor/qrcode.js b/server/static/vendor/qrcode.js
new file mode 100644
index 0000000..fde008b
--- /dev/null
+++ b/server/static/vendor/qrcode.js
@@ -0,0 +1,2297 @@
+//---------------------------------------------------------------------
+//
+// QR Code Generator for JavaScript
+//
+// Copyright (c) 2009 Kazuhiko Arase
+//
+// URL: http://www.d-project.com/
+//
+// Licensed under the MIT license:
+// http://www.opensource.org/licenses/mit-license.php
+//
+// The word 'QR Code' is registered trademark of
+// DENSO WAVE INCORPORATED
+// http://www.denso-wave.com/qrcode/faqpatent-e.html
+//
+//---------------------------------------------------------------------
+
+var qrcode = function() {
+
+ //---------------------------------------------------------------------
+ // qrcode
+ //---------------------------------------------------------------------
+
+ /**
+ * qrcode
+ * @param typeNumber 1 to 40
+ * @param errorCorrectionLevel 'L','M','Q','H'
+ */
+ var qrcode = function(typeNumber, errorCorrectionLevel) {
+
+ var PAD0 = 0xEC;
+ var PAD1 = 0x11;
+
+ var _typeNumber = typeNumber;
+ var _errorCorrectionLevel = QRErrorCorrectionLevel[errorCorrectionLevel];
+ var _modules = null;
+ var _moduleCount = 0;
+ var _dataCache = null;
+ var _dataList = [];
+
+ var _this = {};
+
+ var makeImpl = function(test, maskPattern) {
+
+ _moduleCount = _typeNumber * 4 + 17;
+ _modules = function(moduleCount) {
+ var modules = new Array(moduleCount);
+ for (var row = 0; row < moduleCount; row += 1) {
+ modules[row] = new Array(moduleCount);
+ for (var col = 0; col < moduleCount; col += 1) {
+ modules[row][col] = null;
+ }
+ }
+ return modules;
+ }(_moduleCount);
+
+ setupPositionProbePattern(0, 0);
+ setupPositionProbePattern(_moduleCount - 7, 0);
+ setupPositionProbePattern(0, _moduleCount - 7);
+ setupPositionAdjustPattern();
+ setupTimingPattern();
+ setupTypeInfo(test, maskPattern);
+
+ if (_typeNumber >= 7) {
+ setupTypeNumber(test);
+ }
+
+ if (_dataCache == null) {
+ _dataCache = createData(_typeNumber, _errorCorrectionLevel, _dataList);
+ }
+
+ mapData(_dataCache, maskPattern);
+ };
+
+ var setupPositionProbePattern = function(row, col) {
+
+ for (var r = -1; r <= 7; r += 1) {
+
+ if (row + r <= -1 || _moduleCount <= row + r) continue;
+
+ for (var c = -1; c <= 7; c += 1) {
+
+ if (col + c <= -1 || _moduleCount <= col + c) continue;
+
+ if ( (0 <= r && r <= 6 && (c == 0 || c == 6) )
+ || (0 <= c && c <= 6 && (r == 0 || r == 6) )
+ || (2 <= r && r <= 4 && 2 <= c && c <= 4) ) {
+ _modules[row + r][col + c] = true;
+ } else {
+ _modules[row + r][col + c] = false;
+ }
+ }
+ }
+ };
+
+ var getBestMaskPattern = function() {
+
+ var minLostPoint = 0;
+ var pattern = 0;
+
+ for (var i = 0; i < 8; i += 1) {
+
+ makeImpl(true, i);
+
+ var lostPoint = QRUtil.getLostPoint(_this);
+
+ if (i == 0 || minLostPoint > lostPoint) {
+ minLostPoint = lostPoint;
+ pattern = i;
+ }
+ }
+
+ return pattern;
+ };
+
+ var setupTimingPattern = function() {
+
+ for (var r = 8; r < _moduleCount - 8; r += 1) {
+ if (_modules[r][6] != null) {
+ continue;
+ }
+ _modules[r][6] = (r % 2 == 0);
+ }
+
+ for (var c = 8; c < _moduleCount - 8; c += 1) {
+ if (_modules[6][c] != null) {
+ continue;
+ }
+ _modules[6][c] = (c % 2 == 0);
+ }
+ };
+
+ var setupPositionAdjustPattern = function() {
+
+ var pos = QRUtil.getPatternPosition(_typeNumber);
+
+ for (var i = 0; i < pos.length; i += 1) {
+
+ for (var j = 0; j < pos.length; j += 1) {
+
+ var row = pos[i];
+ var col = pos[j];
+
+ if (_modules[row][col] != null) {
+ continue;
+ }
+
+ for (var r = -2; r <= 2; r += 1) {
+
+ for (var c = -2; c <= 2; c += 1) {
+
+ if (r == -2 || r == 2 || c == -2 || c == 2
+ || (r == 0 && c == 0) ) {
+ _modules[row + r][col + c] = true;
+ } else {
+ _modules[row + r][col + c] = false;
+ }
+ }
+ }
+ }
+ }
+ };
+
+ var setupTypeNumber = function(test) {
+
+ var bits = QRUtil.getBCHTypeNumber(_typeNumber);
+
+ for (var i = 0; i < 18; i += 1) {
+ var mod = (!test && ( (bits >> i) & 1) == 1);
+ _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod;
+ }
+
+ for (var i = 0; i < 18; i += 1) {
+ var mod = (!test && ( (bits >> i) & 1) == 1);
+ _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
+ }
+ };
+
+ var setupTypeInfo = function(test, maskPattern) {
+
+ var data = (_errorCorrectionLevel << 3) | maskPattern;
+ var bits = QRUtil.getBCHTypeInfo(data);
+
+ // vertical
+ for (var i = 0; i < 15; i += 1) {
+
+ var mod = (!test && ( (bits >> i) & 1) == 1);
+
+ if (i < 6) {
+ _modules[i][8] = mod;
+ } else if (i < 8) {
+ _modules[i + 1][8] = mod;
+ } else {
+ _modules[_moduleCount - 15 + i][8] = mod;
+ }
+ }
+
+ // horizontal
+ for (var i = 0; i < 15; i += 1) {
+
+ var mod = (!test && ( (bits >> i) & 1) == 1);
+
+ if (i < 8) {
+ _modules[8][_moduleCount - i - 1] = mod;
+ } else if (i < 9) {
+ _modules[8][15 - i - 1 + 1] = mod;
+ } else {
+ _modules[8][15 - i - 1] = mod;
+ }
+ }
+
+ // fixed module
+ _modules[_moduleCount - 8][8] = (!test);
+ };
+
+ var mapData = function(data, maskPattern) {
+
+ var inc = -1;
+ var row = _moduleCount - 1;
+ var bitIndex = 7;
+ var byteIndex = 0;
+ var maskFunc = QRUtil.getMaskFunction(maskPattern);
+
+ for (var col = _moduleCount - 1; col > 0; col -= 2) {
+
+ if (col == 6) col -= 1;
+
+ while (true) {
+
+ for (var c = 0; c < 2; c += 1) {
+
+ if (_modules[row][col - c] == null) {
+
+ var dark = false;
+
+ if (byteIndex < data.length) {
+ dark = ( ( (data[byteIndex] >>> bitIndex) & 1) == 1);
+ }
+
+ var mask = maskFunc(row, col - c);
+
+ if (mask) {
+ dark = !dark;
+ }
+
+ _modules[row][col - c] = dark;
+ bitIndex -= 1;
+
+ if (bitIndex == -1) {
+ byteIndex += 1;
+ bitIndex = 7;
+ }
+ }
+ }
+
+ row += inc;
+
+ if (row < 0 || _moduleCount <= row) {
+ row -= inc;
+ inc = -inc;
+ break;
+ }
+ }
+ }
+ };
+
+ var createBytes = function(buffer, rsBlocks) {
+
+ var offset = 0;
+
+ var maxDcCount = 0;
+ var maxEcCount = 0;
+
+ var dcdata = new Array(rsBlocks.length);
+ var ecdata = new Array(rsBlocks.length);
+
+ for (var r = 0; r < rsBlocks.length; r += 1) {
+
+ var dcCount = rsBlocks[r].dataCount;
+ var ecCount = rsBlocks[r].totalCount - dcCount;
+
+ maxDcCount = Math.max(maxDcCount, dcCount);
+ maxEcCount = Math.max(maxEcCount, ecCount);
+
+ dcdata[r] = new Array(dcCount);
+
+ for (var i = 0; i < dcdata[r].length; i += 1) {
+ dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset];
+ }
+ offset += dcCount;
+
+ var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
+ var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1);
+
+ var modPoly = rawPoly.mod(rsPoly);
+ ecdata[r] = new Array(rsPoly.getLength() - 1);
+ for (var i = 0; i < ecdata[r].length; i += 1) {
+ var modIndex = i + modPoly.getLength() - ecdata[r].length;
+ ecdata[r][i] = (modIndex >= 0)? modPoly.getAt(modIndex) : 0;
+ }
+ }
+
+ var totalCodeCount = 0;
+ for (var i = 0; i < rsBlocks.length; i += 1) {
+ totalCodeCount += rsBlocks[i].totalCount;
+ }
+
+ var data = new Array(totalCodeCount);
+ var index = 0;
+
+ for (var i = 0; i < maxDcCount; i += 1) {
+ for (var r = 0; r < rsBlocks.length; r += 1) {
+ if (i < dcdata[r].length) {
+ data[index] = dcdata[r][i];
+ index += 1;
+ }
+ }
+ }
+
+ for (var i = 0; i < maxEcCount; i += 1) {
+ for (var r = 0; r < rsBlocks.length; r += 1) {
+ if (i < ecdata[r].length) {
+ data[index] = ecdata[r][i];
+ index += 1;
+ }
+ }
+ }
+
+ return data;
+ };
+
+ var createData = function(typeNumber, errorCorrectionLevel, dataList) {
+
+ var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectionLevel);
+
+ var buffer = qrBitBuffer();
+
+ for (var i = 0; i < dataList.length; i += 1) {
+ var data = dataList[i];
+ buffer.put(data.getMode(), 4);
+ buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) );
+ data.write(buffer);
+ }
+
+ // calc num max data.
+ var totalDataCount = 0;
+ for (var i = 0; i < rsBlocks.length; i += 1) {
+ totalDataCount += rsBlocks[i].dataCount;
+ }
+
+ if (buffer.getLengthInBits() > totalDataCount * 8) {
+ throw 'code length overflow. ('
+ + buffer.getLengthInBits()
+ + '>'
+ + totalDataCount * 8
+ + ')';
+ }
+
+ // end code
+ if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
+ buffer.put(0, 4);
+ }
+
+ // padding
+ while (buffer.getLengthInBits() % 8 != 0) {
+ buffer.putBit(false);
+ }
+
+ // padding
+ while (true) {
+
+ if (buffer.getLengthInBits() >= totalDataCount * 8) {
+ break;
+ }
+ buffer.put(PAD0, 8);
+
+ if (buffer.getLengthInBits() >= totalDataCount * 8) {
+ break;
+ }
+ buffer.put(PAD1, 8);
+ }
+
+ return createBytes(buffer, rsBlocks);
+ };
+
+ _this.addData = function(data, mode) {
+
+ mode = mode || 'Byte';
+
+ var newData = null;
+
+ switch(mode) {
+ case 'Numeric' :
+ newData = qrNumber(data);
+ break;
+ case 'Alphanumeric' :
+ newData = qrAlphaNum(data);
+ break;
+ case 'Byte' :
+ newData = qr8BitByte(data);
+ break;
+ case 'Kanji' :
+ newData = qrKanji(data);
+ break;
+ default :
+ throw 'mode:' + mode;
+ }
+
+ _dataList.push(newData);
+ _dataCache = null;
+ };
+
+ _this.isDark = function(row, col) {
+ if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) {
+ throw row + ',' + col;
+ }
+ return _modules[row][col];
+ };
+
+ _this.getModuleCount = function() {
+ return _moduleCount;
+ };
+
+ _this.make = function() {
+ if (_typeNumber < 1) {
+ var typeNumber = 1;
+
+ for (; typeNumber < 40; typeNumber++) {
+ var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, _errorCorrectionLevel);
+ var buffer = qrBitBuffer();
+
+ for (var i = 0; i < _dataList.length; i++) {
+ var data = _dataList[i];
+ buffer.put(data.getMode(), 4);
+ buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) );
+ data.write(buffer);
+ }
+
+ var totalDataCount = 0;
+ for (var i = 0; i < rsBlocks.length; i++) {
+ totalDataCount += rsBlocks[i].dataCount;
+ }
+
+ if (buffer.getLengthInBits() <= totalDataCount * 8) {
+ break;
+ }
+ }
+
+ _typeNumber = typeNumber;
+ }
+
+ makeImpl(false, getBestMaskPattern() );
+ };
+
+ _this.createTableTag = function(cellSize, margin) {
+
+ cellSize = cellSize || 2;
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
+
+ var qrHtml = '';
+
+ qrHtml += '';
+ qrHtml += '';
+
+ for (var r = 0; r < _this.getModuleCount(); r += 1) {
+
+ qrHtml += '';
+
+ for (var c = 0; c < _this.getModuleCount(); c += 1) {
+ qrHtml += ' ';
+ }
+
+ qrHtml += ' ';
+ }
+
+ qrHtml += ' ';
+ qrHtml += '
';
+
+ return qrHtml;
+ };
+
+ _this.createSvgTag = function(cellSize, margin, alt, title) {
+
+ var opts = {};
+ if (typeof arguments[0] == 'object') {
+ // Called by options.
+ opts = arguments[0];
+ // overwrite cellSize and margin.
+ cellSize = opts.cellSize;
+ margin = opts.margin;
+ alt = opts.alt;
+ title = opts.title;
+ }
+
+ cellSize = cellSize || 2;
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
+
+ // Compose alt property surrogate
+ alt = (typeof alt === 'string') ? {text: alt} : alt || {};
+ alt.text = alt.text || null;
+ alt.id = (alt.text) ? alt.id || 'qrcode-description' : null;
+
+ // Compose title property surrogate
+ title = (typeof title === 'string') ? {text: title} : title || {};
+ title.text = title.text || null;
+ title.id = (title.text) ? title.id || 'qrcode-title' : null;
+
+ var size = _this.getModuleCount() * cellSize + margin * 2;
+ var c, mc, r, mr, qrSvg='', rect;
+
+ rect = 'l' + cellSize + ',0 0,' + cellSize +
+ ' -' + cellSize + ',0 0,-' + cellSize + 'z ';
+
+ qrSvg += '';
+ qrSvg += (title.text) ? '' +
+ escapeXml(title.text) + ' ' : '';
+ qrSvg += (alt.text) ? '' +
+ escapeXml(alt.text) + ' ' : '';
+ qrSvg += ' ';
+ qrSvg += ' ';
+ qrSvg += ' ';
+
+ return qrSvg;
+ };
+
+ _this.createDataURL = function(cellSize, margin) {
+
+ cellSize = cellSize || 2;
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
+
+ var size = _this.getModuleCount() * cellSize + margin * 2;
+ var min = margin;
+ var max = size - margin;
+
+ return createDataURL(size, size, function(x, y) {
+ if (min <= x && x < max && min <= y && y < max) {
+ var c = Math.floor( (x - min) / cellSize);
+ var r = Math.floor( (y - min) / cellSize);
+ return _this.isDark(r, c)? 0 : 1;
+ } else {
+ return 1;
+ }
+ } );
+ };
+
+ _this.createImgTag = function(cellSize, margin, alt) {
+
+ cellSize = cellSize || 2;
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
+
+ var size = _this.getModuleCount() * cellSize + margin * 2;
+
+ var img = '';
+ img += ' ';
+
+ return img;
+ };
+
+ var escapeXml = function(s) {
+ var escaped = '';
+ for (var i = 0; i < s.length; i += 1) {
+ var c = s.charAt(i);
+ switch(c) {
+ case '<': escaped += '<'; break;
+ case '>': escaped += '>'; break;
+ case '&': escaped += '&'; break;
+ case '"': escaped += '"'; break;
+ default : escaped += c; break;
+ }
+ }
+ return escaped;
+ };
+
+ var _createHalfASCII = function(margin) {
+ var cellSize = 1;
+ margin = (typeof margin == 'undefined')? cellSize * 2 : margin;
+
+ var size = _this.getModuleCount() * cellSize + margin * 2;
+ var min = margin;
+ var max = size - margin;
+
+ var y, x, r1, r2, p;
+
+ var blocks = {
+ '██': '█',
+ '█ ': '▀',
+ ' █': '▄',
+ ' ': ' '
+ };
+
+ var blocksLastLineNoMargin = {
+ '██': '▀',
+ '█ ': '▀',
+ ' █': ' ',
+ ' ': ' '
+ };
+
+ var ascii = '';
+ for (y = 0; y < size; y += 2) {
+ r1 = Math.floor((y - min) / cellSize);
+ r2 = Math.floor((y + 1 - min) / cellSize);
+ for (x = 0; x < size; x += 1) {
+ p = '█';
+
+ if (min <= x && x < max && min <= y && y < max && _this.isDark(r1, Math.floor((x - min) / cellSize))) {
+ p = ' ';
+ }
+
+ if (min <= x && x < max && min <= y+1 && y+1 < max && _this.isDark(r2, Math.floor((x - min) / cellSize))) {
+ p += ' ';
+ }
+ else {
+ p += '█';
+ }
+
+ // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square.
+ ascii += (margin < 1 && y+1 >= max) ? blocksLastLineNoMargin[p] : blocks[p];
+ }
+
+ ascii += '\n';
+ }
+
+ if (size % 2 && margin > 0) {
+ return ascii.substring(0, ascii.length - size - 1) + Array(size+1).join('▀');
+ }
+
+ return ascii.substring(0, ascii.length-1);
+ };
+
+ _this.createASCII = function(cellSize, margin) {
+ cellSize = cellSize || 1;
+
+ if (cellSize < 2) {
+ return _createHalfASCII(margin);
+ }
+
+ cellSize -= 1;
+ margin = (typeof margin == 'undefined')? cellSize * 2 : margin;
+
+ var size = _this.getModuleCount() * cellSize + margin * 2;
+ var min = margin;
+ var max = size - margin;
+
+ var y, x, r, p;
+
+ var white = Array(cellSize+1).join('██');
+ var black = Array(cellSize+1).join(' ');
+
+ var ascii = '';
+ var line = '';
+ for (y = 0; y < size; y += 1) {
+ r = Math.floor( (y - min) / cellSize);
+ line = '';
+ for (x = 0; x < size; x += 1) {
+ p = 1;
+
+ if (min <= x && x < max && min <= y && y < max && _this.isDark(r, Math.floor((x - min) / cellSize))) {
+ p = 0;
+ }
+
+ // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square.
+ line += p ? white : black;
+ }
+
+ for (r = 0; r < cellSize; r += 1) {
+ ascii += line + '\n';
+ }
+ }
+
+ return ascii.substring(0, ascii.length-1);
+ };
+
+ _this.renderTo2dContext = function(context, cellSize) {
+ cellSize = cellSize || 2;
+ var length = _this.getModuleCount();
+ for (var row = 0; row < length; row++) {
+ for (var col = 0; col < length; col++) {
+ context.fillStyle = _this.isDark(row, col) ? 'black' : 'white';
+ context.fillRect(row * cellSize, col * cellSize, cellSize, cellSize);
+ }
+ }
+ }
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // qrcode.stringToBytes
+ //---------------------------------------------------------------------
+
+ qrcode.stringToBytesFuncs = {
+ 'default' : function(s) {
+ var bytes = [];
+ for (var i = 0; i < s.length; i += 1) {
+ var c = s.charCodeAt(i);
+ bytes.push(c & 0xff);
+ }
+ return bytes;
+ }
+ };
+
+ qrcode.stringToBytes = qrcode.stringToBytesFuncs['default'];
+
+ //---------------------------------------------------------------------
+ // qrcode.createStringToBytes
+ //---------------------------------------------------------------------
+
+ /**
+ * @param unicodeData base64 string of byte array.
+ * [16bit Unicode],[16bit Bytes], ...
+ * @param numChars
+ */
+ qrcode.createStringToBytes = function(unicodeData, numChars) {
+
+ // create conversion map.
+
+ var unicodeMap = function() {
+
+ var bin = base64DecodeInputStream(unicodeData);
+ var read = function() {
+ var b = bin.read();
+ if (b == -1) throw 'eof';
+ return b;
+ };
+
+ var count = 0;
+ var unicodeMap = {};
+ while (true) {
+ var b0 = bin.read();
+ if (b0 == -1) break;
+ var b1 = read();
+ var b2 = read();
+ var b3 = read();
+ var k = String.fromCharCode( (b0 << 8) | b1);
+ var v = (b2 << 8) | b3;
+ unicodeMap[k] = v;
+ count += 1;
+ }
+ if (count != numChars) {
+ throw count + ' != ' + numChars;
+ }
+
+ return unicodeMap;
+ }();
+
+ var unknownChar = '?'.charCodeAt(0);
+
+ return function(s) {
+ var bytes = [];
+ for (var i = 0; i < s.length; i += 1) {
+ var c = s.charCodeAt(i);
+ if (c < 128) {
+ bytes.push(c);
+ } else {
+ var b = unicodeMap[s.charAt(i)];
+ if (typeof b == 'number') {
+ if ( (b & 0xff) == b) {
+ // 1byte
+ bytes.push(b);
+ } else {
+ // 2bytes
+ bytes.push(b >>> 8);
+ bytes.push(b & 0xff);
+ }
+ } else {
+ bytes.push(unknownChar);
+ }
+ }
+ }
+ return bytes;
+ };
+ };
+
+ //---------------------------------------------------------------------
+ // QRMode
+ //---------------------------------------------------------------------
+
+ var QRMode = {
+ MODE_NUMBER : 1 << 0,
+ MODE_ALPHA_NUM : 1 << 1,
+ MODE_8BIT_BYTE : 1 << 2,
+ MODE_KANJI : 1 << 3
+ };
+
+ //---------------------------------------------------------------------
+ // QRErrorCorrectionLevel
+ //---------------------------------------------------------------------
+
+ var QRErrorCorrectionLevel = {
+ L : 1,
+ M : 0,
+ Q : 3,
+ H : 2
+ };
+
+ //---------------------------------------------------------------------
+ // QRMaskPattern
+ //---------------------------------------------------------------------
+
+ var QRMaskPattern = {
+ PATTERN000 : 0,
+ PATTERN001 : 1,
+ PATTERN010 : 2,
+ PATTERN011 : 3,
+ PATTERN100 : 4,
+ PATTERN101 : 5,
+ PATTERN110 : 6,
+ PATTERN111 : 7
+ };
+
+ //---------------------------------------------------------------------
+ // QRUtil
+ //---------------------------------------------------------------------
+
+ var QRUtil = function() {
+
+ var PATTERN_POSITION_TABLE = [
+ [],
+ [6, 18],
+ [6, 22],
+ [6, 26],
+ [6, 30],
+ [6, 34],
+ [6, 22, 38],
+ [6, 24, 42],
+ [6, 26, 46],
+ [6, 28, 50],
+ [6, 30, 54],
+ [6, 32, 58],
+ [6, 34, 62],
+ [6, 26, 46, 66],
+ [6, 26, 48, 70],
+ [6, 26, 50, 74],
+ [6, 30, 54, 78],
+ [6, 30, 56, 82],
+ [6, 30, 58, 86],
+ [6, 34, 62, 90],
+ [6, 28, 50, 72, 94],
+ [6, 26, 50, 74, 98],
+ [6, 30, 54, 78, 102],
+ [6, 28, 54, 80, 106],
+ [6, 32, 58, 84, 110],
+ [6, 30, 58, 86, 114],
+ [6, 34, 62, 90, 118],
+ [6, 26, 50, 74, 98, 122],
+ [6, 30, 54, 78, 102, 126],
+ [6, 26, 52, 78, 104, 130],
+ [6, 30, 56, 82, 108, 134],
+ [6, 34, 60, 86, 112, 138],
+ [6, 30, 58, 86, 114, 142],
+ [6, 34, 62, 90, 118, 146],
+ [6, 30, 54, 78, 102, 126, 150],
+ [6, 24, 50, 76, 102, 128, 154],
+ [6, 28, 54, 80, 106, 132, 158],
+ [6, 32, 58, 84, 110, 136, 162],
+ [6, 26, 54, 82, 110, 138, 166],
+ [6, 30, 58, 86, 114, 142, 170]
+ ];
+ var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0);
+ var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0);
+ var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1);
+
+ var _this = {};
+
+ var getBCHDigit = function(data) {
+ var digit = 0;
+ while (data != 0) {
+ digit += 1;
+ data >>>= 1;
+ }
+ return digit;
+ };
+
+ _this.getBCHTypeInfo = function(data) {
+ var d = data << 10;
+ while (getBCHDigit(d) - getBCHDigit(G15) >= 0) {
+ d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15) ) );
+ }
+ return ( (data << 10) | d) ^ G15_MASK;
+ };
+
+ _this.getBCHTypeNumber = function(data) {
+ var d = data << 12;
+ while (getBCHDigit(d) - getBCHDigit(G18) >= 0) {
+ d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18) ) );
+ }
+ return (data << 12) | d;
+ };
+
+ _this.getPatternPosition = function(typeNumber) {
+ return PATTERN_POSITION_TABLE[typeNumber - 1];
+ };
+
+ _this.getMaskFunction = function(maskPattern) {
+
+ switch (maskPattern) {
+
+ case QRMaskPattern.PATTERN000 :
+ return function(i, j) { return (i + j) % 2 == 0; };
+ case QRMaskPattern.PATTERN001 :
+ return function(i, j) { return i % 2 == 0; };
+ case QRMaskPattern.PATTERN010 :
+ return function(i, j) { return j % 3 == 0; };
+ case QRMaskPattern.PATTERN011 :
+ return function(i, j) { return (i + j) % 3 == 0; };
+ case QRMaskPattern.PATTERN100 :
+ return function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0; };
+ case QRMaskPattern.PATTERN101 :
+ return function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0; };
+ case QRMaskPattern.PATTERN110 :
+ return function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0; };
+ case QRMaskPattern.PATTERN111 :
+ return function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0; };
+
+ default :
+ throw 'bad maskPattern:' + maskPattern;
+ }
+ };
+
+ _this.getErrorCorrectPolynomial = function(errorCorrectLength) {
+ var a = qrPolynomial([1], 0);
+ for (var i = 0; i < errorCorrectLength; i += 1) {
+ a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0) );
+ }
+ return a;
+ };
+
+ _this.getLengthInBits = function(mode, type) {
+
+ if (1 <= type && type < 10) {
+
+ // 1 - 9
+
+ switch(mode) {
+ case QRMode.MODE_NUMBER : return 10;
+ case QRMode.MODE_ALPHA_NUM : return 9;
+ case QRMode.MODE_8BIT_BYTE : return 8;
+ case QRMode.MODE_KANJI : return 8;
+ default :
+ throw 'mode:' + mode;
+ }
+
+ } else if (type < 27) {
+
+ // 10 - 26
+
+ switch(mode) {
+ case QRMode.MODE_NUMBER : return 12;
+ case QRMode.MODE_ALPHA_NUM : return 11;
+ case QRMode.MODE_8BIT_BYTE : return 16;
+ case QRMode.MODE_KANJI : return 10;
+ default :
+ throw 'mode:' + mode;
+ }
+
+ } else if (type < 41) {
+
+ // 27 - 40
+
+ switch(mode) {
+ case QRMode.MODE_NUMBER : return 14;
+ case QRMode.MODE_ALPHA_NUM : return 13;
+ case QRMode.MODE_8BIT_BYTE : return 16;
+ case QRMode.MODE_KANJI : return 12;
+ default :
+ throw 'mode:' + mode;
+ }
+
+ } else {
+ throw 'type:' + type;
+ }
+ };
+
+ _this.getLostPoint = function(qrcode) {
+
+ var moduleCount = qrcode.getModuleCount();
+
+ var lostPoint = 0;
+
+ // LEVEL1
+
+ for (var row = 0; row < moduleCount; row += 1) {
+ for (var col = 0; col < moduleCount; col += 1) {
+
+ var sameCount = 0;
+ var dark = qrcode.isDark(row, col);
+
+ for (var r = -1; r <= 1; r += 1) {
+
+ if (row + r < 0 || moduleCount <= row + r) {
+ continue;
+ }
+
+ for (var c = -1; c <= 1; c += 1) {
+
+ if (col + c < 0 || moduleCount <= col + c) {
+ continue;
+ }
+
+ if (r == 0 && c == 0) {
+ continue;
+ }
+
+ if (dark == qrcode.isDark(row + r, col + c) ) {
+ sameCount += 1;
+ }
+ }
+ }
+
+ if (sameCount > 5) {
+ lostPoint += (3 + sameCount - 5);
+ }
+ }
+ };
+
+ // LEVEL2
+
+ for (var row = 0; row < moduleCount - 1; row += 1) {
+ for (var col = 0; col < moduleCount - 1; col += 1) {
+ var count = 0;
+ if (qrcode.isDark(row, col) ) count += 1;
+ if (qrcode.isDark(row + 1, col) ) count += 1;
+ if (qrcode.isDark(row, col + 1) ) count += 1;
+ if (qrcode.isDark(row + 1, col + 1) ) count += 1;
+ if (count == 0 || count == 4) {
+ lostPoint += 3;
+ }
+ }
+ }
+
+ // LEVEL3
+
+ for (var row = 0; row < moduleCount; row += 1) {
+ for (var col = 0; col < moduleCount - 6; col += 1) {
+ if (qrcode.isDark(row, col)
+ && !qrcode.isDark(row, col + 1)
+ && qrcode.isDark(row, col + 2)
+ && qrcode.isDark(row, col + 3)
+ && qrcode.isDark(row, col + 4)
+ && !qrcode.isDark(row, col + 5)
+ && qrcode.isDark(row, col + 6) ) {
+ lostPoint += 40;
+ }
+ }
+ }
+
+ for (var col = 0; col < moduleCount; col += 1) {
+ for (var row = 0; row < moduleCount - 6; row += 1) {
+ if (qrcode.isDark(row, col)
+ && !qrcode.isDark(row + 1, col)
+ && qrcode.isDark(row + 2, col)
+ && qrcode.isDark(row + 3, col)
+ && qrcode.isDark(row + 4, col)
+ && !qrcode.isDark(row + 5, col)
+ && qrcode.isDark(row + 6, col) ) {
+ lostPoint += 40;
+ }
+ }
+ }
+
+ // LEVEL4
+
+ var darkCount = 0;
+
+ for (var col = 0; col < moduleCount; col += 1) {
+ for (var row = 0; row < moduleCount; row += 1) {
+ if (qrcode.isDark(row, col) ) {
+ darkCount += 1;
+ }
+ }
+ }
+
+ var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
+ lostPoint += ratio * 10;
+
+ return lostPoint;
+ };
+
+ return _this;
+ }();
+
+ //---------------------------------------------------------------------
+ // QRMath
+ //---------------------------------------------------------------------
+
+ var QRMath = function() {
+
+ var EXP_TABLE = new Array(256);
+ var LOG_TABLE = new Array(256);
+
+ // initialize tables
+ for (var i = 0; i < 8; i += 1) {
+ EXP_TABLE[i] = 1 << i;
+ }
+ for (var i = 8; i < 256; i += 1) {
+ EXP_TABLE[i] = EXP_TABLE[i - 4]
+ ^ EXP_TABLE[i - 5]
+ ^ EXP_TABLE[i - 6]
+ ^ EXP_TABLE[i - 8];
+ }
+ for (var i = 0; i < 255; i += 1) {
+ LOG_TABLE[EXP_TABLE[i] ] = i;
+ }
+
+ var _this = {};
+
+ _this.glog = function(n) {
+
+ if (n < 1) {
+ throw 'glog(' + n + ')';
+ }
+
+ return LOG_TABLE[n];
+ };
+
+ _this.gexp = function(n) {
+
+ while (n < 0) {
+ n += 255;
+ }
+
+ while (n >= 256) {
+ n -= 255;
+ }
+
+ return EXP_TABLE[n];
+ };
+
+ return _this;
+ }();
+
+ //---------------------------------------------------------------------
+ // qrPolynomial
+ //---------------------------------------------------------------------
+
+ function qrPolynomial(num, shift) {
+
+ if (typeof num.length == 'undefined') {
+ throw num.length + '/' + shift;
+ }
+
+ var _num = function() {
+ var offset = 0;
+ while (offset < num.length && num[offset] == 0) {
+ offset += 1;
+ }
+ var _num = new Array(num.length - offset + shift);
+ for (var i = 0; i < num.length - offset; i += 1) {
+ _num[i] = num[i + offset];
+ }
+ return _num;
+ }();
+
+ var _this = {};
+
+ _this.getAt = function(index) {
+ return _num[index];
+ };
+
+ _this.getLength = function() {
+ return _num.length;
+ };
+
+ _this.multiply = function(e) {
+
+ var num = new Array(_this.getLength() + e.getLength() - 1);
+
+ for (var i = 0; i < _this.getLength(); i += 1) {
+ for (var j = 0; j < e.getLength(); j += 1) {
+ num[i + j] ^= QRMath.gexp(QRMath.glog(_this.getAt(i) ) + QRMath.glog(e.getAt(j) ) );
+ }
+ }
+
+ return qrPolynomial(num, 0);
+ };
+
+ _this.mod = function(e) {
+
+ if (_this.getLength() - e.getLength() < 0) {
+ return _this;
+ }
+
+ var ratio = QRMath.glog(_this.getAt(0) ) - QRMath.glog(e.getAt(0) );
+
+ var num = new Array(_this.getLength() );
+ for (var i = 0; i < _this.getLength(); i += 1) {
+ num[i] = _this.getAt(i);
+ }
+
+ for (var i = 0; i < e.getLength(); i += 1) {
+ num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i) ) + ratio);
+ }
+
+ // recursive call
+ return qrPolynomial(num, 0).mod(e);
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // QRRSBlock
+ //---------------------------------------------------------------------
+
+ var QRRSBlock = function() {
+
+ var RS_BLOCK_TABLE = [
+
+ // L
+ // M
+ // Q
+ // H
+
+ // 1
+ [1, 26, 19],
+ [1, 26, 16],
+ [1, 26, 13],
+ [1, 26, 9],
+
+ // 2
+ [1, 44, 34],
+ [1, 44, 28],
+ [1, 44, 22],
+ [1, 44, 16],
+
+ // 3
+ [1, 70, 55],
+ [1, 70, 44],
+ [2, 35, 17],
+ [2, 35, 13],
+
+ // 4
+ [1, 100, 80],
+ [2, 50, 32],
+ [2, 50, 24],
+ [4, 25, 9],
+
+ // 5
+ [1, 134, 108],
+ [2, 67, 43],
+ [2, 33, 15, 2, 34, 16],
+ [2, 33, 11, 2, 34, 12],
+
+ // 6
+ [2, 86, 68],
+ [4, 43, 27],
+ [4, 43, 19],
+ [4, 43, 15],
+
+ // 7
+ [2, 98, 78],
+ [4, 49, 31],
+ [2, 32, 14, 4, 33, 15],
+ [4, 39, 13, 1, 40, 14],
+
+ // 8
+ [2, 121, 97],
+ [2, 60, 38, 2, 61, 39],
+ [4, 40, 18, 2, 41, 19],
+ [4, 40, 14, 2, 41, 15],
+
+ // 9
+ [2, 146, 116],
+ [3, 58, 36, 2, 59, 37],
+ [4, 36, 16, 4, 37, 17],
+ [4, 36, 12, 4, 37, 13],
+
+ // 10
+ [2, 86, 68, 2, 87, 69],
+ [4, 69, 43, 1, 70, 44],
+ [6, 43, 19, 2, 44, 20],
+ [6, 43, 15, 2, 44, 16],
+
+ // 11
+ [4, 101, 81],
+ [1, 80, 50, 4, 81, 51],
+ [4, 50, 22, 4, 51, 23],
+ [3, 36, 12, 8, 37, 13],
+
+ // 12
+ [2, 116, 92, 2, 117, 93],
+ [6, 58, 36, 2, 59, 37],
+ [4, 46, 20, 6, 47, 21],
+ [7, 42, 14, 4, 43, 15],
+
+ // 13
+ [4, 133, 107],
+ [8, 59, 37, 1, 60, 38],
+ [8, 44, 20, 4, 45, 21],
+ [12, 33, 11, 4, 34, 12],
+
+ // 14
+ [3, 145, 115, 1, 146, 116],
+ [4, 64, 40, 5, 65, 41],
+ [11, 36, 16, 5, 37, 17],
+ [11, 36, 12, 5, 37, 13],
+
+ // 15
+ [5, 109, 87, 1, 110, 88],
+ [5, 65, 41, 5, 66, 42],
+ [5, 54, 24, 7, 55, 25],
+ [11, 36, 12, 7, 37, 13],
+
+ // 16
+ [5, 122, 98, 1, 123, 99],
+ [7, 73, 45, 3, 74, 46],
+ [15, 43, 19, 2, 44, 20],
+ [3, 45, 15, 13, 46, 16],
+
+ // 17
+ [1, 135, 107, 5, 136, 108],
+ [10, 74, 46, 1, 75, 47],
+ [1, 50, 22, 15, 51, 23],
+ [2, 42, 14, 17, 43, 15],
+
+ // 18
+ [5, 150, 120, 1, 151, 121],
+ [9, 69, 43, 4, 70, 44],
+ [17, 50, 22, 1, 51, 23],
+ [2, 42, 14, 19, 43, 15],
+
+ // 19
+ [3, 141, 113, 4, 142, 114],
+ [3, 70, 44, 11, 71, 45],
+ [17, 47, 21, 4, 48, 22],
+ [9, 39, 13, 16, 40, 14],
+
+ // 20
+ [3, 135, 107, 5, 136, 108],
+ [3, 67, 41, 13, 68, 42],
+ [15, 54, 24, 5, 55, 25],
+ [15, 43, 15, 10, 44, 16],
+
+ // 21
+ [4, 144, 116, 4, 145, 117],
+ [17, 68, 42],
+ [17, 50, 22, 6, 51, 23],
+ [19, 46, 16, 6, 47, 17],
+
+ // 22
+ [2, 139, 111, 7, 140, 112],
+ [17, 74, 46],
+ [7, 54, 24, 16, 55, 25],
+ [34, 37, 13],
+
+ // 23
+ [4, 151, 121, 5, 152, 122],
+ [4, 75, 47, 14, 76, 48],
+ [11, 54, 24, 14, 55, 25],
+ [16, 45, 15, 14, 46, 16],
+
+ // 24
+ [6, 147, 117, 4, 148, 118],
+ [6, 73, 45, 14, 74, 46],
+ [11, 54, 24, 16, 55, 25],
+ [30, 46, 16, 2, 47, 17],
+
+ // 25
+ [8, 132, 106, 4, 133, 107],
+ [8, 75, 47, 13, 76, 48],
+ [7, 54, 24, 22, 55, 25],
+ [22, 45, 15, 13, 46, 16],
+
+ // 26
+ [10, 142, 114, 2, 143, 115],
+ [19, 74, 46, 4, 75, 47],
+ [28, 50, 22, 6, 51, 23],
+ [33, 46, 16, 4, 47, 17],
+
+ // 27
+ [8, 152, 122, 4, 153, 123],
+ [22, 73, 45, 3, 74, 46],
+ [8, 53, 23, 26, 54, 24],
+ [12, 45, 15, 28, 46, 16],
+
+ // 28
+ [3, 147, 117, 10, 148, 118],
+ [3, 73, 45, 23, 74, 46],
+ [4, 54, 24, 31, 55, 25],
+ [11, 45, 15, 31, 46, 16],
+
+ // 29
+ [7, 146, 116, 7, 147, 117],
+ [21, 73, 45, 7, 74, 46],
+ [1, 53, 23, 37, 54, 24],
+ [19, 45, 15, 26, 46, 16],
+
+ // 30
+ [5, 145, 115, 10, 146, 116],
+ [19, 75, 47, 10, 76, 48],
+ [15, 54, 24, 25, 55, 25],
+ [23, 45, 15, 25, 46, 16],
+
+ // 31
+ [13, 145, 115, 3, 146, 116],
+ [2, 74, 46, 29, 75, 47],
+ [42, 54, 24, 1, 55, 25],
+ [23, 45, 15, 28, 46, 16],
+
+ // 32
+ [17, 145, 115],
+ [10, 74, 46, 23, 75, 47],
+ [10, 54, 24, 35, 55, 25],
+ [19, 45, 15, 35, 46, 16],
+
+ // 33
+ [17, 145, 115, 1, 146, 116],
+ [14, 74, 46, 21, 75, 47],
+ [29, 54, 24, 19, 55, 25],
+ [11, 45, 15, 46, 46, 16],
+
+ // 34
+ [13, 145, 115, 6, 146, 116],
+ [14, 74, 46, 23, 75, 47],
+ [44, 54, 24, 7, 55, 25],
+ [59, 46, 16, 1, 47, 17],
+
+ // 35
+ [12, 151, 121, 7, 152, 122],
+ [12, 75, 47, 26, 76, 48],
+ [39, 54, 24, 14, 55, 25],
+ [22, 45, 15, 41, 46, 16],
+
+ // 36
+ [6, 151, 121, 14, 152, 122],
+ [6, 75, 47, 34, 76, 48],
+ [46, 54, 24, 10, 55, 25],
+ [2, 45, 15, 64, 46, 16],
+
+ // 37
+ [17, 152, 122, 4, 153, 123],
+ [29, 74, 46, 14, 75, 47],
+ [49, 54, 24, 10, 55, 25],
+ [24, 45, 15, 46, 46, 16],
+
+ // 38
+ [4, 152, 122, 18, 153, 123],
+ [13, 74, 46, 32, 75, 47],
+ [48, 54, 24, 14, 55, 25],
+ [42, 45, 15, 32, 46, 16],
+
+ // 39
+ [20, 147, 117, 4, 148, 118],
+ [40, 75, 47, 7, 76, 48],
+ [43, 54, 24, 22, 55, 25],
+ [10, 45, 15, 67, 46, 16],
+
+ // 40
+ [19, 148, 118, 6, 149, 119],
+ [18, 75, 47, 31, 76, 48],
+ [34, 54, 24, 34, 55, 25],
+ [20, 45, 15, 61, 46, 16]
+ ];
+
+ var qrRSBlock = function(totalCount, dataCount) {
+ var _this = {};
+ _this.totalCount = totalCount;
+ _this.dataCount = dataCount;
+ return _this;
+ };
+
+ var _this = {};
+
+ var getRsBlockTable = function(typeNumber, errorCorrectionLevel) {
+
+ switch(errorCorrectionLevel) {
+ case QRErrorCorrectionLevel.L :
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
+ case QRErrorCorrectionLevel.M :
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
+ case QRErrorCorrectionLevel.Q :
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
+ case QRErrorCorrectionLevel.H :
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
+ default :
+ return undefined;
+ }
+ };
+
+ _this.getRSBlocks = function(typeNumber, errorCorrectionLevel) {
+
+ var rsBlock = getRsBlockTable(typeNumber, errorCorrectionLevel);
+
+ if (typeof rsBlock == 'undefined') {
+ throw 'bad rs block @ typeNumber:' + typeNumber +
+ '/errorCorrectionLevel:' + errorCorrectionLevel;
+ }
+
+ var length = rsBlock.length / 3;
+
+ var list = [];
+
+ for (var i = 0; i < length; i += 1) {
+
+ var count = rsBlock[i * 3 + 0];
+ var totalCount = rsBlock[i * 3 + 1];
+ var dataCount = rsBlock[i * 3 + 2];
+
+ for (var j = 0; j < count; j += 1) {
+ list.push(qrRSBlock(totalCount, dataCount) );
+ }
+ }
+
+ return list;
+ };
+
+ return _this;
+ }();
+
+ //---------------------------------------------------------------------
+ // qrBitBuffer
+ //---------------------------------------------------------------------
+
+ var qrBitBuffer = function() {
+
+ var _buffer = [];
+ var _length = 0;
+
+ var _this = {};
+
+ _this.getBuffer = function() {
+ return _buffer;
+ };
+
+ _this.getAt = function(index) {
+ var bufIndex = Math.floor(index / 8);
+ return ( (_buffer[bufIndex] >>> (7 - index % 8) ) & 1) == 1;
+ };
+
+ _this.put = function(num, length) {
+ for (var i = 0; i < length; i += 1) {
+ _this.putBit( ( (num >>> (length - i - 1) ) & 1) == 1);
+ }
+ };
+
+ _this.getLengthInBits = function() {
+ return _length;
+ };
+
+ _this.putBit = function(bit) {
+
+ var bufIndex = Math.floor(_length / 8);
+ if (_buffer.length <= bufIndex) {
+ _buffer.push(0);
+ }
+
+ if (bit) {
+ _buffer[bufIndex] |= (0x80 >>> (_length % 8) );
+ }
+
+ _length += 1;
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // qrNumber
+ //---------------------------------------------------------------------
+
+ var qrNumber = function(data) {
+
+ var _mode = QRMode.MODE_NUMBER;
+ var _data = data;
+
+ var _this = {};
+
+ _this.getMode = function() {
+ return _mode;
+ };
+
+ _this.getLength = function(buffer) {
+ return _data.length;
+ };
+
+ _this.write = function(buffer) {
+
+ var data = _data;
+
+ var i = 0;
+
+ while (i + 2 < data.length) {
+ buffer.put(strToNum(data.substring(i, i + 3) ), 10);
+ i += 3;
+ }
+
+ if (i < data.length) {
+ if (data.length - i == 1) {
+ buffer.put(strToNum(data.substring(i, i + 1) ), 4);
+ } else if (data.length - i == 2) {
+ buffer.put(strToNum(data.substring(i, i + 2) ), 7);
+ }
+ }
+ };
+
+ var strToNum = function(s) {
+ var num = 0;
+ for (var i = 0; i < s.length; i += 1) {
+ num = num * 10 + chatToNum(s.charAt(i) );
+ }
+ return num;
+ };
+
+ var chatToNum = function(c) {
+ if ('0' <= c && c <= '9') {
+ return c.charCodeAt(0) - '0'.charCodeAt(0);
+ }
+ throw 'illegal char :' + c;
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // qrAlphaNum
+ //---------------------------------------------------------------------
+
+ var qrAlphaNum = function(data) {
+
+ var _mode = QRMode.MODE_ALPHA_NUM;
+ var _data = data;
+
+ var _this = {};
+
+ _this.getMode = function() {
+ return _mode;
+ };
+
+ _this.getLength = function(buffer) {
+ return _data.length;
+ };
+
+ _this.write = function(buffer) {
+
+ var s = _data;
+
+ var i = 0;
+
+ while (i + 1 < s.length) {
+ buffer.put(
+ getCode(s.charAt(i) ) * 45 +
+ getCode(s.charAt(i + 1) ), 11);
+ i += 2;
+ }
+
+ if (i < s.length) {
+ buffer.put(getCode(s.charAt(i) ), 6);
+ }
+ };
+
+ var getCode = function(c) {
+
+ if ('0' <= c && c <= '9') {
+ return c.charCodeAt(0) - '0'.charCodeAt(0);
+ } else if ('A' <= c && c <= 'Z') {
+ return c.charCodeAt(0) - 'A'.charCodeAt(0) + 10;
+ } else {
+ switch (c) {
+ case ' ' : return 36;
+ case '$' : return 37;
+ case '%' : return 38;
+ case '*' : return 39;
+ case '+' : return 40;
+ case '-' : return 41;
+ case '.' : return 42;
+ case '/' : return 43;
+ case ':' : return 44;
+ default :
+ throw 'illegal char :' + c;
+ }
+ }
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // qr8BitByte
+ //---------------------------------------------------------------------
+
+ var qr8BitByte = function(data) {
+
+ var _mode = QRMode.MODE_8BIT_BYTE;
+ var _data = data;
+ var _bytes = qrcode.stringToBytes(data);
+
+ var _this = {};
+
+ _this.getMode = function() {
+ return _mode;
+ };
+
+ _this.getLength = function(buffer) {
+ return _bytes.length;
+ };
+
+ _this.write = function(buffer) {
+ for (var i = 0; i < _bytes.length; i += 1) {
+ buffer.put(_bytes[i], 8);
+ }
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // qrKanji
+ //---------------------------------------------------------------------
+
+ var qrKanji = function(data) {
+
+ var _mode = QRMode.MODE_KANJI;
+ var _data = data;
+
+ var stringToBytes = qrcode.stringToBytesFuncs['SJIS'];
+ if (!stringToBytes) {
+ throw 'sjis not supported.';
+ }
+ !function(c, code) {
+ // self test for sjis support.
+ var test = stringToBytes(c);
+ if (test.length != 2 || ( (test[0] << 8) | test[1]) != code) {
+ throw 'sjis not supported.';
+ }
+ }('\u53cb', 0x9746);
+
+ var _bytes = stringToBytes(data);
+
+ var _this = {};
+
+ _this.getMode = function() {
+ return _mode;
+ };
+
+ _this.getLength = function(buffer) {
+ return ~~(_bytes.length / 2);
+ };
+
+ _this.write = function(buffer) {
+
+ var data = _bytes;
+
+ var i = 0;
+
+ while (i + 1 < data.length) {
+
+ var c = ( (0xff & data[i]) << 8) | (0xff & data[i + 1]);
+
+ if (0x8140 <= c && c <= 0x9FFC) {
+ c -= 0x8140;
+ } else if (0xE040 <= c && c <= 0xEBBF) {
+ c -= 0xC140;
+ } else {
+ throw 'illegal char at ' + (i + 1) + '/' + c;
+ }
+
+ c = ( (c >>> 8) & 0xff) * 0xC0 + (c & 0xff);
+
+ buffer.put(c, 13);
+
+ i += 2;
+ }
+
+ if (i < data.length) {
+ throw 'illegal char at ' + (i + 1);
+ }
+ };
+
+ return _this;
+ };
+
+ //=====================================================================
+ // GIF Support etc.
+ //
+
+ //---------------------------------------------------------------------
+ // byteArrayOutputStream
+ //---------------------------------------------------------------------
+
+ var byteArrayOutputStream = function() {
+
+ var _bytes = [];
+
+ var _this = {};
+
+ _this.writeByte = function(b) {
+ _bytes.push(b & 0xff);
+ };
+
+ _this.writeShort = function(i) {
+ _this.writeByte(i);
+ _this.writeByte(i >>> 8);
+ };
+
+ _this.writeBytes = function(b, off, len) {
+ off = off || 0;
+ len = len || b.length;
+ for (var i = 0; i < len; i += 1) {
+ _this.writeByte(b[i + off]);
+ }
+ };
+
+ _this.writeString = function(s) {
+ for (var i = 0; i < s.length; i += 1) {
+ _this.writeByte(s.charCodeAt(i) );
+ }
+ };
+
+ _this.toByteArray = function() {
+ return _bytes;
+ };
+
+ _this.toString = function() {
+ var s = '';
+ s += '[';
+ for (var i = 0; i < _bytes.length; i += 1) {
+ if (i > 0) {
+ s += ',';
+ }
+ s += _bytes[i];
+ }
+ s += ']';
+ return s;
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // base64EncodeOutputStream
+ //---------------------------------------------------------------------
+
+ var base64EncodeOutputStream = function() {
+
+ var _buffer = 0;
+ var _buflen = 0;
+ var _length = 0;
+ var _base64 = '';
+
+ var _this = {};
+
+ var writeEncoded = function(b) {
+ _base64 += String.fromCharCode(encode(b & 0x3f) );
+ };
+
+ var encode = function(n) {
+ if (n < 0) {
+ // error.
+ } else if (n < 26) {
+ return 0x41 + n;
+ } else if (n < 52) {
+ return 0x61 + (n - 26);
+ } else if (n < 62) {
+ return 0x30 + (n - 52);
+ } else if (n == 62) {
+ return 0x2b;
+ } else if (n == 63) {
+ return 0x2f;
+ }
+ throw 'n:' + n;
+ };
+
+ _this.writeByte = function(n) {
+
+ _buffer = (_buffer << 8) | (n & 0xff);
+ _buflen += 8;
+ _length += 1;
+
+ while (_buflen >= 6) {
+ writeEncoded(_buffer >>> (_buflen - 6) );
+ _buflen -= 6;
+ }
+ };
+
+ _this.flush = function() {
+
+ if (_buflen > 0) {
+ writeEncoded(_buffer << (6 - _buflen) );
+ _buffer = 0;
+ _buflen = 0;
+ }
+
+ if (_length % 3 != 0) {
+ // padding
+ var padlen = 3 - _length % 3;
+ for (var i = 0; i < padlen; i += 1) {
+ _base64 += '=';
+ }
+ }
+ };
+
+ _this.toString = function() {
+ return _base64;
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // base64DecodeInputStream
+ //---------------------------------------------------------------------
+
+ var base64DecodeInputStream = function(str) {
+
+ var _str = str;
+ var _pos = 0;
+ var _buffer = 0;
+ var _buflen = 0;
+
+ var _this = {};
+
+ _this.read = function() {
+
+ while (_buflen < 8) {
+
+ if (_pos >= _str.length) {
+ if (_buflen == 0) {
+ return -1;
+ }
+ throw 'unexpected end of file./' + _buflen;
+ }
+
+ var c = _str.charAt(_pos);
+ _pos += 1;
+
+ if (c == '=') {
+ _buflen = 0;
+ return -1;
+ } else if (c.match(/^\s$/) ) {
+ // ignore if whitespace.
+ continue;
+ }
+
+ _buffer = (_buffer << 6) | decode(c.charCodeAt(0) );
+ _buflen += 6;
+ }
+
+ var n = (_buffer >>> (_buflen - 8) ) & 0xff;
+ _buflen -= 8;
+ return n;
+ };
+
+ var decode = function(c) {
+ if (0x41 <= c && c <= 0x5a) {
+ return c - 0x41;
+ } else if (0x61 <= c && c <= 0x7a) {
+ return c - 0x61 + 26;
+ } else if (0x30 <= c && c <= 0x39) {
+ return c - 0x30 + 52;
+ } else if (c == 0x2b) {
+ return 62;
+ } else if (c == 0x2f) {
+ return 63;
+ } else {
+ throw 'c:' + c;
+ }
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // gifImage (B/W)
+ //---------------------------------------------------------------------
+
+ var gifImage = function(width, height) {
+
+ var _width = width;
+ var _height = height;
+ var _data = new Array(width * height);
+
+ var _this = {};
+
+ _this.setPixel = function(x, y, pixel) {
+ _data[y * _width + x] = pixel;
+ };
+
+ _this.write = function(out) {
+
+ //---------------------------------
+ // GIF Signature
+
+ out.writeString('GIF87a');
+
+ //---------------------------------
+ // Screen Descriptor
+
+ out.writeShort(_width);
+ out.writeShort(_height);
+
+ out.writeByte(0x80); // 2bit
+ out.writeByte(0);
+ out.writeByte(0);
+
+ //---------------------------------
+ // Global Color Map
+
+ // black
+ out.writeByte(0x00);
+ out.writeByte(0x00);
+ out.writeByte(0x00);
+
+ // white
+ out.writeByte(0xff);
+ out.writeByte(0xff);
+ out.writeByte(0xff);
+
+ //---------------------------------
+ // Image Descriptor
+
+ out.writeString(',');
+ out.writeShort(0);
+ out.writeShort(0);
+ out.writeShort(_width);
+ out.writeShort(_height);
+ out.writeByte(0);
+
+ //---------------------------------
+ // Local Color Map
+
+ //---------------------------------
+ // Raster Data
+
+ var lzwMinCodeSize = 2;
+ var raster = getLZWRaster(lzwMinCodeSize);
+
+ out.writeByte(lzwMinCodeSize);
+
+ var offset = 0;
+
+ while (raster.length - offset > 255) {
+ out.writeByte(255);
+ out.writeBytes(raster, offset, 255);
+ offset += 255;
+ }
+
+ out.writeByte(raster.length - offset);
+ out.writeBytes(raster, offset, raster.length - offset);
+ out.writeByte(0x00);
+
+ //---------------------------------
+ // GIF Terminator
+ out.writeString(';');
+ };
+
+ var bitOutputStream = function(out) {
+
+ var _out = out;
+ var _bitLength = 0;
+ var _bitBuffer = 0;
+
+ var _this = {};
+
+ _this.write = function(data, length) {
+
+ if ( (data >>> length) != 0) {
+ throw 'length over';
+ }
+
+ while (_bitLength + length >= 8) {
+ _out.writeByte(0xff & ( (data << _bitLength) | _bitBuffer) );
+ length -= (8 - _bitLength);
+ data >>>= (8 - _bitLength);
+ _bitBuffer = 0;
+ _bitLength = 0;
+ }
+
+ _bitBuffer = (data << _bitLength) | _bitBuffer;
+ _bitLength = _bitLength + length;
+ };
+
+ _this.flush = function() {
+ if (_bitLength > 0) {
+ _out.writeByte(_bitBuffer);
+ }
+ };
+
+ return _this;
+ };
+
+ var getLZWRaster = function(lzwMinCodeSize) {
+
+ var clearCode = 1 << lzwMinCodeSize;
+ var endCode = (1 << lzwMinCodeSize) + 1;
+ var bitLength = lzwMinCodeSize + 1;
+
+ // Setup LZWTable
+ var table = lzwTable();
+
+ for (var i = 0; i < clearCode; i += 1) {
+ table.add(String.fromCharCode(i) );
+ }
+ table.add(String.fromCharCode(clearCode) );
+ table.add(String.fromCharCode(endCode) );
+
+ var byteOut = byteArrayOutputStream();
+ var bitOut = bitOutputStream(byteOut);
+
+ // clear code
+ bitOut.write(clearCode, bitLength);
+
+ var dataIndex = 0;
+
+ var s = String.fromCharCode(_data[dataIndex]);
+ dataIndex += 1;
+
+ while (dataIndex < _data.length) {
+
+ var c = String.fromCharCode(_data[dataIndex]);
+ dataIndex += 1;
+
+ if (table.contains(s + c) ) {
+
+ s = s + c;
+
+ } else {
+
+ bitOut.write(table.indexOf(s), bitLength);
+
+ if (table.size() < 0xfff) {
+
+ if (table.size() == (1 << bitLength) ) {
+ bitLength += 1;
+ }
+
+ table.add(s + c);
+ }
+
+ s = c;
+ }
+ }
+
+ bitOut.write(table.indexOf(s), bitLength);
+
+ // end code
+ bitOut.write(endCode, bitLength);
+
+ bitOut.flush();
+
+ return byteOut.toByteArray();
+ };
+
+ var lzwTable = function() {
+
+ var _map = {};
+ var _size = 0;
+
+ var _this = {};
+
+ _this.add = function(key) {
+ if (_this.contains(key) ) {
+ throw 'dup key:' + key;
+ }
+ _map[key] = _size;
+ _size += 1;
+ };
+
+ _this.size = function() {
+ return _size;
+ };
+
+ _this.indexOf = function(key) {
+ return _map[key];
+ };
+
+ _this.contains = function(key) {
+ return typeof _map[key] != 'undefined';
+ };
+
+ return _this;
+ };
+
+ return _this;
+ };
+
+ var createDataURL = function(width, height, getPixel) {
+ var gif = gifImage(width, height);
+ for (var y = 0; y < height; y += 1) {
+ for (var x = 0; x < width; x += 1) {
+ gif.setPixel(x, y, getPixel(x, y) );
+ }
+ }
+
+ var b = byteArrayOutputStream();
+ gif.write(b);
+
+ var base64 = base64EncodeOutputStream();
+ var bytes = b.toByteArray();
+ for (var i = 0; i < bytes.length; i += 1) {
+ base64.writeByte(bytes[i]);
+ }
+ base64.flush();
+
+ return 'data:image/gif;base64,' + base64;
+ };
+
+ //---------------------------------------------------------------------
+ // returns qrcode function.
+
+ return qrcode;
+ }();
+
+ // multibyte support
+ !function() {
+
+ qrcode.stringToBytesFuncs['UTF-8'] = function(s) {
+ // http://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array
+ function toUTF8Array(str) {
+ var utf8 = [];
+ for (var i=0; i < str.length; i++) {
+ var charcode = str.charCodeAt(i);
+ if (charcode < 0x80) utf8.push(charcode);
+ else if (charcode < 0x800) {
+ utf8.push(0xc0 | (charcode >> 6),
+ 0x80 | (charcode & 0x3f));
+ }
+ else if (charcode < 0xd800 || charcode >= 0xe000) {
+ utf8.push(0xe0 | (charcode >> 12),
+ 0x80 | ((charcode>>6) & 0x3f),
+ 0x80 | (charcode & 0x3f));
+ }
+ // surrogate pair
+ else {
+ i++;
+ // UTF-16 encodes 0x10000-0x10FFFF by
+ // subtracting 0x10000 and splitting the
+ // 20 bits of 0x0-0xFFFFF into two halves
+ charcode = 0x10000 + (((charcode & 0x3ff)<<10)
+ | (str.charCodeAt(i) & 0x3ff));
+ utf8.push(0xf0 | (charcode >>18),
+ 0x80 | ((charcode>>12) & 0x3f),
+ 0x80 | ((charcode>>6) & 0x3f),
+ 0x80 | (charcode & 0x3f));
+ }
+ }
+ return utf8;
+ }
+ return toUTF8Array(s);
+ };
+
+ }();
+
+ (function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ define([], factory);
+ } else if (typeof exports === 'object') {
+ module.exports = factory();
+ }
+ }(function () {
+ return qrcode;
+ }));
\ No newline at end of file