﻿// Credit Card brands
published.Brands =
{
  VISA: "Visa",
  MASTER_CARD: "MasterCard",
  AMERICAN_EXPRESS: "AmEx",
  DISCOVER: "Discover",
  JCB: "JCB",
  DINERS_CLUB: "Diners Club",
  UNKNOWN: "Card"
};

// Error types
published.ErrorTypes =
{
  OK: 0,                    // Everything work as expected.
  API_CONNECTION_ERROR: 1,  // Failure to connect to Stripe's API.
  API_ERROR: 2,	            // API errors cover any other type of problem (e.g., a temporary problem with Stripe's servers) and are extremely uncommon.
  AUTHENTICATION_ERROR: 3,  // Failure to properly authenticate yourself in the request.
  CARD_ERROR: 4,	          // Card errors are the most common type of error you should expect to handle. They result when the user enters a card that can't be charged for some reason.
  INVALID_REQUEST_ERROR: 5, // Invalid request errors arise when your request has invalid parameters.
  RATE_LIMIT_ERROR: 6,	    // Too many requests hit the API too quickly.
  GENERAL_ERROR: 100
};

// Credit Card errors
published.CardErrors =
{
  OK: 0,                   // Everything work as expected.
  INVALID_NUMBER: 1,	     // The card number is not a valid credit card number.
  INVALID_EXPIRY_MONTH: 2, // The card's expiration month is invalid.
  INVALID_EXPIRY_YEAR: 3,  // The card's expiration year is invalid.
  INVALID_CVC: 4,          // The card's security code is invalid.
  INCORRECT_NUMBER: 5,     // The card number is incorrect.
  EXPIRED_CARD: 6,         // The card has expired.
  INCORRECT_CVC: 7,        // The card's security code is incorrect.
  INCORRECT_ZIP: 8,        // The card's zip code failed validation.
  CARD_DECLINED: 9,        // The card was declined.
  MISSING: 10,             // There is no card on a customer that is being charged.
  PROCESSING_ERROR: 11,    // An error occurred while processing the card.
  GENERAL_ERROR: 100
};

// Pay Terminal Facade
published.Facade = function (providerName, provider, init) {
  if (WAVE.strEmpty(providerName))
    throw new WAVE.Pay.RequiredArgumentError("Facade.ctor()", "providerName");
  if (WAVE.strEmpty(provider))
    throw new WAVE.Pay.RequiredArgumentError("Facade.ctor()", "provider");
  if (WAVE.strEmpty(init))
    throw new WAVE.Pay.RequiredArgumentError("Facade.ctor()", "init");

  var facade = this;
  var fProviderName = providerName;

  // returns pay provider name
  this.providerName = function(){ return fProviderName; };

  WAVE.extend(facade, WAVE.EventManager);

  try {
    // Payment provider specific setup
    provider.initialize(init);
  }
  catch (error) {
    throw new WAVE.Pay.ProviderInitError(error);
  }

  // Payment provider tokenization interface:
  // paymentData - contains CC card data (cc_number, cc_cvc, cc_exp_month, cc_exp_year, cc_holder),
  // callback - a handler function that accepts 'result' object with fields: 'error' and 'token'.
  this.tokenize = function (paymentData, callback) {
    if (WAVE.strEmpty(paymentData))
      throw new WAVE.Pay.RequiredArgumentError("tokenize()", "paymentData");
    if (WAVE.strEmpty(callback))
      throw new WAVE.Pay.RequiredArgumentError("tokenize()", "callback");

    try {
      provider.tokenize(facade, paymentData, callback);
    }
    catch (error) {
      throw new WAVE.Pay.TokenizeError(error);
    }
  };
};

// Errors
published.RequiredArgumentError = function (srcFuncName, argName, inner) {
  this.name = "RequiredArgumentError";
  this.message = srcFuncName + " requires argument '" + argName + "'";
  this.inner = inner;
};
published.RequiredArgumentError.prototype = Error.prototype;

published.ProviderInitError = function (inner) {
  this.name = "ProviderInitError";
  var message = "Critical error during provider setup";
  if (typeof(inner) !== tUNDEFINED && !WAVE.strEmpty(inner.message))
    message += ": " + inner.message;
  this.message = message;
  this.inner = inner;
};
published.ProviderInitError.prototype = Error.prototype;

published.TokenizeError = function (inner) {
  this.name = "TokenizeError";
  var message = "Critical error during tokenization";
  if (typeof(inner) !== tUNDEFINED && !WAVE.strEmpty(inner.message))
    message += ": " + inner.message;
  this.message = message;
  this.inner = inner;
};
published.TokenizeError.prototype = Error.prototype;

published.CardValidationError = function (inner) {
  this.name = "CardValidationError";
  var message = "Card is invalid";
  if (typeof(inner) !== tUNDEFINED && !WAVE.strEmpty(inner.message))
    message += ": " + inner.message;
  this.message = message;
  this.inner = inner;
};
published.CardValidationError.prototype = Error.prototype;

// Utilities
published.validateCardNumber = function (ccNum) {
  if (WAVE.strEmpty(ccNum)) return false;
  ccNum = ccNum.replace(/\s+|-/g, "");
  return ccNum.length >= 10 && ccNum.length <= 16 && published.checkLuhn(ccNum);
};

published.checkLuhn = function (ccNum) {
  if (WAVE.strEmpty(ccNum)) return false;

  var nCheck = 0;
  var nDigit = 0;
  var bEven = false;
  ccNum = ccNum.replace(/\D/g, "");

  for (var n = ccNum.length - 1; n >= 0; n--) {
    var cDigit = ccNum.charAt(n);
    nDigit = parseInt(cDigit, 10);

    if (bEven) {
      if ((nDigit *= 2) > 9) nDigit -= 9;
    }

    nCheck += nDigit;
    bEven = !bEven;
  }

  return (nCheck % 10) === 0;
};

published.validateCVC = function (cvc) {
  if (WAVE.strEmpty(cvc)) return false;
  return cvc.length >= 3 && cvc.length <= 4 && /^\d+$/.test(cvc);
};

published.validateExpirationDate = function (month, year) {
  if (WAVE.strEmpty(month) || WAVE.strEmpty(year)) return false;
  if (year.length === 2) year = "20"+year;

  return /^\d+$/.test(month) &&
          /^\d+$/.test(year) &&
          1 <= month && month <= 12 &&
          year.length === 4 && year > 2015;
};

published.getCardBrand = function (ccNum) {
  if (WAVE.strEmpty(ccNum) || !/^\d{2}/.test(ccNum))
      return published.Brands.UNKNOWN;

  if (/^4/.test(ccNum)) return published.Brands.VISA;
  if (/^5[1-5]/.test(ccNum)) return published.Brands.MASTER_CARD;
  if (/^3[47]/.test(ccNum)) return published.Brands.AMERICAN_EXPRESS;
  if (/^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)/.test(ccNum)) return published.Brands.DISCOVER;
  if (/^35(2[89]|[3-8][0-9])/.test(ccNum)) return published.Brands.JCB;
  if (/^3[0689]/.test(ccNum)) return published.Brands.DINERS_CLUB;

  return published.Brands.UNKNOWN;
};