Stripe существует уже некоторое время, и его довольно просто скопировать и вставить из примера кода Stripe.js в документации и получить работающую проверку кредитной карты. При недавней интеграции Stripe для клиента это именно то, что я сделал вначале, но до того, как обновления были отправлены, наша команда приняла решение использовать Vue.js, чтобы помочь привести в порядок весь интерфейсный JavaScript, используемый сайтом. и дадут нам более удобный в обслуживании путь вперед.

Оказывается, ваша типичная форма оформления заказа Stripe может быть значительно улучшена, если переопределить ее как компонент Vue. Я хотел бы показать вам три части нашего решения: определение типа кредитной карты, обработка конфиденциальных полей и токенизация Stripe. Каждый из них использует несколько функций, предоставляемых Vue.js, поэтому он должен дать вам хорошее представление о том, как его можно практически применить в ваших собственных проектах.

Определение типа кредитной карты

Одной из первых функций, которыми я занялся, было определение типа кредитной карты. Раньше мы использовали jQuery для настройки прослушивателя событий для замены классов CSS при вводе номера карты. Это был очень ручной процесс, требующий выбора входа и передачи функции обнаружения. Он также использует весь танец удаления всех возможных имен классов перед добавлением желаемого.

<form>
  <div class="has-field--cc">
    <input type="text" />
  </div>
</form>
<script>
  cardTypeDetect($('.has-field--cc'));
</script>

JavaScript, который заставил это работать, выглядел примерно так.

function cardType(cardField) {
  var input = cardField.find('input');
  var cardTypeClass = function() {
    var cardType = Stripe.card.cardType(input.val());
    switch (cardType) {
      case 'Visa': return 'is-visa';
      case 'MasterCard': return 'is-mastercard';
      case 'American Express': return 'is-amex';
      case 'Discover': return 'is-discover';
    }
  };
  var detectCardType = function() {
    cardField.removeClass('is-visa is-mastercard is-amex is-discover');
    cardField.addClass(cardTypeClass());
  };
  $(input).on('keyup', detectCardType);
  detectCardType();
};

Преобразовывая это в Vue.js, я создал credit-card компонент. На эти компоненты можно ссылаться в разметке, как на ваши собственные расширения HTML. Vue выбирает эти ссылки и запускает их, автоматически настраивая все привязки данных и прослушиватели событий.

<credit-card inline-template>
  <form>
    <div class="has-field--cc" :class="cardTypeClass">
      <input type="text" v-model="cardNumber" />
    </div>
  </form>
</credit-card>

Здесь мы видим двустороннюю привязку данных через атрибут v-model к свойству cardNumber в компоненте Vue. Затем на это значение ссылается свойство computed cardType, поэтому каждый раз при изменении входного значения вычисленное значение свойства будет обновляться.

Вычисленное свойство отвечает за определение класса типа карты, который привязан к has-field--cc <div> в разметке атрибутом :class. Поскольку эта привязка является динамической, имя класса, применяемое в разметке, будет изменяться при каждом обновлении значения свойства компьютера.

Vue.component('credit-card', {
  data: function() {
    return {
      cardNumber: '',
    };
  },
  computed: {
    cardType: function() {
      return Stripe.card.cardType(this.cardNumber);
    },
    cardTypeClass: function() {
     switch (this.cardType) {
        case 'Visa': return 'is-visa';
        case 'MasterCard': return 'is-mastercard';
        case 'American Express': return 'is-amex';
        case 'Discover': return 'is-discover';
      }
    },
  },
});

Чувствительное обращение с полями

При использовании Stripe.js одно из требований - не включать атрибут name во входные данные формы, содержащие конфиденциальные данные, чтобы предотвратить отправку данных на сервер с самого начала. В этом прелесть Stripe и то, как они могут выполнять транзакции по кредитным картам, соответствующие стандарту PCI, без каких-либо проблем, связанных с соблюдением требований самостоятельно.

Поскольку мы используем Rails для генерации разметки и нет чистого способа пропустить вывод атрибутов name, мы изначально использовали jQuery для удаления этих атрибутов из этих полей.

<form>
  <input type="text" name="cardholder_name" data-stripe="name" />
  <input type="text" name="card_number" data-stripe="number" />
  <input type="text" name="expiration" data-stripe="exp" />
  <input type="text" name="security_code" data-stripe="cvc" />
  <input type="text" name="postal_code" data-stripe="address_zip" />
</form>

Код JavaScript для удаления атрибутов имени выглядел так.

$('form').find('[data-stripe]').removeAttr('name');

Vue.js предоставляет возможность определять пользовательские директивы. При добавлении атрибута директивы к элементу он будет обрабатываться с помощью ряда хуков жизненного цикла, которые вы можете привязать по мере необходимости.

<form>
  <input type="text" name="cardholder_name" v-sensitive />
  <input type="text" name="card_number" v-sensitive />
  <input type="text" name="expiration" v-sensitive />
  <input type="text" name="security_code" v-sensitive />
  <input type="text" name="postal_code" v-sensitive />
</form>

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

Vue.directive('sensitive', {
  bind: function (el) {
    el.removeAttribute('name');
  },
});

Ничто из этого не имело бы смысла, если бы вы просто писали простой HTML, поскольку вместо этого вы бы просто оставили атрибут name, но если вы полагаетесь на язык шаблонов или библиотеку для создания ваших форм, это может быть полезно.

Stripe Tokenization

Stripe.js автоматически использует поля с data-stripe атрибутами, когда вы передаете ему <form> элемент.

Stripe.card.createToken(form, function(status, response) {
  // handle success and failure
});

Для Vue я добавил атрибуты привязки данных thev-model для остальных полей формы.

<credit-card inline-template>
  <form>
    <input type="text" name="cardholder_name" v-model="name" v-sensitive />
    <input type="text" name="card_number" v-model="cardNumber" v-sensitive />
    <input type="text" name="expiration" v-model="expiration" v-sensitive />
    <input type="text" name="security_code" v-model="securityCode" v-sensitive />
    <input type="text" name="postal_code" v-model="postalCode" v-sensitive />
    <input type="hidden" name="token" v-model="token" />
  </form>
</credit-card>

Они сопоставляются с ключами объекта, возвращаемыми методом data в компоненте Vue.

Vue.component('credit-card', {
  data: function() {
    return {
      name: '',
      cardNumber: '',
      expiration: '',
      securityCode: '',
      postalCode: '',
      token: '',
    };
  },
});

Вы также можете связывать события с помощью атрибутов HTML. Здесь я указал @submit обработчик событий, а также дал ему модификатор prevent, чтобы тоже не запускалось исходное событие (это эквивалентно вызову event.preventDefault() в обработчике событий).

<credit-card inline-template>
  <form action="/purchases" method="post"  @submit.prevent="tokenize">
  </form>
</credit-card>

В компоненте Vue я добавил метод tokenize, который будет вызываться при отправке формы. Теперь он отвечает за передачу данных в Stripe, чтобы превратить необработанные данные карты в безопасный токен, который позже будет использоваться для обработки платежа. Он получает данные из нового вычисляемого свойства stripeData, которое просто возвращает объект с ключевыми именами, которые Stripe.js ожидает, используя значения из ввода нашей формы с использованием связанных свойств данных.

Vue.component('credit-card', {
  data: function() { … },
  computed: {
    stripeData: function() {
      return {
        name: this.name,
        number: this.cardNumber,
        cvc: this.securityCode,
        exp: this.expiration,
        address_zip: this.postalCode,
      };
    },
  },
  methods: {
    tokenize: function(event) {
      Stripe.card.createToken(this.stripeData, function(status, response) {
        if (!response.error) {
          this.token = response.id;
          this.$nextTick(function() {
            $.rails.handleRemote($(this.$el));
          });
        } else {
          // handle errors
        }
      });
    },
  },
});

Метод this.$nextTick предоставляется Vue и используется здесь для обеспечения обновления токена в DOM перед отправкой формы обратно на сервер; без этого его значение может быть пустым.

Поскольку в этом приложении используется гибридный подход Rails / Vue, мы используем метод Rails UJS $.rails.handleRemote для отправки формы Ajax. Свойство this.$el относится к элементу верхнего уровня, содержащемуся в компоненте. Он заключен в селектор jQuery, чтобы успокоить Rails.

Чего мы достигли

Модульный подход Vue сделал наш интерфейсный код более структурированным. Теперь есть чистый способ определения модульных компонентов, которые мы можем повторно использовать на сайте, а его настраиваемые директивы обеспечивают более прозрачные средства управления DOM, чем jQuery. Хотя кода может быть не меньше, его легче рассуждать, чем раньше, и это должно упростить дальнейшее продвижение.

Постскриптум

Stripe недавно отказался от пользовательских форм, подобных тем, которые показаны в этом посте, с выпуском его компонентов пользовательского интерфейса Elements, но принципы, представленные здесь, все еще могут быть применены в более общем случае к случаю извлечения компонентов Vue из ваших фрагментов JavaScript.