Ο 6ψήφιος κωδικός μιας χρήσης για αγορές χωρίς ΦΠΑ, κατευθείαν από την ΑΑΔΕ. Χωρίς Java, χωρίς εγκατάσταση, από το κινητό ή τον υπολογιστή.
κωδικοί μιας χρήσης έχουν εκδοθεί από το kodikos39a.app
Για μια αγορά. Δώστε τον στον πωλητή μαζί με τον ΑΦΜ σας.
Μας ζητάτε να βάλετε κωδικούς της ΑΑΔΕ σε σελίδα που δεν είναι της ΑΑΔΕ. Εύλογα θέλετε να ξέρετε τι γίνεται με αυτούς. Σύντομα: οι κωδικοί σας δεν αποθηκεύονται σε διακομιστή, δεν υπάρχουν logs, cookies ή διαφημίσεις, και ό,τι κρατιέται, κρατιέται στον browser σας μόνο αν το επιλέξετε. Για ανώνυμα στατιστικά επισκέψεων χρησιμοποιούμε το Cloudflare Web Analytics, χωρίς cookies. Η αναλυτική εξήγηση, μαζί με ολόκληρο τον κώδικα του διακομιστή, είναι παρακάτω.
Με το άρθρο 39α, παρ. 5 του Κώδικα ΦΠΑ, ένας αγοραστής του κανονικού καθεστώτος ΦΠΑ αγοράζει κινητά, κονσόλες παιχνιδιών, laptop και tablet χωρίς να καταβάλει ΦΠΑ στον πωλητή (αντίστροφη επιβάρυνση). Ο πωλητής επιβεβαιώνει την ιδιότητά σας στην ΑΑΔΕ πριν εκδώσει το τιμολόγιο.
Ο κωδικός μιας χρήσης είναι 6ψήφιος και τον δημιουργεί ο αγοραστής. Με αυτόν ταυτοποιείται ο εκπρόσωπος που κάνει την αγορά, όταν ο αγοραστής έχει ορίσει ότι απαιτείται κωδικός για εκείνον.
Μπείτε στη Διαχείριση Ειδικών Κωδικών της ΑΑΔΕ με τους κωδικούς TAXISnet και δημιουργήστε ζεύγος για την «Υπηρεσία άρθρου 39α, παρ. 5 κώδικα ΦΠΑ». Αυτό το όνομα χρήστη και τον κωδικό βάζετε εδώ.
Για νομικά πρόσωπα, τους δημιουργεί το φυσικό πρόσωπο που έχει οριστεί εκπρόσωπος του νομικού προσώπου.
Εγγραφείτε στην υπηρεσία ως αγοραστής και ορίστε τουλάχιστον έναν εκπρόσωπο: τον εαυτό σας ή κάποιον άλλον, με τη συγκατάθεσή του. Γίνεται από το επίσημο πρόγραμμα της ΑΑΔΕ.
Συνδεθείτε στο μηχάνημα στην κορυφή της σελίδας, διαλέξτε πόσο θα ισχύει και πατήστε «Βγάλτε κωδικό» (ή «Νέος κωδικός», αν έχετε ήδη έναν). Αν την αγορά θα την κάνει άλλος, βάλτε τον ΑΦΜ του ως εκπροσώπου.
Δώστε στον πωλητή τον ΑΦΜ και τον κωδικό. Ο πωλητής επιβεβαιώνει στην ΑΑΔΕ και εκδίδει τιμολόγιο χωρίς ΦΠΑ.
Εδώ εξηγούμε τα πάντα: από πού περνούν οι κωδικοί, γιατί, τι χρειάζεται να μας εμπιστευτείτε, και πώς το ελέγχετε μόνοι σας.
Θα θέλαμε να το κάνει: τότε οι κωδικοί σας δεν θα περνούσαν ούτε στιγμή από κανέναν άλλον. Δεν γίνεται, για λόγο που δεν εξαρτάται από εμάς.
Για την προστασία σας, ο browser αφήνει μια σελίδα να μιλήσει με άλλον διακομιστή μόνο αν εκείνος το επιτρέπει
ρητά. Ο μηχανισμός λέγεται CORS. Η υπηρεσία 39α είναι
φτιαγμένη για προγράμματα υπολογιστή, όπως το πρόγραμμα Java της ΑΑΔΕ, και δεν το επιτρέπει. Το ελέγξαμε: στο
ερώτημα του browser απαντά με σφάλμα 405.
Οι επιλογές ήταν τρεις: να κατεβάσετε πρόγραμμα, να εγκαταστήσετε επέκταση στον browser (δεν δουλεύει στο κινητό), ή να περάσει το αίτημα από έναν ενδιάμεσο που απλώς το προωθεί. Επιλέξαμε το τρίτο και τον κρατήσαμε όσο πιο μικρό και ελέγξιμο γίνεται.
Να είμαστε ειλικρινείς: επειδή το αίτημα περνά από τον αναμεταδότη, βρίσκεται στη μνήμη του όσο διαρκεί η προώθηση, κλάσματα του δευτερολέπτου. Γι' αυτό δημοσιεύουμε ολόκληρο τον κώδικα που τρέχει εκεί. Όπως κάθε πάροχος δικτύου, η Cloudflare βλέπει βασικά τεχνικά στοιχεία της σύνδεσης, π.χ. τη διεύθυνση IP, για προστασία από επιθέσεις. Το περιεχόμενο των αιτημάτων δεν καταγράφεται.
Αυτό είναι όλο το πρόγραμμα που τρέχει στον διακομιστή. Ενσωματώνεται αυτόματα από το ίδιο αρχείο που ανεβαίνει στο Cloudflare, οπότε είναι πάντα η τρέχουσα έκδοση.
// The whole server side of kodikos39a.app.
//
// AADE's 39a service does not allow browsers to call it directly (no CORS), so the page sends
// its SOAP request to /api/aade and this Worker forwards it, unchanged, to one fixed AADE address.
// It does not read, store or log the request or the response.
//
// The only thing stored is one number: how many one-time codes were issued through the site.
// The page reports each success to /api/issued with an empty request, and /api/stats returns
// the total. Everything else on the site is static files.
interface Env {
ASSETS: { fetch(request: Request): Promise<Response> };
LIMITER: { limit(options: { key: string }): Promise<{ success: boolean }> };
DB: D1Database;
}
// The only address the relay ever talks to.
const AADE = "https://www1.gsis.gr/wsaade/VtWs39aFPA/VtWs39aFPA";
const SERVICE = "http://vtws39afpa/VtWs39aFPAService";
// Only these operations are forwarded: version check and the buyer's one-time codes.
const OPERATIONS = new Set(["vt39afpaVersionInfo", "vt39afpaBu9GetOtp"]);
const MAX_BODY = 8 * 1024;
const reply = (status: number, message = "") =>
new Response(message, { status, headers: { "Cache-Control": "no-store", "Content-Type": "text/plain; charset=utf-8" } });
// Rate limit per connection, so the relay can't be used to guess passwords and the counter
// can't be pumped. The IP is used only as a counter key in memory and is not stored.
async function limited(request: Request, env: Env, scope: string) {
const ip = request.headers.get("CF-Connecting-IP") ?? "unknown";
return !(await env.LIMITER.limit({ key: `${scope}:${ip}` })).success;
}
async function relay(request: Request, env: Env, url: URL): Promise<Response> {
if (request.method !== "POST") return reply(405);
// Only this site's own page may use the relay.
if (request.headers.get("Origin") !== url.origin) return reply(403);
const operation = request.headers.get("X-AADE-Operation") ?? "";
if (!OPERATIONS.has(operation)) return reply(400);
if (await limited(request, env, "aade")) return reply(429);
const body = await request.arrayBuffer();
if (body.byteLength === 0 || body.byteLength > MAX_BODY) return reply(413);
let upstream: Response;
try {
upstream = await fetch(AADE, {
method: "POST",
headers: { "Content-Type": `application/soap+xml; charset=utf-8; action="${SERVICE}:${operation}"` },
body,
signal: AbortSignal.timeout(30_000),
});
} catch {
return reply(504);
}
// AADE's answer goes back to the browser as it is.
return new Response(upstream.body, {
status: upstream.status,
headers: { "Content-Type": "application/soap+xml; charset=utf-8", "Cache-Control": "no-store" },
});
}
const total = (issued: number | undefined) =>
Response.json({ issued: issued ?? 0 }, { headers: { "Cache-Control": "no-store" } });
// +1 to the public counter. The request carries no data at all; the reply is the new total.
async function issued(request: Request, env: Env, url: URL): Promise<Response> {
if (request.method !== "POST") return reply(405);
if (request.headers.get("Origin") !== url.origin) return reply(403);
if (await limited(request, env, "issued")) return reply(429);
const row = await env.DB.prepare("UPDATE stats SET issued = issued + 1 WHERE id = 1 RETURNING issued").first<{ issued: number }>();
return total(row?.issued);
}
// The current total for everyone, read fresh on every visit.
async function stats(request: Request, env: Env): Promise<Response> {
if (request.method !== "GET") return reply(405);
const row = await env.DB.prepare("SELECT issued FROM stats WHERE id = 1").first<{ issued: number }>();
return total(row?.issued);
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
switch (url.pathname) {
case "/api/aade":
return relay(request, env, url);
case "/api/issued":
return issued(request, env, url);
case "/api/stats":
return stats(request, env);
default:
return env.ASSETS.fetch(request);
}
},
};
localStorage του browser σας, σε αυτή τη συσκευή μόνο.
Η ΑΑΔΕ εκδίδει ξεχωριστό ζεύγος ειδικών κωδικών για κάθε υπηρεσία. Οι κωδικοί 39α δεν ανοίγουν το TAXISnet, το myAADE ή τις δηλώσεις σας: λειτουργούν μόνο στην υπηρεσία 39α.
Μπορείτε οποιαδήποτε στιγμή να τους αλλάξετε ή να τους ανακαλέσετε από τη Διαχείριση Ειδικών Κωδικών. Εμείς δεν ζητάμε ποτέ τους κωδικούς TAXISnet.
Για να ξέρουμε πόσοι χρησιμοποιούν την υπηρεσία, μετράμε επισκέψεις με το Cloudflare Web Analytics. Δεν χρησιμοποιεί cookies και δεν σας παρακολουθεί από σελίδα σε σελίδα. Στέλνει στην Cloudflare τη διεύθυνση της σελίδας, από πού ήρθατε, τον τύπο browser και συσκευής και πόσο γρήγορα φόρτωσε η σελίδα. Δεν στέλνει ό,τι πληκτρολογείτε, τους κωδικούς σας ή τις απαντήσεις της ΑΑΔΕ. Το σενάριο το σερβίρουμε εμείς, από το kodikos39a.app, σε κλειδωμένη έκδοση που μπορείτε να διαβάσετε στο /cf-beacon.js.
kodikos39a.app/api/aade: ένα για την έκδοση και ένα που ανανεώνει τον
κωδικό της οθόνης. Το μόνο άλλο αίτημα είναι το στατιστικό επισκέψεων προς cloudflareinsights.com.
Content-Security-Policy με
script-src 'self' και connect-src 'self' https://cloudflareinsights.com. Ο ίδιος ο
browser σας απαγορεύει στη σελίδα να φορτώσει σενάρια από άλλους διακομιστές ή να στείλει δεδομένα οπουδήποτε
αλλού.
Δεν θέλετε να εμπιστευτείτε κανέναν; Απόλυτα κατανοητό. Η ίδια λειτουργία υπάρχει στο επίσημο πρόγραμμα της ΑΑΔΕ, που μιλά απευθείας με την υπηρεσία.
Ένας 6ψήφιος κωδικός που δημιουργεί ο αγοραστής στην υπηρεσία του άρθρου 39α, παρ. 5 του Κώδικα ΦΠΑ. Με αυτόν ο εκπρόσωπός του ταυτοποιείται στον πωλητή, ώστε να αγοράσει κινητά, κονσόλες παιχνιδιών, laptop ή tablet χωρίς να καταβάλει ΦΠΑ στον πωλητή.
Οι ειδικοί κωδικοί πρόσβασης για την υπηρεσία 39α, που βγάζετε μια φορά από τη Διαχείριση Ειδικών Κωδικών της ΑΑΔΕ. Οι κωδικοί TAXISnet δεν λειτουργούν εδώ, και δεν τους ζητάμε ποτέ.
Όσο ορίσετε, ή όσο η προεπιλογή της ΑΑΔΕ. Πρέπει να ισχύει τη στιγμή που ο πωλητής καταχωρίζει τη συναλλαγή, δηλαδή ακριβώς πριν εκδώσει το τιμολόγιο. Κάθε κωδικός χρησιμοποιείται σε μια αγορά.
Για την έκδοση κωδικών όχι: ούτε Java ούτε πρόγραμμα. Η αρχική εγγραφή ως αγοραστής και ο ορισμός εκπροσώπου γίνονται μια φορά από το επίσημο πρόγραμμα της ΑΑΔΕ.
Όχι. Είναι ανεξάρτητη εφαρμογή που χρησιμοποιεί την επίσημη δημόσια διαδικτυακή υπηρεσία της ΑΑΔΕ για το άρθρο 39α. Οι κωδικοί εκδίδονται από την ίδια την ΑΑΔΕ, όπως και στο επίσημο πρόγραμμα.