移动端 SDK(仅限公钥)
移动端 SDK 在您的应用内显示 SOAMTAR 托管收银台,并告诉您它是如何结束的。它们从不创建付款,也从不持有私钥:只接受公钥(pk_test_… / pk_live_…),并拒绝 sk_… 密钥。
| 平台 | 包 | 主要入口 |
|---|---|---|
| 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 | SoamtarCheckout 组件 |
| React Native | @soamtar/react-native 1.1.0 | <Checkout /> 组件 |
安装
这些包目前都尚未发布到公共仓库:在发布之前,请从 SOAMTAR 代码仓库安装。
工作原理
- 应用将订单发送到您的后端。
- 您的后端使用私钥创建付款(
POST/v1/payments),并传入您的success_url和failed_url,然后把data.payment_url(https://app.so-amtar.com/pay/checkout/{id})返回给应用。 - 应用通过 SDK 打开
payment_url。付款人输入其 Orange Money 号码(+232加 8 位数字),并在自己的手机上用 Orange Money PIN 批准请求。PIN 绝不会经过您的应用。 - 托管结果页会重定向到您的
success_url或failed_url。SDK 在该跳转处停止并回调您。 - 您的后端在交付订单之前确认最终状态(webhook
payment.success,或GET/v1/payments/{id})。
SDK 回调不是付款凭证
onSuccess / STATUS_SUCCESS 仅表示 WebView 到达了您的 success_url。只有经过验证的 webhook 或使用私钥读取 API 的结果才能确认订单。
后端请求(示例)
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"
}'将 payment_url 连同相同的 success_url / failed_url 一起发送给应用:SDK 需要它们来识别收银台何时结束。使用 sk_test_ 密钥时,付款在同一主机上走模拟流程。
success_url 与 failed_url 请使用两个不同的路径
四个 SDK 都按 scheme、host 和 path 精确匹配 您的返回 URL。查询字符串和片段会被忽略,且绝不做前缀匹配。failed_url 优先检查:如果两个 URL 路径相同(例如 https://shop.example/return?status=ok 和 https://shop.example/return?status=ko),所有支付都会被报告为失败。请使用不同的路径,例如 /orders/42/paid 和 /orders/42/failed。
iOS (Swift)
要求:iOS 14+、Swift 5.7+、Xcode 14+。
安装(Swift Package Manager)
在 Xcode 中:File → Add Package Dependencies…,URL 填 https://github.com/soamtar/soamtar-ios-sdk,产品选 SoamtarSDK。或在 Package.swift 中:
.package(url: "https://github.com/soamtar/soamtar-ios-sdk", from: "1.0.1")配置(可选)
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
| 符号 | 说明 |
|---|---|
Soamtar.shared.configure(publicKey:baseURL:) | 保存公钥。baseURL 默认为 https://app.so-amtar.com。 |
Soamtar.validatePublicKey(_:) throws | 密钥不以 pk_test_ / pk_live_ 开头时抛出 SoamtarError.invalidPublicKey。 |
Soamtar.shared.isCheckoutURL(_:) -> Bool | 对已配置主机上的 HTTPS …/pay/checkout/{id} 页面返回 true。 |
SoamtarReturnURLs(success:failure:) | 您的 success_url / failed_url;outcome(for:) 比较协议、主机和路径。 |
SoamtarCheckoutView(paymentUrl:returnURLs:onSuccess:onFailure:) | SwiftUI 收银台(WKWebView)。每个回调都会收到匹配到的 URL。 |
SoamtarCheckoutViewController(…) | UIKit 收银台,参数相同。 |
SoamtarMoney.format(_:currency:) | Decimal 或十进制字符串 → "Le 1,250.00"。 |
Android (Kotlin)
要求:Android API 21+、Kotlin 1.8+。库的清单文件已声明 INTERNET 权限和收银台 Activity。
安装
dependencies {
implementation 'com.soamtar:soamtar-android:1.1.0'
}初始化
import com.soamtar.sdk.Soamtar
Soamtar.init("pk_test_your_public_key")
// 非 pk_test_… / pk_live_… 的密钥会抛出 IllegalArgumentException。打开收银台(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)
)旧版 onActivityResult:
Soamtar.checkout(this, paymentUrl, REQUEST_PAY, successUrl, failedUrl)返回状态
| 常量 | 含义 |
|---|---|
SoamtarCheckoutActivity.STATUS_SUCCESS | 托管页面重定向到了 success_url |
SoamtarCheckoutActivity.STATUS_FAILED | 托管页面重定向到了 failed_url(失败、过期或取消) |
SoamtarCheckoutActivity.STATUS_CANCELLED | 付款人在任何重定向之前关闭了收银台 |
payment_url 必须是 SOAMTAR 主机(app.so-amtar.com)上的 HTTPS 地址。如果不传 successUrl / failedUrl,付款人会停留在 SOAMTAR 结果页,关闭时 Activity 返回 STATUS_CANCELLED。
当某次导航的 scheme、host 和 path 与 failedUrl 或 successUrl 完全相同时(忽略查询和片段,绝不按前缀匹配),收银台结束;failedUrl 优先检查。
显示金额
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
要求:Dart 3.0+、Flutter 3.10+、webview_flutter 4.0+。
安装
dependencies:
soamtar_sdk:
git:
url: https://github.com/soamtar/soamtar_flutter_sdk
ref: v1.0.1flutter pub getAndroid:在 android/app/src/main/AndroidManifest.xml 中添加 <uses-permission android:name="android.permission.INTERNET"/>。iOS:无需额外配置。
使用
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),
),
);
}
}显示金额:
SoamtarMoney.format('1250.00'); // "Le 1,250.00"
SoamtarMoney.formatMinorUnits(BigInt.from(15000)); // "Le 150.00"| 符号 | 说明 |
|---|---|
SoamtarClient(String publicKey, {Uri? baseUrl}) | 仅限 pk_test_ / pk_live_。默认主机 https://app.so-amtar.com。isCheckoutUrl(url) 检查 HTTPS、主机和 /pay/checkout/…。 |
SoamtarCheckout({paymentUrl, returnUrls, onSuccess, onFailure}) | WebView 组件;只触发一个回调,且只触发一次。 |
SoamtarReturnUrls({success, failure}) | resultFor(uri) → SoamtarCheckoutResult.success / .failure / null。 |
SoamtarMoney.format / formatMinorUnits | 十进制字符串或整数分 → "Le 1,250.00"(从不使用 double)。 |
React Native
安装
npm install @soamtar/react-native react-native-webview使用
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)}
/>
);
}| 属性 | 说明 |
|---|---|
paymentUrl(必填) | POST/v1/payments 返回的 payment_url,必须是 https。 |
successUrl | 您发送的 success_url。到达时调用一次 onSuccess(url)。 |
failureUrl | 您发送的 failed_url。到达时调用一次 onFailure(url)。 |
onResult(url) | 已到达 SOAMTAR 结果页(/pay/result/{id}):付款已提交,正在确认。 |
onError(message) | URL 无效、导航被拦截或 WebView 加载错误。 |
loaderColor | 加载指示器颜色(默认 #FF7900)。 |
Client 还提供 isTestMode(pk_test_ 密钥时为 true)。在收银台内,https 页面正常加载,http:、javascript: 和 data: URL 被拦截,其他协议(如 tel:)交给操作系统处理。
移动端安全
- 仅限公钥:所有 SDK 都拒绝私钥(
sk_…),因为私钥可能被从 APK/IPA 中提取。 - 隔离的 Web 视图:付款页面运行在
WKWebView/WebView中,仅限 HTTPS。付款人在自己的手机上用 Orange Money PIN 确认;PIN 绝不会经过您的应用。 - 不存储任何数据:SDK 不在设备上保存任何数据。
- 服务端确认:应用只是一个展示层,交易在您的服务器上创建并确认。