open-standards/openpay/crates/op-ewallets-asia/tests/alipay_qr.rs
Transaction Science 6bd1d3ed41
All checks were successful
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 8m28s
OpenPay at zero clippy findings, and held there in CI; FedNow's cap at the network's 10 million; a low-s bound that is N/2
Every OpenPay crate now passes clippy with -D warnings, and the openpay
entry of the workspace build sets clippy: true so it stays there. Most
of the findings were mechanical (clippy's own fixes, write! for
push_str(&format!), let-else); where a lint is wrong for the code it is
allowed at the narrowest scope with its reason: code tables keep one
arm per code, units_cents amounts keep their grouping, op-fraud-graph's
index casts rest on a bound upsert_entity now enforces (it used to hand
every vertex past 2^32 the same id).

Two findings were bugs. op-payouts capped FedNow at USD 1,000,000, the
limit before November 2025; the network maximum is USD 10,000,000 and a
test now sends five million. op-crypto-extra's EIP-7702 low-s bound was
N - 2^255, not N/2: it refused valid low-s signatures in a band just
below N/2. It is N/2 now, with tests at both edges.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-11 22:21:23 -05:00

109 lines
3.4 KiB
Rust

//! End-to-end shape test for the Alipay precreate (QR) flow.
//!
//! Uses an in-memory transport + signer to verify the wire shape
//! the adapter sends and the parsing of a canned response.
use op_core::{Currency, Money};
use op_ewallets_asia::alipay::{
AlipayAdapter, AlipaySigner, AlipayTransport,
};
use op_ewallets_asia::wallet::{
AsiaWallet, ChargeIntent, ChargeStatus, PresentmentMode,
};
use std::sync::Mutex;
/// The path, body and headers of the request the transport was given.
type Seen = Option<(String, String, Vec<(String, String)>)>;
struct CapturingTransport {
canned_response: String,
seen: Mutex<Seen>,
}
impl AlipayTransport for CapturingTransport {
fn post_json(
&self,
path: &str,
headers: &[(String, String)],
body: &str,
) -> op_ewallets_asia::Result<String> {
*self.seen.lock().unwrap() = Some((path.into(), body.into(), headers.to_vec()));
Ok(self.canned_response.clone())
}
}
struct DummySigner;
impl AlipaySigner for DummySigner {
fn sign(&self, _payload: &[u8]) -> op_ewallets_asia::Result<String> {
Ok("dummy-signature".into())
}
}
#[test]
fn precreate_emits_qr_url() {
let canned = r#"{
"alipay_trade_precreate_response": {
"code": "10000",
"msg": "Success",
"out_trade_no": "ord-123",
"qr_code": "https://qr.alipay.com/bax01234567890"
}
}"#;
let adapter = AlipayAdapter {
app_id: "2021000123456789".into(),
host: "https://openapi.alipay.com".into(),
transport: Box::new(CapturingTransport {
canned_response: canned.into(),
seen: Mutex::new(None),
}),
signer: Box::new(DummySigner),
};
let intent = ChargeIntent {
merchant_order_id: "ord-123".into(),
amount: Money::from_minor(9_999, Currency::CNY),
description: "OpenPay test".into(),
presentment: PresentmentMode::MerchantPresentedQr,
consumer_hint: None,
notify_url: Some("https://merchant.example/notify".into()),
};
let result = adapter.create_charge(&intent).expect("alipay precreate");
assert_eq!(result.merchant_order_id, "ord-123");
assert_eq!(result.status, ChargeStatus::Pending);
assert!(result.presentment_payload.starts_with("https://qr.alipay.com/"));
}
#[test]
fn precreate_rejects_non_cny() {
let adapter = AlipayAdapter {
app_id: "x".into(),
host: "https://openapi.alipay.com".into(),
transport: Box::new(CapturingTransport {
canned_response: "{}".into(),
seen: Mutex::new(None),
}),
signer: Box::new(DummySigner),
};
let intent = ChargeIntent {
merchant_order_id: "ord".into(),
amount: Money::from_minor(100, Currency::USD),
description: "x".into(),
presentment: PresentmentMode::MerchantPresentedQr,
consumer_hint: None,
notify_url: None,
};
assert!(adapter.create_charge(&intent).is_err());
}
#[test]
fn precreate_surfaces_provider_error() {
let canned = r#"{
"alipay_trade_precreate_response": {
"code": "40004",
"msg": "Business Failed",
"sub_code": "ACQ.TRADE_HAS_SUCCESS",
"sub_msg": "Trade exists"
}
}"#;
let parsed = AlipayAdapter::parse_precreate_response(canned);
assert!(parsed.is_err());
}