From 55999decbd7458078caeb86e769ec5bb3ed9b2c7 Mon Sep 17 00:00:00 2001 From: Niush Date: Mon, 18 Oct 2021 23:38:09 +0545 Subject: [PATCH] Major v1.0.2 - Use Nano.to POST Method as default checkout page generator - Ability to add additional Metadata when creating checkout page (Can be received back in Webhook) - Added Business (Name, Logo, Favicon), Background & Color customization - Ability to generate QR Code for RAW Nano (Supporting Natrium) - NanoToApi Advanced / Helper functions - PHP Unit Tests Updated & Added with multiple Issue Fixes --- .github/workflows/main.yml | 4 +- CHANGELOG.md | 9 ++ README.md | 101 ++++++++++++----- config/config.php | 15 +++ phpunit.xml | 1 + src/LaravelNanoTo.php | 183 ++++++++++++++++++++++++++++--- src/NanoToApi.php | 97 +++++++++++++++++ tests/LaravelNanoToTest.php | 140 ++++++++++++++++++------ tests/NanoToApiTest.php | 209 ++++++++++++++++++++++++++++++++++++ 9 files changed, 691 insertions(+), 68 deletions(-) create mode 100644 src/NanoToApi.php create mode 100644 tests/NanoToApiTest.php diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index addd840..e806f6b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: run-tests +name: tests on: push: @@ -9,6 +9,8 @@ on: jobs: test: runs-on: ${{ matrix.os }} + env: + USE_REAL_API: true strategy: fail-fast: true matrix: diff --git a/CHANGELOG.md b/CHANGELOG.md index c763428..4e49fea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to `laravel-nano-to` will be documented in this file +## 1.0.2 - 2021-10-18 + +- Use Nano.to POST Method as default checkout page generator +- Ability to add additional Metadata when creating checkout page (Can be received back in Webhook) +- Added Business (Name, Logo, Favicon), Background & Color customization +- Ability to generate QR Code for RAW Nano (Supporting Natrium) +- NanoToApi Advanced / Helper functions +- PHP Unit Tests Updated & Added with multiple Issue Fixes + ## 1.0.1 - 2021-09-19 - Allow Custom Webhook Secret diff --git a/README.md b/README.md index d101098..11b3511 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Easily integrate [Nano.to](https://nano.to/) Payment Gateway in Laravel Application, with full control. -[![Latest Stable Version](http://poser.pugx.org/niush/laravel-nano-to/v)](https://packagist.org/packages/niush/laravel-nano-to) -[![Total Downloads](http://poser.pugx.org/niush/laravel-nano-to/downloads)](https://packagist.org/packages/niush/laravel-nano-to) -[![PHP Version Require](http://poser.pugx.org/niush/laravel-nano-to/require/php)](https://packagist.org/packages/niush/laravel-nano-to) +[![Latest Stable Version](https://poser.pugx.org/niush/laravel-nano-to/v)](https://packagist.org/packages/niush/laravel-nano-to) +[![Total Downloads](https://poser.pugx.org/niush/laravel-nano-to/downloads)](https://packagist.org/packages/niush/laravel-nano-to) +[![PHP Version Require](https://poser.pugx.org/niush/laravel-nano-to/require/php)](https://packagist.org/packages/niush/laravel-nano-to) ![GitHub Actions](https://github.com/niush/laravel-nano-to/actions/workflows/main.yml/badge.svg) ## Installation on Laravel @@ -31,7 +31,7 @@ NANO_WEBHOOK_SECRET= Go through the generated config file and update as required. Make sure the Nano address all belongs to you and is accessible. You can provide, default title and description. ## Required Named Routes -For this to work properly, you must have these 3 named routes created to handle your business logic. +For this to work properly, you must have these 3 named routes created to handle your business logic. **The named route can always be customized in config file**. - `nano-to-success`: Redirected when Payment is successful. Do not confirm the payment using this route. The confirmation must be handled in webhook url. ```php Route::get('/order/success/{id}', [OrderController::class, 'success'])->name('nano-to-success'); // E.g. Shows a static order success page. @@ -54,14 +54,15 @@ By default the config accepts named route for webhook and success page. If you w As, you have full control, it can easily be implemented via APIs also. ## Usage -**NOTE: Amount is always in USD Currency.** +**NOTE: Amount is always in USD Currency.** (You can use rough USDT to Fiat conversion [helper function](#advanced-usage)) For initiating Payment process: ```php // 1) With Specific Amount -return LaravelNanoTo::amount(10)->create($order->id, function($checkout_url, $original_url) { - // Do Something with $checkout_url or $original_url if required. +return LaravelNanoTo::amount(10)->create($order->id, function($checkout_url) { + // Do Something with $checkout_url if required. + // For SPA send link as JSON response etc. })->send(); // 2) With Custom Info (Else uses title and description from config) @@ -69,18 +70,28 @@ return LaravelNanoTo::info("Payment for Subscription", "Also accepts HTML ->amount(9.99) ->create($order->id)->send(); -// 3) For Suggest based payment. Useful in cases like Donation. +// 3) With Additional Metadata (Can be received in Webhook) +return LaravelNanoTo::amount(9.99)->metadata([ + "user_id" => $user->id, + "order_id" => $order->id +])->create($order->id)->send(); + +// 4) For Suggest based payment. Useful in cases like Donation. return LaravelNanoTo::info("Donate Us") ->suggest([ - ["name" => "Coffee", "amount" => "10"], - ["name" => "Meal", "amount" => "50"] + ["name" => "Coffee", "price" => "10"], + ["name" => "Meal", "price" => "50"] ]) ->create($uuid)->send(); -// 4) Or Simply, if no need to track anything. And, required routes do not need {id} param. +// 5) Use RAW friendly Amount in QR Codes (e.g. for Natrium) +return LaravelNanoTo::asRaw()->amount(9.99)->create($order->id)->send(); + +// 6) Or Simply, if no need to track anything. And, required routes do not need {id} param. return LaravelNanoTo::create(); // Receiving Nano Address will randomly be picked from config file. +// The first parameter of create (e.g. $order->id) will be used as params in named routes. ``` **You might want to use custom Webhook Secret. So that, it is always different for each checkout. So, instead of using same environment variable. You can do:** @@ -119,25 +130,33 @@ $request->header('Webhook-Secret') == config("laravel-nano-to.webhook_secret") / "name": "Nano", "rate": "5.589960", // Currency Rate (Nano → USD) "amount": "1.788115", // Nano Received - "value": "0.01" + "value": "0.01", + "raw": false + }, + "plan": { // If using Suggest mode + "price": 10, + "name": "Meal" }, - "metadata": { - "payment": { // Block Information + "block": { // Block Information "type": "state", "representative": "nano_3chart...", "link": "391D8B81DB...", - "balance": "372647920414...", + "balance": "3.726479", "previous": "1922BFA40E86C....", "subtype": "receive", // You must be receiving :) "account": "nano_36qn7ydq...", // Sender Address - "amount": "1788115000000000000...", // RAW Nano + "amount": "1.788115", // Nano Received "local_timestamp": "1631954...", "height": "37", "hash": "9829B0306E5269A9A0...", // Transaction Identifier (Most important piece to store.) "work": "210862fa...", "signature": "CC16D6519C1113767EA36..", - "timestamp": "16319544.." - } + "timestamp": "16319544..", + "balance_raw": "372647920414...", + "amount_raw": "1788115000000000000..." // RAW Nano + }, + "metadata": { // All Additional Metadata sent + "user_id": "my_meta" } } ``` @@ -145,11 +164,11 @@ $request->header('Webhook-Secret') == config("laravel-nano-to.webhook_secret") / // Compare the body, store required info in DB and finally update the order status. In Webhook Controller. $request->input('amount') == $order->amount_in_usd; $request->input('status') == "complete"; -$request->input('metadata.payment.subtype') == "receive"; +$request->input('block.subtype') == "receive"; // You can also compare receiver address is in config or not. $order->via = "nano"; -$order->hash = $request->input('metadata.payment.hash'); +$order->hash = $request->input('block.hash'); $order->status = "complete"; $order-save(); ``` @@ -168,12 +187,12 @@ public function webhook(Request $request, Order $order) { if( $request->input('amount') == $order->amount_in_usd && $request->input('status') == "complete" && - $request->input('metadata.payment.subtype') == "receive" && - $request->input('metadata.payment.hash') + $request->input('block.subtype') == "receive" && + $request->input('block.hash') ) { $order->status = "complete"; - $order->hash = $request->input('metadata.payment.hash'); - $order->remarks = "Payment Complete from Address: " . $request->input('metadata.payment.account') . " , with Amount: " . $request->input('method.amount'); + $order->hash = $request->input('block.hash'); + $order->remarks = "Payment Complete from Address: " . $request->input('block.account') . " , with Amount: " . $request->input('method.amount'); $order->save(); } else { @@ -182,6 +201,9 @@ public function webhook(Request $request, Order $order) { } } + // You can also utilize Metadata for verification: + // $request->input('metadata.user_id') == $order->user_id; + $order->save(); return ["success" => true]; @@ -189,6 +211,37 @@ public function webhook(Request $request, Order $order) { ``` + +### Advanced Usage (API / Helpers) +[View details and response here](https://github.com/formsend/nano#advanced-usage-api) + +```php +use Niush\LaravelNanoTo\NanoToApi; + +// 1) Get CoinMarketCap conversion rate +NanoToApi::getPrice("NANO", "USD"); +NanoToApi::getPrice("XMR", "NPR"); +NanoToApi::getPrice("NANO", "XMR"); + +// 2) Get Nano.to Custom Username alias information +NanoToApi::getUsername("moon"); + +// 3) Get Nano Address Information +NanoToApi::getNanoAddressInfo("nano_3xxxx"); + +// 4) Get Total Nano Balance from all nano address provided in config file +NanoToApi::getTotalNanoBalance(); + +// 5) Get Pending Nano Blocks +NanoToApi::getPendingNanoBlocks("nano_3xxxx"); + +// 6) Get Last 20+ Nano Address History +NanoToApi::getNanoAddressHistory("nano_3xxxx"); + +// 6) Get Nano Transaction by specific Amount (Amount must be in Nano decimal format) +NanoToApi::getNanoTransactionByAmount("nano_3xxxx", "2.101"); +``` + ### Translation Add translation for these messages if required. - `nano-to.checkout-page-not-loaded` = "Unable to load Checkout Page." diff --git a/config/config.php b/config/config.php index 32b0f2e..350f8bf 100644 --- a/config/config.php +++ b/config/config.php @@ -35,6 +35,21 @@ */ 'description' => 'Please make the payment as specified.', + /** + * Business Name & Logo (Publicly Accessible Full URL) for customization + */ + 'business' => [ + "name" => env('APP_NAME'), + "logo" => "", + "favicon" => "" + ], + + /** + * Basic UI Customization + */ + "background" => "#eeeeee", + "color" => "#111111", + /** * Named Route to Re-direct when Nano payment is successful. e.g. /order/success/{id} * If Named Route not found, it will use the string as full url itself. Useful for sending to different domain etc. diff --git a/phpunit.xml b/phpunit.xml index c4b3800..2ac22e2 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -38,6 +38,7 @@ + diff --git a/src/LaravelNanoTo.php b/src/LaravelNanoTo.php index 235f248..8ea4409 100644 --- a/src/LaravelNanoTo.php +++ b/src/LaravelNanoTo.php @@ -15,15 +15,19 @@ class LaravelNanoTo public $description = ""; public $amount; public $suggest; + public $business; public $checkout_url; public $webhook_secret; public $symbol = 'nano'; + public $metadata = null; + public $raw = false; public function __construct() { $this->title = config('laravel-nano-to.title'); $this->description = config('laravel-nano-to.description'); $this->webhook_secret = config('laravel-nano-to.webhook_secret'); + $this->business = config('laravel-nano-to.business'); } /** @@ -48,26 +52,38 @@ public function info($title = null, $description = null) */ public function amount($amount) { + if($amount < 0.1) { + throw new Error("Minimum allowed amount in USD is 0.1"); + } $this->amount = $amount; return $this; } /** - * Sets the suggested name and amount. Useful for quick donations. + * Sets the suggested name and price. Useful for quick donations. * - * [ ["name" => "Coffee", "amount" => "10"], ["name" => "Meal", "amount" => "50"] ] + * [ ["name" => "Coffee", "price" => "10"], ["name" => "Meal", "price" => "50"] ] * * @params array $suggest * @return Niush\LaravelNanoTo\LaravelNanoTo */ public function suggest($suggest) { - $parameters = array_map(function ($s) { - return $s["name"] . ":" . $s["amount"]; - }, $suggest); - - $this->suggest = implode(",", $parameters); + $this->suggest = $suggest; + return $this; + } + /** + * Override Business name and logo from config + * + * ["name"=>"Company Name", "logo"=>"https://logo.png", "favicon"=>"https://logo.ico"] + * + * @params array $business + * @return Niush\LaravelNanoTo\LaravelNanoTo + */ + public function business($business) + { + $this->business = $business; return $this; } @@ -84,11 +100,34 @@ public function secret($secret) } /** - * Redirect to the Nano.to Gateway URL + * Set the additional Metadata in request body + * + * @params array $metadata + * @return Niush\LaravelNanoTo\LaravelNanoTo + */ + public function metadata($metadata) + { + $this->metadata = $metadata; + return $this; + } + + /** + * Generate RAW friendly QR Codes (e.g. for Natrium) + * + * @return Niush\LaravelNanoTo\LaravelNanoTo + */ + public function asRaw() + { + $this->raw = true; + return $this; + } + + /** + * Get the Nano.to Gateway URL. * * @params integer|string $unique_id * @params function $callback - * @return Illuminate\Http\RedirectResponse + * @return Niush\LaravelNanoTo\LaravelNanoTo || Illuminate\Http\RedirectResponse */ public function create($unique_id = null, $callback = null) { @@ -110,6 +149,112 @@ public function create($unique_id = null, $callback = null) $webhook_url = config('laravel-nano-to.local_webhook_url'); } + if ($accounts && sizeof($accounts) > 0) { + $address = $accounts[array_rand($accounts)]; + $url = $this->base_url . '/' . $address; + $body = [ + "title" => $this->title, + "description" => $this->description, + "success_url" => $success_url, + "cancel_url" => $cancel_url, + "webhook_url" => $webhook_url, + "webhook_secret" => $this->webhook_secret, + "background" => config('laravel-nano-to.background'), + "color" => config('laravel-nano-to.color'), + "raw" => $this->raw, + ]; + + if ($this->amount) { + $body['price'] = $this->amount; + } elseif ($this->suggest) { + $body['plans'] = $this->suggest; + } + + if($this->business) { + $body['business'] = $this->business; + } + + if($this->metadata) { + $body['metadata'] = $this->metadata; + } + + try { + $client = new Client(); + + // Fake Nano.to response if testing. + if (App::environment() == 'testing') { + $response = new Response(200, [ + 'Content-Type' => 'application/json; charset=utf-8', + ], '{"id":"test_id","url":"https://example.com/1","exp":"2021-10-10T01:51:23.853Z"}'); + } else { + $response = $client->post($url, [ + 'json' => $body + ]); + } + // var_dump($response->getBody()->getContents()); + $this->checkout_url = json_decode($response->getBody()->getContents(), true)["url"]; + + if (!$this->checkout_url) { + return $this->throw_checkout_page_not_loaded(); + } else { + if ($callback) { + if (App::environment() == 'testing') { + $callback($this->checkout_url, $body); + } + else { + $callback($this->checkout_url); + } + } else { + if (!$unique_id) { + return $this->send(); + } + } + + return $this; + } + } catch (\Exception $e) { + if (App::environment() == 'testing') { dd($e); } + return $this->throw_checkout_page_not_loaded($e); + } + } else { + if (\Lang::has('nano-to.no-receiver')) { + throw new Error(__("nano-to.no-receiver")); + } else { + throw new Error("Receiver Account was not available."); + } + } + } + + /** + * Get the Nano.to Gateway URL. + * Uses GET method (Default and favorable option is POST) + * + * @deprecated Use create function that uses POST action instead. And, has more features. + * + * @params integer|string $unique_id + * @params function $callback + * @return Niush\LaravelNanoTo\LaravelNanoTo || Illuminate\Http\RedirectResponse + */ + public function createWithGetRequest($unique_id = null, $callback = null) + { + $accounts = config('laravel-nano-to.accounts.'.$this->symbol, []); + $success_url = Route::has(config('laravel-nano-to.success_url')) + ? route(config('laravel-nano-to.success_url'), $unique_id) + : config('laravel-nano-to.success_url'); + + $cancel_url = Route::has(config('laravel-nano-to.cancel_url')) + ? route(config('laravel-nano-to.cancel_url'), $unique_id) + : config('laravel-nano-to.cancel_url'); + + $webhook_url = Route::has(config('laravel-nano-to.webhook_url')) + ? route(config('laravel-nano-to.webhook_url'), $unique_id) + : config('laravel-nano-to.webhook_url'); + + // If Local use local_webhook_url from config instead. + if (!App::environment(['production', 'prod']) && config('laravel-nano-to.local_webhook_url')) { + $webhook_url = config('laravel-nano-to.local_webhook_url'); + } + if ($accounts && sizeof($accounts) > 0) { $address = $accounts[array_rand($accounts)]; $url = $this->base_url . @@ -119,11 +264,17 @@ public function create($unique_id = null, $callback = null) '&success_url=' . $success_url . '&cancel_url=' . $cancel_url . '&webhook_url=' . $webhook_url . - '&webhook_secret=' . $this->webhook_secret; + '&webhook_secret=' . $this->webhook_secret . + '&raw=' . ($this->raw ? 'true' : 'false'); if ($this->amount) { $url .= '&price=' . $this->amount; } elseif ($this->suggest) { + $parameters = array_map(function ($s) { + return $s["name"] . ":" . $s["price"]; + }, $this->suggest); + + $this->suggest = implode(",", $parameters); $url .= '&suggest=' . $this->suggest; } @@ -164,7 +315,7 @@ public function create($unique_id = null, $callback = null) } } catch (\Exception $e) { if (App::environment() == 'testing') { dd($e); } - return $this->throw_checkout_page_not_loaded(); + return $this->throw_checkout_page_not_loaded($e); } } else { if (\Lang::has('nano-to.no-receiver')) { @@ -175,13 +326,21 @@ public function create($unique_id = null, $callback = null) } } + /** + * Redirect to Nano.to checkout URL + * + * @return Niush\LaravelNanoTo\LaravelNanoTo + */ public function send() { return redirect()->to($this->checkout_url); } - public function throw_checkout_page_not_loaded() + public function throw_checkout_page_not_loaded($e=null) { + if($e) { + throw $e; + } if (\Lang::has('nano-to.checkout-page-not-loaded')) { throw new Error(__("nano-to.checkout-page-not-loaded")); } else { diff --git a/src/NanoToApi.php b/src/NanoToApi.php new file mode 100644 index 0000000..31eba82 --- /dev/null +++ b/src/NanoToApi.php @@ -0,0 +1,97 @@ +get(self::$api_base_url . '/price?symbol=' . $symbol . '¤cy=' . $currency); + return json_decode($response->getBody()->getContents(), true); + } + + /** + * Get Nano.to Custom Username alias information + */ + public static function getUsername($username) + { + $client = new Client(); + $response = $client->get(self::$api_base_url . '/name/' . $username); + return json_decode($response->getBody()->getContents(), true); + } + + /** + * Get Nano Address Information + */ + public static function getNanoAddressInfo($address) + { + $client = new Client(); + $response = $client->get(self::$api_base_url . '/account/' . $address); + return json_decode($response->getBody()->getContents(), true); + } + + /** + * Get Total Nano Balance from all address provided in config file + */ + public static function getTotalNanoBalance() + { + $accounts = config('laravel-nano-to.accounts.nano', []); + $result = [ + "balance" => 0, + "pending" => 0, + "balance_raw" => 0, + "usd_value" => 0, + ]; + $client = new Client(); + foreach($accounts as $account) { + $response = json_decode($client->get(self::$api_base_url . '/account/' . $account)->getBody()->getContents(), true); + $result['balance'] += $response["balance"] ?? 0; + $result['pending'] += $response["pending"] ?? 0; + $result['balance_raw'] += $response["balance_raw"] ?? 0; + $result['usd_value'] += $response["usd_value"] ?? 0; + } + return $result; + } + + /** + * Get Pending Nano Blocks + */ + public static function getPendingNanoBlocks($address) + { + $client = new Client(); + $response = $client->get(self::$api_base_url . '/pending/' . $address); + return json_decode($response->getBody()->getContents(), true); + } + + /** + * Get Last 20 Nano Address History + */ + public static function getNanoAddressHistory($address) + { + $client = new Client(); + $response = $client->get(self::$api_base_url . '/history/' . $address); + return json_decode($response->getBody()->getContents(), true); + } + + /** + * Get Nano Transaction by specific Amount + * Amount must be in Nano (MEGA) format. + */ + public static function getNanoTransactionByAmount($address, $amount) + { + $client = new Client(); + $response = $client->get(self::$api_base_url . '/payment/' . $address . '/' . $amount); + return json_decode($response->getBody()->getContents(), true); + } +} diff --git a/tests/LaravelNanoToTest.php b/tests/LaravelNanoToTest.php index 04ed5ba..0ecdc73 100644 --- a/tests/LaravelNanoToTest.php +++ b/tests/LaravelNanoToTest.php @@ -11,13 +11,12 @@ use GuzzleHttp\Handler\MockHandler; use Niush\LaravelNanoTo\Tests\TestCase; use GuzzleHttp\Exception\RequestException; -use Illuminate\Foundation\Testing\RefreshDatabase; use Niush\LaravelNanoTo\LaravelNanoToFacade as LaravelNanoTo; class LaravelNanoToTest extends TestCase { protected $app; - protected $testing_checkout_url = "https://example.com/2"; + protected $testing_checkout_url = "https://example.com/1"; /** * Set up the test @@ -62,7 +61,7 @@ function fails_if_accounts_config_is_empty() /** @test */ function redirects_to_checkout_and_create_functions_callback_is_also_equal() { - $response = LaravelNanoTo::amount(9.99)->create('unique_id', function($checkout_url, $original_url) { + $response = LaravelNanoTo::amount(9.99)->create('unique_id', function($checkout_url) { $this->assertEquals($this->testing_checkout_url, $checkout_url); })->send(); @@ -73,57 +72,136 @@ function redirects_to_checkout_and_create_functions_callback_is_also_equal() /** @test */ function info_and_suggest_function_works() { - $nano_to_url = "https://nano.to/nano_3xxx". - "?title=Payment for Subscription". - "&description=Also accepts HTML". - "&success_url=".route(config('laravel-nano-to.success_url'), 'unique_id'). - "&cancel_url=".route(config('laravel-nano-to.cancel_url'), 'unique_id'). - "&webhook_url=".route(config('laravel-nano-to.webhook_url'), 'unique_id'). - "&webhook_secret=". - "&suggest=Coffee:10,Meal:50"; + $expected_body = [ + "title" => "Payment for Subscription", + "description" => "Also accepts HTML", + "success_url" => route(config('laravel-nano-to.success_url'), 'unique_id'), + "cancel_url" => route(config('laravel-nano-to.cancel_url'), 'unique_id'), + "webhook_url" => route(config('laravel-nano-to.webhook_url'), 'unique_id'), + "webhook_secret" => "", + "background" => config('laravel-nano-to.background'), + "color" => config('laravel-nano-to.color'), + "raw" => false, + "business" => [ + "name" => config('laravel-nano-to.business.name'), + "logo" => config('laravel-nano-to.business.logo'), + "favicon" => config('laravel-nano-to.business.favicon'), + ], + "plans" => [ + ["name" => "Coffee", "price" => "10"], + ["name" => "Meal", "price" => "50"] + ] + ]; $response = LaravelNanoTo::info("Payment for Subscription", "Also accepts HTML") ->suggest([ - ["name" => "Coffee", "amount" => "10"], - ["name" => "Meal", "amount" => "50"] + ["name" => "Coffee", "price" => "10"], + ["name" => "Meal", "price" => "50"] ]) - ->create('unique_id', function($checkout_url, $original_url) use ($nano_to_url) { - $this->assertEquals($nano_to_url, $original_url); + ->create('unique_id', function($checkout_url, $body) use ($expected_body) { + $this->assertEquals($expected_body, $body); }); } /** @test */ function webhook_secret_env_appends_if_exists() { + $expected_body = [ + "title" => config('laravel-nano-to.title'), + "description" => config('laravel-nano-to.description'), + "success_url" => route(config('laravel-nano-to.success_url'), 'unique_id'), + "cancel_url" => route(config('laravel-nano-to.cancel_url'), 'unique_id'), + "webhook_url" => route(config('laravel-nano-to.webhook_url'), 'unique_id'), + "webhook_secret" => "123456", + "background" => config('laravel-nano-to.background'), + "color" => config('laravel-nano-to.color'), + "raw" => false, + "business" => [ + "name" => config('laravel-nano-to.business.name'), + "logo" => config('laravel-nano-to.business.logo'), + "favicon" => config('laravel-nano-to.business.favicon'), + ] + ]; + $this->app['config']->set('laravel-nano-to.webhook_secret', '123456'); - $nano_to_url = "https://nano.to/nano_3xxx". - "?title=".config('laravel-nano-to.title'). - "&description=".config('laravel-nano-to.description'). - "&success_url=".route(config('laravel-nano-to.success_url'), 'unique_id'). - "&cancel_url=".route(config('laravel-nano-to.cancel_url'), 'unique_id'). - "&webhook_url=".route(config('laravel-nano-to.webhook_url'), 'unique_id'). - "&webhook_secret=123456"; - $response = LaravelNanoTo::create('unique_id', function($checkout_url, $original_url) use ($nano_to_url) { - $this->assertEquals($nano_to_url, $original_url); + $response = LaravelNanoTo::create('unique_id', function($checkout_url, $body) use ($expected_body) { + $this->assertEquals($expected_body, $body); }); } /** @test */ - function can_use_custom_webook_secret() + function can_use_custom_webhook_secret() { $this->app['config']->set('laravel-nano-to.webhook_secret', '123456'); + + $response = LaravelNanoTo::secret( + config("laravel-nano-to.webhook_secret") . "-custom" + )->create('unique_id', function($checkout_url, $body) { + $this->assertEquals($body["webhook_secret"], '123456-custom'); + }); + } + + /** @test */ + function can_apply_business_customization() + { + $business = [ + "name" => "My Company", + "logo" => "https://example.com/logo.png", + "favicon" => "https://example.com/logo.ico" + ]; + $this->app['config']->set('laravel-nano-to.business', $business); + + $response = LaravelNanoTo::create('unique_id', function($checkout_url, $body) use ($business) { + $this->assertEquals($body["business"], $business); + }); + + $response = LaravelNanoTo::business([ + "name" => "My Custom Company" + ])->create('unique_id', function($checkout_url, $body) use ($business) { + $this->assertEquals($body["business"]["name"], "My Custom Company"); + }); + } + + /** @test */ + function metadata_can_be_added() + { + $metadata = [ + "payment_type" => "monthly" + ]; + + $response = LaravelNanoTo::metadata($metadata)->create('unique_id', function($checkout_url, $body) use ($metadata) { + $this->assertEquals($body["metadata"], $metadata); + }); + } + + /** @test */ + function can_ask_to_use_raw_amount_in_qr() + { + $response = LaravelNanoTo::asRaw()->amount(9.99)->create("unique_id", function($checkout_url, $body) { + $this->assertEquals($body["raw"], true); + })->send(); + } + + /** @test */ + function deprecated_get_function_is_still_working_as_expected() + { $nano_to_url = "https://nano.to/nano_3xxx". - "?title=".config('laravel-nano-to.title'). - "&description=".config('laravel-nano-to.description'). + "?title=Payment for Subscription". + "&description=Also accepts HTML". "&success_url=".route(config('laravel-nano-to.success_url'), 'unique_id'). "&cancel_url=".route(config('laravel-nano-to.cancel_url'), 'unique_id'). "&webhook_url=".route(config('laravel-nano-to.webhook_url'), 'unique_id'). - "&webhook_secret=123456-custom"; + "&webhook_secret=". + "&raw=false". + "&suggest=Coffee:10,Meal:50"; - $response = LaravelNanoTo::secret( - config("laravel-nano-to.webhook_secret") . "-custom" - )->create('unique_id', function($checkout_url, $original_url) use ($nano_to_url) { + $response = LaravelNanoTo::info("Payment for Subscription", "Also accepts HTML") + ->suggest([ + ["name" => "Coffee", "price" => "10"], + ["name" => "Meal", "price" => "50"] + ]) + ->createWithGetRequest('unique_id', function($checkout_url, $original_url) use ($nano_to_url) { $this->assertEquals($nano_to_url, $original_url); }); } diff --git a/tests/NanoToApiTest.php b/tests/NanoToApiTest.php new file mode 100644 index 0000000..e5e858f --- /dev/null +++ b/tests/NanoToApiTest.php @@ -0,0 +1,209 @@ +set('app.env', 'local'); + $app['config']->set('app.debug', true); + $app['config']->set('laravel-nano-to.accounts', [ + 'nano' => [ + 'nano_3xxx' + ] + ]); + $this->app = $app; + if(env("USE_REAL_API")) { + $this->use_real_api = true; + } + } + + /** @test */ + function can_get_price_of_crypto_currency() + { + if (!$this->use_real_api) { + $response = ["symbol" => "NANO", "price" => 5.233, "currency" => "USD", "timestamp" => "2021-09-23T01:57:52.020Z"]; + } else { + $response = NanoToApi::getPrice(); + } + + $this->assertArrayHasKey("symbol", $response); + $this->assertArrayHasKey("price", $response); + $this->assertArrayHasKey("currency", $response); + $this->assertArrayHasKey("timestamp", $response); + $this->assertEquals($response["currency"], "USD"); + $this->assertEquals($response["symbol"], "NANO"); + } + + /** @test */ + function can_get_username_alias_info() + { + if (!$this->use_real_api) { + $response = [ + "id" => "0c873b370ee", + "status" => "Active", + "address" => "nano_37y6iq8m1zx9inwkkcgqh34kqsihzpjfwgp9jir8xpb9jrcwhkmoxpo61f4o", + "namespace" => "moon", + "expires" => "September 16, 2030 7:27 PM", + "created" => "September 15, 2021 7:27 PM", + "updated" => "September 17, 2021 8:39 PM" + ]; + } else { + $response = NanoToApi::getUsername("moon"); + } + + $this->assertArrayHasKey("status", $response); + $this->assertEquals($response["namespace"], "moon"); + } + + /** @test */ + function can_get_nano_address_information() + { + if (!$this->use_real_api) { + $response = [ + "balance" => "3.726745204144926111560083887031", + "block_count" => "100", + "account_version" => "2", + "confirmation_height" => "100", + "representative" => "nano_3chxxx", + "weight" => "0", + "pending" => "0", + "balance_raw" => "3726745204144926111560083887031", + "pending_raw" => "0", + "usd_rate" => "5.22", + "usd_value" => "19.45" + ]; + } else { + $response = NanoToApi::getNanoAddressInfo("nano_37y6iq8m1zx9inwkkcgqh34kqsihzpjfwgp9jir8xpb9jrcwhkmoxpo61f4o"); + } + + $this->assertArrayHasKey("balance", $response); + $this->assertArrayHasKey("usd_value", $response); + $this->assertArrayHasKey("pending", $response); + if(isset($response["address"])) { + $this->assertEquals($response["address"], "nano_37y6iq8m1zx9inwkkcgqh34kqsihzpjfwgp9jir8xpb9jrcwhkmoxpo61f4o"); + } + } + + /** @test */ + function can_get_total_nano_balance_of_all_address_combined() + { + if (!$this->use_real_api) { + $response = [ + "balance" => "3.726745204144926111560083887031", + "pending" => "0", + "balance_raw" => "3726745204144926111560083887031", + "usd_value" => "19.45" + ]; + } else { + $this->app['config']->set('laravel-nano-to.accounts', [ + 'nano' => [ + 'nano_378shkx4k3wd5gxmj3xnjwuxtaf9xrehyz7ugakpiemh8arxq8w9a9xniush' + ] + ]); + $response = NanoToApi::getTotalNanoBalance(); + } + + $this->assertArrayHasKey("balance", $response); + $this->assertArrayHasKey("usd_value", $response); + $this->assertArrayHasKey("pending", $response); + } + + /** @test */ + function can_get_pending_nano_blocks() + { + if (!$this->use_real_api) { + $response = [ + [ + "type" => "pending", + "amount" => "0.02112", + "hash" => "844FFE6D39D1F28673198E7C35A61C960148520FCBB8E2B2B0855C72D033FBF4", + "source" => "nano_19o64g3cy484nwfen76tfzz94icr1wn9bccw3ruefaham6x5hggpf6pz185x", + "timestamp" => null, + "amount_raw" => "21120000000000000000000000000" + ] + ]; + } else { + $response = NanoToApi::getPendingNanoBlocks("nano_37y6iq8m1zx9inwkkcgqh34kqsihzpjfwgp9jir8xpb9jrcwhkmoxpo61f4o"); + } + + $this->assertEquals("array", gettype($response)); + if(sizeof($response) > 0) { + $this->assertArrayHasKey("amount", $response[0]); + $this->assertArrayHasKey("hash", $response[0]); + } + } + + /** @test */ + function can_get_last_20_nano_address_history() + { + if (!$this->use_real_api) { + $response = [ + [ + "type" => "state", + "balance" => "0.215288", + "subtype" => "receive", + "account" => "nano_37y6iq8m1zx9inwkkcgqh34kqsihzpjfwgp9jir8xpb9jrcwhkmoxpo61f4o", + "hash" => "94E74C2EDAE153C181858BD28CFB67BA990EC8D1C43427658A118C947121A995", + ] + ]; + } else { + $response = NanoToApi::getNanoAddressHistory("nano_378shkx4k3wd5gxmj3xnjwuxtaf9xrehyz7ugakpiemh8arxq8w9a9xniush"); + } + + $this->assertEquals("array", gettype($response)); + if(sizeof($response) > 0) { + $this->assertArrayHasKey("balance", $response[0]); + $this->assertArrayHasKey("hash", $response[0]); + $this->assertArrayHasKey("account", $response[0]); + } + } + + /** @test */ + function can_get_nano_transaction_by_amount() + { + if (!$this->use_real_api) { + $response = [ + "type" => "state", + "balance" => "0.215288", + "subtype" => "receive", + "account" => "nano_37y6iq8m1zx9inwkkcgqh34kqsihzpjfwgp9jir8xpb9jrcwhkmoxpo61f4o", + "amount" => "0.02143", + "hash" => "94E74C2EDAE153C181858BD28CFB67BA990EC8D1C43427658A118C947121A995", + ]; + } else { + $response = NanoToApi::getNanoTransactionByAmount("nano_378shkx4k3wd5gxmj3xnjwuxtaf9xrehyz7ugakpiemh8arxq8w9a9xniush", "0.021"); + } + + $this->assertEquals("array", gettype($response)); + + if(sizeof($response) > 0) { + $this->assertArrayHasKey("balance", $response); + $this->assertArrayHasKey("hash", $response); + $this->assertArrayHasKey("account", $response); + $this->assertArrayHasKey("amount", $response); + } + } +}