@@ -14,7 +14,8 @@ import * as camelcaseKeys from 'camelcase-keys';
14
14
import * as snakeCaseKeys from 'snakecase-keys' ;
15
15
16
16
import AliPayForm from './form' ;
17
- import { sign , ALIPAY_ALGORITHM_MAPPING } from './util' ;
17
+ import { sign , ALIPAY_ALGORITHM_MAPPING , aesDecrypt } from './util' ;
18
+ import { getSNFromPath , getSN , loadPublicKey , loadPublicKeyFromPath } from './antcertutil' ;
18
19
19
20
const pkg = require ( '../package.json' ) ;
20
21
@@ -43,6 +44,26 @@ export interface AlipaySdkConfig {
43
44
urllib ?: any ;
44
45
/** 指定private key类型, 默认: PKCS1, PKCS8: PRIVATE KEY, PKCS1: RSA PRIVATE KEY */
45
46
keyType ?: 'PKCS1' | 'PKCS8' ;
47
+ /** 应用公钥证书文件路径 */
48
+ appCertPath ?: string ;
49
+ /** 应用公钥证书文件内容 */
50
+ appCertContent ?: string | Buffer ;
51
+ /** 应用公钥证书sn */
52
+ appCertSn ?: string ;
53
+ /** 支付宝根证书文件路径 */
54
+ alipayRootCertPath ?: string ;
55
+ /** 支付宝根证书文件内容 */
56
+ alipayRootCertContent ?: string | Buffer ;
57
+ /** 支付宝根证书sn */
58
+ alipayRootCertSn ?: string ;
59
+ /** 支付宝公钥证书文件路径 */
60
+ alipayPublicCertPath ?: string ;
61
+ /** 支付宝公钥证书文件内容 */
62
+ alipayPublicCertContent ?: string | Buffer ;
63
+ /** 支付宝公钥证书sn */
64
+ alipayCertSn ?: string ;
65
+ /** AES密钥,调用AES加解密相关接口时需要 */
66
+ encryptKey ?: string ;
46
67
}
47
68
48
69
export interface AlipaySdkCommonResult {
@@ -55,6 +76,8 @@ export interface AlipaySdkCommonResult {
55
76
export interface IRequestParams {
56
77
[ key : string ] : any ;
57
78
bizContent ?: any ;
79
+ // 自动AES加解密
80
+ needEncrypt ?:boolean ;
58
81
}
59
82
60
83
export interface IRequestOption {
@@ -75,11 +98,26 @@ class AlipaySdk {
75
98
if ( ! config . privateKey ) { throw Error ( 'config.privateKey is required' ) ; }
76
99
77
100
const privateKeyType = config . keyType === 'PKCS8' ? 'PRIVATE KEY' : 'RSA PRIVATE KEY' ;
78
- config . privateKey = this . formatKey ( config . privateKey , privateKeyType ) ;
79
- if ( config . alipayPublicKey ) {
101
+ config . privateKey = this . formatKey ( config . privateKey , privateKeyType ) ;
102
+ // 普通公钥模式和证书模式二选其一,传入了证书路径或内容认为是证书模式
103
+ if ( config . appCertPath || config . appCertContent ) {
104
+ // 证书模式,优先处理传入了证书内容的情况,其次处理传入证书文件路径的情况
105
+ // 应用公钥证书序列号提取
106
+ config . appCertSn = is . empty ( config . appCertContent ) ? getSNFromPath ( config . appCertPath , false )
107
+ : getSN ( config . appCertContent , false ) ;
108
+ // 支付宝公钥证书序列号提取
109
+ config . alipayCertSn = is . empty ( config . alipayPublicCertContent ) ? getSNFromPath ( config . alipayPublicCertPath , false )
110
+ : getSN ( config . alipayPublicCertContent , false ) ;
111
+ // 支付宝根证书序列号提取
112
+ config . alipayRootCertSn = is . empty ( config . alipayRootCertContent ) ? getSNFromPath ( config . alipayRootCertPath , true )
113
+ : getSN ( config . alipayRootCertContent , true ) ;
114
+ config . alipayPublicKey = is . empty ( config . alipayPublicCertContent ) ? loadPublicKeyFromPath ( config . alipayPublicCertPath )
115
+ : loadPublicKey ( config . alipayPublicCertContent ) ;
116
+ config . alipayPublicKey = this . formatKey ( config . alipayPublicKey , 'PUBLIC KEY' ) ;
117
+ } else if ( config . alipayPublicKey ) {
118
+ // 普通公钥模式,传入了支付宝公钥
80
119
config . alipayPublicKey = this . formatKey ( config . alipayPublicKey , 'PUBLIC KEY' ) ;
81
120
}
82
-
83
121
this . config = Object . assign ( {
84
122
urllib,
85
123
gateway : 'https://openapi.alipay.com/gateway.do' ,
@@ -116,12 +154,13 @@ class AlipaySdk {
116
154
'app_id' , 'method' , 'format' , 'charset' ,
117
155
'sign_type' , 'sign' , 'timestamp' , 'version' ,
118
156
'notify_url' , 'return_url' , 'auth_token' , 'app_auth_token' ,
157
+ 'appCertSn' , 'alipayRootCertSn' ,
119
158
] ;
120
159
121
160
for ( const key in params ) {
122
161
if ( urlArgs . indexOf ( key ) > - 1 ) {
123
162
const val = encodeURIComponent ( params [ key ] ) ;
124
- requestUrl = `${ requestUrl } ${ requestUrl . includes ( '?' ) ? '&' : '?' } ${ key } =${ val } ` ;
163
+ requestUrl = `${ requestUrl } ${ requestUrl . includes ( '?' ) ? '&' : '?' } ${ key } =${ val } ` ;
125
164
// 删除 postData 中对应的数据
126
165
delete params [ key ] ;
127
166
}
@@ -135,8 +174,8 @@ class AlipaySdk {
135
174
const config = this . config ;
136
175
let signParams = { } as { [ key : string ] : string | Object } ;
137
176
let formData = { } as { [ key : string ] : string | Object | fs . ReadStream } ;
138
- const infoLog = ( option . log && is . fn ( option . log . info ) ) ? option . log . info : null ;
139
- const errorLog = ( option . log && is . fn ( option . log . error ) ) ? option . log . error : null ;
177
+ const infoLog = ( option . log && is . fn ( option . log . info ) ) ? option . log . info : null ;
178
+ const errorLog = ( option . log && is . fn ( option . log . error ) ) ? option . log . error : null ;
140
179
141
180
option . formData . getFields ( ) . forEach ( ( field ) => {
142
181
// 字段加入签名参数(文件不需要签名)
@@ -171,7 +210,7 @@ class AlipaySdk {
171
210
json : false ,
172
211
timeout : config . timeout ,
173
212
headers : { 'user-agent' : this . sdkVersion } ,
174
- } , ( err , { } , body ) => {
213
+ } , ( err , { } , body ) => {
175
214
if ( err ) {
176
215
err . message = '[AlipaySdk]exec error' ;
177
216
errorLog && errorLog ( err ) ;
@@ -204,7 +243,7 @@ class AlipaySdk {
204
243
private pageExec ( method : string , option : IRequestOption = { } ) : Promise < string > {
205
244
let signParams = { alipaySdk : this . sdkVersion } as { [ key : string ] : string | Object } ;
206
245
const config = this . config ;
207
- const infoLog = ( option . log && is . fn ( option . log . info ) ) ? option . log . info : null ;
246
+ const infoLog = ( option . log && is . fn ( option . log . info ) ) ? option . log . info : null ;
208
247
209
248
option . formData . getFields ( ) . forEach ( ( field ) => {
210
249
signParams [ field . name ] = field . value ;
@@ -248,7 +287,7 @@ class AlipaySdk {
248
287
249
288
// 消息验签
250
289
private notifyRSACheck ( signArgs : { [ key : string ] : any } , signStr : string , signType : 'RSA' | 'RSA2' ) {
251
- const signContent = Object . keys ( signArgs ) . sort ( ) . filter ( val => val ) . map ( ( key ) => {
290
+ const signContent = Object . keys ( signArgs ) . sort ( ) . filter ( val => val ) . map ( ( key ) => {
252
291
let value = signArgs [ key ] ;
253
292
254
293
if ( Array . prototype . toString . call ( value ) !== '[object String]' ) {
@@ -334,8 +373,8 @@ class AlipaySdk {
334
373
// 计算签名
335
374
const signData = sign ( method , params , config ) ;
336
375
const { url, execParams } = this . formatUrl ( config . gateway , signData ) ;
337
- const infoLog = ( option . log && is . fn ( option . log . info ) ) ? option . log . info : null ;
338
- const errorLog = ( option . log && is . fn ( option . log . error ) ) ? option . log . error : null ;
376
+ const infoLog = ( option . log && is . fn ( option . log . info ) ) ? option . log . info : null ;
377
+ const errorLog = ( option . log && is . fn ( option . log . error ) ) ? option . log . error : null ;
339
378
340
379
infoLog && infoLog ( '[AlipaySdk]start exec, url: %s, method: %s, params: %s' ,
341
380
url , method , JSON . stringify ( execParams ) ) ;
@@ -349,46 +388,50 @@ class AlipaySdk {
349
388
timeout : config . timeout ,
350
389
headers : { 'user-agent' : this . sdkVersion } ,
351
390
} )
352
- . then ( ( ret : { status : number , data : string } ) => {
353
- infoLog && infoLog ( '[AlipaySdk]exec response: %s' , ret ) ;
354
-
355
- if ( ret . status === 200 ) {
356
- /**
357
- * 示例响应格式
358
- * {"alipay_trade_precreate_response":
359
- * {"code": "10000","msg": "Success","out_trade_no": "111111","qr_code": "https:\/\/"},
360
- * "sign": "abcde="
361
- * }
362
- * 或者
363
- * {"error_response":
364
- * {"code":"40002","msg":"Invalid Arguments","sub_code":"isv.code-invalid","sub_msg":"授权码code无效"},
365
- * }
366
- */
367
- const result = JSON . parse ( ret . data ) ;
368
- const responseKey = `${ method . replace ( / \. / g, '_' ) } _response` ;
369
- const data = result [ responseKey ] ;
370
-
371
- if ( data ) {
372
- // 按字符串验签
373
- const validateSuccess = option . validateSign ? this . checkResponseSign ( ret . data , responseKey ) : true ;
374
-
375
- if ( validateSuccess ) {
376
- resolve ( config . camelcase ? camelcaseKeys ( data , { deep : true } ) : data ) ;
377
- } else {
378
- reject ( { serverResult : ret , errorMessage : '[AlipaySdk]验签失败' } ) ;
391
+ . then ( ( ret : { status : number , data : string } ) => {
392
+ infoLog && infoLog ( '[AlipaySdk]exec response: %s' , ret ) ;
393
+
394
+ if ( ret . status === 200 ) {
395
+ /**
396
+ * 示例响应格式
397
+ * {"alipay_trade_precreate_response":
398
+ * {"code": "10000","msg": "Success","out_trade_no": "111111","qr_code": "https:\/\/"},
399
+ * "sign": "abcde="
400
+ * }
401
+ * 或者
402
+ * {"error_response":
403
+ * {"code":"40002","msg":"Invalid Arguments","sub_code":"isv.code-invalid","sub_msg":"授权码code无效"},
404
+ * }
405
+ */
406
+ const result = JSON . parse ( ret . data ) ;
407
+ const responseKey = `${ method . replace ( / \. / g, '_' ) } _response` ;
408
+ let data = result [ responseKey ] ;
409
+
410
+ if ( data ) {
411
+ if ( params . needEncrypt ) {
412
+ data = aesDecrypt ( data , config . encryptKey ) ;
413
+ }
414
+
415
+ // 按字符串验签
416
+ const validateSuccess = option . validateSign ? this . checkResponseSign ( ret . data , responseKey ) : true ;
417
+
418
+ if ( validateSuccess ) {
419
+ resolve ( config . camelcase ? camelcaseKeys ( data , { deep : true } ) : data ) ;
420
+ } else {
421
+ reject ( { serverResult : ret , errorMessage : '[AlipaySdk]验签失败' } ) ;
422
+ }
379
423
}
424
+
425
+ reject ( { serverResult : ret , errorMessage : '[AlipaySdk]HTTP 请求错误' } ) ;
380
426
}
381
427
382
428
reject ( { serverResult : ret , errorMessage : '[AlipaySdk]HTTP 请求错误' } ) ;
383
- }
384
-
385
- reject ( { serverResult : ret , errorMessage : '[AlipaySdk]HTTP 请求错误' } ) ;
386
- } )
387
- . catch ( ( err ) => {
388
- err . message = '[AlipaySdk]exec error' ;
389
- errorLog && errorLog ( err ) ;
390
- reject ( err ) ;
391
- } ) ;
429
+ } )
430
+ . catch ( ( err ) => {
431
+ err . message = '[AlipaySdk]exec error' ;
432
+ errorLog && errorLog ( err ) ;
433
+ reject ( err ) ;
434
+ } ) ;
392
435
} ) ;
393
436
}
394
437
0 commit comments