SDK móviles (solo claves públicas)
Los SDK móviles muestran el checkout alojado de SOAMTAR dentro de su aplicación y le indican cómo terminó. Nunca crean pagos ni guardan una clave secreta: solo aceptan claves públicas (pk_test_… / pk_live_…) y rechazan las claves sk_….
| Plataforma | Paquete | Puntos de entrada |
|---|---|---|
| iOS (Swift) | SoamtarSDK (Swift Package) 1.0.1 | SoamtarCheckoutView, SoamtarCheckoutViewController |
| Android (Kotlin) | com.soamtar:soamtar-android:1.1.0 | Soamtar.checkoutIntent(), SoamtarCheckoutActivity |
| Flutter | soamtar_sdk 1.0.1 | widget SoamtarCheckout |
| React Native | @soamtar/react-native 1.1.0 | componente <Checkout /> |
Instalación
Ninguno de estos paquetes está publicado todavía en un registro público: instálelos desde el repositorio de SOAMTAR mientras tanto.
Funcionamiento
- La aplicación envía el pedido a su backend.
- Su backend crea el pago con su clave secreta (
POST/v1/payments), con susuccess_urly sufailed_url, y devuelvedata.payment_url(https://app.so-amtar.com/pay/checkout/{id}) a la aplicación. - La aplicación abre
payment_urlcon el SDK. El pagador introduce su número Orange Money (+232seguido de 8 dígitos) y aprueba la solicitud en su teléfono con su PIN de Orange Money. El PIN nunca pasa por su aplicación. - La página de resultado alojada redirige a su
success_urlofailed_url. El SDK se detiene en esa navegación y le avisa. - Su backend confirma el estado final (webhook
payment.success, oGET/v1/payments/{id}) antes de entregar el pedido.
El callback del SDK no es una prueba de pago
onSuccess / STATUS_SUCCESS solo significa que la WebView llegó a su success_url. Solo un webhook verificado o una lectura de la API con su clave secreta confirma un pedido.
Solicitud del backend (ejemplo)
curl -X POST https://app.so-amtar.com/v1/payments \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0c6f3a52-6a0e-4f43-9a0e-2a7f4b1d9e11" \
-d '{
"merchant_ref": "ORDER-42",
"amount": "150.00",
"currency": "SLE",
"provider": "orange_money_sl",
"success_url": "https://shop.example/orders/42/paid",
"failed_url": "https://shop.example/orders/42/failed"
}'Envíe payment_url a la aplicación junto con los mismos success_url / failed_url: los SDK los necesitan para detectar el final del checkout. Con claves sk_test_ el pago pasa por la simulación, en el mismo host.
Use dos rutas distintas para success_url y failed_url
Los cuatro SDK comparan la navegación con sus URL de retorno exactamente por esquema, host y ruta. La query string y el fragmento se ignoran, y nunca es una comparación por prefijo. failed_url se comprueba primero: si ambas URL comparten la misma ruta (por ejemplo https://shop.example/return?status=ok y https://shop.example/return?status=ko), todos los pagos se notifican como fallidos. Use rutas distintas, como /orders/42/paid y /orders/42/failed.
iOS (Swift)
Requisitos: iOS 14+, Swift 5.7+, Xcode 14+.
Instalación (Swift Package Manager)
En Xcode: File → Add Package Dependencies…, URL https://github.com/soamtar/soamtar-ios-sdk, producto SoamtarSDK. O en Package.swift:
.package(url: "https://github.com/soamtar/soamtar-ios-sdk", from: "1.0.1")Configuración (opcional)
import SoamtarSDK
Soamtar.shared.configure(publicKey: "pk_test_your_public_key")
// Anything other than pk_test_ / pk_live_ stops the app (fatalError).SwiftUI
import SwiftUI
import SoamtarSDK
struct PayButton: View {
let paymentUrl: URL // from your backend
@State private var showCheckout = false
private let returnURLs = SoamtarReturnURLs(
success: URL(string: "https://shop.example/orders/42/paid")!,
failure: URL(string: "https://shop.example/orders/42/failed")!
)
var body: some View {
Button("Pay \(SoamtarMoney.format("150.00") ?? "")") { showCheckout = true } // "Pay Le 150.00"
.sheet(isPresented: $showCheckout) {
SoamtarCheckoutView(
paymentUrl: paymentUrl,
returnURLs: returnURLs,
onSuccess: { _ in showCheckout = false /* then confirm with your backend */ },
onFailure: { _ in showCheckout = false }
)
}
}
}UIKit
let checkout = SoamtarCheckoutViewController(
paymentUrl: paymentUrl,
returnURLs: returnURLs,
onSuccess: { [weak self] _ in self?.dismiss(animated: true) },
onFailure: { [weak self] _ in self?.dismiss(animated: true) }
)
present(checkout, animated: true)API
| Símbolo | Descripción |
|---|---|
Soamtar.shared.configure(publicKey:baseURL:) | Guarda la clave pública. baseURL es https://app.so-amtar.com por defecto. |
Soamtar.validatePublicKey(_:) throws | Lanza SoamtarError.invalidPublicKey si la clave no empieza por pk_test_ / pk_live_. |
Soamtar.shared.isCheckoutURL(_:) -> Bool | true para una página HTTPS …/pay/checkout/{id} del host configurado. |
SoamtarReturnURLs(success:failure:) | Sus success_url / failed_url; outcome(for:) compara esquema, host y ruta. |
SoamtarCheckoutView(paymentUrl:returnURLs:onSuccess:onFailure:) | Checkout SwiftUI (WKWebView). Cada callback recibe la URL alcanzada. |
SoamtarCheckoutViewController(…) | Checkout UIKit, mismos parámetros. |
SoamtarMoney.format(_:currency:) | Decimal o cadena decimal → "Le 1,250.00". |
Android (Kotlin)
Requisitos: Android API 21+, Kotlin 1.8+. El manifiesto de la biblioteca declara el permiso INTERNET y la actividad de checkout.
Instalación
dependencies {
implementation 'com.soamtar:soamtar-android:1.1.0'
}Inicialización
import com.soamtar.sdk.Soamtar
Soamtar.init("pk_test_your_public_key")
// Cualquier clave distinta de pk_test_… / pk_live_… lanza IllegalArgumentException.Abrir el checkout (Activity Result API)
import com.soamtar.sdk.ui.SoamtarCheckoutActivity
private val checkoutLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
when (result.data?.getStringExtra(SoamtarCheckoutActivity.RESULT_STATUS)) {
SoamtarCheckoutActivity.STATUS_SUCCESS -> showPending("Confirming your payment…")
SoamtarCheckoutActivity.STATUS_FAILED -> showError("Payment not completed.")
else -> showPending("Checking your payment…") // closed by the payer
}
refreshOrderFromBackend() // the backend status is the source of truth
}
// paymentUrl, successUrl and failedUrl come from your backend
checkoutLauncher.launch(
Soamtar.checkoutIntent(this, paymentUrl, successUrl = successUrl, failedUrl = failedUrl)
)Con la API antigua onActivityResult:
Soamtar.checkout(this, paymentUrl, REQUEST_PAY, successUrl, failedUrl)Estados devueltos
| Constante | Significado |
|---|---|
SoamtarCheckoutActivity.STATUS_SUCCESS | La página alojada redirigió a success_url |
SoamtarCheckoutActivity.STATUS_FAILED | La página alojada redirigió a failed_url (fallido, expirado o cancelado) |
SoamtarCheckoutActivity.STATUS_CANCELLED | El pagador cerró el checkout antes de cualquier redirección |
payment_url debe ser HTTPS en el host de SOAMTAR (app.so-amtar.com). Sin successUrl / failedUrl, el pagador se queda en la página de resultado de SOAMTAR y la actividad devuelve STATUS_CANCELLED al cerrarla.
El checkout se detiene cuando una navegación tiene el mismo esquema, host y ruta que failedUrl o successUrl (query y fragmento ignorados, nunca por prefijo); failedUrl se comprueba primero.
Mostrar un importe
SoamtarMoney.formatLe(BigDecimal("1250")) // "Le 1,250.00"
SoamtarMoney.formatLe("150.5") // "Le 150.50" (null)
Soamtar.formatLe("2000000.00") // "Le 2,000,000.00"Flutter
Requisitos: Dart 3.0+, Flutter 3.10+, webview_flutter 4.0+.
Instalación
dependencies:
soamtar_sdk:
git:
url: https://github.com/soamtar/soamtar_flutter_sdk
ref: v1.0.1flutter pub getAndroid: añada <uses-permission android:name="android.permission.INTERNET"/> en android/app/src/main/AndroidManifest.xml. iOS: no hay nada que configurar.
Uso
import 'package:flutter/material.dart';
import 'package:soamtar_sdk/soamtar_sdk.dart';
class PaymentPage extends StatelessWidget {
const PaymentPage({super.key, required this.paymentUrl});
/// `payment_url` received from your backend.
final String paymentUrl;
@override
Widget build(BuildContext context) {
final client = SoamtarClient('pk_test_your_public_key'); // ArgumentError on sk_
if (!client.isCheckoutUrl(paymentUrl)) {
return const Center(child: Text('Invalid payment link.'));
}
return Scaffold(
appBar: AppBar(title: const Text('Pay with Orange Money')),
body: SoamtarCheckout(
paymentUrl: Uri.parse(paymentUrl),
returnUrls: SoamtarReturnUrls(
success: Uri.parse('https://shop.example/orders/42/paid'),
failure: Uri.parse('https://shop.example/orders/42/failed'),
),
onSuccess: (_) => Navigator.pop(context, true), // then confirm with your backend
onFailure: (_) => Navigator.pop(context, false),
),
);
}
}Mostrar un importe:
SoamtarMoney.format('1250.00'); // "Le 1,250.00"
SoamtarMoney.formatMinorUnits(BigInt.from(15000)); // "Le 150.00"| Símbolo | Notas |
|---|---|
SoamtarClient(String publicKey, {Uri? baseUrl}) | Solo pk_test_ / pk_live_. Host por defecto https://app.so-amtar.com. isCheckoutUrl(url) comprueba HTTPS, host y /pay/checkout/…. |
SoamtarCheckout({paymentUrl, returnUrls, onSuccess, onFailure}) | Widget WebView; dispara un único callback, una sola vez. |
SoamtarReturnUrls({success, failure}) | resultFor(uri) → SoamtarCheckoutResult.success / .failure / null. |
SoamtarMoney.format / formatMinorUnits | Cadena decimal o céntimos enteros → "Le 1,250.00" (nunca double). |
React Native
Instalación
npm install @soamtar/react-native react-native-webviewUso
import { Client, Checkout } from '@soamtar/react-native';
const soamtar = new Client('pk_test_...'); // throws on a sk_ key
export function PayScreen({ navigation, route }) {
// payment_url returned by your backend (must be https)
const paymentUrl = soamtar.getCheckoutUrl(route.params.paymentUrl);
return (
<Checkout
paymentUrl={paymentUrl}
successUrl="https://shop.example/orders/42/paid"
failureUrl="https://shop.example/orders/42/failed"
onSuccess={() => navigation.replace('OrderStatus', { orderId: route.params.orderId })}
onFailure={() => navigation.replace('PaymentFailed')}
onError={(message) => console.warn(message)}
/>
);
}| Prop | Descripción |
|---|---|
paymentUrl (obligatoria) | payment_url devuelta por POST/v1/payments. Debe ser https. |
successUrl | La success_url enviada. Al alcanzarla se llama a onSuccess(url) una vez. |
failureUrl | La failed_url enviada. Al alcanzarla se llama a onFailure(url) una vez. |
onResult(url) | Se alcanzó la página de resultado de SOAMTAR (/pay/result/{id}): el pago se envió y se está confirmando. |
onError(message) | URL no válida, navegación bloqueada o error de carga de la WebView. |
loaderColor | Color del indicador de carga (por defecto #FF7900). |
Client también expone isTestMode (true con claves pk_test_). Dentro del checkout se cargan las páginas https, se bloquean las URL http:, javascript: y data:, y los demás esquemas (como tel:) se entregan al sistema operativo.
Seguridad móvil
- Solo claves públicas: todos los SDK rechazan las claves secretas (
sk_…), que podrían extraerse del APK/IPA. - Vista web aislada: la página de pago se ejecuta en una
WKWebView/WebView, solo por HTTPS. El pagador confirma con su PIN de Orange Money en su propio teléfono; el PIN nunca pasa por su aplicación. - Nada almacenado: los SDK no guardan datos en el dispositivo.
- Confirmación en el servidor: la aplicación es solo una capa de visualización de una transacción creada y confirmada en su servidor.