fix: 修复抖音支付双端UTS编译兼容
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$HBuilderXHome,
|
||||
[Parameter(Mandatory = $true)][string]$SdkJar
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
# Compile the generated UTS and the original native bridge against real SDKs.
|
||||
# First export App resources using HBuilderX. SdkJar is classes.jar extracted
|
||||
# from the official dy-pay-sdk-tob 1.1.0.9 AAR, not a mock or reflection shim.
|
||||
$projectDir = Split-Path -Parent $PSScriptRoot
|
||||
$runtimeDir = Join-Path $HBuilderXHome 'plugins/uniapp-runextension'
|
||||
$compilerDir = Join-Path $runtimeDir 'kotlinc'
|
||||
$generated = Join-Path $projectDir 'unpackage/resources/uni_modules/tb-douyin-pay/utssdk/app-android/src/index.kt'
|
||||
$native = Join-Path $projectDir 'uni_modules/tb-douyin-pay/utssdk/app-android/TbDouyinPayNative.kt'
|
||||
foreach ($required in @($SdkJar, $generated, $native, (Join-Path $compilerDir 'lib/kotlin-compiler.jar'))) {
|
||||
if (!(Test-Path -LiteralPath $required)) { throw "Missing dependency or source: $required" }
|
||||
}
|
||||
$taskClasspath = ((Get-ChildItem -LiteralPath (Join-Path $runtimeDir 'lib') -Filter '*.jar').FullName + @((Resolve-Path -LiteralPath $SdkJar).Path)) -join ';'
|
||||
$resultDir = New-Item -ItemType Directory -Path (Join-Path ([IO.Path]::GetTempPath()) ('tb-douyin-kotlin-' + [guid]::NewGuid().ToString('N')))
|
||||
$resultJar = Join-Path $resultDir.FullName 'tb-douyin-pay.jar'
|
||||
& java -cp (Join-Path $compilerDir 'lib/*') org.jetbrains.kotlin.cli.jvm.K2JVMCompiler `
|
||||
-kotlin-home $compilerDir $generated $native -classpath $taskClasspath -d $resultJar
|
||||
if ($LASTEXITCODE -ne 0) { throw "Kotlin compilation failed: $LASTEXITCODE" }
|
||||
Write-Output "Kotlin compilation passed: $resultJar"
|
||||
Write-Output 'This check does not build/sign an APK or execute a payment.'
|
||||
@@ -0,0 +1,108 @@
|
||||
// Exercises the actual shared UTS logic as JS, not a duplicate implementation.
|
||||
// This does not replace Kotlin/Swift native compilation or device tests.
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const vm = require('node:vm');
|
||||
const hx = process.env.HBUILDERX_HOME;
|
||||
assert.ok(hx, 'Set HBUILDERX_HOME to the HBuilderX installation directory');
|
||||
const ts = require(path.join(hx, 'plugins/uniapp-uts-v1/node_modules/@dcloudio/uni-uts-v1/lib/typescript/lib/typescript.js'));
|
||||
const source = fs.readFileSync(path.join(__dirname, '../uni_modules/tb-douyin-pay/utssdk/common.uts'), 'utf8');
|
||||
const compiled = ts.transpileModule(source, {
|
||||
compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS },
|
||||
}).outputText;
|
||||
const info = { appid: 'app', partnerid: 'merchant', prepayid: 'prepay', package: 'Sign=DYPay',
|
||||
noncestr: 'nonce', timestamp: '1780000000', sign: 'signed+value/==' };
|
||||
function load(stringify = JSON.stringify) {
|
||||
const timers = new Map();
|
||||
let timerId = 0;
|
||||
const exports = {};
|
||||
const ctx = { exports, JSON: { stringify, parseObject(raw) {
|
||||
const value = JSON.parse(raw);
|
||||
return value == null ? null : { getString: key => typeof value[key] === 'string' ? value[key] : null };
|
||||
} }, setTimeout(fn) { timers.set(++timerId, fn); return timerId; },
|
||||
clearTimeout(id) { timers.delete(id); } };
|
||||
vm.runInNewContext(compiled, ctx);
|
||||
assert.equal(exports.configure('app'), true);
|
||||
return { ...exports, timers };
|
||||
}
|
||||
|
||||
test('shared bridge keeps signed fields unchanged and handles default/explicit loading', () => {
|
||||
for (const showLoading of [undefined, null, true, false]) {
|
||||
const bridge = load();
|
||||
const results = [];
|
||||
bridge.invoke({ payInfo: info, showLoading }, result => results.push(result), (payload, loading, done) => {
|
||||
assert.deepEqual(JSON.parse(payload), info);
|
||||
assert.equal(loading, showLoading ?? true);
|
||||
done('{"resultCode":"0","errorMsg":""}');
|
||||
});
|
||||
assert.equal(results.length, 1);
|
||||
assert.equal(results[0].resultCode, '0');
|
||||
assert.equal(results[0].needsQuery, true);
|
||||
assert.equal(bridge.timers.size, 0);
|
||||
}
|
||||
});
|
||||
|
||||
test('missing/null/empty payment fields never reach native code or take a lock', () => {
|
||||
const invalid = [null, undefined, {}, { ...info, appid: 'wrong' },
|
||||
{ ...info, package: 'wrong' }, { ...info, timestamp: '1780000000000' },
|
||||
{ ...info, noncestr: 'n'.repeat(33) }];
|
||||
for (const key of Object.keys(info)) {
|
||||
for (const value of [null, undefined, '', ' ']) invalid.push({ ...info, [key]: value });
|
||||
}
|
||||
for (const payInfo of invalid) {
|
||||
const bridge = load();
|
||||
const results = [];
|
||||
bridge.invoke({ payInfo }, r => results.push(r), () => assert.fail('Invalid native call'));
|
||||
assert.equal(results.length, 1);
|
||||
assert.equal(results[0].resultCode, '-1');
|
||||
assert.equal(bridge.timers.size, 0);
|
||||
assert.equal(bridge.configure('app'), true);
|
||||
}
|
||||
});
|
||||
|
||||
test('null or failed serialization returns an error before the lock and native call', () => {
|
||||
for (const stringify of [() => null, () => undefined, () => '', () => { throw Error('serialize'); }]) {
|
||||
const bridge = load(stringify);
|
||||
bridge.invoke({ payInfo: info }, r => assert.equal(r.resultCode, '-1'), () => assert.fail('Invalid payload'));
|
||||
assert.equal(bridge.timers.size, 0);
|
||||
assert.equal(bridge.configure('app'), true);
|
||||
}
|
||||
});
|
||||
|
||||
test('reentrant calls, duplicate callbacks and late callbacks after reset are isolated', () => {
|
||||
const bridge = load();
|
||||
const results = [];
|
||||
let oldDone;
|
||||
bridge.invoke({ payInfo: info }, r => results.push(r), (_, __, done) => { oldDone = done; });
|
||||
bridge.invoke({ payInfo: info }, r => assert.equal(r.resultCode, '103'), () => assert.fail('Duplicate pay'));
|
||||
assert.equal(bridge.configure('other'), false);
|
||||
bridge.resetPending();
|
||||
let newDone;
|
||||
bridge.invoke({ payInfo: info }, r => results.push(r), (_, __, done) => { newDone = done; });
|
||||
oldDone('{"resultCode":"0"}');
|
||||
assert.equal(results.length, 0);
|
||||
newDone('{"resultCode":"1"}');
|
||||
newDone('{"resultCode":"0"}');
|
||||
assert.equal(results.length, 1);
|
||||
assert.equal(results[0].resultCode, '1');
|
||||
for (const timer of bridge.timers.values()) timer();
|
||||
assert.equal(results.length, 1);
|
||||
});
|
||||
|
||||
test('timeout, malformed result and native exception remain query-required and unlock', () => {
|
||||
for (const mode of ['timeout', 'malformed', 'throw']) {
|
||||
const bridge = load();
|
||||
const results = [];
|
||||
bridge.invoke({ payInfo: info }, r => results.push(r), (_, __, done) => {
|
||||
if (mode === 'throw') throw Error('native');
|
||||
if (mode === 'malformed') done('{');
|
||||
});
|
||||
if (mode === 'timeout') for (const timer of bridge.timers.values()) timer();
|
||||
assert.equal(results.length, 1);
|
||||
assert.notEqual(results[0].resultCode, '0');
|
||||
assert.equal(results[0].needsQuery, true);
|
||||
assert.equal(bridge.configure('app'), true);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user