Delphi 如何接收 Android Intent 支付回调
结论
支付应用返回的是 deep link Intent。支付结果通常保存在 Intent.getData 表示的 URI 中,而不在 Intent.getExtras 里。
所以下面的读取方式不适用于这种回调:
Extras := Data.getExtras;
Extras.getString(TJIntent.JavaClass.EXTRA_TEXT);
EXTRA_TEXT 主要用于文本分享等 Intent。deep link 回调通常类似:
scheme_return://pay-response?参数1=值1&参数2=值2
应通过 Data.getData 获取 URI,再用 getQueryParameter 读取结果。
正确读取回调 URI
可以把 HandleIntentAction 改成下面这样:
uses
System.SysUtils,
Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNI.Net,
Androidapi.Helpers;
function TForm1.HandleIntentAction(const Data: JIntent): Boolean;
var
CallbackUri: Jnet_Uri;
RawUri: string;
Scheme: string;
Host: string;
begin
Result := False;
if Data = nil then
Exit;
if not TJIntent.JavaClass.ACTION_VIEW.equals(Data.getAction) then
Exit;
CallbackUri := Data.getData;
if CallbackUri = nil then
Exit;
RawUri := JStringToString(CallbackUri.toString);
Scheme := JStringToString(CallbackUri.getScheme);
Host := JStringToString(CallbackUri.getHost);
Memo1.Lines.BeginUpdate;
try
Memo1.Clear;
Memo1.Lines.Add('Raw URI: ' + RawUri);
Memo1.Lines.Add('Scheme: ' + Scheme);
Memo1.Lines.Add('Host: ' + Host);
{ 使用支付 SDK 实际规定的参数名读取,例如:
Memo1.Lines.Add(
'status: ' +
JStringToString(
CallbackUri.getQueryParameter(StringToJString('status'))
)
);
}
finally
Memo1.Lines.EndUpdate;
end;
Result := True;
end;
先输出完整的 Raw URI,查看实际收到的地址。然后根据 Stone SDK 对回调字段的定义,使用相应的参数名读取:
Value := CallbackUri.getQueryParameter(
StringToJString('SDK规定的参数名')
);
在看到实际回调地址之前,不要假定参数名一定是 status、transaction_id 或其他名称。
如果只是想确认应用是否收到了 deep link,也可以直接读取:
if Data.getDataString <> nil then
Memo1.Text := JStringToString(Data.getDataString);
Manifest 必须与回调地址一致
当前配置只能匹配这种结构的地址:
scheme_return://pay-response/...
对应的配置是:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="scheme_return"
android:host="pay-response" />
</intent-filter>
这里的 scheme_return 对应 URI 的 scheme,pay-response 对应 URI 的 host。两者都必须与支付应用实际返回的 URI 一致。
例如,下面的地址可以匹配上述过滤器:
scheme_return://pay-response?result=success
下面这个地址则不一定能匹配:
scheme_return://payment-result?result=success
因为它的 host 是 payment-result。
如果支付 SDK 只允许指定 return_scheme,而回调 host 由 SDK 固定生成,就要按照 SDK 实际生成的 host 配置 Manifest,不能只根据发送请求时使用的参数推断 host。
不要编码整个请求 URI
发送代码中还有一个潜在问题:
TIdURI.URLEncode(uri)
不要对完整 URI 进行 URL 编码,否则 :、/、?、& 等结构字符可能也会被编码,进而改变 URI 的含义。完整 URI 应直接解析,只对各个参数值单独编码。
如果 valorTotal 只包含 SDK 要求的数字格式,可以先这样写:
var
Uri: string;
Intent: JIntent;
begin
Uri :=
'payment-app://pay' +
'?return_scheme=scheme_return' +
'&amount=' + valorTotal +
'&transaction_type=debit';
Intent := TJIntent.JavaClass.init(
TJIntent.JavaClass.ACTION_VIEW,
TJnet_Uri.JavaClass.parse(StringToJString(Uri))
);
SharedActivity.startActivity(Intent);
end;
更稳妥的方式是使用 Uri.Builder 构造查询参数,让 Android API 负责参数值的编码:
var
PaymentUri: Jnet_Uri;
Intent: JIntent;
begin
PaymentUri :=
TJnet_Uri.JavaClass.parse(
StringToJString('payment-app://pay')
).buildUpon
.appendQueryParameter(
StringToJString('return_scheme'),
StringToJString('scheme_return')
)
.appendQueryParameter(
StringToJString('amount'),
StringToJString(valorTotal)
)
.appendQueryParameter(
StringToJString('transaction_type'),
StringToJString('debit')
)
.build;
Intent := TJIntent.JavaClass.init(
TJIntent.JavaClass.ACTION_VIEW,
PaymentUri
);
SharedActivity.startActivity(Intent);
end;
同时处理冷启动和已运行状态
Android 回调进入应用时有两种情况:
- 应用尚未运行,回调 Intent 会成为启动 Intent。
- 应用已经运行,Activity 会通过
onNewIntent收到新的 Intent。
现有代码同时使用 MainActivity.getIntent 和 TMessageReceivedNotification,处理方向没有问题,可以保留这两条接收路径:
procedure TForm1.FormCreate(Sender: TObject);
var
AppEventService: IFMXApplicationEventService;
begin
if TPlatformServices.Current.SupportsPlatformService(
IFMXApplicationEventService,
AppEventService
) then
AppEventService.SetApplicationEventHandler(HandleAppEvent);
MainActivity.registerIntentAction(
TJIntent.JavaClass.ACTION_VIEW
);
TMessageManager.DefaultManager.SubscribeToMessage(
TMessageReceivedNotification,
HandleActivityMessage
);
end;
窗体销毁时要取消订阅,以免处理器被重复注册,或者继续访问已经释放的窗体:
procedure TForm1.FormDestroy(Sender: TObject);
begin
TMessageManager.DefaultManager.Unsubscribe(
TMessageReceivedNotification,
HandleActivityMessage
);
end;
应用恢复时读取启动 Intent:
function TForm1.HandleAppEvent(
AAppEvent: TApplicationEvent;
AContext: TObject
): Boolean;
var
StartupIntent: JIntent;
begin
Result := False;
if AAppEvent <> TApplicationEvent.BecameActive then
Exit;
StartupIntent := MainActivity.getIntent;
if StartupIntent <> nil then
Result := HandleIntentAction(StartupIntent);
end;
收到新的 Intent 消息时直接处理:
procedure TForm1.HandleActivityMessage(
const Sender: TObject;
const M: TMessage
);
begin
if M is TMessageReceivedNotification then
HandleIntentAction(
TMessageReceivedNotification(M).Value
);
end;
避免重复处理同一个回调
应用多次进入前台时,MainActivity.getIntent 可能一直返回同一个 Intent,导致同一笔支付结果被重复处理。业务层需要根据支付流水号等字段进行幂等判断。
处理完成后,也可以清除当前 Activity 保存的 Intent:
MainActivity.setIntent(
TJIntent.JavaClass.init
);
是否清除要根据应用的生命周期设计决定。即使已经清除 Intent,仍要通过订单号或交易号对支付结果去重,不能只依赖界面状态。
为什么之前始终得到 nil
应用并不是还在“等待 response”,问题在于读取位置不对:
Data.getExtras
这里读取的是 Intent 附加数据,而 deep link 的主要内容在:
Data.getData
可以按以下顺序排查:
- 确认
Data不为nil。 - 确认
Data.getAction是ACTION_VIEW。 - 确认
Data.getData不为nil。 - 输出
Data.getDataString,查看完整的回调地址。 - 校验 scheme 和 host。
- 按照 SDK 定义的参数名调用
getQueryParameter。 - 对支付结果进行签名校验和幂等处理。
如果 HandleIntentAction 已经被调用,但 Data.getData 仍然是 nil,就需要记录 Data.getAction、Data.getType 和 extras 的键名,确认支付应用实际采用的是 deep link、extras,还是其他返回协议。按照当前 Manifest 中的 ACTION_VIEW 和 URI 配置,应优先从 Intent.getData 读取。
备注:内容仅供参考。
