Принципы генерации промокодов с помощью PHP/JAVA

Пользовательские данные

Email

Омниканальность

Автоматизация

Принципы генерации промокодов с помощью 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)

  1. По порядку суммируется десятичное ASCII-значение каждого символа в исходной строке.
  2. Полученная сумма делится с остатком на 32.
  3. Полученный остаток используется как индекс символа в алфавите 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()); 
}

Кодирование/декодирование промокода

Для кодирования/декодирования используется симметричный алгоритм шифрования Triple DES.

Чтобы получить человекочитаемый текст, полученные с помощью шифрования данные кодируются с помощью Base32.

Используемые параметры шифрования:

  • ключ длиной 24 байта
  • initialisation_vector длиной 8 байт
  • режим шифрования: CFB8 (Cipher Feedback Mode)
  • дополнение: NoPadding

Последовательность действий при кодировании промокода

  1. К промокоду применяется алгоритм шифрования Triple DES.
  2. Чтобы получить человекочитаемый текст, полученные с помощью шифрования данные кодируются с помощью Base32.
  3. Через каждые 4 символа для читабельности добавляется “-”. 

Последовательность действий при декодировании промокода

  1. Удалите “-”.
  2. Сначала данные декодируются при помощи Base32.
  3. Далее декодированные данные расшифровываются при помощи Triple DES.
  4. Должна получиться строка из 10 символов согласно описанному выше формату. Для контроля правильности расшифровки используется контрольная сумма.

Секретный ключ

Для кодирования и декодирования промокодов секретный ключ используется на стороне eSputnik и на стороне сервиса, который проверяет промокоды.

Сгенерируйте ключ eSSuperKeyXXXXXXXXXXXXXX (xxx… – случайные цифры). Придумайте вектор инициализации в таком формате: 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);
    }
}

Пример PHP-кода

 '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 = "eSSuperKeyXXXXXXXXXXXXXX";
    $iv = "12345678";
    $encoded = "ILDD-2V7W-SBWD-2X34";

    echo "DECODED: " . mcrypt_decrypt(MCRYPT_3DES, $key, $base32::decode(str_replace("-", "", $encoded)), MCRYPT_MODE_CFB, $iv) . "\n";
?>

Важно!

Используйте Format Preserving Encryption, чтобы упростить перевод кода в человекочитаемый вид.

Остались вопросы?
Специалисты обязательно ответят и помогут решить вашу проблему!
Обратный звонок
Оставьте заявку – и наш специалист свяжется с вами в рабочее время.
Отправить заявку
Консультация в чате
Готовы к вашим вопросам!
Написать в чат
Электронная почта
Напишите в службу поддержки eSputnik.
Отправить email