Suscripciones

En este tutorial, podrás enviar información sensitiva a Conekta desde tu sitio web, luego podrás guardar la información de la tarjeta para cobrar por suscripción.

Tutorial para sobre

1 Entendiendo el flujo

Para empezar el proceso de cobrar a una tarjeta, primero deberás capturar la información de la tarjeta por medio de Conekta.js o un SDK para móviles, para después enviarla a Conekta. Conekta regresará un token id seguro que puede ser operado sin tener obligaciones de certificación de PCI. Este proceso se le conoce como tokenización.

El siguiente paso es enviar el token a tu servidor y crear un objeto cliente, este puede ser usado para guardar información de la tarjeta de manera prolongada permitiendo facturación flexible. Una vez la tarjeta ha sido guardada, podrás cobrar a tu cliente.

Al ser el procesamiento de tarjetas en tiempo real, recibirás un estatus de ‘paid’ or un error del tipo de procesamiento si el pago fue rechazado. En contraste con el proceso regular de pago, podrás cobrar a tu cliente (paso 5) tantas veces sea necesario.

Reproducir flujo animado


2 Tokeniza información de tarjetas

Para este tutorial, usaremos conekta.js para guardar la tarjeta de crédito. Conekta.js permite enviar información sensitiva del tarjetahabiente directamente a los servidores de Conekta. De esta manera, la información sensitiva del tarjetahabiente no pasa por tus servidores y no necesitas preocuparte por certificaciones de PCI DSS.

Captura información de la tarjeta

El primer paso es capturar la información de la tarjeta. Puedes hacerlo directamente en HTML o por medio del web framework que estés utilizando.

<form action="" method="POST" id="card-form">
  <span class="card-errors"></span>
  <div class="form-row">
    <label>
      <span>Nombre del tarjetahabiente</span>
      <input type="text" size="20" data-conekta="card[name]"/>
    </label>
  </div>
  <div class="form-row">
    <label>
      <span>Número de tarjeta de crédito</span>
      <input type="text" size="20" data-conekta="card[number]"/>
    </label>
  </div>
  <div class="form-row">
    <label>
      <span>CVC</span>
      <input type="text" size="4" data-conekta="card[cvc]"/>
    </label>
  </div>
  <div class="form-row">
    <label>
      <span>Fecha de expiración (MM/AAAA)</span>
      <input type="text" size="2" data-conekta="card[exp_month]"/>
    </label>
    <span>/</span>
    <input type="text" size="4" data-conekta="card[exp_year]"/>
  </div>
<!-- Información recomendada para sistema antifraude -->
  <div class="form-row">
    <label>
      <span>Calle</span>
      <input type="text" size="25" data-conekta="card[address][street1]"/>
    </label>
  </div>
<div class="form-row">
    <label>
      <span>Colonia</span>
      <input type="text" size="25" data-conekta="card[address][street2]"/>
    </label>
  </div>
<div class="form-row">
    <label>
      <span>Ciudad</span>
      <input type="text" size="25" data-conekta="card[address][city]"/>
    </label>
  </div>
<div class="form-row">
    <label>
      <span>Estado</span>
      <input type="text" size="25" data-conekta="card[address][state]"/>
    </label>
  </div>
<div class="form-row">
    <label>
      <span>CP</span>
      <input type="text" size="5" data-conekta="card[address][zip]"/>
    </label>
  </div>
<div class="form-row">
    <label>
      <span>País</span>
      <input type="text" size="25" data-conekta="card[address][country]"/>
    </label>
  </div>
  <button type="submit">¡Pagar ahora!</button>
</form>

Es importante notar que los campos sensitivos como CVC, número de tarjeta, etc. no tienen el atributo de nombre. Esto previene que la información sea cargada a tu servidor. También incluimos un atributo data-conekta en todos los campos de tarjeta.

En modo sandbox puedes usar la tarjeta 4242424242424242 con cualquier cvc y fecha de expiración. Lee más sobre otras técnicas y tarjetas de prueba.

Envía información de la tarjeta a Conekta

Para empezar, incluye conekta.js:

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" src="https://conektaapi.s3.amazonaws.com/v0.5.0/js/conekta.js"></script>

Para prevenir problemas con navegadores antiguos, sugerimos agregar esta información sobre la etiqueta </head> o al final antes de cerrar la etiqueta <body>. jQuery no es requerido para usar conekta.js, pero es incluido para simplificar el ejemplo.

En una etiqueta <script> separada después de las que ya has incluido, ajusta tu llave pública:

<script type="text/javascript">
 
 // Conekta Public Key
  Conekta.setPublishableKey('key_KJysdbf6PotS2ut2'); //v3.2
 //Conekta.setPublicKey('key_KJysdbf6PotS2ut2'); //v5+
 
 // ...
</script>

Conekta.setPublishableKey() permite a Conekta identificar tu cuenta cuando te comunicas con nuestros servidores. Recuerda usar tu llave de API pública en modo de producción cuando tu cuenta esté activa para que puedas crear tokens reales.

Tokeniza la tarjeta

El siguiente paso es tokenizar la tarjeta. Agrega un controlador de eventos para tu forma. Queremos capturar el evento submit y crear un token.

$(function () {
  $("#card-form").submit(function(event) {
    var $form = $(this);

    // Previene hacer submit más de una vez
    $form.find("button").prop("disabled", true);
    Conekta.token.create($form, conektaSuccessResponseHandler, conektaErrorResponseHandler);
   //Conekta.Token.create($form, conektaSuccessResponseHandler, conektaErrorResponseHandler); //v5+
   
    // Previene que la información de la forma sea enviada al servidor
    return false;
  });
});
jQuery ($) ->
  $("#card-form").submit (event) ->
    $form = $(this)

    ### Previene hacer submit más de una vez ###
    $form.find("button").prop "disabled", true
    Conekta.token.create $form, conektaSuccessResponseHandler, conektaErrorResponseHandler

    ### Previene que la información de la forma sea enviada al servidor ###
    false
  return

Podrás ver que enviamos la forma que contiene toda la información de la tarjeta como un argumento dentro de Conekta.token.create. Mientras que JQuery es usado para conseguir y enviar la forma en este ejemplo, conekta.js es una librería independiente que no tiene dependencias externas.

Esta función requiere el número de la tarjeta, el nombre que aparece en la tarjeta, CVC e información de expiración para crear el token. La lista completa de parámetros está disponible en la documentación de conekta.js.

El segundo argumento es conektaSuccessResponseHandler el cual es el callback que opera sobre una respuesta exitosa. Conekta.token.create es una llamada asíncrona, el cual invoca a conektaSuccessResponseHandler al recibir una respuesta exitosa de los servidores de Conekta. Dentro de conektaSuccessResponseHandler, podrás incluir al menos un argumento que es la respuesta.

token es un objeto con las siguientes propiedades:

{
  "id": "tok_a4Ff0dD2xYZZq82d9",
  "object": "token",
  "used": false,
  "livemode": true
}
  "id": "tok_a4Ff0dD2xYZZq82d9",
  "object": "token",
  "used": false,
  "livemode": true

El tercer argumento conektaErrorResponseHandler es un callback que opera sobre la respuesta en caso de que exista un error, usando Conekta.token.create.

error es un objeto de error con las siguientes propiedades:

{
  "type": "parameter_validation_error",
   "message": "Something went wrong on Conekta's end",
   "message_to_purchaser": "Your code could not be processed, please try again later",
  "error_code": "invalid_expiry_month",
  "param": "card[exp_month]"
}
  "type": "parameter_validation_error",
  "message": "Something went wrong on Conekta's end",
  "message_to_purchaser": "Your code could not be processed, please try again later",
  "error_code": "invalid_expiry_month",
  "param": "card[exp_month]"

Recuerda que el token sólo puede ser utilizado una vez. Para generar más de un cargo deberás guardar la tarjeta, puedes visitar nuestro tutorial de pagos on-demand para ver un ejemplo.


3 Envía el token a tu servidor

Para enviar la información del token a tu servidor, usa conektaSuccessResponseHandler.

Si no hay errores creando el token, conektaSuccessResponseHandler será invocado. Agrega el token_id a la forma usando conektaTokenId y envía la información a tu servidor:

var conektaSuccessResponseHandler = function(token) {
  var $form = $("#card-form");

  /* Inserta el token_id en la forma para que se envíe al servidor */
  $form.append($("<input type='hidden' name='conektaTokenId'>").val(token.id));
 
  /* and submit */
  $form.get(0).submit();
};
conektaSuccessResponseHandler = (token) ->
  $form = $("#card-form")

  ### Inserta el token_id en la forma para que se envíe al servidor ###
  $form.append $("<input type=\"hidden\" name=\"conektaTokenId\" />").val(token.id)

  ### and submit ###
  $form.get(0).submit()
  return

Como puedes ver, agregamos token.id a la forma usando un campo “escondido” para que el usuario no pueda acceder el id.

Después de agregar el token.id a la forma, podrás enviar información a tu servidor. Esta información será enviada haciendo HTTP POST al URL que está en “action” dentro de la forma. Recomendamos ejecutar la llamada submit() dentro de la forma en lugar de usar el wrapper de jQuery para evitar un loop infinito.

En modo sandbox puedes usar la tarjeta 4242424242424242 con cualquier cvc y una fecha de expiración. Lee más sobre otras técnicas y tarjetas de prueba.

var conektaErrorResponseHandler = function(response) {
  var $form = $("#card-form");
  
  /* Muestra los errores en la forma */
  $form.find(".card-errors").text(response.message_to_purchaser);
  $form.find("button").prop("disabled", false);
};
conektaErrorResponseHandler = (response) ->
  $form = $("#card-form")

  ### Muestra los errores en la forma ###
  $form.find(".card-errors").text response.message_to_purchaser
  $form.find("button").prop "disabled", false
  return

Puedes probar este callback en modo sandbox usando la tarjeta 4000000000000002 o usando un número de CVC o fecha de expiración inválida. Lee más sobre nuestra documentación sobre pruebas.

Además de controlar eventos en javascript en tu servidor, podrás también recibir notificaciones de pago por medio de webhooks para que puedas verificar de manera segura los pagos. Para leer más sobre cómo recibir notificaciones de tokens por medio de tarjetas, revisa la sección de webhooks.


4 Guarda la tarjeta

El primer paso es incluir una de nuestras librerías en tu proyecto. Una vez instalada la librería de tu preferencia necesitarás utilizar tu llave privada, de esta manera podrás autenticar todas las llamadas que realices.

Recuerda que solamente las llaves de producción pueden procesar pagos reales. Las llaves sandbox están diseñadas para realizar pruebas solamente. También, toma en cuenta que las llamadas del lado del servidor deberán utilizar las llaves privadas ya que, por cuestiones de seguridad, las llaves públicas tienen permisos limitados.

#N/A: curl -u {global_api_key}: \
require "conekta"
Conekta.api_key = "{global_api_key}"
require_once("/path/to/lib/Conekta.php");
\Conekta\Conekta::setApiKey("{global_api_key}");
import conekta
conekta.api_key = "{global_api_key}"
var conekta = require('conekta');
conekta.api_key = "{global_api_key}";
import com.conekta;
Conekta.setApiKey("{global_api_key}");
using conekta;
conekta.Api.apiKey = "{global_api_key}"; 

Ahora puedes asociar el token que enviaste desde la página de ‘customer’ para guardarla por un tiempo prolongado.

curl -H "Accept: application/vnd.conekta-v{global_api_version}+json" \
     -H "Content-type: application/json" \
     -u {global_api_key}: \
     -X POST -d '{
       "name": "Lews Therin",
       "email": "lews.therin@gmail.com",
       "phone": "55-5555-5555",
       "cards": ["tok_a4Ff0dD2xYZZq82d9"]
     }' https://api.conekta.io/customers
begin
  customer = Conekta::Customer.create({
    name: "Lews Therin",
    email: "lews.therin@gmail.com",
    phone: "55-5555-5555",
    cards: [params[:conektaTokenId]]  #["tok_a4Ff0dD2xYZZq82d9"]
  })
rescue Conekta::ParameterValidationError => e
  puts e.message_to_purchaser 
  #alguno de los parámetros fueron inválidos
end
try{
  $customer = Conekta_Customer::create(array(
    "name"=> "Lews Therin",
    "email"=> "lews.therin@gmail.com",
    "phone"=> "55-5555-5555",
    "cards"=>  array($_POST['conektaTokenId'])   //"tok_a4Ff0dD2xYZZq82d9"
  ));
}catch (Conekta_Error $e){
  echo $e->getMessage();
 //el cliente no pudo ser creado
}
try:
  customer = conekta.Customer.create({
    "name": "Lews Therin",
    "email": "lews.therin@gmail.com",
    "phone": "55-5555-5555",  
    "cards": [request.POST["conektaTokenId"]] # "tok_a4Ff0dD2xYZZq82d9"
  })
except conekta.ConektaError as e:
  #el cliente no pudo ser creado
  print e.message_to_purchaser 
conekta.Customer.create({
  "name": "Lews Therin",
  "email": "lews.therin@gmail.com",
  "phone": "55-5555-5555",
  "cards": [req.body.conektaTokenId]
}, function(err, res) {
    console.log(res.toObject());
});
try {
  customer_data = new JSONObject(
    "{'name': 'Lews Therin', "
    + "'email': 'logan@x-men.org',"
    + "'phone': '55-5555-5555',"
    + "'cards':['tok_test_visa_4242']"
    +"}");
  Customer customer = Customer.create(customer_data);
} catch (Exception e) {
  logger.info(e.message_to_purchaser);
}
try {
 conekta.Customer customer = new conekta.Customer ().create(@"{
  ""name"":""James Howlett"",
  ""email"":""james.howlett@forces.gov"",
  ""phone"":""55-5555-5555"",
  ""cards"":[""tok_test_visa_4242""]
}");
} catch (Exception e) {
 Console.WriteLine(e);
}

En modo de desarrollo puedes usar el token tok_test_visa_4242 para procesar rápidamente del lado del servidor cargos de prueba. Lee más sobre otras técnicas y tokens de prueba.


5 Crea un plan

Ahora de que has guardado propiamente una tarjeta asociada a un cliente, podremos crear una suscripción. El primer paso es crear un plan. El plan que creamos definirá el ciclo de cobro para la suscripción del cliente y el monto que se le debe cobrar al cliente.

curl -H "Accept: application/vnd.conekta-v{global_api_version}+json" \
     -H "Content-type: application/json" \
     -u {global_api_key}: \
     -X POST -d '{
        "id": "Monthly Internet Bill",
        "name": "monthly-internet-bill",
        "amount": 51000,
        "currency": "MXN",
        "interval": "month"
     }' https://api.conekta.io/plans
plan = Conekta::Plan.create({
  id: "monthly-internet-plan",
  name: "Monthly Internet Bill"
  amount: 51000,
  currency: "MXN",
  interval: "month"
})
$plan = Conekta_Plan::create(array(
  "id"=> "monthly-internet-plan",
  "name"=> "Monthly Internet Bill",
  "amount"=> 51000,
  "currency"=> "MXN",
  "interval"=> "month"
));
plan = conekta.Plan.create({
  "id": "monthly-internet-plan",
  "name": "Monthly Internet Bill",
  "amount": 51000,
  "currency": "MXN",
  "interval": "month"
})
conekta.Plan.create({
  "id": "monthly-internet-plan",
  "name": "Monthly Internet Bill",
  "amount": 51000,
  "currency": "MXN",
  "interval": "month"
}, function(err, res) {
  console.log(res.toObject());
});
try {
  plan_params = new JSONObject(
    "{'id' : 'gold-plan',"
    + "'name' : 'Gold Plan',"
    + "'amount' : 10000,"
    + "'currency' : 'MXN',"
    + "'interval' : 'month',"
    + "'frequency' : 10,"
    + "'trial_period_days' : 15,"
    + "'expiry_count' : 12}");
  Plan plan = Plan.create(plan_params);
} catch (Exception e) {
  logger.info(e);
}
try {
conekta.Plan plan = new conekta.Plan ().create (@"{
   ""id"":""gold-plan999"",
   ""name"":""Gold Plan 999"",
   ""amount"":10000
}");
} catch (Exception e){
  Console.WriteLine(e.message_to_purchaser)
}

6 Crear una suscripción

Al haber creado el plan, ahora estás listo para asociarlo con el cliente y empezar el ciclo de cobro. La llamada para hacer esto es de una sola línea y se puede ejecutar de esta manera:

curl -H "Accept: application/vnd.conekta-v0.3.0+json" \
     -H "Content-type: application/json" \
     -u {global_api_key}: \
     -X POST -d '{
        "plan_id": "monthly-internet-plan"
     }' https://api.conekta.io/customers/cus_8dksd82jd6ls9319d/subscription
subscription = customer.create_subscription({
  "plan_id": plan.id
})
$subscription = $customer->createSubscription(array(
  "plan_id"=> $plan->id
));
subscription = customer.createSubscription({
  "plan_id": plan.id
})
customer.createSubscription({
  plan: 'gold-plan'
}, function(err, res) {
  console.log(res.toObject());
});
try {
  Customer customer = Customer.find(customer.id);
  JSONObject plan_params = new JSONObject("{'plan':'gold-plan'}");
  customer.createSubscription(plan_params);
} catch (Exception e) {
    logger.info(e);
}  
customer.createSubscription (@"{
   ""plan"":""gold-plan""
}");

7 Procesa la respuesta

En la respuesta de creación de una suscripción encontrarás el atributo de status, que te dirá si la suscripción ha sido exitosamente cobrada a la tarjeta de tu cliente o no. Si la tarjeta no pudo ser cobrada, la suscripción intentará cobrar el día siguiente.

# N/A
if subscription.status == 'active'
  #la suscripción inicializó exitosamente!

elsif subscription.status == 'past_due'
  #la suscripción falló a inicializarse

end
if ($subscription->status == 'active') { 
 //la suscripción inicializó exitosamente!

}
elseif ($subscription->status == 'past_due') {
 //la suscripción falló a inicializarse

}
if subscription.status == 'active'
  #la suscripción inicializó exitosamente!

else if subscription.status == 'past_due'
  #la suscripción falló a inicializarse

end
if (subscription.status == 'active') { 
 //the subscription initialized successful!

} else if (subscription.status == 'past_due') {
 //the subscription failed to initialize

}
if (customer.subscription.status == 'active') {
 //la suscripción inicializó exitosamente!

} else if (customer.subscription.status == 'past_due') {
 //la suscripción falló para inicializarse
}
if (customer.subscription.status == 'active') {
 //la suscripción inicializó exitosamente!

} else if (customer.subscription.status == 'past_due') {
 //la suscripción falló para inicializarse
}

8 Recibir la notificación

Al momento de procesar pagos de manera asíncrona, necesitarás recibir notificaciones para confirmar que los cargos realizados han sido pagados. Conekta enviará webhooks de todas las notificaciones de pagos asíncronas a tu servidor. Estas notificaciones son mensajes POST de tipo HTTP jSON. Cada notificación contiene un objeto jSON con información particular del evento y suscripción asociada.

Al configurar notificaciones de pago, necesitarás configurar los webhooks dentro de la sección de webhooks del admin. La configuración es simple. Todo lo que necesitas hacer es especificar el URL (o los URL) a donde te gustaría recibir notificaciones y Conekta enviará todas las notificaciones al (los) URL especificado(s).

Para operar las notificaciones en tu servidor, deberás configurar una ruta para el webhook que creaste, escribir código para analizar el mensaje POST y operar sobre él. Para más información sobre webhooks, ve a la sección de webhooks.

Ten en cuenta que el URL usado en la configuración de webhook debe poder ser accedido de manera pública. Si no tienes un IP público o un domino a tu disposición, sugerimos usar un servicio local->público como ultrahook o localtunnel. Abajo te compartimos un ejemplo de cómo usar ultrahook. Antes de utilizar ultrahook deberás registrarte en ultrahook para obtener una llave de API.

$ gem install ultrahook
$ echo "api_key: my-ultrahook-api-hey" > ~/.ultrahook
$ ultrahook webhook-test 5000

  Authenticated as conekta
  Forwarding activated...
  http://webhook-test.conekta.ultrahook.com -> http://localhost:5000

Para recibir notificaciones de webhooks, puedes usar el siguiente código:

# N/A
require 'json'

# Ejemplo en Sinatra
post '/my/webhook/url' do
  
#Parse the event data as json
  event_json = JSON.parse(request.body.read)

  case event_json['type']
  when 'subscription.paid'
    
  #Hacer algo con la información como actualizar los atributos de la orden en tu base de datos
    
  #subscription = Subscription.find(event_json['object']['id'])

  end
end
// Analizar la notificación en forma de JSON
$body = @file_get_contents('php://input');
$event_json = json_decode($body);

if ($event_json->type == 'subscription.paid'){
 
 //Hacer algo con la información como actualizar los atributos de la orden en tu base de datos
 
 //subscription = $this->Subscription->find('first', array(
 
 //  'conditions' => array('Subscription.id' => $event_json->object->id)
 
 //))
}
import json

# Analizar la información del evento en forma de json en Django
event_json = json.loads(HttpRequest.body)

if event_json.type == 'subscription.paid':
  
#Hacer algo con la información como actualizar los atributos de la orden en tu base de datos
  
#subscription = EcommerceSubscription.objects.get(pk=event_json['object']['id'])
// Parse the notification request's body as JSON
event_json = JSON.parse(req.body);

if (event_json.type == 'subscription.paid') {
 
 //Do something with the data, e.g. update an order's attributes in your database 
}
import org.json.JSONObject;
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
      BufferedReader get_body = request.getReader();
     
 //Parsear JSON y manejar respuesta
    }
var event = JObject.Parse('req.body');

if (event['type'] == 'subscription.paid') {
 
 //Do something with the data, e.g. update an order's attributes in your database 
}

Además de notificaciones de subscription.paid, también recibirás otras notificaciones útiles como subscription.payment_failed y subscription.canceled. Puedes ver una lista completa de eventos en la la referencia de eventos. Revisa la sección de webhooks para más información.


9 Actualizando, pausando, resumiendo y cancelando suscripciones

Una parte importante del ciclo de tu cliente es permitirles cambiarles los planes, pausarles y cancelarles la suscripción. Además de proveerles un buen servicio a cliente, estas funciones son importantes para proveer a tu cliente con opciones de pago que hacen sentido para evitar contracargos y clientes insatisfechos. A continuación encontrarás un par de bloques de código que podrás usar para modificar la suscripción de un cliente. Para más información sobre estas llamadas, por favor revisa la documentación sobre suscripciones.

# N/A
#Cambiar el plan a ser mensual
customer.subscription.update({plan_id: "monthly-plan"})

#Pausar la suscripción
customer.subscription.pause

#Posteriormente resumir la suscripción
customer.subscription.resume

#Cancelar la suscripción
customer.subscription.cancel
//Cambiar el plan a ser mensual
$customer->subscription->update(array("plan_id"=>"monthly-plan"));

//Pausar la suscripción
$customer->subscription->pause();

//Posteriormente resumir la suscripción
$customer->subscription->resume();

//Cancelar la suscripción
$customer->subscription->cancel();
#Cambiar el plan a ser mensual
customer.subscription.update({plan_id: "monthly-plan"})

#Pausar la suscripción
customer.subscription.pause()

#Posteriormente resumir la suscripción
customer.subscription.resume()

#Cancelar la suscripción
customer.subscription.cancel()
//Change the user to the monthly plan
customer.subscription.update({plan_id: "monthly-plan"});

//pause the user's subscription
customer.subscription.pause()

//At a later date resume the user's subscription
customer.subscription.resume()

//Cancel the user's subscription
customer.subscription.cancel()
// Cambiar el plan por el plan con id "monthly_plan"
customer.subscription.update(new JSONObject("{'plan':'monthly-plan'}"));

// Pausar la suscripción
customer.subscription.pause()

// Resumir la suscripción
customer.subscription.resume()

// Cancelar la suscripción
customer.subscription.cancel()
//Cambiar de suscripción
customer.subscription.update (@"{
   ""plan"":""opal-plan""
}");
//Pausar suscripción
customer.subscription.pause ();
//Continuar suscripción
customer.subscription.resume ();
//Cancelar suscripción
customer.subscription.cancel ();