fix(alipay): 适配 tobias 5.x 新 auth API,后端生成签名 authString

tobias 3.x+ 移除了顶层函数 aliPayAuth(appId, scope),
改为需要后端预签名的 Tobias().auth(authString)。

变更:
- alipay.provider.ts: 新增 generateMobileAuthString(scope) 方法,
  用 RSA2 私钥生成符合 Alipay SDK 格式的签名授权字符串
- auth.controller.ts: 新增 GET /auth/alipay/auth-string 接口
- pubspec.yaml: tobias ^3.0.0 → ^5.0.0
- auth_service.dart: 新增 getAlipayAuthString() 方法
- welcome_page.dart: 更新支付宝登录流程,先获取 authString 再调用 tobias

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-04 09:26:30 -08:00
parent a27baa1181
commit 828770add8
5 changed files with 115 additions and 8 deletions

View File

@ -86,10 +86,67 @@ export class AlipayProvider {
private readonly logger = new Logger('AlipayProvider'); private readonly logger = new Logger('AlipayProvider');
private readonly appId = process.env.ALIPAY_APP_ID; private readonly appId = process.env.ALIPAY_APP_ID;
// PIDPartner ID支付宝商户号16位数字2088xxxxxxxxxx
// 在支付宝开放平台「账号信息」页面查看。
// 若未配置则降级使用 appId部分沙箱环境可行正式环境必须配置真实 PID
private readonly pid = process.env.ALIPAY_PID || process.env.ALIPAY_APP_ID;
private readonly privateKey = process.env.ALIPAY_PRIVATE_KEY; private readonly privateKey = process.env.ALIPAY_PRIVATE_KEY;
private readonly gateway = private readonly gateway =
process.env.ALIPAY_GATEWAY || 'openapi.alipay.com'; process.env.ALIPAY_GATEWAY || 'openapi.alipay.com';
/**
* App tobias Flutter 使
*
* tobias 3.x+ `Tobias().auth(authString)`
* appId+scope Alipay SDK
*
*
* authString Alipay SDK App
* App auth_code POST /auth/alipay
*
* URL sign
* apiname=com.alipay.account.auth
* app_id=2021xxxxxxxx
* biz_type=openservice
* charset=UTF-8
* pid=2088xxxxxxxx PIDALIPAY_PID app_id
* product_id=APP_FAST_LOGIN
* scope=auth_user kuaijie
* sign_type=RSA2
* timestamp=2024-01-15 10:30:00
* sign=BASE64SIGNATURE
*
* @param scope 'auth_user'| 'kuaijie' user_id
*/
generateMobileAuthString(scope: string = 'auth_user'): string {
const timestamp = new Date()
.toISOString()
.replace('T', ' ')
.substring(0, 19);
const params: Record<string, string> = {
apiname: 'com.alipay.account.auth',
app_id: this.appId!,
biz_type: 'openservice',
charset: 'UTF-8',
pid: this.pid!,
product_id: 'APP_FAST_LOGIN',
scope,
sign_type: 'RSA2',
timestamp,
};
// 签名:与网关请求签名算法一致(字典序拼接 → RSA-SHA256 → Base64
const sign = this.sign(params);
// 拼接最终 authString字典序排列值 URL 编码,末尾附 sign
const sortedKeys = Object.keys(params).sort();
const encoded = sortedKeys
.map((k) => `${k}=${encodeURIComponent(params[k])}`)
.join('&');
return `${encoded}&sign=${encodeURIComponent(sign)}`;
}
/** /**
* Step 2: auth_code access_token * Step 2: auth_code access_token
* *

View File

@ -1,6 +1,8 @@
import { import {
Controller, Controller,
Post, Post,
Get,
Query,
Body, Body,
HttpCode, HttpCode,
HttpStatus, HttpStatus,
@ -14,6 +16,7 @@ import { ThrottlerGuard } from '@nestjs/throttler';
import { AuthService } from '../../../application/services/auth.service'; import { AuthService } from '../../../application/services/auth.service';
import { WechatService } from '../../../application/services/wechat.service'; import { WechatService } from '../../../application/services/wechat.service';
import { AlipayService } from '../../../application/services/alipay.service'; import { AlipayService } from '../../../application/services/alipay.service';
import { AlipayProvider } from '../../../infrastructure/alipay/alipay.provider';
import { GoogleService } from '../../../application/services/google.service'; import { GoogleService } from '../../../application/services/google.service';
import { AppleService } from '../../../application/services/apple.service'; import { AppleService } from '../../../application/services/apple.service';
import { RegisterDto } from '../dto/register.dto'; import { RegisterDto } from '../dto/register.dto';
@ -42,6 +45,7 @@ export class AuthController {
private readonly alipayService: AlipayService, private readonly alipayService: AlipayService,
private readonly googleService: GoogleService, private readonly googleService: GoogleService,
private readonly appleService: AppleService, private readonly appleService: AppleService,
private readonly alipayProvider: AlipayProvider,
) {} ) {}
/* ── SMS 验证码 ── */ /* ── SMS 验证码 ── */
@ -273,6 +277,28 @@ export class AuthController {
/* ── 支付宝登录 / 注册 ── */ /* ── 支付宝登录 / 注册 ── */
/**
* tobias 3.x+ 使
*
* tobias 3.x `Tobias().auth(authString)` authString
* appId+scope
*
*
* Flutter GET /auth/alipay/auth-string
* Tobias().auth(authString) App
* auth_code
* POST /auth/alipay ()
*/
@Get('alipay/auth-string')
@ApiOperation({ summary: '获取支付宝移动端签名授权字符串tobias 3.x+' })
@ApiResponse({ status: 200, description: '返回签名后的 authString' })
getAlipayAuthString(@Query('scope') scope?: string) {
const authString = this.alipayProvider.generateMobileAuthString(
scope || 'auth_user',
);
return { code: 0, data: { authString }, message: 'ok' };
}
@Post('alipay') @Post('alipay')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '支付宝一键登录(新用户自动注册)' }) @ApiOperation({ summary: '支付宝一键登录(新用户自动注册)' })

View File

@ -256,9 +256,21 @@ class AuthService {
// //
/// tobias 3.x+
///
/// tobias 3.x appId+scope
/// RSA2 authString
/// `Tobias().auth(authString)`
///
/// : `{ 'authString': '...' }`
Future<String> getAlipayAuthString() async {
final resp = await _api.get('/api/v1/auth/alipay/auth-string');
return resp.data['data']['authString'] as String;
}
/// / /// /
/// ///
/// [authCode] tobias Alipay.authCode()3 /// [authCode] tobias Tobias().auth() auth_code3
Future<AuthResult> loginByAlipay({ Future<AuthResult> loginByAlipay({
required String authCode, required String authCode,
String? referralCode, String? referralCode,

View File

@ -29,7 +29,7 @@ import '../../../../core/services/auth_service.dart';
/// ///
/// ///
/// : fluwx.authBy(NormalAuth) WeChatAuthResponse(code) POST /auth/wechat /// : fluwx.authBy(NormalAuth) WeChatAuthResponse(code) POST /auth/wechat
/// : tobias.aliPayAuth Map(result auth_code) POST /auth/alipay /// : GET /auth/alipay/auth-string Tobias().auth(authStr) Map(result auth_code) POST /auth/alipay
/// Google: google_sign_in.signIn GoogleSignInAuthentication.idToken POST /auth/google /// Google: google_sign_in.signIn GoogleSignInAuthentication.idToken POST /auth/google
/// Apple: sign_in_with_apple.getAppleIDCredential identityToken POST /auth/apple /// Apple: sign_in_with_apple.getAppleIDCredential identityToken POST /auth/apple
class WelcomePage extends StatefulWidget { class WelcomePage extends StatefulWidget {
@ -58,6 +58,10 @@ class _WelcomePageState extends State<WelcomePage> {
// registerApi main.dart app 使 // registerApi main.dart app 使
final _fluwx = Fluwx(); final _fluwx = Fluwx();
// tobias 5.x 使tobias 3.x+ aliPayAuth / isAliPayInstalled
// auth(authString) authString appId+scope
final _tobias = Tobias();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -138,7 +142,8 @@ class _WelcomePageState extends State<WelcomePage> {
// <queries><package android:name="com.eg.android.AlipayGphone" /></queries> // <queries><package android:name="com.eg.android.AlipayGphone" /></queries>
Future<void> _onAlipayTap() async { Future<void> _onAlipayTap() async {
final installed = await isAliPayInstalled; // tobias 5.x: getter isAliPayInstalled
final installed = await _tobias.isAliPayInstalled;
if (!installed) { if (!installed) {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@ -150,10 +155,17 @@ class _WelcomePageState extends State<WelcomePage> {
setState(() => _alipayLoading = true); setState(() => _alipayLoading = true);
try { try {
// ALIPAY_APP_ID --dart-define=ALIPAY_APP_ID=xxx // tobias 3.x+ aliPayAuth(appId, scope)
// .env ALIPAY_APP_ID AppID // Tobias().auth(authString)authString RSA2
const alipayAppId = String.fromEnvironment('ALIPAY_APP_ID', defaultValue: ''); // : Alipay
final result = await aliPayAuth(alipayAppId, 'auth_user'); //
// :
// 1. GET /auth/alipay/auth-string authString
// 2. Tobias().auth(authString) App
// 3. result['result'] auth_code
// 4. auth_code POST /auth/alipay
final authString = await AuthService.instance.getAlipayAuthString();
final result = await _tobias.auth(authString);
final status = result['resultStatus']?.toString() ?? ''; final status = result['resultStatus']?.toString() ?? '';
if (status != '9000') { if (status != '9000') {

View File

@ -27,7 +27,7 @@ dependencies:
share_plus: ^10.0.2 share_plus: ^10.0.2
flutter_secure_storage: ^9.2.2 flutter_secure_storage: ^9.2.2
fluwx: ^5.0.0 fluwx: ^5.0.0
tobias: ^3.0.0 tobias: ^5.0.0
google_sign_in: ^6.2.1 google_sign_in: ^6.2.1
sign_in_with_apple: ^6.1.0 sign_in_with_apple: ^6.1.0