Last active
August 18, 2022 13:15
-
-
Save harshals/ac345fd6979fe2a5fb7fa1a398fe8430 to your computer and use it in GitHub Desktop.
sample code to authenticate on einvoice sandbox
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
#clone of https://einv-apisandbox.nic.in/sample-code-in-java.html | |
# you need to save the public key of einv-apisandbox.nic.in in same folder | |
use Crypt::OpenSSL::Random qw/random_bytes/; | |
use Crypt::OpenSSL::RSA; | |
use Crypt::Mode::ECB; | |
use Crypt::PK::RSA; | |
use Mojo::File; | |
use Mojo::JSON qw/encode_json decode_json/; | |
use Mojo::Util qw/b64_encode b64_decode dumper/; | |
use Mojo::URL; | |
use Mojo::UserAgent; | |
use strict; | |
use warnings; | |
use feature 'say'; | |
Crypt::OpenSSL::RSA->import_random_seed(); | |
## Setting up bunch of defaults | |
use constant { | |
APP_KEY => random_bytes(32), | |
PUBLIC_KEY_FILE => 'einv_sandbox.pem', ## to be saved in same folder | |
CLIENT_ID => '<< Your Client ID >>', | |
CLIENT_SECRET => '<< Your Client Secret >>', | |
GSTIN => '<< Your Gstin >>', | |
USERNAME => '<< API Username >>', | |
PASSWORD => '<<API Password>>' | |
}; | |
use constant { | |
DEFAULT_HEADERS => { | |
"client-id" => CLIENT_ID, | |
"client-secret" => CLIENT_SECRET, | |
"gstin" => GSTIN, | |
"KeepAlive" => "true", | |
"AllowAutoRedirect" => "false", | |
"Accept" => "application/json" | |
} | |
}; | |
my $url = Mojo::URL->new; | |
my $ua = Mojo::UserAgent->new; | |
$url->scheme('https') | |
->host('einv-apisandbox.nic.in'); | |
my ($token, $sek) = authenticate($url, $ua); | |
identify($url, $ua, $token, $sek, GSTIN); | |
cancelIRN($url, $ua, $token, $sek); | |
sub authenticate { | |
my ($url, $ua) = @_; | |
$url->path('/eivital/v1.04/auth'); | |
my $path = Mojo::File->new(PUBLIC_KEY_FILE); | |
my $json = { | |
"username" => USERNAME, | |
"password" => PASSWORD, | |
"appkey" => b64_encode(APP_KEY,""), | |
"ForceRefreshAccessToken" => \0 | |
}; | |
#my $cipher = Crypt::OpenSSL::RSA->new_public_key($path->slurp); | |
#$cipher->use_pkcs1_padding(); | |
#my $encrypted = $cipher->encrypt(b64_encode(encode_json($json))); | |
my $rsa_cipher = Crypt::PK::RSA->new(PUBLIC_KEY_FILE); | |
my $encrypted = $rsa_cipher->encrypt(b64_encode(encode_json($json)), "v1.5"); | |
my $encoded_text = b64_encode($encrypted); | |
say "posting to $url"; | |
my $tx = $ua->build_tx(POST => $url , json => { "Data" => $encoded_text } ); | |
$tx->req->headers->from_hash(DEFAULT_HEADERS); | |
$tx = $ua->start($tx); | |
my $res = $tx->result; | |
if ($res->is_success) { | |
my $auth_data = decode_json($res->body)->{"Data"}; | |
my $aes_cipher = new Crypt::Mode::ECB("AES", 1); | |
my $binary_sek = b64_decode($auth_data->{Sek}); | |
my $decrypted_sek = $aes_cipher->decrypt($binary_sek, APP_KEY); | |
return ($auth_data->{AuthToken}, $decrypted_sek); | |
} | |
elsif ($res->is_error) { say $res->message } | |
elsif ($res->code == 301) { say $res->headers->location } | |
else { say 'Whatever...' }; | |
} | |
sub identify { | |
my ($url, $ua, $token, $sek, $gstin ) = @_; | |
$url->path("/eivital/v1.04/Master/gstin/$gstin"); | |
say "getting from $url"; | |
my $tx = $ua->build_tx(GET => $url ); | |
$tx->req->headers->from_hash(DEFAULT_HEADERS); | |
$tx->req->headers->header("AuthToken", $token); | |
$tx->req->headers->header("user_name", USERNAME); | |
$tx = $ua->start($tx); | |
my $res = $tx->result; | |
if ($res->is_success) { say dumper(decrypt($res->body, $sek)); } | |
elsif ($res->is_error) { say $res->message } | |
elsif ($res->code == 301) { say $res->headers->location } | |
else { say 'Whatever...' }; | |
} | |
sub cancelIRN { | |
my ($url, $ua, $token, $sek) = @_; | |
$url->path("/eicore/v1.03/Invoice/Cancel"); | |
my $payload = { | |
"Irn"=> "a5c12dca80e743321740b001fd70953e8738d109865d28ba4013750f2046d444", | |
"CnlRsn"=> "1", | |
"CnlRem"=> "cancel Remarks" | |
}; | |
say "posting to $url"; | |
my $tx = $ua->build_tx(POST => $url , json => { "Data" => encrypt($payload, $sek)} ); | |
$tx->req->headers->from_hash(DEFAULT_HEADERS); | |
$tx->req->headers->header("AuthToken", $token); | |
$tx->req->headers->header("user_name", USERNAME); | |
$tx = $ua->start($tx); | |
my $res = $tx->result; | |
if ($res->is_success) { say $res->body } | |
elsif ($res->is_error) { say $res->message } | |
elsif ($res->code == 301) { say $res->headers->location } | |
else { say 'Whatever...' }; | |
} | |
sub encrypt { | |
my ($data, $key) = @_; | |
my $encoded_json = encode_json($data); | |
my $aes_cipher = new Crypt::Mode::ECB("AES", 1); | |
my $encrypted_payload = $aes_cipher->encrypt($encoded_json, $key); | |
my $encoded_payload = b64_encode($encrypted_payload); | |
return $encoded_payload; | |
} | |
sub decrypt { | |
my ($data, $key, $data_key) = @_; | |
$data_key ||= "Data"; | |
my $aes_cipher = new Crypt::Mode::ECB("AES", 1); | |
my $json = decode_json($data); | |
my $decrypted_data = $aes_cipher->decrypt(b64_decode($json->{$data_key}), $key); | |
return $decrypted_data; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment