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:
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.
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/
dapp® utiliza "Basic Access Authentication" para el REST API, usando el API KEY como usuario y dejando el password vacío.
También debe incluirse el header “User-Agent” de lo contrario se rechazará la petición con un código 403, se recomienda incluir un nombre distintivo y un número de versión de aplicación, sin embargo cualquier valor será aceptado, ejemplo:
“User-Agent”: “MiIntegracion1.0”
curl --location --request GET 'https://wallets-sandbox.dapp.mx/v2' \ --header 'User-Agent: MiIntegracion1.0' \ --header 'Authorization: Basic eW91ci1hcGkta2V5Og=='
var request = new RestRequest(Method.GET); request.AddHeader("User-Agent", "MiIntegracion1.0"); request.AddHeader("Authorization", "Basic eW91ci1hcGkta2V5Og==");
Request request = new Request.Builder() .url("https://wallets-sandbox.dapp.mx/v2") .method("GET", null) .addHeader("User-Agent", "MiIntegracion1.0") .addHeader("Authorization", "Basic eW91ci1hcGkta2V5Og==")
url = "https://wallets-sandbox.dapp.mx/v2" payload={} headers = { 'Authorization': 'Basic eW91ci1hcGkta2V5Og==', 'User-Agent': 'MiIntegracion1.0', } 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==", "User-Agent: MiIntegracion1.0" ], ]); $response = curl_exec($curl); curl_close($curl);
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:
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, ]; }
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}
https://wallets-sandbox.dapp.mx/v2/dapp-codes/{qr_str}
GET
https://wallets-sandbox.dapp.mx/v2/dapp-codes/xxxxxxxxx
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);
{
"rc": 0,
"msg": "Ok",
"data": {
"id": "mVjAFubx",
"description": "dummy code",
"reference_num": "343529",
"currency": "MXN",
"merchant": {
"name": "Test",
"image": "https://media.ppad.mx/usuarios/3e80f3636b294d7f971a8afaaa3e35c1_1527363626.jpg",
"category": {
"id": 1,
"name": "Gasolineras"
},
"type": 1,
"address": null
}
}
}
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. |
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
https://wallets-sandbox.dapp.mx/v2/payments
POST
Parámetro |
Descripción |
Requerido u opcional |
---|---|---|
name |
Nombre del cliente. |
Opcional |
|
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 |
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);
{
"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
}
}
}
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. |
En casos extraordinarios en los que la caja no pueda recibir la autorización de la transacción debido a problemas de conexión, la cadena enviará un reverso automático de la operación original para asegurarse de que la transacción sea cancelada en caso de haber sido recibida por parte de dapp® y el Wallet.
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.
{
"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 |
Usuario inválido |
-20 |
Servicio no disponible |
-99 |
Otro |
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'; } }
Para obtener la información de las tiendas cercanas a una ubicación se debe consumir el siguiente endpoint:
/stores
https://wallets-sandbox.dapp.mx/v2/stores
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 |
https://wallets-sandbox.dapp.mx/v2/stores?latitude=20.6714012&longitude=-100.4379101
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 |
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);
{
"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"
}
]
}
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. |
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.