Integrar con Acceso a MoneyGram
Este documento guía al lector a través de los requisitos técnicos para integrar Acceso a MoneyGram en una aplicación existente. Acceso a MoneyGram es un producto de MoneyGram que permite a los usuarios de aplicaciones de terceros, como billeteras de criptomonedas y exchanges, depositar (cash-in) y retirar (cash-out) USDC Stellar.
MoneyGram requiere que las empresas pasen por un proceso de incorporación para obtener acceso a sus entornos de prueba y producción. Para comenzar con este proceso, contacta a [email protected].
Recursos
- [Implementación MVP de Billetera de Acceso a MoneyGram]
- Utiliza esta implementación MVP como referencia para construir tu propia integración. Muchos de los fragmentos de código compartidos en este documento se obtuvieron de este proyecto.
- Documentación del SDK de Billetera Stellar
- Utiliza este SDK de Billetera para facilitar la construcción de tu propia integración. Muchos de los fragmentos de código compartidos en este documento se obtuvieron del repositorio del SDK de Billetera Stellar.
- Ancla de Prueba Stellar
- Before getting access to MoneyGram's test environment, you can use the SDF's test anchor while developing your integration
- [Billetera de Demostración Stellar]
- This application visualizes the API calls necessary to connect to a Stellar Anchor
- Propuesta del Ecosistema Stellar 24 (SEP-24)
- The standardized API protocol for Stellar on & off ramps, implemented by MoneyGram
- Propuesta del Ecosistema Stellar 10 (SEP-10)
- The standardized API protocol for Stellar authentication, implemented by MoneyGram
Información sobre Activos
Antes de que obtengas acceso al entorno de prueba de MoneyGram, debes probar tu implementación con la Ancla de Prueba Stellar del SDF. Implementa las mismas APIs que el servicio de MoneyGram, pero utiliza un activo diferente. La información para cada activo está a continuación.
Token de Referencia Stellar
Este token está solo en testnet.
- Cuenta Emisora: GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B
- Código del Activo: SRT
USD Coin
Testnet:
- Cuenta Emisora: GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5
- Código del Activo: USDC
Pubnet:
- Cuenta Emisora: GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN
- Código del Activo: USDC
Introducción
Las aplicaciones que buscan integrar Acceso a MoneyGram deben implementar el lado del cliente de Propuesta del Ecosistema Stellar 24 (SEP-24), un protocolo estandarizado definido para que las aplicaciones se conecten a empresas como MoneyGram, generalmente llamadas anclas, que ofrecen servicios de depósito y retiro Stellar utilizando rieles de pago locales.
Este documento te guiará a través de los pasos necesarios para desarrollar una implementación funcional de este estándar.
La guía asumirá que tu aplicación se está desarrollando primero en la red de prueba de Stellar y utilizando la implementación de prueba de MoneyGram de Acceso, pero no hay diferencias funcionales al desplegar la aplicación en la red pública de Stellar y utilizar la implementación de producción de MoneyGram.
Instalando el SDK de Billetera
Recomendamos encarecidamente usar el SDK de billetera para facilitar la construcción de tu integración. Encuentra más información en la Documentación del SDK de Billetera Stellar.
Puedes usar yarn
para instalarlo:
- bash
yarn add @stellar/typescript-wallet-sdk
Aplicaciones Custodiales vs. No Custodiales
Algunas aplicaciones, como los exchanges centralizados, son custodiales, lo que significa que la aplicación tiene acceso a las claves privadas de las cuentas que contienen los fondos de sus usuarios en Stellar. Típicamente, las aplicaciones custodiales agrupan los fondos de los usuarios en un conjunto más pequeño de cuentas Stellar gestionadas, llamadas cuentas agrupadas, compartidas u omnibuses.
Otras aplicaciones son no custodiales, lo que significa que la aplicación no tiene acceso a las claves privadas de las cuentas que contienen los fondos de sus usuarios en Stellar. Típicamente, las aplicaciones no custodiales crean o importan una cuenta Stellar preexistente para cada usuario.
Estos dos enfoques requieren diferencias menores pero concretas en cómo las aplicaciones se integran con Acceso a MoneyGram. Las subsecciones a continuación describirán estas diferencias, pero el resto de este tutorial asumirá que la aplicación es custodial.
Autenticación
MoneyGram necesita autenticar tanto al usuario como a la aplicación que se está utilizando a través del protocolo SEP-10 de Stellar.
Las aplicaciones custodiales se identifican por la clave pública de la cuenta Stellar que registran con MoneyGram durante el proceso de incorporación. Al autenticarte, la aplicación debe pasar esta clave pública como el parámetro de consulta account
, y para identificar al usuario, la aplicación debe pasar un ID entero único como el parámetro de consulta memo
. MoneyGram devolverá una transacción Stellar que debe ser firmada por la clave privada de la aplicación y enviada de regreso para su verificación.
Dado que cada usuario de una aplicación no custodial tiene su propia cuenta Stellar, las aplicaciones no custodiales se identifican por el dominio de origen que registran con MoneyGram durante el proceso de incorporación. Al autenticarte, la aplicación debe pasar la clave pública del usuario como el parámetro de consulta account
, y pasar su dominio de origen como el parámetro de consulta client_domain
. MoneyGram buscará el valor SIGNING_KEY
en https://<client_domain>/.well-known/stellar.toml
, y devolverá una transacción Stellar que requiere firmas tanto de la clave privada del usuario como de la clave privada de la clave pública SIGNING_KEY
en el archivo stellar.toml
de la aplicación SEP-1. Un archivo de ejemplo se puede encontrar en https://vibrantapp.com/.well-known/stellar.toml.
Iniciación de Transacciones
Dado que los usuarios de aplicaciones custodiales no tienen cuentas Stellar individuales, solo la aplicación sabe cuánto dinero un usuario tiene para retirar. Debido a esto, MoneyGram requiere que las aplicaciones custodiales siempre pasen el campo amount
en la solicitud para iniciar una nueva transacción. Las aplicaciones no custodiales no necesitan hacer esto, aunque pueden hacerlo si lo prefieren.
Fuente y Destino de Fondos
MoneyGram requiere que las aplicaciones custodiales proporcionen las cuentas Stellar que pueden ser utilizadas como fuente o destino de fondos durante el proceso de incorporación. Para las aplicaciones no custodiales, MoneyGram requiere que la fuente y el destino de los fondos para cada transacción sean la misma cuenta que fue autenticada a través de SEP-10.
Flujo de Aplicación y Arquitectura
Esta guía asumirá que la aplicación tiene una arquitectura básica cliente-servidor. El cliente de la aplicación solicitará recursos e iniciará acciones con el servidor de la aplicación, que se comunicará directamente con el servidor de MoneyGram.
A continuación se muestran los 7 pasos generales a seguir para facilitar una transacción de efectivo (retirada).
Después del Paso 4, la aplicación debería abrir la URL proporcionada por MoneyGram en una vista web móvil o pestaña del navegador. MoneyGram luego pedirá al usuario que proporcione información de KYC y de la transacción. Al completar este flujo, el cliente de la aplicación debería cerrar la pestaña o vista web de MoneyGram e iniciar la distribución de fondos.
El número de referencia proporcionado se llevaría a cualquier agente de efectivo de MoneyGram para recibir dinero en la moneda fiat del usuario. Estos pasos documentan el flujo de efectivo o de retiro. El flujo de depósito es similar y se detalla en los pasos a continuación.
Generar Keypairs de Stellar
En esta sección, generarás al menos dos keypairs de Stellar, uno que se utilizará para probar la identidad de tu aplicación al autenticarte con Acceso a MoneyGram, y otro que mantendrá, enviará y recibirá USDC en Stellar. Siempre debes usar un keypair para la autenticación, pero la aplicación puede usar muchos keypairs para enviar y recibir pagos. En esta guía, asumiremos que la aplicación utiliza un keypair para cada propósito.
Esta sección asume que tu aplicación no tiene soporte para la red Stellar. Si tu aplicación ya soporta depósitos y retiros de XLM, ya tienes uno o más cuentas Stellar que se pueden usar para estos propósitos, aunque se recomienda poner un nuevo keypair para autenticación.
Ve a [Stellar Lab] y genera 2 keypairs. Las claves secretas deben ser tratadas de forma segura, porque se utilizarán para autenticar y distribuir fondos a MoneyGram.
El primer keypair se llamará el keypair de “autenticación” (o clave pública / secreta). El segundo keypair será el keypair de “fondos” (o cuenta, clave pública, o clave secreta). A diferencia del keypair de autenticación, el keypair de fondos hará referencia a una cuenta financiada en la red Stellar. El keypair de autenticación no necesita estar financiado.
Proporciona las claves públicas (que comienzan con una G) de ambos keypairs de autenticación y de fondos a MoneyGram. Ellos añadirán estas claves a sus listas conocidas de claves, otorgándoles acceso a su implementación.
Obtener XLM y USDC
Muchos exchanges de criptomonedas soportan la compra de XLM o USDC en Stellar. El SDF también mantiene un Directorio de Anclajes que intenta listar todas las rampas y bajadas para la Red Stellar.
Cuando hayas comprado XLM y/o USDC en un exchange, puedes hacer un pago a una cuenta externa, específicamente a la clave pública de fondos que generaste en el paso anterior. Ten en cuenta que primero necesitarás enviar XLM para crear la cuenta, luego agregar una trustline de USDC, y luego enviar el USDC. Crear una trustline a USDC se puede hacer utilizando [Stellar Lab] o cualquier aplicación de billetera compatible con Stellar, como Lobstr.
Algunos exchanges soportan XLM pero no soportan USDC en Stellar. Esto no es un problema, porque siempre puedes vender XLM por USDC en el exchange descentralizado de Stellar (o SDEX).
Para hacer esto, envía tu XLM a la clave pública de fondos desde el exchange, añade una trustline de USDC, y vende XLM por USDC usando una oferta de venta.
Autenticarse
Esta sección engloba los pasos 1 y 2 del diagrama mostrado en la sección de Arquitectura arriba. El cliente de la aplicación debe solicitar una URL de transacción de MoneyGram desde el servidor de la aplicación al iniciar el usuario. Esto debería activar un proceso de autenticación entre el servidor de la aplicación y el servidor de MoneyGram. Este proceso está estandarizado en SEP-10.
Esta sección asume que el servidor de la aplicación tiene las siguientes piezas de información:
- El ID entero del usuario (debe ser positivo y representado usando 64 bits o menos) el endpoint de autenticación de MoneyGram
- La clave pública de autenticación de MoneyGram
- Pruebas:
GCUZ6YLL5RQBTYLTTQLPCM73C5XAIUGK2TIMWQH7HPSGWVS2KJ2F3CHS
- Producción:
GD5NUMEX7LYHXGXCAD4PGW7JDMOUY2DKRGY5XZHJS5IONVHDKCJYGVCL
- Pruebas:
- La clave pública y secreta de autenticación de la aplicación
El flujo se puede describir con los siguientes pasos:
- La aplicación solicita un reto de autenticación
- El servidor (MoneyGram) proporciona el reto de autenticación
- La aplicación verifica que MoneyGram firmó la autenticación con su
SIGNING_KEY
- La aplicación firma el reto de autenticación con su propia clave
- La aplicación envía de regreso el reto de autenticación al servidor
- El servidor verifica que la aplicación firmó el reto con la cuenta que usó inicialmente para solicitar el reto
- El servidor devuelve un token de sesión para la cuenta y memo utilizados en la solicitud de autenticación inicial
El siguiente código demuestra cómo implementar el lado de la aplicación de este flujo. Ten en cuenta que este código no maneja reintentos en caso de problemas de conexión de red. Tampoco maneja códigos de estado inesperados y no incluye registro o métricas.
- TypeScript
- Python
import { Wallet, SigningKeypair } from "@stellar/typescript-wallet-sdk";
const wallet = Wallet.TestNet();
// Testnet
const MGI_ACCESS_HOST = "extmgxanchor.moneygram.com";
// Pubnet
// const MGI_ACCESS_HOST = "stellar.moneygram.com";
// First we create an anchor object with MoneyGram's home domain.
const anchor = wallet.anchor({ homeDomain: MGI_ACCESS_HOST });
// Then we create the sep10 object which handles all the athentication steps.
const sep10 = await anchor.sep10();
// Finally, we authenticate using the wallet's SIGNING_KEY secret.
const authKey = SigningKeypair.fromSecret(AUTH_SECRET_KEY);
const authToken = await sep10.authenticate({ accountKp: authKey });
import requests
from stellar_sdk import Network
from stellar_sdk.sep.stellar_web_authentication import read_challenge_transaction
# Testnet
MGI_ACCESS_HOST = "extmgxanchor.moneygram.com"
# Pubnet
# MGI_ACCESS_HOST = "stellar.moneygram.com"
def get_token() -> str:
query = f"{AUTH_URL}?account={AUTH_PUBLIC_KEY}&memo={USER_ID}"
response = requests.get(query)
body = response.json()
challenge = read_challenge_transaction(
challenge_transaction=body["transaction"],
server_account_id=MGI_ACCESS_SIGNING_KEY,
home_domains=MGI_ACCESS_HOST,
web_auth_domain=MGI_ACCESS_HOST,
network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE
)
challenge.transaction.sign(AUTH_SECRET_KEY)
post_body = {
"transaction": challenge.transaction.to_xdr()
}
response = requests.post(f"{AUTH_URL}", json=post_body)
response_body = response.json()
return response_body["token"]
Iniciar una Transacción
Esta sección engloba los pasos 3 y 4 del diagrama de la arquitectura mostrado arriba. El servidor de la aplicación hará una solicitud de iniciación de depósito o retiro al servidor de MoneyGram, y MoneyGram devolverá un ID de transacción, que se usará más adelante para consultar el estado de la transacción, y una URL de transacción, que debe ser devuelta al cliente de la aplicación y abierta para el usuario.
Con el propósito de esta guía, pasaremos por el caso de retiro.
Nota: MoneyGram requiere el campo amount
en solicitudes tanto para depósitos como para transacciones de retiro si tu aplicación es custodial.
Necesitarás las siguientes piezas de información:
- El token de autenticación proporcionado por MoneyGram. Este token solo se puede usar para acciones asociadas con el usuario identificado por el ID utilizado en los pasos anteriores.
- La clave pública del keypair que la aplicación utilizará para enviar fondos
- El código de idioma con el que MoneyGram debería renderizar el contenido de su interfaz de usuario
- La cantidad que el usuario desea retirar / sacar en caso de que sea una billetera custodial
- Esto debe ser recolectado del usuario antes de iniciar esta transacción
El siguiente código se puede usar como referencia para implementar esta lógica tú mismo.
- TypeScript
- Python
import { IssuedAssetId } from "@stellar/typescript-wallet-sdk";
// First let's make sure Anchor supports the asset we want to withdraw.
const assetCode = "USDC";
const info = await anchor.getInfo();
const currency = info.currencies.find(({ code }) => code === assetCode);
if (!currency?.code || !currency?.issuer) {
throw new Error(
`Anchor does not support ${assetCode} asset or is not correctly configured on TOML file`,
);
}
// Use same "anchor" object from previous step.
const { url, id } = await anchor.sep24().withdraw({
authToken: authToken, // Use same "authToken" string from previous step
withdrawalAccount: FUNDS_STELLAR_KEYPAIR.public_key,
assetCode,
lang: "en", // "lang" is optional, defaults to "en" if ommited
extraFields: {
// "amount" is optional for non-custodial wallets and mandatory for custodial wallets
amount: "<amount to withdraw / cash-out>",
},
});
import requests
def initiate_withdraw(token: str, amount: str) -> Tuple[str, str]:
post_body = {
"asset_code": ASSET_CODE, # USDC
"account": FUNDS_STELLAR_KEYPAIR.public_key,
"lang": "en",
"amount": amount
}
response = requests.post(
MGI_ACCESS_WITHDRAW_URL,
json=post_body,
headers={
"Authorization": f"Bearer {token}"
}
)
body = response.json()
return body["url"], body["id"]
La lógica para iniciar una transacción de depósito se ve muy similar. Consulta la especificación estándar de SEP-24 para información detallada.
Escuchar la Notificación de Cierre
El siguiente paso es abrir la URL proporcionada en el cliente de la aplicación utilizando una vista web móvil, pestaña del navegador o popup. El usuario pasará por KYC si no lo ha hecho antes en una transacción anterior. En el caso de depósito, el usuario también puede seleccionar una ubicación de agente de MoneyGram a la que ir al proporcionar efectivo.
Finalmente, cuando el usuario termine con la interfaz de usuario de MoneyGram, el usuario seleccionará un botón mostrado en la interfaz de usuario de MoneyGram y MoneyGram enviará un [postMessage] a la ventana o aplicación que abrió su flujo inicialmente. El mensaje enviado será el objeto JSON de transacción SEP-24 que representa la transacción.
Si la transacción recibida en el mensaje [postMessage] está en un estado de pending_user_transfer_start
, esto significa que el usuario ha terminado con la interfaz de usuario de MoneyGram y es seguro cerrar la ventana.
A continuación, se muestra un ejemplo simple en JavaScript escuchando por una notificación [postMessage].
- JavaScript
webview = window.open(moneygramURL, "webview", "width=500,height=800");
window.addEventListener("message", closeWebView);
function closeWebView(e) {
const txJson = e.data.transaction;
console.log(`Transaction ${txJson.id} is ${txJson.status}`);
// If we get a postMessage event and the transaction status is
// "pending_user_transfer_start" let's interpret that as a signal to close
// the webview window and take user back to the application experience
if (txJson.status === "pending_user_transfer_start") {
webview.close();
}
}
Enviar o Recibir Fondos
En las transacciones de retiro (o cash-out), las aplicaciones deben enviar USDC a la cuenta Stellar que MoneyGram especifica. En las transacciones de depósito (cash-in), las aplicaciones deben monitorear su cuenta Stellar para un pago de MoneyGram.
En cada caso, la transacción presentada a Stellar debe tener un memo adjunto. Este memo es proporcionado por MoneyGram en el caso de la retirada de fondos, y proporcionado por la aplicación en el caso del depósito. El memo es un identificador que permite a las partes vincular el pago on-chain con el registro de la transacción en la base de datos de la aplicación o de MoneyGram.
Esperar hasta que MoneyGram esté listo
Antes de que la aplicación pueda enviar fondos o instruir al usuario a proporcionar efectivo a un agente de MoneyGram, la aplicación debe confirmar con el servidor de MoneyGram que la transacción está lista para proceder, lo cual se indica con el estado pending_user_transfer_start
.
Necesitarás la siguiente información para hacerlo.
- El token de autenticación proporcionado por MoneyGram
- El ID de la transacción proporcionado por MoneyGram
- El endpoint de la transacción de MoneyGram (no necesitas preocuparte por esto si estás usando el Wallet SDK)
Este código utiliza un mecanismo simple de monitoreo (polling) sin condición de salida. El código de la aplicación debe ser más robusto.
- TypeScript
- Python
// We can keep the transaction "id" from the withdraw() call,
// authToken and assetCode from previous steps.
const { url, id: transactionId } = await anchor.sep24().withdraw({
authToken,
assetCode,
// ...other params
});
// First, let's initialize a watcher object from the Wallet SDK.
let watcher = anchor.sep24().watcher();
// Then we have the option to watch for a particular transaction.
let { stop, refresh } = watcher.watchOneTransaction({
authToken,
assetCode,
id: transactionId,
onMessage: (transaction) => {
if (transaction.status === "pending_user_transfer_start") {
// begin transfer code
}
},
onSuccess: (transaction) => {
// transaction comes back as completed / refunded / expired
},
onError: (transaction) => {
// runtime error, or the transaction comes back as
// no_market / too_small / too_large / error
},
});
// We also have the option to watch for ALL transactions of a particular asset.
let { stop, refresh } = watcher.watchAllTransactions({
authToken,
assetCode,
onMessage: (transaction) => {
if (transaction.status === "pending_user_transfer_start") {
// begin transfer code
}
},
onError: (transaction) => {
// runtime error, or the transaction comes back as
// no_market / too_small / too_large / error
},
});
// While the Watcher class offers powerful tracking capabilities, sometimes
// it's required to just fetch a transaction (or transactions) once. The Anchor
// class allows you to fetch a transaction by ID, Stellar transaction ID, or
// external transaction ID like illustrated below.
// "id" is the actual Anchor transaction id, all transactions should have it.
const transaction = await anchor.sep24().getTransactionBy({
authToken,
id: transactionId,
});
// "stellarTransactionId" (aka "stellar_transaction_id" on the SEP spec)
// is the hash of the Stellar network transaction payment related to this
// Anchor transaction.
// The "stellarTransactionId" has a SHA256 hash format like the below:
// - "a35135d8ed4b29b66d821444f6760f8ca1e77bea1fb49541bebeb2c3d844364a"
// E.g. we'll only have this transaction id field AFTER the wallet sends funds
// to Anchor on the withdrawal flow or receives funds from Anchor on the
// deposit flow.
const transaction = await anchor.sep24().getTransactionBy({
authToken,
stellarTransactionId,
});
// "externalTransactionId" (aka "external_transaction_id" on the SEP spec)
// could refer to some ID of transaction on external network.
// E.g. for MoneyGram this is the "reference number" displayed to the user on
// the last step of MoneyGram's UI which the user should then use on a physical
// MoneyGram location to complete the cash out operation and pick-up the money.
const transaction = await anchor.sep24().getTransactionBy({
authToken,
externalTransactionId,
});
// It's also possible to fetch multiple transactions for an asset.
const transactions = await anchor.sep24().getTransactionsForAsset({
authToken,
assetCode,
});
import requests
response_body = poll_transaction_until_status(
transaction_id,
token=token,
until_status="pending_user_transfer_start"
)
def poll_transaction_until_status(
txid: str,
token: str,
until_status: str
) -> dict:
first_iteration = True
response_body = None
status = None
while status != until_status:
if first_iteration:
first_iteration = False
else:
time.sleep(1)
query = f"{MGI_ACCESS_TRANSACTION_URL}?id={txid}"
response = requests.get(
query,
headers={
"Authorization": f"Bearer {token}"
}
)
response_body = response.json()
status = response_body["transaction"]["status"]
return response_body
Enviar fondos
Una vez que MoneyGram esté listo para recibir fondos, tu aplicación debe extraer la cuenta Stellar, el memo y el monto a usar en la transacción de pago, construir una transacción Stellar y enviarla a la red Stellar. Necesitarás:
- Una copia del objeto de transacción de MoneyGram
- La clave pública y secreta de fondos de la aplicación
El código para enviar transacciones a Stellar debe desarrollarse de manera cuidadosa. La SDF tiene una página de documentación dedicada a enviar transacciones y manejar errores de forma adecuada. Aquí hay varias cosas que debes tener en cuenta:
- Ofrece una tarifa alta. Tu tarifa debe ser tan alta como la que ofrecerías antes de decidir que la transacción ya no vale la pena enviar. Stellar solo te cobrará lo mínimo necesario para ser incluido en el ledger; no te cobrarán la cantidad que ofreces a menos que todos los demás ofrezcan la misma cantidad o más. De lo contrario, pagarás la tarifa más pequeña ofrecida en el conjunto de transacciones incluidas en el ledger.
- Establece un límite de tiempo máximo en la transacción. Esto asegura que si tu transacción no se incluye en el ledger antes del tiempo establecido, puedes reconstruir la transacción con una tarifa ofrecida más alta y volver a enviarla con mejores posibilidades de inclusión.
- Reenvía la transacción cuando recibas códigos de estado 504. Los códigos de estado 504 simplemente te están indicando que tu transacción sigue pendiente; no que ha sido cancelada o que tu solicitud fue inválida. Simplemente deberías hacer la solicitud nuevamente con la misma transacción para obtener un estado final (ya sea incluido o caducado).
A continuación, se presenta un ejemplo de código Typescript
para enviar una transacción de pago ("transferencia"). Utiliza límites de tiempo y maneja códigos de estado 504 (dentro de submitTransaction
), pero no maneja la caducidad de una transacción.
Ten en cuenta que el código Python
está muy simplificado. No utiliza límites de tiempo, no maneja códigos de estado 504 ni la caducidad de una transacción.
- TypeScript
- Python
import { Wallet, IssuedAssetId } from "@stellar/typescript-wallet-sdk";
import { Horizon } from "@stellar/stellar-sdk";
const wallet = Wallet.TestNet();
// This creates a Stellar instance to manage the connection with Horizon.
const stellar = wallet.stellar();
// Let's make sure Anchor supports the token we want to withdraw.
const assetCode = "USDC";
const info = await anchor.getInfo();
const currency = info.currencies.find(({ code }) => code === assetCode);
if (!currency?.code || !currency?.issuer) {
throw new Error(
`Anchor does not support ${assetCode} asset or is not correctly configured on TOML file`,
);
}
// This creates the Stellar asset object which we'll need while creating the
// transfer withdrawal transaction below.
const asset = new IssuedAssetId(currency.code, currency.issuer);
// This creates a transaction builder which we'll be using to assemble
// our transfer withdrawal transaction as shown below.
const txBuilder = await stellar.transaction({
sourceAddress: FUNDS_STELLAR_KEYPAIR,
baseFee: 10000, // this is 0.001 XLM
timebounds: 180, // in seconds
});
// We can use the transaction object received on the onMessage callback from
// the watcher, or, we can also fetch the transaction object using either
// getTransactionBy or getTransactionsForAsset as illustrated in previous step.
onMessage: (transaction) => {
if (transaction.status === "pending_user_transfer_start") {
// Use the builder to assemble the transfer transaction. Behind the scenes
// it extracts the Stellar account (withdraw_anchor_account), memo (withdraw_memo)
// and amount (amount_in) to use in the Stellar payment transaction that will
// be submitted to the Stellar network.
const transferTransaction = txBuilder
.transferWithdrawalTransaction(transaction, asset)
.build();
// Signs it with the source (funds) account key pair
transferTransaction.sign(FUNDS_STELLAR_KEYPAIR);
// Finally submits it to the stellar network. This stellar.submitTransaction()
// function handles '504' status codes (timeout) by keep retrying it until
// submission succeeds or we get a different error.
try {
const response = await stellar.submitTransaction(transferTransaction);
console.log("Stellar-generated transaction ID: ", response.id);
} catch (error) {
/*
In case it's not a 504 (timeout) error, the application could try some
resolution strategy based on the error kind.
On Stellar docs you can find a page dedicated to error handling:
https://developers.stellar.org/docs/learn/encyclopedia/errors-and-debugging/error-handling
And status/result codes:
https://developers.stellar.org/docs/data/horizon/api-reference/errors
*/
// Let's illustrate here how we could handle an 'invalid sequence number' error.
// We can access all possible result codes through Horizon's API.
const sdkResultCodes = Horizon.HorizonApi.TransactionFailedResultCodes;
// We can access error's response data to check for useful error details.
const errorData = error.response?.data;
/*
Sample of errorData object returned by the Wallet SDK:
{
type: 'https://stellar.org/horizon-errors/transaction_failed',
title: 'Transaction Failed',
status: 400,
detail: 'The transaction failed when submitted to the stellar network.
The `extras.result_codes` field on this response contains further details.
Descriptions of each code can be found at:
https://developers.stellar.org/docs/data/horizon/api-reference/errors/http-status-codes/horizon-specific/transaction-failed',
extras: {
envelope_xdr: 'AAAAAgAAAADBjF7n9gfByOwlnyaJH...k4BRagf/////////8AAAAAAAAAAA==',
result_codes: { transaction: 'tx_bad_seq' },
result_xdr: 'AAAAAAAAAGT////6AAAAAA=='
}
}
*/
/*
Example scenario: invalid sequence numbers.
These errors typically occur when you have an outdated view of an account.
This could be because multiple devices are using this account, you have
concurrent submissions happening, or other reasons. The solution is relatively
simple: retrieve the account details and try again with an updated sequence number.
*/
if (
errorData?.status == 400 &&
errorData?.extras?.result_codes?.transaction ===
sdkResultCodes.TX_BAD_SEQ
) {
// Creating a new transaction builder means retrieving an updated sequence number.
const txBuilder2 = await stellar.transaction({
sourceAddress: FUNDS_STELLAR_KEYPAIR,
baseFee: 10000,
timebounds: 180,
});
// ...
// Repeat all the steps until submitting the transaction again.
// ...
const response2 = await stellar.submitTransaction(transferTransaction);
console.log(
"Stellar-generated transaction ID on retry: ",
response2.id,
);
// The application should take care to not resubmit the same transaction
// blindly with an updated sequence number as it could result in more than
// one payment being made when only one was intended.
}
}
}
};
from stellar_sdk import (
Server, TransactionBuilder, Network, Asset, IdMemo
)
submit_payment(
destination=response_body["transaction"]["withdraw_anchor_account"],
memo=response_body["transaction"]["withdraw_memo"],
amount=response_body["transaction"]["amount_in"]
)
def submit_payment(destination: str, memo: str, amount: str):
server = Server()
account = server.load_account(FUNDS_STELLAR_KEYPAIR.public_key)
transaction = TransactionBuilder(
source_account=account,
network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE,
base_fee=10000 # this is 0.001 XLM
).append_payment_op(
destination=destination,
asset=Asset(ASSET_CODE, ASSET_ISSUER),
amount=amount,
).add_memo(
IdMemo(int(memo))
).build()
transaction.sign(FUNDS_STELLAR_KEYPAIR)
response = server.submit_transaction(transaction)
print(f"Stellar-generated transaction ID: {response['id']}")
Recibir fondos
Una vez que MoneyGram esté listo para que el usuario entregue efectivo a un agente de MGI (en casos de depósito o cash-in), el servidor de la aplicación debe comenzar a monitorear su cuenta Stellar en busca de un pago entrante de USDC enviado por MoneyGram.
La aplicación debe haber proporcionado un memo para que MoneyGram lo use cuando inicie el depósito. MoneyGram adjuntará este memo a la transacción utilizada para enviar el pago a la aplicación, y la aplicación debe utilizar esto para verificar el memo de las transacciones involucrando su cuenta para asociar el pago de regreso al usuario y a la transacción específica.
La mejor manera de monitorear los pagos realizados a una cuenta es transmitir eventos desde el endpoint de pagos de Stellar. El uso de cursores de transmisión puede ayudar a asegurarte de que nunca pierdas un evento, incluso si el proceso de transmisión de tu aplicación se cae por un período de tiempo.
Ten en cuenta que este código no maneja pagos de ruta o saldos reclamables, dos formas de pago ligeramente diferentes. En el momento de escribir esto, MoneyGram no utiliza ninguna de estas opciones, pero puede que desees agregar soporte para ellas en caso de que lo hagan en el futuro.
- TypeScript
- Python
// The Wallet SDK does not support payments streaming yet so let's build
// it using the underlying Horizon SDK
import { Horizon } from "@stellar/stellar-sdk";
import { getTransactionByMemo } from "./queries";
const streamPayments = (account: string, cursor: string) => {
const server = new Horizon.Server("https://horizon-testnet.stellar.org");
server
.payments()
.forAccount(account)
.join("transactions")
.cursor(cursor)
.stream({
onmessage: (payment) => {
if (
payment["type"] !== "payment" ||
payment["from"] === account ||
payment["asset_type"] === "native" ||
payment["asset_code"] !== ASSET_CODE ||
payment["asset_issuer"] !== ASSET_ISSUER
) {
return;
}
const transaction = getTransactionByMemo(
payment["transaction_attr"]["memo"],
payment["transaction_attr"]["memo_type"],
); // this is your own DB query function
if (!transaction) {
return;
}
console.log(
`Payment for deposit transaction ${transaction.id}`,
`matched with Stellar transaction `,
`${payment["transaction_attr"]["id"]}`,
);
},
onerror: (error) => {
// handle error
},
});
};
from stellar_sdk import Server
from .queries import get_transaction_by_memo
def stream_payments(account: str, cursor: str):
s = Server()
payments = s.payments().for_account(account).join("transactions")
for payment in payments.cursor(cursor).stream():
if (
payment["type"] != "payment"
or payment["from"] == account
or payment["asset_type"] == "native"
or payment["asset_code"] != ASSET_CODE
or payment["asset_issuer"] != ASSET_ISSUER
):
continue
transaction = get_transaction_by_memo(
payment["transaction"]["memo"],
payment["transaction"]["memo_type"]
) # DB query
if not transaction:
continue
print(
f"Payment for deposit transaction {transaction.id} "
"matched with Stellar transaction "
f"{payment['transaction']['id']}"
)
Obtener el número de referencia
Para transacciones de depósito o cash-in, MoneyGram no proporciona números de referencia. Todo lo que el usuario necesita hacer es entregar efectivo en la ubicación del agente que eligió en la interfaz de usuario de MoneyGram más temprano en el flujo, y la aplicación debe completar la transacción cuando se detecte un pago coincidente en Stellar.
Para transacciones de retirada o cash-out, MoneyGram proporciona un número de referencia en su UI y API una vez que MoneyGram detecta el pago de USDC de la aplicación en Stellar. Los usuarios deberían poder usar la interfaz del cliente de la aplicación para ver el número de referencia directamente o encontrar la página de detalles de la transacción de MoneyGram y verlo allí.
Ten en cuenta que la página de detalles de transacciones de MoneyGram está protegida con un token JWT en la URL que caduca relativamente rápido después de ser recuperada. Esto significa que las aplicaciones deben recuperar la URL en el momento en que el usuario solicita la página, lo que potencialmente requeriría re-autenticación a través de SEP-10.
- TypeScript
- Python
// Watcher's onMessage callback, see previous steps for more info on this
onMessage: (transaction) => {
if (transaction.status === "pending_user_transfer_complete") {
console.log(
`Transaction reference number ${transaction.external_transaction_id}`,
`also viewable at ${transaction.more_info_url}`,
);
}
};
from .api import poll_transction_until_status
response_body = poll_transaction_until_status(
transaction_id,
"pending_user_transfer_complete"
)
tx_dict = response_body["transaction"]
print(
f"Transaction reference number {tx_dict['external_transaction_id']} "
f"also viewable at {tx_dict['more_info_url']}"
)