dapp
Pagos Moviles

Con la integración de pagos móviles, tu Medio de Pago adquirirá la capacidad de leer los códigos de cobro generados por los comercios físicos y digitales integrados al ecosistema de pagos móviles by dapp®, así como procesar el pago de los mismos. Tus usuarios tendrán la opción de pagar directamente desde su teléfono celular a través del escaneo de un Código QR, recibiendo una notificación push, o abriendo un deeplink a través del dapp® checkout.

En esta documentación encontrarás toda la información necesaria para:

  • Obtener información de un código de cobro
  • Realizar un pago
  • Recibir notificación de un reverso

Te recomendamos ir a la sección de Glosario antes de continuar para que te familiarices con los términos que usamos en esta documentación.

En caso de tener alguna duda, estaremos felices de ayudarte a través del botón de contacto que se encuentra en cada una de las secciones de esta documentación.

Paso a Paso
Ambientes

dapp® cuenta con un sandbox para poder realizar pruebas durante el desarrollo.
Las URL base para cada ambiente son las siguientes.


Sandbox:

https://wallets-sandbox.dapp.mx/v2/


Producción:

https://wallets.dapp.mx/v2/
Autenticación

dapp® utiliza "Basic Access Authentication" para el REST API, usando el API KEY como usuario y dejando el password vacío.


curl --location --request GET 'https://wallets-sandbox.dapp.mx/v2' \
--header 'Authorization: Basic eW91ci1hcGkta2V5Og=='
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Basic eW91ci1hcGkta2V5Og==");
Request request = new Request.Builder()
  .url("https://wallets-sandbox.dapp.mx/v2")
  .method("GET", null)
  .addHeader("Authorization", "Basic eW91ci1hcGkta2V5Og==")
url = "https://wallets-sandbox.dapp.mx/v2"

payload={}
headers = {
  'Authorization': 'Basic eW91ci1hcGkta2V5Og==',
}

response = requests.request("GET", url, headers=headers, data=payload)
$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "https://wallets-sandbox.dapp.mx/v2",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => [
    "Authorization: Basic eW91ci1hcGkta2V5Og=="
  ],
]);

$response = curl_exec($curl);

curl_close($curl);
Seguridad

Para las peticiones POST la estructura del body siempre será un json que contenga el IV utilizado para el encriptamiento en el campo nonce y el timestamp además de la información pertinente de cada petición. Además el content type para estas peticiones debe ser text/plain.


Por Ejemplo:

{ "nonce": "387cdd7c58395a2246f2a8532678fbb6", "timestamp": 1532639717, "data": { ... } }

Es necesario cifrar el json completo con el Api Secret utilizando AES en modo GCM y enviarlo usando base64. El nonce debe ser un hex string de máximo 16 bytes que NUNCA debe ser reutilizado.
Adémas del body cifrado se deben enviar dos headers adicionales:

  • mac: Se debe enviar el tag de verificación que se generó al cifrar el body de la petición en base64
  • nonce: Se debe enviar el IV utilizado al cifrar el body
using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;

namespace PostExample
{

    public class Data
    {
        public string name { get; set; }
        public string email { get; set; }
        public string phone { get; set; }
        public string amount { get; set; }
        public string code { get; set; }
        public string description { get; set; }
    }

    public class Request
    {
        public Data data { get; set; }
        public string nonce { get; set; }
        public long timestamp { get; set; }
    }

    class Program
    {
        static async Task Main(string[] args)
        {
            // Convierte la clave de hexadecimal a bytes
            var keyHex = "your-api-secret";


            var key = new byte[keyHex.Length / 2];
            for (int i = 0; i < key.Length; i++)
            {
                key[i] = Convert.ToByte(keyHex.Substring(i * 2, 2), 16);
            };

            // Obtiene bytes aleatorios de longitud 12 para crear nonce
            byte[] nonce = new byte[12];
            var randomNumberGenerator = RandomNumberGenerator.Create();
            randomNumberGenerator.GetBytes(nonce);
            string nonceHexString = BitConverter.ToString(nonce).Replace("-", "");

            // Datos de Ejemplo para el body

            Data data = new Data()
            {
                name = "name",
                email = "email",
                phone = "1234567890",
                amount = "100.00",
                code = "CodeID",
                description = "code description",
            };
            string data_serializada = JsonConvert.SerializeObject(data);
            long timestamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
            Request req = new Request()
            {
                data = data,
                nonce = nonceHexString,
                timestamp = timestamp
            };
            string req_str = JsonConvert.SerializeObject(req);
            byte[] jsonBytes = Encoding.UTF8.GetBytes(req_str);

            // Crea un objeto AesGcm usando la key
            AesGcm aesGcm = new AesGcm(key);

            // Cifrar el texto y crear tag
            int diff = jsonBytes.Length - req_str.Length + AesGcm.TagByteSizes.MaxSize;
            byte[] ciphertext = new byte[AesGcm.TagByteSizes.MaxSize + req_str.Length - diff];
            byte[] tag = new byte[AesGcm.TagByteSizes.MaxSize];
            aesGcm.Encrypt(nonce, jsonBytes, ciphertext, tag);
            string tagBase64String = Convert.ToBase64String(tag);
            string bodyBase64String = Convert.ToBase64String(ciphertext);

            Console.WriteLine(bodyBase64String);
            Console.WriteLine(nonceHexString);
            Console.WriteLine(tagBase64String);

        }
    }
}
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;

import org.json.simple.JSONObject;

import javax.crypto.AEADBadTagException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Main {

	// AES-GCM parameters
	public static final int GCM_NONCE_LENGTH = 12; // bytes
	public static final int GCM_TAG_LENGTH = 16; // bytes

	public static byte[] hexStringToByteArray(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
		}
		return data;
	}

	public static final String byteArrayToHexString(final byte[] bytes) {
		final char[] digits = "0123456789abcdef".toCharArray();

		final StringBuilder buf = new StringBuilder();
		for (int i = 0; i < bytes.length; i++) {
			buf.append(digits[(bytes[i] >> 4) & 0x0f]);
			buf.append(digits[bytes[i] & 0x0f]);
		}
		return buf.toString();
	}

	public static void gcmEncryption() throws Exception {
		long timestamp = System.currentTimeMillis();

		final byte[] nonce = new byte[GCM_NONCE_LENGTH];
		SecureRandom random = SecureRandom.getInstanceStrong();
		random.nextBytes(nonce);
		final String nonceString = byteArrayToHexString(nonce);

		JSONObject data = new JSONObject();
		data.put("name", "Nombre del usuario");
		data.put("mail", "usuario@mail.com");
		data.put("phone", "1234567890");
		data.put("amount", 50);
		data.put("code", "codeID");
		data.put("description", "Code description");

		JSONObject message = new JSONObject();
		message.put("data", data);
		message.put("nonce", nonceString);
		message.put("timestamp", timestamp);

		byte[] input = message.toJSONString().getBytes();

		String dappSecret = "your-api-secret";
		// Initialize key and nonce
		byte[] encoded = hexStringToByteArray(dappSecret);
		SecretKey key = new SecretKeySpec(encoded, "AES");

		// Encrypt
		Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
		GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, nonce);
		cipher.init(Cipher.ENCRYPT_MODE, key, spec);

		byte[] cipherTextMac = cipher.doFinal(input);

		int cipherLength = cipherTextMac.length;
		byte[] cipherText = Arrays.copyOfRange(cipherTextMac, 0, cipherLength - GCM_TAG_LENGTH);
		byte[] mac = Arrays.copyOfRange(cipherTextMac, cipherLength - GCM_TAG_LENGTH, cipherLength);

		String base64Ciphertext = Base64.getEncoder().encodeToString(cipherText);
		String base64Mac = Base64.getEncoder().encodeToString(mac);

		System.out.println(base64Ciphertext);
		System.out.println(base64Mac);
		System.out.println(nonceString);
	}

	public static void main(String[] args) throws Exception {
		gcmEncryption();
	}

}
import json
import base64

from Cryptodome.Random import get_random_bytes
from datetime import datetime
from cryptography.hazmat.primitives.ciphers.aead import AESGCM


def generate_test_request(code: str, amount: float, api_secret: str):
    b = get_random_bytes(12)
    hex_nonce = b.hex()

    data = {
            "name": "Nombre del usuario",
            "email": "correo@usuario.com",
            "phone": "4444444444",
            "amount": amount,
            "code": code,
            "description": "descripcion del codigo",
          }

    message = {
        "nonce": hex_nonce,
        "timestamp": datetime.now().timestamp(),
        "data": data
    }

    message_json = json.dumps(message)
    message_bytes = message_json.encode("utf-8")
    key = bytes.fromhex(api_secret)

    cipher = AESGCM(key)
    ciphertext_mac = cipher.encrypt(bytes.fromhex(hex_nonce), message_bytes, None)

    ciphertext = ciphertext_mac[0:-16]
    mac = ciphertext_mac[-16:]

    ciphertext_decoded = base64.b64encode(ciphertext).decode('utf-8')
    mac_decoded = base64.b64encode(mac).decode('utf-8')
    print(ciphertext_decoded)
    print(mac_decoded)
    print(hex_nonce)
// AES-GCM parameters
define('GCM_NONCE_LENGTH', 12); // bytes
define('GCM_TAG_LENGTH', 16); // bytes

function generate_test_request(string $code, float $amount, string $api_secret): array
{
    // Generate a random nonce
    $nonce = random_bytes(GCM_NONCE_LENGTH);
    $hex_nonce = bin2hex($nonce);

    // Create the message data
    $data = [
        'name' => 'Nombre del usuario',
        'email' => 'correo@usuario.com',
        'phone' => '4444444444',
        'amount' => $amount,
        'code' => $code,
        'description' => 'descripcion del codigo',
    ];

    // Create the message object
    $message = [
        'nonce' => $hex_nonce,
        'timestamp' => time(),
        'data' => $data,
    ];

    // Serialize the message object to JSON
    $message_json = json_encode($message);

    // Convert the JSON to bytes
    $message_bytes = utf8_encode($message_json);

    // Convert the API secret to bytes
    $key = hex2bin($api_secret);

    // Initialize the AESGCM cipher
    $cipher = openssl_encrypt($message_bytes, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $nonce, $tag);

    if ($cipher === false) {
        throw new Exception('Failed to encrypt data');
    }

    // Split the ciphertext and tag
    $ciphertext = substr($cipher, 0, -GCM_TAG_LENGTH);
    $mac = substr($cipher, -GCM_TAG_LENGTH);

    // Encode the ciphertext and tag to Base64
    $ciphertext_decoded = base64_encode($ciphertext);
    $mac_decoded = base64_encode($mac);

    return [
        'ciphertext' => $ciphertext_decoded,
        'mac' => $mac_decoded,
        'nonce' => $hex_nonce,
    ];
}
Obtener información de cobro

Los comercios conectados a dapp® generan un Código QR que el usuario debe escanear. Una vez obtenido el texto del QR, este se debe enviar al siguiente endpoint para obtener la información asociada al QR.

/dapp-codes/{qr_str}

Url completa en sandbox:
https://wallets-sandbox.dapp.mx/v2/dapp-codes/{qr_str}

Método HTTP
GET

Ejemplo completo de URL:
https://wallets-sandbox.dapp.mx/v2/dapp-codes/xxxxxxxxx

Ejemplos de código
curl --location --request GET 'http://wallets-sandbox.dapp.mx/v2/dapp-codes/{QR_TEXT}' \
--header 'Authorization: Basic eW91ci1hcGkta2V5Og=='
var client = new RestClient("http://wallets-sandbox.dapp.mx/v2/dapp-codes/{QR_TEXT}");
client.Timeout = 60;
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Basic eW91ci1hcGkta2V5Og==");
IRestResponse response = client.Execute(request);
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
Request request = new Request.Builder()
  .url("http://wallets-sandbox.dapp.mx/v2/dapp-codes/{QR_TEXT}")
  .method("GET", null)
  .addHeader("Authorization", "Basic eW91ci1hcGkta2V5Og==")
  .build();
Response response = client.newCall(request).execute();
import requests

url = "http://wallets-sandbox.dapp.mx/v2/dapp-codes/{QR_TEXT}"

payload={}
headers = {
  'Authorization': 'Basic eW91ci1hcGkta2V5Og=='
}

response = requests.request("GET", url, headers=headers, data=payload)
$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "http://wallets-sandbox.dapp.mx/v2/dapp-codes/{QR_TEXT}",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => [
    "Authorization: Basic Basic eW91ci1hcGkta2V5Og=="
  ],
]);

$response = curl_exec($curl);

curl_close($curl);
Respuesta de ejemplo:
{ "rc": 0, "msg": "Ok", "data": { "id": "mVjAFubx", "description": "dummy code", "reference_num": "343529", "cashout": { "amount": "5.00", "currency": "MXN" }, "merchant": { "name": "Test", "image": "https://media.ppad.mx/usuarios/3e80f3636b294d7f971a8afaaa3e35c1_1527363626.jpg", "category": { "id": 1, "name": "Gasolineras" }, "type": 1, "address": null } } }
Parámetros devueltos:

Sección

Parámetro

Descripción

Código QR

id

Id del Código QR.

description

Descripción del Código QR.

amount

Monto del Código QR.

currency

Moneda del Código QR.

reference_num

Número de la referencia.

Merchant

name

Nombre del comercio.

address

Dirección del comercio.

image

Imagen del comercio.

type

Indica el tipo de comercio. Recibe 1 = Regular, 2 = Gasolinera, 3 = Grandes superficies.

Category

id

Id de la categoría.

name

Nombre de la categoría.


Realizar un pago

Una vez que el usuario confirma que desea realizar el pago, se debe consumir el siguiente endpoint para notificar a dapp® de la intención de pago.

/payments

Url completa en sandbox:
https://wallets-sandbox.dapp.mx/v2/payments

Método HTTP
POST

Parámetros aceptados:

Parámetro

Descripción

Requerido u opcional

name

Nombre del cliente.

Opcional

mail

Correo del cliente.

Opcional

phone

Teléfono del cliente.

Opcional

amount

Monto del cobro.

Requerido

code

Código identificador del Código QR.

Requerido

description

Descripción del Código QR.

Requerido

reference

Identificador interno del wallet.

Opcional



Ejemplos de código
curl --location --request POST 'https://wallets-sandbox.dapp.mx/v2/payments/' \
--header 'mac: k9XVYOR++bcy0kQF0VgPAA==' \
--header 'nonce: f3de28e4574508d48e1e5798' \
--header 'Authorization: Basic eW91ci1hcGkta2V5Og==' \
--header 'Content-Type: text/plain' \
--data-raw 'Op19hUGElqkoaG9381pC9XN/dx3/x7IVfgoEPPPKNJGCstPInVkxftyKEI7EkXzZhPvaUT4QEpHD1Ay3h2b3ZL7zliT9JRSjC9p0cz15Me1SHgBNpNk8XQzHXs3dOmgdtNlvMEHVZ3gxTle1C6WCAzXp6BzP/GPNK33gjZhyBcKNxWUlonCwDK9XODYX3vyiXglE0Io9T61HPDFeraQTqHNVJfqFFVvNSIw2gpfsEx27taT+xY5ywTD0J70cQMTQteEIQFuhvXExNLAkJtFRy1aumTBAMa7FLTR+MsH0xgtSzRGnT66t'
var client = new RestClient("https://wallets-sandbox.dapp.mx/v2/payments/");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("mac", "k9XVYOR++bcy0kQF0VgPAA==");
request.AddHeader("nonce", "f3de28e4574508d48e1e5798");
request.AddHeader("Authorization", "Basic eW91ci1hcGkta2V5Og==");
request.AddHeader("Content-Type", "text/plain");
var body = @"Op19hUGElqkoaG9381pC9XN/dx3/x7IVfgoEPPPKNJGCstPInVkxftyKEI7EkXzZhPvaUT4QEpHD1Ay3h2b3ZL7zliT9JRSjC9p0cz15Me1SHgBNpNk8XQzHXs3dOmgdtNlvMEHVZ3gxTle1C6WCAzXp6BzP/GPNK33gjZhyBcKNxWUlonCwDK9XODYX3vyiXglE0Io9T61HPDFeraQTqHNVJfqFFVvNSIw2gpfsEx27taT+xY5ywTD0J70cQMTQteEIQFuhvXExNLAkJtFRy1aumTBAMa7FLTR+MsH0xgtSzRGnT66t" + "\n" +
@"";
request.AddParameter("text/plain", body,  ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = RequestBody.create(mediaType, "Op19hUGElqkoaG9381pC9XN/dx3/x7IVfgoEPPPKNJGCstPInVkxftyKEI7EkXzZhPvaUT4QEpHD1Ay3h2b3ZL7zliT9JRSjC9p0cz15Me1SHgBNpNk8XQzHXs3dOmgdtNlvMEHVZ3gxTle1C6WCAzXp6BzP/GPNK33gjZhyBcKNxWUlonCwDK9XODYX3vyiXglE0Io9T61HPDFeraQTqHNVJfqFFVvNSIw2gpfsEx27taT+xY5ywTD0J70cQMTQteEIQFuhvXExNLAkJtFRy1aumTBAMa7FLTR+MsH0xgtSzRGnT66t\n");
Request request = new Request.Builder()
  .url("https://wallets-sandbox.dapp.mx/v2/payments/")
  .method("POST", body)
  .addHeader("mac", "k9XVYOR++bcy0kQF0VgPAA==")
  .addHeader("nonce", "f3de28e4574508d48e1e5798")
  .addHeader("Authorization", "Basic eW91ci1hcGkta2V5Og==")
  .addHeader("Content-Type", "text/plain")
  .build();
Response response = client.newCall(request).execute();
import requests

url = "https://wallets-sandbox.dapp.mx/v2/payments/"

payload = "Op19hUGElqkoaG9381pC9XN/dx3/x7IVfgoEPPPKNJGCstPInVkxftyKEI7EkXzZhPvaUT4QEpHD1Ay3h2b3ZL7zliT9JRSjC9p0cz15Me1SHgBNpNk8XQzHXs3dOmgdtNlvMEHVZ3gxTle1C6WCAzXp6BzP/GPNK33gjZhyBcKNxWUlonCwDK9XODYX3vyiXglE0Io9T61HPDFeraQTqHNVJfqFFVvNSIw2gpfsEx27taT+xY5ywTD0J70cQMTQteEIQFuhvXExNLAkJtFRy1aumTBAMa7FLTR+MsH0xgtSzRGnT66t\n"
headers = {
  'mac': 'k9XVYOR++bcy0kQF0VgPAA==',
  'nonce': 'f3de28e4574508d48e1e5798',
  'Authorization': 'Basic eW91ci1hcGkta2V5Og==',
  'Content-Type': 'text/plain'
}

response = requests.request("POST", url, headers=headers, data=payload)
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://wallets-sandbox.dapp.mx/v2/payments/',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS =>'Op19hUGElqkoaG9381pC9XN/dx3/x7IVfgoEPPPKNJGCstPInVkxftyKEI7EkXzZhPvaUT4QEpHD1Ay3h2b3ZL7zliT9JRSjC9p0cz15Me1SHgBNpNk8XQzHXs3dOmgdtNlvMEHVZ3gxTle1C6WCAzXp6BzP/GPNK33gjZhyBcKNxWUlonCwDK9XODYX3vyiXglE0Io9T61HPDFeraQTqHNVJfqFFVvNSIw2gpfsEx27taT+xY5ywTD0J70cQMTQteEIQFuhvXExNLAkJtFRy1aumTBAMa7FLTR+MsH0xgtSzRGnT66t\n',
  CURLOPT_HTTPHEADER => array(
    'mac' => 'k9XVYOR++bcy0kQF0VgPAA==',
    'nonce' => 'f3de28e4574508d48e1e5798',
    'Authorization' => 'Basic eW91ci1hcGkta2V5Og==',
    'Content-Type' => 'text/plain'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
Respuesta de ejemplo:
{ "rc": 0, "msg": "Ok", "data": { "id": "2b13e457-cb29-4d6f-bcc2-5267f7a8ca66", "currency": "MXN", "amount": 1.0, "reference": "dummy reference", "reference_num": "343529", "description": "Pago QR Pago: prueba wallets", "date": "2022-01-18T23:31:54.307463+00:00", "refunded": 0, "payments": [ { "id": "b059e7f4-475f-44eb-974e-132f9f97f82f", "amount": 1.0, "currency": "MXN", "type": 0, "type_description": "Balance", "wallet": "QR Pago", "wallet_id": "431", "reference_num": "343529", “reference” : "referenciaprueba", "product": { "id": 1, "name": "saldo" }, "refunds": [], "client": { "name": "Juan" } } ], "code": "mVjAFubx", "merchant": { "id": "c431e64b-e65c-4af1-ac11-2029310df2a2", "name": "Test", "image": "https://media.ppad.mx/usuarios/3e80f3636b294d7f971a8afaaa3e35c1_1527363626.jpg", "category": { "id": 1, "name": "Gasolineras" }, "type": 1, "address": null } }
Parámetros devueltos:

Sección

Parámetro

Descripción

id

Ticket id de la operación.

amount

Indica el monto de la operación.

currency

Indica la moneda.

reference_num

Número de referencia.

description

Descripción del cobro.

date

Fecha en que se realizó la operación

code

Código identificador del Qr.

refunded

Estatus del reembolso. ‘0’= no, ‘1’ = reembolso total, ‘2’ = reembolso parcial.

Payments

id

Id del pago.

amount

Monto del pago.

currency

Moneda del pago.

type

Tipo de pago numérico (0 = Balance, 1 = Credit Card, 2 = Debit Card, 5 = CoDi®).

type_description

Tipo de pago en texto (Balance, Credit Card, Debit Card, CoDi®).

wallet

Nombre del wallet pagador.

wallet_id

Id de la wallet.

reference_num

Número de referencia del pago.

reference

Identificador interno del wallet.

product

Id

Id del producto. Puede recibir valores del 1 al 16.

name

Nombre del producto. Recibe 1 = saldo, 2 = CoDi®, 3 = Crédito, 4 = bnpl, 5 = vales, 6 = tarjeta de débito, 7 = tarjeta de crédito, 8 = cashin, 9 = cashout, 10 = crédito 3 msi, 11 = crédito 6 msi, 12 = crédito 9 msi, 13 = crédito 12 msi. 14 = crédito 18 msi, 15 = crédito 24 msi, 16 = Enrolamiento de tarjetas

Refunds

id

Id del reembolso.

amount

Monto del reembolso.

currency

Moneda del reembolso.

date

Fecha del reembolso.

Client

name

Nombre del cliente.

Merchant

name

Nombre del negocio.

address

Dirección del negocio.

image

Imagen del negocio.

type

Indica el tipo de comercio. Recibe 1 = Regular, 2 = Gasolinera, 3 = Grandes superficies.

Category

id

Id de la categoría.

name

Nombre de la categoría.

Terminal

employee

Nombre del empleado.

name

Nombre de la terminal.


Recibir notificación de reversos

El wallet debe exponer un servicio que acepte peticiones donde dapp® le notificará que una transacción realizada por parte de uno de sus usuarios ha sido reversada por el comercio.
El JSON de la petición contiene la misma estructura de la respuesta indicada en la sección realizar un pago, además de un nuevo nodo “security” que incluye una firma digital como método de autenticación.



Ejemplo del nodo Security
{ "security": { "key": "1020a1fd5a0a127eadb044ce2e96f009", "version": 1, "signature": "ikekwTR5jKd9mXlijovOmTkVjm3dPNrgEas0IZ1ooqAXvuiZh/HEWezyQAnNT/4Pq0QSLE9930ty4+ESg74sNLVoiAbbHWKfxQjIViXCUG79wya9s/4ZW1cpN5ZdKXJ6HRs095SQrRAZzfQprMc/csZ3+d4qRwzwADfRfKCE52X311XJY5FnQcI2v/kMx8H3oogMWzbb0PP0MFYmZb7/x2BtNMlog3KrRCQdHL2PgfvrBRLbQvKAmACIbb6yPIiKtMgj43nwJoz+pypFUPW3zsh5tOxTiwOmpBrhB1HhhVv0lO23qw73N1VrY1o2IIXrCWWDK1x4KW265r9SAo2/Fg==" } }

La firma consiste en una cadena formada por los campos id, reference_num, amount, currency, description, date del nodo principal separados por pipes con una función hash SHA-512.

Ejemplo de cadena antes del hash:

2c2725ca-eada-426a-b928-8bb851128059|123456|100.0|MXN|Prueba wallet|2020-11-04T18:13:46.613399-06:00/

Las llaves públicas que se deben utilizar para realizar esta validación se encuentran en el siguiente enlace:

https://media.dapp.mx/sw/dapp_public_keys.zip

Una vez que el wallet valide la veracidad de la firma, deberá regresar como respuesta un objeto JSON con los campos rc y msg.



rc

msg

0

Operación exitosa

-10

Referencia inválida

-20

Error de validación

-99

Otro

Ejemplos de código
static void Main(string[] args)
{
	var cadena = "3c6f084f-3949-4236-a0bc-d85ff9e05495|MXN|10.0|Pago de prueba QR 5|Prueba 5|2021-06-07T17:04:56.900046+00:00";
	var cadenaBytes = Encoding.UTF8.GetBytes(cadena);

	var firma = "hxkqkKaar5jstZMJeCK3Bv3IByGOZrVyb9t+kt1hEL7pt/FrvxATuc+9b/95lkqkeVSnK98/cD0xSs0p2fdkAhskHZJTHbTWu3bbNHWBpIXmqnRNzuhi7zP+tmwsaGJ830o1BL5Vyq5rvqq4ll/ILkJVCEbru4VwE/+0W7OWiETV4Nqe6M9Yhu09/+GtW0KCAgVaX8Yol3SgGGp8z6+T1WJ6hw6K5EIg34UfBujJzHfr+39LpBJvPO2JykGs1lwNrOaon+FYb8AzmVoRAu49VwJ380Gb3Wv6j1oy+O/8Ry3X0b8NY83JA9mdtz5/M0pIVvgqIcNjKH4KBuambSig1g==";
	var firmaBytes = Convert.FromBase64String(firma);

	var fileStream = File.OpenText("../../../dapp_public_prod.pem");
	var pemReader = new PemReader(fileStream);
	var keyParameter = (AsymmetricKeyParameter)pemReader.ReadObject();
	ISigner signer = SignerUtilities.GetSigner("SHA-512withRSA");
	signer.Init(false, keyParameter);
	signer.BlockUpdate(cadenaBytes, 0, cadenaBytes.Length);
	Console.WriteLine(signer.VerifySignature(firmaBytes));
}
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

public class dapp {

    public static String encryptThisString(String input)
    {
        try {
            // getInstance() method is called with algorithm SHA-512
            MessageDigest md = MessageDigest.getInstance("SHA-512");
            // digest() method is called
            // to calculate message digest of the input string
            // returned as array of byte
            byte[] messageDigest = md.digest(input.getBytes());
            // Convert byte array into signum representation
            BigInteger no = new BigInteger(1, messageDigest);
            // Convert message digest into hex value
            String hashtext = no.toString(16);
            // Add preceding 0s to make it 32 bit
            while (hashtext.length() < 32) {
                hashtext = "0" + hashtext;
            }
            // return the HashText
            return hashtext;
        }
        // For specifying wrong message digest algorithms
        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();

    public static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }

    // Driver code
    public static void main(String args[]) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, NoSuchProviderException, SignatureException
    {
        String cadena = "3c6f084f-3949-4236-a0bc-d85ff9e05495|MXN|10.0|Pago de prueba QR 5|Prueba 5|2021-06-07T17:04:56.900046+00:00";
        String signature = "hxkqkKaar5jstZMJeCK3Bv3IByGOZrVyb9t+kt1hEL7pt/FrvxATuc+9b/95lkqkeVSnK98/cD0xSs0p2fdkAhskHZJTHbTWu3bbNHWBpIXmqnRNzuhi7zP+tmwsaGJ830o1BL5Vyq5rvqq4ll/ILkJVCEbru4VwE/+0W7OWiETV4Nqe6M9Yhu09/+GtW0KCAgVaX8Yol3SgGGp8z6+T1WJ6hw6K5EIg34UfBujJzHfr+39LpBJvPO2JykGs1lwNrOaon+FYb8AzmVoRAu49VwJ380Gb3Wv6j1oy+O/8Ry3X0b8NY83JA9mdtz5/M0pIVvgqIcNjKH4KBuambSig1g==";

        String publicKeyContent8 = null;
        String file8 = "resource/dapp_public_pk8_prod.pem";
        Path path8 = Paths.get(file8);
        List<String> lines8 = Files.readAllLines(path8);

        publicKeyContent8 = lines8.toString().replaceAll("\\n", "").replace("[-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----]", "").replaceAll(", ", "");

        KeyFactory kf8 = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec keySpecX5098 = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyContent8));
        RSAPublicKey pubKey8 = (RSAPublicKey) kf8.generatePublic(keySpecX5098);

        final Signature sig = Signature.getInstance( "SHA512withRSA");
        sig.initVerify( pubKey8 );
        sig.update( cadena.getBytes("utf8") );
        final byte[] signatureBytes = Base64.getDecoder().decode(signature);
        System.out.println("\n*Signature decode Base64*: " + bytesToHex(signatureBytes));
        System.out.println("\n*Verificar*: " + sig.verify( signatureBytes ));
    }
}
import base64
import os

from Cryptodome.Hash import SHA512
from Cryptodome.PublicKey import RSA
from Cryptodome.Signature import pkcs1_15

def test_signature():
	cadena = "3c6f084f-3949-4236-a0bc-d85ff9e05495|MXN|10.0|Pago de prueba QR 5|Prueba 5|2021-06-07T17:04:56.900046+00:00"
	hash_cadena = SHA512.new(cadena.encode("utf-8"))

	signature = "hxkqkKaar5jstZMJeCK3Bv3IByGOZrVyb9t+kt1hEL7pt/FrvxATuc+9b/95lkqkeVSnK98/cD0xSs0p2fdkAhskHZJTHbTWu3bbNHWBpIXmqnRNzuhi7zP+tmwsaGJ830o1BL5Vyq5rvqq4ll/ILkJVCEbru4VwE/+0W7OWiETV4Nqe6M9Yhu09/+GtW0KCAgVaX8Yol3SgGGp8z6+T1WJ6hw6K5EIg34UfBujJzHfr+39LpBJvPO2JykGs1lwNrOaon+FYb8AzmVoRAu49VwJ380Gb3Wv6j1oy+O/8Ry3X0b8NY83JA9mdtz5/M0pIVvgqIcNjKH4KBuambSig1g=="
	b64_bytes = signature.encode("utf-8")
	byte_str = base64.b64decode(b64_bytes)

	path = os.path.join("MyPath", 'dapp_public_prod.pem')
	with open(path, 'r') as myfile:
		key_data = myfile.read()
	key = RSA.importKey(key_data)
    	pkcs1_15.new(key).verify(hash_cadena, byte_str)
const CERT_FILE = './dapp_public_sb.pem';

$data = [
    "id" => "fe6f8e08-8e38-49b1-8a93-2f9f1dd22ab6",
    "currency" => "MXN",
    "amount" => 10,
    "tip" => 0,
    "code" => "ejWOEX1u",
    "reference" => null,
    "reference_num" => "130953",
    "description" => "Pago Test Wallet Negocio: pago",
    "date" => "2024-04-09T01:27:54.058487+00:00",
    "redirect_url" => null,
    "refunded" => false,
    "client" => [
        "id" => "",
        "name" => "Javier Torres"
    ],
    "payment" => [
        "id" => "797583ee-fc3e-4772-83a1-7941efabea71",
        "amount" => 10,
        "currency" => "MXN",
        "type" => 0,
        "type_description" => "Balance",
        "wallet" => "Test Wallet Negocio",
        "wallet_id" => "328",
        "reference_num" => "130953",
        "reference" => "123456789012",
        "product" => [
            "id" => 1,
            "name" => "saldo"
        ],
        "auth_num" => "130953",
        "refunds" => []
    ],
    "security" => [
        "key" => "3d4612aef1abdeebc9947a04d16da838",
        "version" => 1,
        "signature" => "P8zskMjj3MNHrjZf/NPw+Qju0Eh3V3ykkpK/OoA7XQQXijnM2zK2hrhrHezXqCtlPSfaN2OqCnVK05tCnLuyxBRUg31EztMlUIFxfUd2JoX6fqdwq+46PIv5apPVXHxuUlGHat+2OxvmXbos59sdaEDKJvQ/EeHALb7EAfH8AdgHO2hMrnFSH5BMw0j+E+S9UIF8Z2D9T6/SpYvnDDmDnvj1QbBqMel2PudnJnrje3WDTjB86zVrOYIZyZekBTGA4zwzfwVF2Ev9cGmBVjSnOSw5YH33O81jZ7YAI6sBI3NuTUrRuqew8nRKqVZ7lm0BmzfwoPP1Na7RiZWrH5s2+Q=="
    ]
];

$amount = preg_replace('/^(\d+\.\d)0$/', '\1', number_format($data['amount'], 2));

$original_string = $data['id'] . '|' . $data['currency'] . '|' . $amount . '|' . $data['description'] . '|' . $data['reference'] . '|' . $data['date'];

if (isset($data['security'])) {
    $fp = fopen(CERT_FILE, "r");
    $public_key_file = fread($fp, 8192);
    fclose($fp);
    $pkey_public = openssl_pkey_get_public($public_key_file);
    $signature_decoded = base64_decode($data['security']['signature']);
    $ok = openssl_verify($original_string, $signature_decoded, $pkey_public, OPENSSL_ALGO_SHA512);
    if ($ok == 0) {
        echo 'Firma invalida';
    } else {
        echo 'Firma ok';
    }
}
Obtener ubicación de tiendas

Para obtener la información de las tiendas cercanas a una ubicación se debe consumir el siguiente endpoint:

/stores

Url completa en sandbox:
https://wallets-sandbox.dapp.mx/v2/stores

Método HTTP
GET

No es necesario definir un formato raw-Json para body, hay que asignarle el valor ‘none’. De igual forma hay que especificar los parámetros y sus respectivos valores

KEY

VALUE

latitude

20.6714012

longitude

-100.4379101



Ejemplo completo de URL:
https://wallets-sandbox.dapp.mx/v2/stores?latitude=20.6714012&longitude=-100.4379101

Parámetros Aceptados

Parámetro

Descripción

Requerido u opcional

latitude

Latitud.

Requerido

longitude

Longitud.

Requerido

radius

Radio de búsqueda en km.

Opcional

product

ID provisto por dapp.

Opcional



Ejemplos de código
curl --location 'https://wallets-sandbox.dapp.mx/v2/stores?latitude=20.5684501&longitude=-103.4579542' \
--header 'Authorization: Basic eW91ci1hcGkta2V5Og=='
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://wallets-sandbox.dapp.mx/v2/stores?latitude=20.5684501&longitude=-103.4579542");
request.Headers.Add("Authorization", "Basic eW91ci1hcGkta2V5Og==");
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = RequestBody.create(mediaType, "");
Request request = new Request.Builder()
  .url("https://wallets-sandbox.dapp.mx/v2/stores?latitude=20.5684501&longitude=-103.4579542")
  .method("GET", body)
  .addHeader("Authorization", "Basic eW91ci1hcGkta2V5Og==")
  .build();
Response response = client.newCall(request).execute();

import requests

url = "https://wallets-sandbox.dapp.mx/v2/stores?latitude=20.5684501&longitude=-103.4579542"

payload={}
headers = {
  'Authorization': 'Basic eW91ci1hcGkta2V5Og=='
}

response = requests.request("GET", url, headers=headers, data=payload)
$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "https://wallets-sandbox.dapp.mx/v2/stores?latitude=20.5684501&longitude=-103.4579542",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => [
    "Authorization: Basic eW91ci1hcGkta2V5Og=="
  ],
]);

$response = curl_exec($curl);

curl_close($curl);
Respuesta de ejemplo:
{ "rc": 0, "msg": "Ok", "data": [ { "latitude": 19.39431, "longitude": -99.13871, "address": "Alfonso XIII y Xola No. 18 , Álamos, 3400 Benito Juárez, Ciudad de México", "phone": null, "name": "Hidrosina IV", "merchant": { "id": "19f63cc4-58cc-46ce-9ebb-1aa84b654586", "name": "Hidrosina IV", "image": null, "category": { "id": 1, "name": "Gasolineras" }, "type": 0 }, "sample_qr": "https://media.dapp.mx/samples/dappicono.png" }, { "latitude": 19.45639, "longitude": -99.12866, "address": "Calz. Guadalupe n° 52, Ex Hipodromo de Peralvillo, 6250 Cuauhtémoc, Ciudad de México", "phone": null, "name": "Hidrosina XV", "merchant": { "id": "c66acfb0-6b76-482b-a411-4aafd6e737fb", "name": "Hidrosina XV", "image": null, "category": { "id": 1, "name": "Gasolineras" }, "type": 0 }, "sample_qr": "https://media.dapp.mx/samples/dappicono.png" }, ] }
Parámetros devueltos:

Sección

Parámetro

Descripción

latitude

Valor de la latitud de la ubicación.

longitude

Valor de la longitud de la ubicación.

address

Dirección de la tienda.

phone

Teléfono de la tienda.

name

Nombre de la tienda.

Merchant

id

Id del comercio.

name

Nombre del comercio.

image

Imagen del comercio.

Category

id

Id de la categoría.

name

Nombre de la categoría.

type

Indica el tipo de comercio. Recibe 1 = Regular, 2 = Gasolinera, 3 = Grandes superficies.


Glosario

Te dejamos aquí algunos términos relevantes definidos para facilitar la lectura y entendimiento de nuestras documentaciones.


Código de cobro:
Es el identificador único asignado a una transacción de cobro generada por un comercio y que está asociado a ciertos parámetros específicos como Nombre del Comercio, Monto, Moneda, Fecha, Lugar y Hora, entre otros. Un código de cobro puede ser representado como un Código QR para ser escaneado por el cliente, o ser transferido a un dispositivo móvil a través de un deep link o una notificación push.


Código QR:
Un código de respuesta rápida (o Código QR) es una representación gráfica bidimensional de una cadena de texto generado por que generalmente se asocia a un código de cobro.


Código QR dinámico:
Es aquél Código QR asociado a un solo código de cobro y a una sola transacción única y diferenciable. Sus parámetros pueden tomar valores distintos en cada transacción. Cada Código QR se verá distinto a los demás ya que representará una cadena de texto distinta.


Código QR estático:
Es aquél Código QR que luce permanentemente igual ya que siempre representa una misma cadena de texto. Este tipo de códigos son útiles cuando los parámetros asociados a los códigos de cobro son siempre iguales. Dado que la representación gráfica no cambia, estos códigos pueden ser impresos y usados múltiples veces para pagar un mismo código de cobro.


Referencia Cash in:
Es un código alfanumérico asociado a una transacción de pago en efectivo, ya sea para realizar un depósito de efectivo, para realizar un pago de servicios, para realizar el pago en efectivo de una orden de comercio electrónico, o cualquier otra operación equivalente. Las referencias Cash In están siempre asociadas a parámetros específicos como Monto, Fecha, Hora, entre otros, y son generadas a petición del usuario cuando este desea realizar una operación de este tipo.


Súmate a la revolución.
Intégrate ahora.

Súmate a la revolución.
Intégrate ahora.

Quiero Integrarme
Es la forma de contacto, ingresa un correo correcto
El número debe de ser de al menos 10 dígitos







Olvidé mi contraseña