/* * Copyright (C) 2014 SCVNGR, Inc. d/b/a LevelUp * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.scvngr.levelup.core.model.qr; import android.support.annotation.NonNull; import com.scvngr.levelup.core.annotation.LevelUpApi; import com.scvngr.levelup.core.annotation.VisibleForTesting; import com.scvngr.levelup.core.model.tip.Tip; import com.scvngr.levelup.core.util.LogManager; import net.jcip.annotations.Immutable; import java.math.BigInteger; import java.util.Locale; /** * <p> * This class may be used to encode and parse version 3 payment preferences. In version 3, payment * preferences are represented as an 8 character code which consists of four sections, each of which * is encoded in base 36. A typical payment preferences code: * </p> * <blockquote>{@code 030002LU}</blockquote> * <p> * Each section has a specific length prescribed to it. When necessary, leading zeros may be added * to a section to ensure that it conforms to this length. The following is a description of each * section, using {@code 030002LU} as an example: * </p> * <ul> * <li>[03] The version of the payment preference code. (length 2)</li> * <li>[000] The value of the tip, either as a percentage or US cents. Within this section, each tip * value type is assigned to an exclusive range of base 36 values. (length 3)</li> * <li>[2] The glow color index preference. (length 1)</li> * <li>[LU] The sentinel value which is always encoded as "LU". (length 2)</li> * </ul> * <p> * Percentage tip values are assigned to base 36 values ranging from 000 through 0ZZ. This range * can represent percentage values from 0% through 1295%. * </p> * <p> * US cent tip values are assigned to base 36 values ranging from 100 through ZZZ. This range * can represent US cent values from $0.00 through $453.59. * </p> */ @Immutable @LevelUpApi(contract = LevelUpApi.Contract.INTERNAL) public final class PaymentPreferencesV3 extends PaymentPreferences { /** * The payment preferences version. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) /* package */static final int VERSION = 3; /** * The length of the payment preferences version when encoded. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) /* package */static final int VERSION_LENGTH = 2; /** * The length of the tip when encoded. */ private static final int TIP_LENGTH = 3; /** * The length of the color when encoded. */ private static final int COLOR_LENGTH = 1; /** * The sentinel string. */ private static final String SENTINEL = "LU"; /** * The length of the sentinel when encoded. */ private static final int SENTINEL_LENGTH = SENTINEL.length(); /** * The length of the encoded v3 payment preferences string. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) /* package */static final int TOTAL_ENCODED_LENGTH = VERSION_LENGTH + TIP_LENGTH + COLOR_LENGTH + SENTINEL_LENGTH; /** * @param paymentPreferences the preference string. */ protected PaymentPreferencesV3(@NonNull final String paymentPreferences) { super(paymentPreferences); } @Override protected int getColor() { int color = COLOR_UNKNOWN; /* * The color is the final character before the sentinel, as expected by scanner hardware. * The sentinel is the last two characters of the QR code and must be "LU". As long as the * preferences are at least 3 characters long and end with "LU", this method will parse the * third character from the right as the color. */ final int qrLength = mPaymentPreferences.length(); final int sentinelStart = qrLength - SENTINEL.length(); final int colorStart = sentinelStart - COLOR_LENGTH; if (qrLength >= (COLOR_LENGTH + SENTINEL_LENGTH) && SENTINEL.equals(mPaymentPreferences.substring(sentinelStart, qrLength))) { final String colorCode = mPaymentPreferences.substring(colorStart, sentinelStart); try { color = new BigInteger(colorCode, Character.MAX_RADIX).intValue(); } catch (final NumberFormatException nfe) { LogManager.i("Could not parse the color", nfe); } } return color; } /** * {@inheritDoc} Encodes the payment preferences into a base 36 string consisting of the * version, tip, color, and sentinel. */ @Override @NonNull String encode(final int color, final Tip<?> tip) { // Validate tip string size final String tipStr = Integer.toString(tip.getEncodedValue(), Character.MAX_RADIX); if (tipStr.length() > TIP_LENGTH) { throw new IllegalArgumentException(String.format(Locale.US, "Tip can only be %d characters", TIP_LENGTH)); } final StringBuilder builder = new StringBuilder(TOTAL_ENCODED_LENGTH); // Add the preference version (must be padded). builder.append(CodeVersionUtils.leftPadWithZeros(Integer.toString(VERSION, Character.MAX_RADIX), VERSION_LENGTH)); // Append the tip value (must be padded). builder.append(CodeVersionUtils.leftPadWithZeros(tipStr, TIP_LENGTH)); // Append the color. final String colorStr = Integer.toString(color, Character.MAX_RADIX); if (COLOR_LENGTH == colorStr.length()) { builder.append(colorStr); } else { builder.append(CodeVersionUtils.leftPadWithZeros(null, COLOR_LENGTH)); } // Append the sentinel (LU). builder.append(SENTINEL); return builder.toString().toUpperCase(Locale.US); } }