Hoy en día casi todos los servicios o productos que consumimos en linea como Spotify, Netflix, Audiable, etc… requieren de una tarjeta de crédito si queremos consumirlos de manera “premium” (sin publicidad o con acceso a mas features) ya sea para pagar la suscripción mes con mes o para pagar el servicio que consumimos una vez como en Uber o productos en Amazon.

Así que tarde o temprano como desarrollador tendremos la necesidad de implementar procesamiento de pagos ya sea en nuestra propia startup o lo que sea que estemos desarrollando, es aquí donde entra Stripe. Stripe esta hecho por desarrolladores para desarrolladores y tratan de hacer sus API’s con las mejores practicas para que nos sea muy fácil implementarlo.

No es solo para developers
Stripe no solo puede ser usado consumiendo sus API’s con algún lenguaje de programación, también puede ser implementado en minutos u horas en distintas plataformas, servicios o aplicaciones, basta con ver en su pagina Works with Stripe donde podrás ver si hay alguna herramienta o plataforma que pudieras usar con Stripe.

Integracion de Stripe en nuestra aplicacion web
En este post veremos como integrar Stripe en nuestra aplicacion web en un modelo de supscripcion mensual.

Como crear una cuenta
Crear una cuenta en stripe es tan fácil como crear una cuenta de facebook solo tienes que ingresar y poner tu correo y tu contraseña (Por lo menos para poder aprender a usarlo, para realmetne recibir dinero tienes que dar mas datos).

Como crear planes
Hay dos maneras de crear planes desde el Dashboard o desde la API, para cerar un plan desde la API basta con:

import stripe
stripe.api_key = 'TU API KEY'


stripe.Plan.create(
    amount=500,
    interval="month",
    name="Amazing Basic Plan",
    currency="usd",
    id="basic",
    trial_period_days=30
)

Listar planes
Para traer tus planes desde la API:

import stripe

stripe.api_key = 'TU API KEY'
stripe_plans = stripe.Plan.list(limit=3)

Webhooks
Stripe tiene webhooks que ellos llaman eventos dentro de su plataforma y estos son cualquier cosa que pase relacionado a Customers, Plans, Cards o cualquier otra cosa dentro de Stripe. Para esto (si estan trabajando en Django) yo cree una vista que recibe el webhook y una clase que se llama EventManager para poder procesar el webhook ya que Stripe (si asi tu lo quieres) te manda todos los eventos a la misma URL que tu le indiques.

View:

from django.views.generic import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.http import HttpResponse
import json
from django.conf import settings
from core.eventmanager import EventManager


stripe.api_key = settings.STRIPE_API_KEY


class WebhokView(View):

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super(WebhokView, self).dispatch(request, *args, **kwargs)

    def post(self, request):
        event = json.loads(request.body)

        try:
            event_manager = EventManager(event)
        except Exception as e:
            return HttpResponse(status=500)

        try:
            event_manager.process()
        except Exception as e:
            return HttpResponse(status=500)

        return HttpResponse(status=200)

El proceso de la vista es el siguiente:

  • Recibe el POST request
  • Crea una variable llamada event con el cuerpo del request en JSON
  • Crea un objeto de EventManager pasando como parametro la variable event que es la clase que creé y explicare mas adelante
  • Y por ultimo ejecuta el metodo process de EventManager

La clase EventManager es la encargada de hacer lo que se tenga que hacer (si hay algo que hacer) con el evento recibido, por ejemplo si se hizo un cambio en un Plan la clase ejecutara el metodo que se encarga de ello.

EventManager:

from payments.models import Plan
import stripe

valid_events = {
    'plan.created': 'create_plan',
    'plan.updated': 'update_plan',
    'plan.deleted': 'delete_plan'
}
events = {}
event = lambda f: events.setdefault(f.__name__, f)

class EventManager:

    def __init__(self, event):
        try:
            self.event = stripe.Event.retrieve(event["id"])
        except Exception as e:
            raise Exception('This is not a valid event')

    def process(self):
        try:
            event_type = valid_events[self.event.type]
        except Exception as e:
            raise Exception('This event is not supported')

        return events[event_type](self.event)

    @event
    def create_plan(event):
        data = {
            'amount': event.data.object.amount,
            'currency': event.data.object.currency,
            'plan_id': event.data.object.id,
            'interval': event.data.object.interval,
            'interval_count': event.data.object.interval_count,
            'livemode': event.data.object.livemode,
            'name': event.data.object.name,
            'trial_period_days': event.data.object.trial_period_days or 0,
        }
        plan = Plan(**data)
        plan.save()

    @event
    def update_plan(event):
        try:
            plan = Plan.objects.get(plan_id=event.data.object.id)
        except Plan.DoesNotExist:
            raise Exception('You must create this event in the local database')

        data = {
            'amount': event.data.object.amount,
            'currency': event.data.object.currency,
            'plan_id': event.data.object.id,
            'interval': event.data.object.interval,
            'interval_count': event.data.object.interval_count,
            'livemode': event.data.object.livemode,
            'name': event.data.object.name,
            'trial_period_days': event.data.object.trial_period_days or 0,
        }

        for key, value in data.items():
            setattr(plan, key, value)
        plan.save()

    @event
    def delete_plan(event):
        try:
            plan = Plan.objects.get(plan_id=event.data.object.id)
        except Plan.DoesNotExist:
            raise Exception('You must create this event in the local database')

        plan.deleted = True
        plan.save()

El proceso de EventManager es el siguiente:

  • La clase esperara un objeto json que es el que nos manda Stripe
  • Con ese objeto JSON traera un verdadero objeto Event con la API de Stripe (Esto es importante por que cualquiera podria mandarnos un objeto JSON)
  • Y si se ejecuta el metodo process automaticamente detectara el tipo de evento y ejecutara el metodo correspondiente

Para no hacer una infinidad de if/else en la clase EventManager lo que hice fue crear un decorador que mete las funciones dentro de un diccionario y otro diccionario con los eventos validos y listo.

Con todo lo anterior ya podríamos administrar nuestros planes, ahora vamos a crear Customers y Subscriptions para que Stripe este cobrando mes con mes (o el plazo que tu hayas puesto a la hora de crear tus plans) de manera automática.

Yo uso Django y el flujo es el siguiente:

  • Se crea el usuario local con Django Form (aquí mismo se selecciona el plan)
  • Si se crea correctamente, se loggea automáticamente
  • Es redireccionado a agregar la tarjeta de crédito
  • La tarjeta se manda a Stripe a través de su API
  • Si la tarjeta es valida retorna un token que se manda a nuestro back-end
  • Recibimos el token y creamos un Customer con el
  • Creamos un Subscription con ese customer y el plan seleccionado

Para crear el usuario local y escoger un plan para nuestro usuario uso el siguiente View, Django Form y HTML:

View

from django.contrib.auth import authenticate, login
from django.shortcuts import redirect
from django.shortcuts import render
from users.models import User
from users.forms import UserRegisterForm

def user_register(request):
    form = UserRegisterForm()
    if request.method == 'POST':
        form = UserRegisterForm(request.POST)
        if form.is_valid():
            del form.cleaned_data['confirm_password']
            form.cleaned_data['username'] = form.cleaned_data['username'].lower()

            new_user = User.objects.create_user(**form.cleaned_data)
            user = authenticate(
                username=new_user.username,
                password=form.cleaned_data['password']
            )

            login(request, user)
            return redirect('/add_credit_card/')

    return render(request, 'register.html', {'form': form})

Django Form

from users.models import User
from django import forms
from payments.models import Plan


class UserRegisterForm(forms.ModelForm):
    confirm_password = forms.CharField(widget=forms.PasswordInput())
    plan = forms.ChoiceField(
        label="Plan",
        choices=Plan.objects.all(),
        widget=forms.Select()
    )

    class Meta:
        model = User
        fields = ('username', 'first_name', 'last_name', 'email', 'plan', 'password', 'confirm_password')
        widgets = {
            'password': forms.PasswordInput(),
        }

    def clean(self):
        if len(self.cleaned_data['password']) < 8:
            raise forms.ValidationError("Password must be at least 8 characters")

        if self.cleaned_data['confirm_password'] != self.cleaned_data['password']:
            raise forms.ValidationError("Passwords don't match")

        if User.objects.filter(username=self.cleaned_data['username'].lower()).exists():
            raise forms.ValidationError("A user with this Username already exists")

        if User.objects.filter(email=self.cleaned_data['email']).exists():
            raise forms.ValidationError("A user with this Email already exists")

        self.cleaned_data['plan'] = Plan.objects.get(pk=self.cleaned_data['plan'])

    def __init__(self, *args, **kwargs):
        super(UserRegisterForm, self).__init__(*args, **kwargs)
        choices = [(pt.id, unicode(pt)) for pt in Plan.objects.filter(deleted=0)]
        self.fields['plan'].choices = choices

HTML

{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"/>
        <title>Pystripe</title>
        <!-- CSS  -->
        <link type="text/css" rel="stylesheet" href="{% static 'css/vendor/materialize/materialize.min.css' %}"  media="screen,projection"/>
    </head>
    <body class="dark-background">
        <!-- Main container -->
        <div class="container">
            <div class="section">
                <div class="row">
                    <div class="col s12 m12 l12">
                        <form method="post" action="{% url 'register' %}">
                            {% csrf_token %}
                            {{ form.as_p }}
                            {% if form.errors %}
                                {% for field in form %}
                                    {% for error in field.errors %}
                                        <div class="alert alert-error">
                                            <strong>{{ error|escape }}</strong>
                                        </div>
                                    {% endfor %}
                                {% endfor %}

                            {% endif %}
                            <input type="hidden" name="next" value="{{ next }}" />
                            <button type="submit" class="btn waves-effect waves-light blue darken-0 right">Register</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

En la vista anterior, si el usuario es creado correctamente se hace login automáticamente y lo redirecciona a la vista para agregar la tarjeta de crédito, el trabajo de esta vista es solo mostrar un HTML por que todo es hecho por JavaScript ya que se usa la librería de Stripe para mandar la tarjeta de crédito y tokenizarla.

View

class AddCreditCard(TemplateView):
    template_name = 'add_credit_card.html'
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
  Stripe.setPublishableKey('TU-PUBLIC-KEY');
  $(function() {
    var $form = $('#payment-form');
    $form.submit(function(event) {
    // Disable the submit button to prevent repeated clicks:
    $form.find('.submit').prop('disabled', true);

    // Request a token from Stripe:
    Stripe.card.createToken($form, stripeResponseHandler);

    // Prevent the form from being submitted:
    return false;
  });

  function stripeResponseHandler(status, response) {
    // Grab the form:
    var $form = $('#payment-form');

    if (response.error) { // Problem!

      // Show the errors on the form:
      $form.find('.payment-errors').text(response.error.message);
      $form.find('.submit').prop('disabled', false); // Re-enable submission

    } else { // Token was created!

      // Get the token ID:
      var token = response.id;

      // Insert the token ID into the form so it gets submitted to the server:
      $form.append($('<input type="hidden" name="stripeToken">').val(token));

      // Submit the form:
      $form.get(0).submit();
    }
  };
});
</script>

<form action="/create_suscription/" method="POST" id="payment-form">
  <span class="payment-errors"></span>

  <div class="form-row">
    <label>
      <span>Card Number</span>
      <input type="text" size="20" data-stripe="number">
    </label>
  </div>

  <div class="form-row">
    <label>
      <span>Expiration (MM/YY)</span>
      <input type="text" size="2" data-stripe="exp_month">
    </label>
    <span> / </span>
    <input type="text" size="2" data-stripe="exp_year">
  </div>

  <div class="form-row">
    <label>
      <span>CVC</span>
      <input type="text" size="4" data-stripe="cvc">
    </label>
  </div>


  <input type="submit" class="submit" value="Submit Payment">
</form>

Si todo sale bien con la tokenizacion de la tarjeta de crédito, la función stripeResponseHandler tomara el response de Stripe con el token y nos redireccionara a la vista que crea el Customer y el Suscription.

View

from django.views.decorators.csrf import csrf_exempt
import stripe
from django.shortcuts import redirect


@csrf_exempt
def create_suscription(request):
    if request.method == 'POST':
        stripe_token = request.POST['stripeToken']
        customer = stripe.Customer.create(
            description="Customer for " + request.user.email,
            source=stripe_token
        )

        stripe.Subscription.create(
            customer=customer.id,
            plan=request.user.plan.plan_id
        )

        request.user.stripe_customer_id = customer.id
        request.user.save()

        return redirect('/dashboard/')

    return redirect('/add_credit_card/')

Con esto ya tendríamos un sistema de cobro mensual con planes para nuestros usuarios, espero les haya servido de algo y puedan cobrar por sus servicios en linea.

Todo el código esta en este repo: https://github.com/OmarIbannez/pystripe

Suscríbete

It's only fair to share...Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn0Share on Reddit0Pin on Pinterest0Email this to someone

Leave a Reply

Your email address will not be published. Required fields are marked *

Comment moderation is enabled. Your comment may take some time to appear.