diff --git a/src/main/java/app/controllers/other/ExternalVIPController.java b/src/main/java/app/controllers/other/ExternalVIPController.java
index 7553e8b..b77f6ec 100644
--- a/src/main/java/app/controllers/other/ExternalVIPController.java
+++ b/src/main/java/app/controllers/other/ExternalVIPController.java
@@ -42,6 +42,7 @@ public class ExternalVIPController {
         prices.add(new VipPrice("1 Неделя", 100, "40 рефов", "site_content/images/vip/VIP_7_DAYS.jpg", "week", true, true, true, 10));
         prices.add(new VipPrice("1 День", 35, "10 рефов", "site_content/images/vip/VIP_1_DAY.jpg", "day", true, true, true, 10));
         prices.add(new VipPrice("1 День", 0, "бесплатно", "site_content/images/vip/freevip.jpg", "free", true, true, true, 10));
+        prices.add(new VipPrice("Промокод", 0, "бесплатно", "site_content/images/vip/freevip.jpg", "promocode", true, true, true, 10));
     }
 
     @PostMapping
diff --git a/src/main/java/app/controllers/other/PromoCodeController.java b/src/main/java/app/controllers/other/PromoCodeController.java
new file mode 100644
index 0000000..9a43c7d
--- /dev/null
+++ b/src/main/java/app/controllers/other/PromoCodeController.java
@@ -0,0 +1,93 @@
+package app.controllers.other;
+
+import app.annotations.enums.AuthMethod;
+import app.annotations.enums.CollectStages;
+import app.annotations.interfaces.CheckPermitionFlag;
+import app.annotations.interfaces.CheckWebAccess;
+import app.annotations.interfaces.CollectStatistic;
+import app.annotations.interfaces.WaitAfterNext;
+import app.entities.PromoCodeStatus;
+import app.entities.VipGiveMethod;
+import app.entities.db.PromoCode;
+import app.entities.other.SteamID;
+import app.services.db.PromoCodeService;
+import app.services.db.VIPService;
+import app.utils.SteamIDConverter;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("api/promocode")
+public class PromoCodeController {
+    private VIPService vipService;
+    private PromoCodeService promoCodeService;
+
+    @Autowired
+    PromoCodeController(VIPService vipService, PromoCodeService promoCodeService) {
+        this.vipService = vipService;
+        this.promoCodeService = promoCodeService;
+    }
+
+    @GetMapping
+    @CheckWebAccess(auth_method = AuthMethod.STEAM64)
+    @CheckPermitionFlag(flag = "z")
+    public ResponseEntity<Map<String, String>> getCodes() {
+        return new ResponseEntity<>(promoCodeService.getActualPromocodes().stream().collect(Collectors.toMap(PromoCode::getCode, PromoCode::getAction)), HttpStatus.OK);
+    }
+
+    @PostMapping
+    @CheckWebAccess(auth_method = AuthMethod.STEAM64)
+    @CheckPermitionFlag(flag = "z")
+    @CollectStatistic(stage = CollectStages.COMBINED)
+    public ResponseEntity<String> generateCode(HttpServletRequest request,
+                                   @CookieValue(value = "steam64") String admin_steam64,
+                                   @RequestParam(value = "action") String action,
+                                   @RequestParam(value = "append") String append) {
+        return new ResponseEntity<>(promoCodeService
+                .generatePromoCode(SteamIDConverter.getSteamID(admin_steam64), action, append), HttpStatus.CREATED);
+    }
+
+    @PutMapping
+    @CheckWebAccess(auth_method = AuthMethod.STEAM64)
+    @CollectStatistic(stage = CollectStages.COMBINED)
+    @WaitAfterNext(order = "promocode")
+    public ResponseEntity acceptCode(HttpServletRequest request,
+                                     @CookieValue(value = "steam64") String steam64,
+                                     @RequestParam(value = "code") String code) {
+        SteamID steamID = SteamIDConverter.getSteamID(steam64);
+
+        int status = promoCodeService.acceptPromoCode(steamID, code);
+        PromoCode promoCode = promoCodeService.getPromoCode(code);
+
+        if (status > 0) {
+            String[] action = promoCode.getAction().toLowerCase().split(":");
+            if ("vip".equals(action[0])) {
+                int result = vipService.addVIP(steamID, Integer.parseInt(action[1]), VipGiveMethod.PROMOCODE, code);
+                if (result == 0) return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+                else if (result > 0) {
+                    return new ResponseEntity<>(HttpStatus.CREATED);
+                } else {
+                    return new ResponseEntity<>(HttpStatus.OK);
+                }
+            }
+            return new ResponseEntity<>(HttpStatus.ALREADY_REPORTED);
+        } else if (status == 0) {
+            if (promoCode == null) return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+            if (PromoCodeStatus.DISABLED.equals(promoCode.getStatus())) return new ResponseEntity(HttpStatus.LOCKED);
+            if (PromoCodeStatus.ACCEPTED.equals(promoCode.getStatus())) return new ResponseEntity(
+                    Map.of("steam", promoCode.getAccept(), "utime", promoCode.getAccepted_timestamp().getTime()/1000),
+                    HttpStatus.GONE
+            );
+            return new ResponseEntity<>(HttpStatus.I_AM_A_TEAPOT);
+        } else {
+            return new ResponseEntity(Map.of("cd", status * -1), HttpStatus.CONFLICT);
+        }
+    }
+}
diff --git a/src/main/java/app/entities/PromoCodeStatus.java b/src/main/java/app/entities/PromoCodeStatus.java
new file mode 100644
index 0000000..d73efba
--- /dev/null
+++ b/src/main/java/app/entities/PromoCodeStatus.java
@@ -0,0 +1,5 @@
+package app.entities;
+
+public enum PromoCodeStatus {
+    CREATED, ACCEPTED, DISABLED
+}
diff --git a/src/main/java/app/entities/db/PromoCode.java b/src/main/java/app/entities/db/PromoCode.java
new file mode 100644
index 0000000..eeeb98c
--- /dev/null
+++ b/src/main/java/app/entities/db/PromoCode.java
@@ -0,0 +1,65 @@
+package app.entities.db;
+
+import app.entities.PromoCodeStatus;
+import app.entities.other.SteamID;
+import app.utils.SteamIDConverter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+
+public class PromoCode {
+    @JsonIgnore
+    int id;
+    Timestamp created_timestamp;
+    String code;
+    SteamID creator;
+    SteamID accept;
+    Timestamp accepted_timestamp;
+    PromoCodeStatus status;
+    String action;
+
+    public PromoCode(ResultSet resultSet) throws SQLException {
+        id = resultSet.getInt("id");
+        created_timestamp = resultSet.getTimestamp("created_timestamp");
+        code = resultSet.getString("code");
+        creator = SteamIDConverter.getSteamID(resultSet.getString("creator_steam64"));
+        accept = SteamIDConverter.getSteamID(resultSet.getString("accepted_steam64"));
+        accepted_timestamp = resultSet.getTimestamp("accepted_timestamp");
+        status = PromoCodeStatus.values()[resultSet.getInt("status")];
+        action = resultSet.getString("action");
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public Timestamp getCreated_timestamp() {
+        return created_timestamp;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public SteamID getCreator() {
+        return creator;
+    }
+
+    public SteamID getAccept() {
+        return accept;
+    }
+
+    public Timestamp getAccepted_timestamp() {
+        return accepted_timestamp;
+    }
+
+    public PromoCodeStatus getStatus() {
+        return status;
+    }
+
+    public String getAction() {
+        return action;
+    }
+}
diff --git a/src/main/java/app/services/db/PromoCodeService.java b/src/main/java/app/services/db/PromoCodeService.java
new file mode 100644
index 0000000..42256fd
--- /dev/null
+++ b/src/main/java/app/services/db/PromoCodeService.java
@@ -0,0 +1,69 @@
+package app.services.db;
+
+import app.entities.PromoCodeStatus;
+import app.entities.db.PromoCode;
+import app.entities.other.SteamID;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.List;
+
+@Service
+public class PromoCodeService {
+
+    @Autowired
+    @Qualifier("jt_rw")
+    private JdbcTemplate jdbcTemplate;
+
+    private static int cd = 60 * 60 * 24;
+
+    public String generatePromoCode(SteamID steamID, String action, String append) {
+        String code = generatePromoCode(append);
+        while (null != getPromoCode(code)) {
+            code = generatePromoCode(append);
+        }
+        return jdbcTemplate.update("INSERT INTO `gived_promocode` (`code`, `creator_steam64`, `action`) VALUES (?, ?, ?)",
+                code, String.valueOf(steamID.steam64), action) > 0 ? code : "";
+    }
+
+    public int acceptPromoCode(SteamID steamID, String code) {
+        PromoCode promoCode = getPromoCode(code);
+        Timestamp lastAccept = getLastedAcceptFromUser(steamID);
+        if (promoCode == null) return 0;
+        if (!PromoCodeStatus.CREATED.equals(promoCode.getStatus())) return 0;
+        if (lastAccept != null) {
+            if (Instant.now().getEpochSecond() - lastAccept.getTime()/1000 < cd) return (int) (lastAccept.getTime() / 1000 - Instant.now().getEpochSecond());
+        }
+        return jdbcTemplate.update("UPDATE `gived_promocode` SET accepted_steam64 = ?, accepted_timestamp = ?, status = ? WHERE id = ?",
+                String.valueOf(steamID.steam64), Timestamp.from(Instant.now()), PromoCodeStatus.ACCEPTED.ordinal(), promoCode.getId());
+    }
+
+    public List<PromoCode> getActualPromocodes() {
+        return jdbcTemplate.query("SELECT * FROM `gived_promocode` WHERE `status` = ?",
+                new Object[]{PromoCodeStatus.CREATED.ordinal()},
+                (rs, n) -> new PromoCode(rs));
+    }
+
+    public PromoCode getPromoCode(String code) {
+        return jdbcTemplate.query("SELECT * FROM `gived_promocode` WHERE `code` LIKE ?",
+                new Object[]{code},
+                (rs, n) -> new PromoCode(rs)).stream().findFirst().orElse(null);
+    }
+
+    private String generatePromoCode(String append) {
+        return (RandomStringUtils
+                .randomAlphabetic(48)
+                .replaceAll("(.{12})", "$1-") + append).toUpperCase();
+    }
+
+    private Timestamp getLastedAcceptFromUser(SteamID steamID) {
+        return jdbcTemplate.query("SELECT `accepted_timestamp` FROM `gived_promocode` WHERE accepted_steam64 LIKE ? ORDER BY `gived_promocode`.`id` DESC LIMIT 1",
+                new Object[]{ String.valueOf(steamID.steam64) },
+                (rs, n) -> rs.getTimestamp("accepted_timestamp")).stream().findFirst().orElse(null);
+    }
+}
diff --git a/src/main/java/app/utils/SteamIDConverter.java b/src/main/java/app/utils/SteamIDConverter.java
index 4e6b51a..0ffc210 100644
--- a/src/main/java/app/utils/SteamIDConverter.java
+++ b/src/main/java/app/utils/SteamIDConverter.java
@@ -23,6 +23,7 @@ public class SteamIDConverter {
     }
 
     public static SteamID getSteamID(String text){
+        if (text == null) return null;
         boolean isSteamID = false;
 
         String steam3;