Pagos sobre demanda y facturación automática flexible

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 pagos sobre demanda y facturación automática.

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 luego cobrarle a la tarjeta de tu cliente usando una librería del lado del servidor. Al procesarse las tarjetas en tiempo real, podrás recibir un estatus de ‘paid’ o algún tipo de error de procesamiento si la tarjeta fue declinada.

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 Cobra a tu cliente

Para cobrar a tu cliente, necesitarás crear un objeto de cargo regular, en lugar de enviar un token id puedes elegir enviar un id de customer. A comparación con un token, el customer no expirará después de un uso, permitiéndote cobrar a tu cliente diferentes montos en tiempos diferentes sin necesidad de preguntar por su información otra vez.

$charge = Conekta_Charge::create(array(
  'description'=> 'Stogies',
  'reference_id'=> '9839-wolf_pack',
  'amount'=> 20000,
  'currency'=>'MXN',
  'card'=> $customer->id,
  'details'=> array(
    'name'=> 'Arnulfo Quimare',
    'phone'=> '403-342-0642',
    'email'=> 'logan@x-men.org',
    'customer'=> array(
      'logged_in'=> true,
      'successful_purchases'=> 14,
      'created_at'=> 1379784950,
      'updated_at'=> 1379784950,
      'offline_payments'=> 4,
      'score'=> 9
    ),
    'line_items'=> array(
      array(
        'name'=> 'Box of Cohiba S1s',
        'description'=> 'Imported From Mex.',
        'unit_price'=> 20000,
        'quantity'=> 1,
        'sku'=> 'cohb_s1',
        'category'=> 'food'
      )
    ),
    'billing_address'=> array(
      'street1'=>'77 Mystery Lane',
      'street2'=> 'Suite 124',
      'street3'=> null,
      'city'=> 'Darlington',
      'state'=>'NJ',
      'zip'=> '10192',
      'country'=> 'Mexico',
      'tax_id'=> 'xmn671212drx',
      'company_name'=>'X-Men Inc.',
      'phone'=> '77-777-7777',
      'email'=> 'purshasing@x-men.org'
    )
  )
));
conekta.Charge.create({
  "description":"Stogies",
  "amount": 20000,
  "currency":"MXN",
  "reference_id":"9839-wolf_pack",
  "card": customer.id,
  "details": {
    "name": "Arnulfo Quimare",
    "phone": "403-342-0642",
    "email": "logan@x-men.org",
    "customer": {
      "logged_in": true,
      "successful_purchases": 14,
      "created_at": 1379784950,
      "updated_at": 1379784950,
      "offline_payments": 4,
      "score": 9
    },
    "line_items": [{
      "name": "Box of Cohiba S1s",
      "description": "Imported From Mex.",
      "unit_price": 20000,
      "quantity": 1,
      "sku": "cohb_s1",
      "category": "food"
    }],
    "billing_address": {
      "street1":"77 Mystery Lane",
      "street2": "Suite 124",
      "street3": null,
      "city": "Darlington",
      "state":"NJ",
      "zip": "10192",
      "country": "Mexico",
      "tax_id": "xmn671212drx",
      "company_name":"X-Men Inc.",
      "phone": "77-777-7777",
      "email": "purshasing@x-men.org"
    }
  }
}, function(err, res) {
    console.log(res.toObject());
});
charge = conekta.Charge.create({
  "description":"Stogies",
  "amount": 20000,
  "currency":"MXN",
  "reference_id":"9839-wolf_pack",
  "card": customer.id,
  "details": {
    "name": "Arnulfo Quimare",
    "phone": "403-342-0642",
    "email": "logan@x-men.org",
    "customer": {
      "logged_in": true,
      "successful_purchases": 14,
      "created_at": 1379784950,
      "updated_at": 1379784950,
      "offline_payments": 4,
      "score": 9
    },
    "line_items": [{
      "name": "Box of Cohiba S1s",
      "description": "Imported From Mex.",
      "unit_price": 20000,
      "quantity": 1,
      "sku": "cohb_s1",
      "category": "food"
    }],
    "billing_address": {
      "street1":"77 Mystery Lane",
      "street2": "Suite 124",
      "street3": null,
      "city": "Darlington",
      "state":"NJ",
      "zip": "10192",
      "country": "Mexico",
      "tax_id": "xmn671212drx",
      "company_name":"X-Men Inc.",
      "phone": "77-777-7777",
      "email": "purshasing@x-men.org"
    }
  }
})
JSONObject payment_params;

payment_params = new JSONObject("{"
  + "'description':'Stogies',"
  + "'amount': 20000,"
  + "'currency':'MXN',"
  + "'reference_id':'9839-wolf_pack',"
  + "'card':'" + customer.id + "',"
  + "'details': {"
    + "'name': 'Arnulfo Quimare',"
    + "'phone': '403-342-0642',"
    + "'email': 'logan@x-men.org',"
    + "'customer': {"
      + "'logged_in': true,"
      + "'successful_purchases': 14,"
      + "'created_at': 1379784950,"
      + "'updated_at': 1379784950,"
      + "'offline_payments': 4,"
      + "'score': 9"
    + "},"
    + "'line_items': [{"
      + "'name': 'Box of Cohiba S1s',"
      + "'description': 'Imported From Mex.',"
      + "'unit_price': 20000,"
      + "'quantity': 1,"
      + "'sku': 'cohb_s1',"
      + "'category': 'food'"
    + "}],"
    + "'billing_address': {"
      + "'street1':'77 Mystery Lane',"
      + "'street2': 'Suite 124',"
      + "'street3': null,"
      + "'city': 'Darlington',"
      + "'state':'NJ',"
      + "'zip': '10192',"
      + "'country': 'Mexico',"
      + "'tax_id': 'xmn671212drx',"
      + "'company_name':'X-Men Inc.',"
      + "'phone': '77-777-7777',"
      + "'email': 'purshasing@x-men.org'"
    + "}"
  + "}"
+ "}");

Charge conektaCharge = Charge.create(payment_params);
curl -H "Accept: application/vnd.conekta-v{global_api_version}+json" \
     -H "Content-type: application/json" \
     -u {global_api_key}: \
     -X POST -d '{
      "description":"Stogies",
      "amount": 20000,
      "currency":"MXN",
      "reference_id":"9839-wolf_pack",
      "card": "cus_8dksd82jd6ls9319d",
      "details": {
        "name": "Arnulfo Quimare",
        "phone": "403-342-0642",
        "email": "logan@x-men.org",
        "customer": {
          "logged_in": true,
          "successful_purchases": 14,
          "created_at": 1379784950,
          "updated_at": 1379784950,
          "offline_payments": 4,
          "score": 9
        },
        "line_items": [{
          "name": "Box of Cohiba S1s",
          "description": "Imported From Mex.",
          "unit_price": 20000,
          "quantity": 1,
          "sku": "cohb_s1",
          "category": "food"
        }],
        "billing_address": {
          "street1":"77 Mystery Lane",
          "street2": "Suite 124",
          "street3": null,
          "city": "Darlington",
          "state":"NJ",
          "zip": "10192",
          "country": "Mexico",
          "tax_id": "xmn671212drx",
          "company_name":"X-Men Inc.",
          "phone": "77-777-7777",
          "email": "purshasing@x-men.org"
        }
      }
    }' https://api.conekta.io/charges
begin
  charge = Conekta::Charge.create({
    "amount"=> 51000,
    "currency"=> "MXN",
    "description"=> "Pizza Delivery",
    "reference_id"=> "orden_de_id_interno",
    "card"=> customer.id,
    "details"=> {
      "name"=> "Arnulfo Quimare",
      "phone"=> "403-342-0642",
      "email"=> "logan@x-men.org",
      "customer"=> {
        "logged_in"=> true,
        "successful_purchases"=> 14,
        "created_at"=> 1379784950,
        "updated_at"=> 1379784950,
        "offline_payments"=> 4,
        "score"=> 9
      },
      "line_items"=> [{
        "name"=> "Box of Cohiba S1s",
        "description"=> "Imported From Mex.",
        "unit_price"=> 20000,
        "quantity"=> 1,
        "sku"=> "cohb_s1",
        "category"=> "food"
      }],
      "billing_address"=> {
        "street1"=>"77 Mystery Lane",
        "street2"=> "Suite 124",
        "city"=> "Darlington",
        "state"=>"NJ",
        "zip"=> "10192",
        "country"=> "Mexico",
        "tax_id"=> "xmn671212drx",
        "company_name"=>"X-Men Inc.",
        "phone"=> "77-777-7777",
        "email"=> "purshasing@x-men.org"
      }
    }
  })
rescue Conekta::ParameterValidationError => e
  puts e.message_to_purchaser 
  #alguno de los parámetros fueron inválidos

rescue Conekta::ProcessingError => e
  puts e.message_to_purchaser 
  #la tarjeta no pudo ser procesada

rescue Conekta::Error => e
  puts e.message_to_purchaser 
  #un error ocurrió que no sucede en el flujo normal de cobros como por ejemplo un auth_key incorrecto

end
conekta.Charge charge = new conekta.Charge ().create(@"{
  ""description"":""Stogies"",
  ""amount"": 20000,
  ""currency"":""MXN"",
  ""reference_id"":""9839-wolf_pack"",
  ""card"": ""cus_8dksd82jd6ls9319d"",
  ""details"": {
    ""name"": ""Arnulfo Quimare"",
    ""phone"": ""403-342-0642"",
    ""email"": ""logan@x-men.org"",
    ""customer"": {
      ""logged_in"": true,
      ""successful_purchases"": 14,
      ""created_at"": 1379784950,
      ""updated_at"": 1379784950,
      ""offline_payments"": 4,
      ""score"": 9
    },
    ""line_items"": [{
      ""name"": ""Box of Cohiba S1s"",
      ""description"": ""Imported From Mex."",
      ""unit_price"": 20000,
      ""quantity"": 1,
      ""sku"": ""cohb_s1"",
      ""category"": ""food""
    }],
    ""billing_address"": {
      ""street1"":""77 Mystery Lane"",
      ""street2"": ""Suite 124"",
      ""street3"": null,
      ""city"": ""Darlington"",
      ""state"":""NJ"",
      ""zip"": ""10192"",
      ""country"": ""Mexico"",
      ""tax_id"": ""xmn671212drx"",
      ""company_name"":""X-Men Inc."",
      ""phone"": ""77-777-7777"",
      ""email"": ""purshasing@x-men.org""
    }
  }
}");

6 Procesa la respuesta

Habiendo procesado la tarjeta, recibirás la respuesta. Como en javascript, podrás recibir un error si el procesamiento falla, o un objeto Conekta si la tarjeta ha sido exitosamente cobrada. Es importante atrapar errores cuando se crea un cargo porque algunas tarjetas serán declinadas.

Además de recibir respuestas de cargos en tiempo real, también podrás escoger recibir notificaciones de pagos por medio de webhooks. Webhooks son escenciales para métodos de pagos asíncronos, pero particularmente útiles cuando necesitas propagar notificaciones de pago adicionales a tus servicios. Puedes leer más sobre notificaciones en la sección de webhooks.