Принципи генерації промокодів за допомогою PHP/JAVA
Якщо загальні методи підстановки промокодів вам не підходять, розгляньте можливість реалізації цього завдання за допомогою PHP/JAVA.
Формат промокоду, який підтримує eSputnik
Промокод містить дату, до якої він діє, тип акції, розмір знижки та контрольну суму.
У результаті отримаємо такий рядок:
<YY><MM><DD><Promo type><Discount><CRC>
Поле Довжина Опис Приклад YY 2 символи Рік, двозначне число 15, 54 MM 2 символи Місяць, двозначне число, якщо треба, доповнюється зліва нулем 01, 12 DD 2 символи День, двозначне число, якщо треба, доповнюється зліва нулем 06, 28 Promo type 1 символ Тип промокоду, може набувати значення від 0 до 31, кодується символом з алфавіту Base32 A, D, X Discount 2 символи Розмір знижки у %, двозначне число, якщо треба, доповнюється нулем зліва 15, 02 CRC 1 символ Контрольна сума – одне зі значень алфавіту Base32 B, N, Z У результаті виходить рядок завдовжки 10 символів такого виду: 151231Y16N
Зверніть увагу
Значення промокодів залежить від параметрів блоку та поточного календарного числа. Тобто протягом доби всі контакти отримуватимуть однаковий промокод, якщо у параметри блоку не вноситимуться зміни. Відстеження використання промокоду та терміну його дії має відбуватись на вашому боці.
Приклад використання типу промокоду (Promo type)
- 0 – діє для всього кошика (must-have)
- 1 – діє лише для категорії А
- 2 – діє лише для категорії В
Важливо!
Якщо ви хочете використовувати типи промокоду таким чином, надішліть файл у підтримку eSputnik, вказавши, яка категорія продукту відповідає тому чи іншому коду.
Алгоритм розрахунку контрольної суми (CRC)
- По порядку сумується десяткове ASCII-значення кожного символу у вихідному рядку.
- Отримана сума ділиться із залишком на 32.
- Отриманий залишок використовується як індекс символу в алфавіті Base32.
Символ цього індекса і буде контрольною сумою.
Приклад коду на java:
public static final String BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; public char checkSum(String data) { int sum = 0; for (char c : data.toCharArray()) { sum += c; } return BASE32_ALPHABET.charAt(sum % BASE32_ALPHABET.length()); }
JavaКодування/декодування промокоду
Для кодування/декодування використовується симетричний алгоритм шифрування Triple DES.
Щоб отримати людиночитаний текст, отримані під час шифрування дані кодуються за допомогою Base32.
Параметри шифрування, що використовуються:
- ключ довжиною 24 байти,
- initialization vector довжиною 8 байтів,
- режим шифрування: CFB8 (Cipher Feedback Mode),
- доповнення: NoPadding.
Послідовність дій під час кодування промокоду
- До промокоду застосовується алгоритм шифрування Triple DES.
- Щоб отримати людиночитаний текст, отримані за допомогою шифрування дані кодуються за допомогою Base32.
- Через кожні 4 символи для читабельності додається “-”.
Послідовність дій при декодуванні промокоду
- Видаліть “-”.
- Спочатку дані декодуються за допомогою Base32.
- Далі декодовані дані розшифровуються за допомогою Triple DES.
- Має вийти рядок із 10 символів згідно з описаним вище форматом. Для контролю правильності розшифрування використовується контрольна сума.
Секретний ключ
Для кодування та декодування промокодів секретний ключ використовується на стороні eSputnik і на стороні сервісу, який перевіряє промокоди.
Згенеруйте ключ eSSuperKeyXXXXXXXXXXXXXX (де XXX… – випадкові цифри) та створіть вектор ініціалізації: 12345678.
Важливо
Вектор ініціалізації завжди має бути 12345678.
Передайте обидва ці значення в підтримку eSputnik.
Приклад Java-коду
package promocode; import java.security.spec.KeySpec; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESedeKeySpec; import javax.crypto.spec.IvParameterSpec; import org.apache.commons.codec.binary.Base32; public class CryptData { private KeySpec keySpec; private SecretKey key; private IvParameterSpec iv; public CryptData(String keyString, String ivString) { try { keySpec = new DESedeKeySpec(keyString.getBytes("UTF-8")); key = SecretKeyFactory.getInstance("DESede") .generateSecret(keySpec); iv = new IvParameterSpec(ivString.getBytes("UTF-8")); } catch (Exception e) { e.printStackTrace(); } } public String encrypt(String value) { try { Cipher ecipher = Cipher.getInstance("DESede/CFB8/NoPadding"); // "SunJCE"); ecipher.init(Cipher.ENCRYPT_MODE, key, iv); if (value == null) return null; // Encode the string into bytes using utf-8 byte[] valeur = value.getBytes("UTF-8"); // Encrypt byte[] enc = ecipher.doFinal(valeur); // Encode bytes to base64 to get a string String encodedString = new String(new Base32().encode(enc), "UTF-8"); StringBuilder sb = new StringBuilder(); for (int i = 0 ; i < encodedString.length(); ++i) { if (0 != i && i % 4 == 0) { sb.append("-"); } sb.append(encodedString.charAt(i)); } return sb.toString(); } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) { String encrypt = new CryptData("eSSuperKeyXXXXXXXXXXXXXX", "12345678").encrypt("770101K99N"); System.err.println(encrypt); } }
Java
Приклад PHP-коду
<?php class Base32 { /** * Table for encoding base32 * * @var array */ private static $encode = array( 0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G', 7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N', 14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U', 21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 2, 27 => 3, 28 => 4, 29 => 5, 30 => 6, 31 => 7, 32 => '=', ); /** * Table for decoding base32 * * @var array */ private static $decode = array( 'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6, 'H' => 7, 'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13, 'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20, 'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25, 2 => 26, 3 => 27, 4 => 28, 5 => 29, 6 => 30, 7 => 31, '=' => 32, ); /** * Creates an array from a binary string into a given chunk size * * @param string $binaryString String to chunk * @param integer $bits Number of bits per chunk * @return array */ private static function chunk($binaryString, $bits) { $binaryString = chunk_split($binaryString, $bits, ' '); if (substr($binaryString, (strlen($binaryString)) - 1) == ' ') { $binaryString = substr($binaryString, 0, strlen($binaryString)-1); } return explode(' ', $binaryString); } /** * Encodes into base32 * * @param string $string Clear text string * @return string Base32 encoded string */ public static function encode($string) { if (strlen($string) == 0) { // Gives an empty string return ''; } // Convert string to binary $binaryString = ''; foreach (str_split($string) as $s) { // Return each character as an 8-bit binary string $s = decbin(ord($s)); $binaryString .= str_pad($s, 8, 0, STR_PAD_LEFT); } // Break into 5-bit chunks, then break that into an array $binaryArray = self::chunk($binaryString, 5); // Pad array to be divisible by 8 while (count($binaryArray) % 8 !== 0) { $binaryArray[] = null; } $base32String = ''; // Encode in base32 foreach ($binaryArray as $bin) { $char = 32; if (!is_null($bin)) { // Pad the binary strings $bin = str_pad($bin, 5, 0, STR_PAD_RIGHT); $char = bindec($bin); } // Base32 character $base32String .= self::$encode[$char]; } return $base32String; } /** * Decodes base32 * * @param string $base32String Base32 encoded string * @return string Clear text string */ public static function decode($base32String) { if (strlen($base32String) == 0) { // Gives an empty string return ''; } // Only work in upper cases $base32String = strtoupper($base32String); // Remove anything that is not base32 alphabet $pattern = '/[^A-Z2-7]/'; $base32String = preg_replace($pattern, '', $base32String); $base32Array = str_split($base32String); $string = ''; foreach ($base32Array as $str) { $char = self::$decode[$str]; // Ignore the padding character if ($char !== 32) { $char = decbin($char); $string .= str_pad($char, 5, 0, STR_PAD_LEFT); } } while (strlen($string) %8 !== 0) { $string = substr($string, 0, strlen($string)-1); } $binaryArray = self::chunk($string, 8); $realString = ''; foreach ($binaryArray as $bin) { // Pad each value to 8 bits $bin = str_pad($bin, 8, 0, STR_PAD_RIGHT); // Convert binary strings to ASCII $realString .= chr(bindec($bin)); } return $realString; } } $base32 = new Base32; $key = "eSSuperKey10050012345678"; $iv = "12345678"; $encoded = "ILDD-2V7W-SBWD-2X34"; echo "DECODED: " . mcrypt_decrypt(MCRYPT_3DES, $key, str_replace("-", "", $base32::decode($encoded)), MCRYPT_MODE_CFB, $iv) . "\n"; ?>
PHP
Важливо!
Використовуйте Format Preserving Encryption, щоб спростити переведення коду в людиночитаний формат.