diff --git a/README.md b/README.md index 1c7eb39..6384624 100644 --- a/README.md +++ b/README.md @@ -390,6 +390,32 @@ const signCheckPass = alipaySdk.rsaCheck(signContent, sign, signType); console.log(signCheckPass); ``` +### 通过 HTTP 代理服务器调用 + +在需要固定 IP 白名单调用的场景下,可以通过配置 `config.proxyAgent` 来指定 HTTP 代理服务器调用。 + +```ts +import { AlipaySdk, ProxyAgent } from 'alipay-sdk'; + +// 实例化客户端 +const alipaySdk = new AlipaySdk({ + // 其他配置不展示 + // ... + proxyAgent: new ProxyAgent('http(s)://your-http-proxy-address'), +}); + +// 后续的所有 http 调用都会走此 HTTP 代理服务器 +const result = await alipaySdk.curl('POST', '/v3/alipay/user/deloauth/detail/query', { + body: { + date: '20230102', + offset: 20, + limit: 1, + }, +}); + +console.log(result); +``` + ## alipay-sdk v3 到 v4 的升级说明 从 v3 到 v4 有以下不兼容变更,请参考示例代码进行更新 diff --git a/src/alipay.ts b/src/alipay.ts index a2525ea..c5f62f0 100644 --- a/src/alipay.ts +++ b/src/alipay.ts @@ -4,6 +4,7 @@ import { Readable } from 'node:stream'; import urllib, { Agent, IncomingHttpHeaders } from 'urllib'; import type { HttpClientResponse, HttpMethod, RequestOptions, RawResponseWithMeta, + ProxyAgent, } from 'urllib'; import camelcaseKeys from 'camelcase-keys'; import snakeCaseKeys from 'snakecase-keys'; @@ -174,6 +175,7 @@ export interface AlipayCURLOptions { export class AlipaySdk { public readonly version = 'alipay-sdk-nodejs-4.0.0'; public config: Required; + #proxyAgent?: ProxyAgent; /** * @class @@ -205,6 +207,7 @@ export class AlipaySdk { // 普通公钥模式,传入了支付宝公钥 config.alipayPublicKey = this.formatKey(config.alipayPublicKey, 'PUBLIC KEY'); } + this.#proxyAgent = config.proxyAgent; this.config = Object.assign({ urllib, gateway: 'https://openapi.alipay.com/gateway.do', @@ -337,6 +340,7 @@ export class AlipaySdk { method: httpMethod, dataType: dataType === 'stream' ? 'stream' : 'text', timeout: options?.requestTimeout ?? this.config.timeout, + dispatcher: this.#proxyAgent, }; // 默认需要对响应做验签,确保响应是由支付宝返回的 let validateResponseSignature = true; @@ -598,6 +602,7 @@ export class AlipaySdk { ...formStream.headers(), }, content: new Readable().wrap(formStream as any), + dispatcher: this.#proxyAgent, }; // 计算签名 const signData = sign(method, signParams, config); @@ -839,6 +844,7 @@ export class AlipaySdk { // 'content-type': 'application/json', accept: 'application/json', }, + dispatcher: this.#proxyAgent, }); } catch (err: any) { debug('HttpClient Request error: %s', err); diff --git a/src/index.ts b/src/index.ts index 826e027..99cd2a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +export { ProxyAgent } from 'urllib'; export * from './types.js'; export * from './alipay.js'; export { AlipayFormData } from './form.js'; diff --git a/src/types.ts b/src/types.ts index d84df26..68e2df9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { ProxyAgent } from 'urllib'; + export type AlipaySdkSignType = 'RSA2' | 'RSA'; /** @@ -56,4 +58,6 @@ export interface AlipaySdkConfig { encryptKey?: string; /** 服务器地址 */ wsServiceUrl?: string; + /** httpClient 请求代理 */ + proxyAgent?: ProxyAgent; } diff --git a/test/alipay.test.ts b/test/alipay.test.ts index 0619ec9..5dbf285 100755 --- a/test/alipay.test.ts +++ b/test/alipay.test.ts @@ -12,6 +12,7 @@ import { } from './helper.js'; import { AlipayFormData, AlipayFormStream, AlipayRequestError, AlipaySdk, AlipaySdkConfig, + ProxyAgent, } from '../src/index.js'; import { aesDecryptText } from '../src/util.js'; @@ -127,6 +128,33 @@ describe('test/alipay.test.ts', () => { }); }); + it('使用 HTTP 代理调用', async () => { + if (!process.env.TEST_ALIPAY_HTTP_PROXY) { + return; + } + const proxyAgent = new ProxyAgent(process.env.TEST_ALIPAY_HTTP_PROXY); + const sdkWithProxy = new AlipaySdk({ + ...sdkStableConfig, + proxyAgent, + }); + await assert.rejects(async () => { + await sdkWithProxy.curl('POST', '/v3/alipay/user/info/share', { + body: { + auth_token: '20120823ac6ffaa4d2d84e7384bf983531473993', + }, + }); + }, err => { + console.error(err); + assert(err instanceof AlipayRequestError); + assert.match(err.message, /无效的访问令牌/); + assert.equal(err.links!.length, 1); + assert.equal(err.code, 'invalid-auth-token'); + assert(err.traceId); + assert.equal(err.responseHttpStatus, 401); + return true; + }); + }); + it('POST 文件上传,使用 AlipayFormData', async () => { // https://opendocs.alipay.com/open-v3/5aa91070_alipay.open.file.upload?scene=common&pathHash=c8e11ccc const filePath = getFixturesFile('demo.jpg');