如何在 Flash 中完成信用卡验证与支付流程?
明确结论
Flash 可以承担结账界面,并通过 HTTPS 与自有后端通信,但支付网关密钥、信用卡验证规则和交易处理逻辑不能全部放在 SWF 中。
比较可靠的处理方式是:
- Flash 收集订单和支付信息,检查基本格式。
- Flash 通过 HTTPS POST 将请求发送到自有后端。
- 后端校验订单、执行风控并计算金额。
- 后端调用支付网关,完成令牌化、预授权或扣款。
- 后端将交易结果返回给 Flash。
如果支付网关要求使用 3-D Secure、银行身份验证或托管收银台,支付过程通常无法始终停留在 Flash 内。此时需要按照网关要求打开浏览器页面或其他受支持的验证界面。
Flash Player 已经结束支持,因此下面的方案仅适用于维护遗留系统。新项目应改用 HTML5,以及支付网关提供的 Hosted Fields、Payment Element 或 Checkout 页面。
为什么不能直接从 Flash 调用支付网关
SWF 文件会下载到用户电脑。经过反编译,其中的代码、接口地址和常量都可能暴露。因此,写在 Flash 中的商户密钥、API Secret 或签名私钥不能算作秘密。
直接调用支付网关还会带来以下问题:
- 跨域请求依赖支付网关提供允许访问的
crossdomain.xml。 - Socket 连接也受到 socket policy 限制。
- 部分网关只接受服务器之间的请求。
- 如果直接采用客户端提交的金额、商品和折扣,这些数据很容易被篡改。
- 信用卡数据经过自有服务器后,PCI DSS 合规范围会明显扩大。
- 交易超时或重复提交可能导致重复扣款。
- 3-D Secure 等流程可能需要银行页面参与,无法用普通 POST 代替。
所以,Flash 只负责界面展示和数据提交,支付流程应由自有后端控制。
推荐的处理流程
完整的支付流程可以按以下顺序实现:
- 后端创建订单,并生成不可预测的
orderId。 - Flash 展示后端返回的商品、金额和币种。
- 用户填写持卡人姓名、卡号、有效期和 CVV。
- Flash 执行非权威性的即时检查,例如检查必填项、有效期格式和 Luhn 校验结果。
- Flash 将订单标识和支付数据提交到自有 HTTPS 接口。
- 后端重新读取订单并计算应付金额,不能信任 Flash 提交的金额。
- 后端调用支付网关。
- 网关返回成功、失败、待确认或需要额外验证等状态。
- 后端保存交易编号和状态,再向 Flash 返回必要的结果。
- 通过网关 webhook 再次确认最终交易状态。
如果网关支持客户端令牌化,而且明确兼容 Flash 或通用 HTTPS 客户端,可以先将卡号换成一次性 token,再把 token 发送给自有后端。这样可以减少信用卡数据进入业务服务器的机会。不过,PCI DSS 范围能否因此缩小,仍要根据所用网关的集成文档和合规评估确定,不能只凭”使用了 token”作出判断。
Flash 端提交示例
下面的 ActionScript 3 示例会将表单提交到同源后端,不会直接调用支付网关。
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.URLRequestMethod;
import flash.net.URLVariables;
function submitPayment(
orderId:String,
cardholder:String,
cardNumber:String,
expiryMonth:String,
expiryYear:String,
cvv:String
):void {
if (!isValidLuhn(cardNumber)) {
showError("卡号格式不正确");
return;
}
var data:URLVariables = new URLVariables();
data.orderId = orderId;
data.cardholder = cardholder;
data.cardNumber = cardNumber.replace(/\s|-/g, "");
data.expiryMonth = expiryMonth;
data.expiryYear = expiryYear;
data.cvv = cvv;
var request:URLRequest = new URLRequest(
"https://checkout.example.com/api/payment"
);
request.method = URLRequestMethod.POST;
request.data = data;
request.contentType = "application/x-www-form-urlencoded";
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, onPaymentComplete);
loader.addEventListener(IOErrorEvent.IO_ERROR, onPaymentIOError);
loader.addEventListener(
SecurityErrorEvent.SECURITY_ERROR,
onPaymentSecurityError
);
disablePayButton();
loader.load(request);
}
function onPaymentComplete(event:Event):void {
enablePayButton();
var loader:URLLoader = URLLoader(event.target);
var response:Object = JSON.parse(loader.data);
switch (response.status) {
case "succeeded":
showSuccess(response.orderId);
break;
case "requires_action":
handleAdditionalAuthentication(response);
break;
case "declined":
showError("付款未获批准,请检查支付信息或更换银行卡");
break;
default:
showError("暂时无法确认支付结果,请查询订单状态");
break;
}
}
function onPaymentIOError(event:IOErrorEvent):void {
enablePayButton();
showError("网络请求失败,请查询订单状态后再决定是否重试");
}
function onPaymentSecurityError(event:SecurityErrorEvent):void {
enablePayButton();
showError("安全策略阻止了支付请求");
}
function isValidLuhn(value:String):Boolean {
var digits:String = value.replace(/\s|-/g, "");
if (!/^\d{12,19}$/.test(digits)) {
return false;
}
var sum:int = 0;
var alternate:Boolean = false;
for (var i:int = digits.length - 1; i >= 0; i--) {
var n:int = int(digits.charAt(i));
if (alternate) {
n *= 2;
if (n > 9) {
n -= 9;
}
}
sum += n;
alternate = !alternate;
}
return sum % 10 == 0;
}
原生 JSON.parse() 要求 ActionScript 运行环境达到相应版本。较旧的 Flash Player 如果不支持原生 JSON,需要使用兼容的 JSON 库,或者让后端返回可由 URLVariables 解析的格式。
Luhn 校验只能发现部分输入错误。它无法证明银行卡真实存在,也不能代替发卡行授权。
后端处理示例
下面是与编程语言无关的伪代码。paymentGateway 代表所选支付网关的官方服务端 SDK,实际方法名应以该网关的文档为准。
POST /api/payment
require HTTPS
authenticate current checkout session
apply rate limiting
order = database.getOrder(request.orderId)
if order does not exist:
return 404
if order belongs to another session:
return 403
if order is already paid:
return current order status
amount = calculateAmountFromServerSideOrder(order)
currency = order.currency
validate card field formats
validate order state
validate inventory
idempotencyKey = "payment:" + order.id
result = paymentGateway.createPayment({
amount: amount,
currency: currency,
card: {
number: request.cardNumber,
expiryMonth: request.expiryMonth,
expiryYear: request.expiryYear,
cvv: request.cvv,
holderName: request.cardholder
},
merchantReference: order.id,
idempotencyKey: idempotencyKey
})
database.saveGatewayTransaction(
order.id,
result.transactionId,
result.status
)
return {
orderId: order.id,
status: mapGatewayStatus(result.status)
}
如果条件允许,更适合的做法是让 Flash 提交支付网关生成的一次性 token:
result = paymentGateway.createPayment({
amount: amount,
currency: currency,
paymentMethodToken: request.paymentMethodToken,
merchantReference: order.id,
idempotencyKey: idempotencyKey
})
后端返回的信息应尽可能精简。不要将支付网关的原始错误、风控规则、内部堆栈或敏感字段直接返回给客户端。
信用卡验证应分成两层
Flash 端的验证主要用于改善填写体验,可以检查:
- 卡号是否只包含允许的字符。
- 卡号长度是否合理。
- Luhn 校验是否通过。
- 月份是否在
01到12之间。 - 有效期是否已经过去。
- CVV 是否符合基本长度要求。
后端仍须再次检查所有字段。银行卡是否有效、余额是否充足以及交易是否获批,最终由支付网关和发卡行决定,客户端验证代码无权作出这些判断。
不要只根据卡号前几位自行判断卡组织、卡种或 CVV 长度。这些规则可能发生变化,应优先使用支付网关提供的验证能力。
跨域配置
如果 SWF 和支付接口使用不同的域名,目标服务器通常需要提供 crossdomain.xml。例如:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy
SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<site-control permitted-cross-domain-policies="master-only"/>
<allow-access-from
domain="checkout.example.com"
secure="true"/>
</cross-domain-policy>
不要为了方便而配置:
<allow-access-from domain="*" secure="false"/>
跨域策略只决定 Flash Player 能否发起请求,既不提供身份验证,也无法保护 API 密钥。支付接口仍需使用 HTTPS,并执行会话校验、CSRF 防护、速率限制和服务端授权检查。
交易状态与重复扣款
支付请求超时后,不能直接提示用户”支付失败,请重新付款”。超时可能对应多种情况:
- 请求没有到达后端。
- 后端已经创建交易,但响应丢失。
- 网关已经扣款,业务系统却尚未收到结果。
- 交易仍在处理中。
每次支付都应使用稳定的幂等键。Flash 遇到超时后,应先调用订单状态接口:
GET /api/orders/{orderId}/payment-status
只有后端确认不存在已成功或正在处理的交易时,才能重新发起支付。最终状态还应根据网关 webhook 更新,不能只依赖 Flash 收到的同步响应,因为用户可能关闭窗口或在处理中途断网。
必须注意的安全与合规问题
- 所有支付请求都必须使用有效的 HTTPS,不能降级为 HTTP。
- 不要在 SWF 中保存 API Secret、私钥或网关管理凭据。
- 不要记录卡号、CVV、请求正文或完整的网关响应。
- 授权完成后不应保存 CVV。
- 页面、接口日志、异常监控和分析系统都需要过滤支付字段。
- 金额、币种、折扣、税费和商品信息必须由后端确定。
- 提交支付后应暂时禁用支付按钮,但服务端的幂等控制仍然必不可少。
- 数据库只保存业务需要的交易编号、状态和脱敏信息,例如卡号后四位。
- 上线前应确认支付网关是否允许这种接入方式,并完成对应的 PCI DSS 合规评估。
- 如果网关要求 3-D Secure 或其他强客户认证,必须使用其官方流程,不能通过模拟 POST 或 socket 绕过。
遗留 Flash 系统可以采用这种架构继续处理交易。如果系统仍有改造空间,更稳妥的方案是把结账界面迁移到受支持的浏览器技术,并使用支付网关托管的卡片输入组件。这样既能适应现代身份验证流程,也能减轻直接处理信用卡数据带来的安全负担。
备注:内容仅供参考。
