Repository: Luncher/alipay Branch: master Commit: 61e986e0ca5b Files: 37 Total size: 51.3 KB Directory structure: gitextract_mynp4_bi/ ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── src/ │ ├── config/ │ │ ├── env.ts │ │ └── index.ts │ ├── index.ts │ ├── utils.ts │ └── validator/ │ ├── index.ts │ └── schema/ │ ├── bill_download_query.ts │ ├── cancel_order.ts │ ├── create_app_order.ts │ ├── create_page_order.ts │ ├── create_web_order.ts │ ├── index.ts │ ├── notify.ts │ ├── query_order.ts │ ├── toaccount_transfer.ts │ ├── trade_close.ts │ ├── trade_precreate.ts │ ├── trade_refund.ts │ ├── trade_refund_query.ts │ ├── trade_settle.ts │ └── verify_payment.ts ├── tests/ │ ├── index.spec.ts │ └── keys/ │ ├── alipay_public_key.pem │ ├── app_priv_key.pem │ ├── app_priv_key_with_head.pem │ ├── app_priv_key_with_rsa_head.pem │ └── app_public_key.pem ├── tsconfig.eslint.json └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintignore ================================================ # don't ever lint node_modules node_modules # don't lint build output (make sure it's set to your correct build folder name) dist # don't lint nyc coverage output coverage ================================================ FILE: .eslintrc.js ================================================ module.exports = { root: true, parser: '@typescript-eslint/parser', extends: ['airbnb-typescript/base'], parserOptions: { project: './tsconfig.eslint.json' }, env: { node: true }, rules: { 'max-len': ['error', { code: 120 }], 'comma-dangle': ['error', 'never'], semi: 'off', '@typescript-eslint/semi': ['error', 'never'], 'import/prefer-default-export': 'off' } } ================================================ FILE: .gitignore ================================================ node_modules .DS_Store .vscode dist/** ================================================ FILE: .npmignore ================================================ src/** tests/** coverage/** .babelrc .eslint* debug.js ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - v12.18.2 sudo: false cache: directories: [node_modules] install: - npm install - npm install -g codecov after_script: - npm run cov - codecov script: - npm run lint - npm run test ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 linchen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # alipay [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![David Status][david-image]][david-url] [npm-url]: https://www.npmjs.com/package/alipay-mobile [npm-image]: https://img.shields.io/npm/v/alipay-mobile.svg?style=flat [download-url]: https://www.npmjs.com/package/alipay-mobile [download-image]: https://img.shields.io/npm/dm/alipay-mobile.svg?style=flat [david-url]: https://david-dm.org/Luncher/alipay-mobile [david-image]: https://david-dm.org/Luncher/alipay-mobile.svg?style=flat [travis-url]: https://travis-ci.org/Luncher/alipay [travis-image]: https://img.shields.io/travis/Luncher/alipay.svg?style=flat [codecov-url]: https://codecov.io/gh/Luncher/alipay [codecov-image]: https://img.shields.io/codecov/c/github/Luncher/alipay.svg?style=flat [蚂蚁金服开放平台](https://openhome.alipay.com/platform/home.htm)`Node.js` SDK。 --- ## 安装 ``` javascript npm i alipay-mobile -S ``` ## 基本使用 ``` javascript const fs = require('fs') const Alipay = require('alipay-mobile').default const read = filename => { return fs.readFileSync(path.resolve(__dirname, filename)) } //notify_url: 异步通知url //app_id: 开放平台 appid //appPrivKeyFile: 你的应用私钥 //alipayPubKeyFile: 蚂蚁金服公钥 const options = { app_id: '2016080100137766', appPrivKeyFile: read('./keys/app_priv_key.pem'), alipayPubKeyFile: read('./keys/alipay_public_key.pem') } const service = new Alipay(options) const data = { subject: '辣条', out_trade_no: '1232423', total_amount: '100' } const result = service.createOrder(data) assert(result.code == 0, result.message) ``` ## 说明 >详细参数请参考接口对应的官方文档 ### 构造函数支持的参数 ```ts export interface AlipayOption { appPrivKeyFile: string // 应用私钥 alipayPubKeyFile: string // 支付宝公钥 appId: string // 应用ID notifyUrl?: string // 支付宝异步通知URL gatewayUrl?: string // 接口网关地址 } ``` ### 接口返回错误码以及错误信息 ``` ts export enum AlipayNormalResponseCode { OK = 0, EXCEPTION = -1, SIGNATURE_ERROR = -2, SUCCESS = 10000, UNAVALIABLE = 20000, INSUFFICIENT_AUTHORIZATION = 20001, MISSING_REQUIRED_ARGS = 40001, INVALID_ARGS = 40002, PROCESSING_FAILURE = 40004, PERMISSION_DENIED = 40006 } export enum AlipayPaymentResponseCode { SUCCESS = '9000', PROCESSING = '8000', FAILURE = '4000', REPEAT_REQ = '5000', USER_CANCEL = '6001', NETWORK_ERROR = '6002', UNKNOW = '6004' } ``` ### 接口返回格式 ``` javascript { code: 错误码, message: 错误信息, data: 蚂蚁金服返回的原始数据//可能为空对象 } ``` --- ## 功能列表 - [x] 创建订单 - [x] 取消订单 - [x] 订单查询 - [x] 验证支付状态 - [x] 订单状态异步推送 - [x] 预创建订单 - [x] 申请退款 - [x] 退款查询 - [x] 交易结算 - [x] 关闭交易 - [x] 账单下载地址查询 - [x] 单笔转账到支付宝账户 --- ## API 说明 ### 创建订单`createOrder` [APP支付官方文档](https://docs.open.alipay.com/204/105465/) >用于返回给APP,传递给支付宝端发起交易申请 ```javascript const service = new Alipay(options) const data = { subject: '辣条', out_trade_no: '1232423', total_amount: '100' } const result = service.createOrder(data) assert(result.code == 0, result.message) //result.data 用于返回给APP,传递给支付宝端发起交易申请 ``` --- ### 创建网页订单`createWebOrderURL` [手机网页支付官方文档](https://docs.open.alipay.com/203/107090/) >该接口用于支付宝手机网页支付,服务端调用该接口生成一个`URL`返回给客户端, 客户端拿到该`URL`之后跳转到该URL发起支付请求。支付结束支付宝会跳转到客户端填写的`return_url`。 ``` javascript const service = new Alipay(options) const data = { subject: '辣条', out_trade_no: '1232423', total_amount: '100' } const basicParams = { return_url: 'http://xxx.com' } const result = service.createWebOrderURL(data, basicParams) assert(result.code == 0, result.message) ``` --- ### 创建pc端订单`createPageOrderURL` [创建pc端订单官方文档](https://docs.open.alipay.com/270/105899/) ``` javascript const service = new Alipay(options) const data = { subject: '辣条', out_trade_no: '1232423', total_amount: '100' } const basicParams = { return_url: 'http://xxx.com' } const result = service.createPageOrderURL(data, basicParams) assert(result.code == 0, result.message) ``` --- ### 订单查询`queryOrder` [订单查询官方文档](https://docs.open.alipay.com/api_1/alipay.trade.query) ``` javascript const outTradeNo = '1232423' return service.queryOrder({ out_trade_no: outTradeNo }) .then(result => { assert(result.code == '40004', result.message) }) ``` --- ### 取消订单`cancelOrder` [取消订单官方文档](https://docs.open.alipay.com/api_1/alipay.trade.cancel) ``` javascript const outTradeNo = 'foobar' return service.cancelOrder({ out_trade_no: outTradeNo }) .then(result => { assert(result.code == '40004', result.message) }) ``` --- ### 验证支付结果`verifyPayment` [App支付同步通知参数校验](https://docs.open.alipay.com/204/105302) ```javascript const params = { memo: "xxxx", result: "xxxx", resultStatus: "xxx" } return utils.verifyPayment(params) ``` --- ### 异步通知校验`makeNotifyResponse` [异步通知官方文档](https://docs.open.alipay.com/204/105301/) ```javascript const params = { sign: 'xxxxxxxx', sign_type: 'xxxxx', ... } return service.makeNotifyResponse(params) ``` ### 交易关闭`tradeClose` [关闭交易官方文档](https://docs.open.alipay.com/api_1/alipay.trade.close/) ```javascript const params = { out_trade_no: 'xxxxx' } return service.tradeClose(params) ``` --- ### 交易退款`tradeRefund` [交易退款官方文档](https://docs.open.alipay.com/api_1/alipay.trade.refund/) ```javascript const params = { out_trade_no: 'xxxxx' } return service.tradeRefund(params) ``` --- ### 交易退款查询`tradeRefundQuery` [交易退款查询官方文档](https://docs.open.alipay.com/api_1/alipay.trade.fastpay.refund.query/) ```javascript const params = { out_trade_no: 'xxxxx' } return service.tradeRefundQuery(params) ``` --- ### 查询账单下载地址`billDownloadQuery` [查询账单下载地址文档](https://docs.open.alipay.com/api_15/alipay.data.dataservice.bill.downloadurl.query) ```javascript const params = { bill_type: 'trade', bill_date: '2017-05-06' } return service.billDownloadQuery(params) ``` --- ### 交易预创建`tradePrecreate` [交易预创建官方文档](https://docs.open.alipay.com/api_1/alipay.trade.create/) ```javascript const params = { out_trade_no: 'xxx', seller_id: 'asad', total_amount: '231wawsda', subject: '面包' } return service.tradePrecreate(params) ``` --- ### 交易结算`tradeSettle` [交易结算官方文档](https://docs.open.alipay.com/api_1/alipay.trade.order.settle/) ```javascript const params = { out_request_no: 'xxx' } return service.tradeSettle(params) ``` --- ### 单笔转账到支付宝账户接口`toaccountTransfer` [接口文档](https://opendocs.alipay.com/open/309/alipay.fund.trans.toaccount.transfer) ```javascript const params = { out_biz_no: "1234", payee_type: 'ALIPAY_LOGONID', payee_account: "user666", amount: "100" } return service.toaccountTransfer(params) ``` --- ## LICENSE [MIT](https://mit-license.org/) ================================================ FILE: jest.config.js ================================================ module.exports = { verbose: true, roots: [ '/src/', '/tests/' ], transform: { '^.+\\.ts$': 'ts-jest' }, modulePaths: [ '' ], testEnvironment: 'node', testRegex: '/tests/.*(test|spec).ts$', moduleFileExtensions: ['ts', 'js'] } ================================================ FILE: package.json ================================================ { "name": "alipay-mobile", "version": "4.0.2", "description": "", "main": "dist/index.js", "engines": { "node": ">=12.18" }, "scripts": { "build": "tsc --outDir ./dist", "lint": "yarn eslint . --ext .js,.ts", "test": "jest --no-cache" }, "keywords": [ "alipay", "mobile" ], "repository": { "type": "git", "url": "https://github.com/Luncher/alipay-mobile" }, "author": { "name": "linchen", "email": "gakiclin@gmail.com", "url": "https://github.com/Luncher" }, "license": "MIT", "devDependencies": { "@types/bluebird": "^3.5.32", "@types/jest": "^26.0.20", "@types/joi": "^14.3.4", "@types/node": "^12.18", "@types/urllib": "^2.33.0", "@typescript-eslint/eslint-plugin": "^3.6.1", "@typescript-eslint/parser": "^3.8.0", "debug": "^2.6.9", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^9.0.0", "eslint-plugin-import": "^2.22.0", "jest": "^26.6.3", "ts-jest": "^26.5.3", "typescript": "^3.9.7" }, "dependencies": { "joi": "^14.3.1", "moment": "^2.19.3", "urllib": "^2.21.1" } } ================================================ FILE: src/config/env.ts ================================================ enum NodeEnv { TEST = 'test', DEVELOPMENT = 'development', PRODUCTION = 'production' } export function getEnv(): NodeEnv { return process.env.NODE_ENV || NodeEnv.DEVELOPMENT } export function isTest() { return getEnv() === NodeEnv.TEST } export function isProd() { return getEnv() === NodeEnv.PRODUCTION } ================================================ FILE: src/config/index.ts ================================================ export { isTest, isProd } from './env' type ApiResponseMessage = string type ApiResponseData = string | number | object type ApiResponseCode = AlipayNormalResponseCode | AlipayPaymentResponseCode export interface ApiResponse { code: ApiResponseCode, message: ApiResponseMessage, data: ApiResponseData } export interface AlipayOption { appPrivKeyFile: string // 应用私钥 alipayPubKeyFile: string // 支付宝公钥 appId: string // 应用ID notifyUrl?: string // 支付宝异步通知URL gatewayUrl?: string // 接口网关地址 } export enum AlipayNormalResponseCode { OK = 0, EXCEPTION = -1, SIGNATURE_ERROR = -2, SUCCESS = 10000, UNAVALIABLE = 20000, INSUFFICIENT_AUTHORIZATION = 20001, MISSING_REQUIRED_ARGS = 40001, INVALID_ARGS = 40002, PROCESSING_FAILURE = 40004, PERMISSION_DENIED = 40006 } export const alipayResponseMessage = { 0: '请求成功', '-1': '异常', '-2': '签名错误', 10000: '接口调用成功', 20000: '服务不可用', 20001: '授权权限不足', 40001: '缺少必选参数', 40002: '非法的参数', 40004: '业务处理失败', 40006: '权限不足', // 支付结果信息 9000: '订单支付成功', 8000: '正在处理中,支付结果未知(有可能已经支付成功),请查询商户订单列表中订单的支付状态', 4000: '订单支付失败', 5000: '重复请求', 6001: '用户中途取消', 6002: '网络连接出错', 6004: '支付结果未知(有可能已经支付成功),请查询商户订单列表中订单的支付状态' } export type AlipayAPIArgs = VerifyPamentArgs | AlipayNotifyArgs | AlipayPublicArgs | AlipayCreateOrderArgs | AlipayQueryOrderArgs | AlipayCancelOrderArgs | AlipayTradeCloseArgs | AlipayTradeRefundArgs | AlipayTradeRefundQueryArgs | AlipayBillQueryArgs | AlipayTradePrecreateArgs | AlipayTradeSettleArgs | AlipayToaccountTransferArgs // App支付同步通知参数 export type VerifyPamentResult = string | PaymentResult export interface VerifyPamentArgs { memo: string, // 描述信息 result: VerifyPamentResult, // 处理结果(类型为json结构字符串) resultStatus: AlipayPaymentResponseCode // 结果码(类型为字符串) } export interface PaymentResult { alipay_trade_app_pay_response: AlipayTradeAppPayResponse, sign: string, sign_type: AlipaySignType } export type AlipayTradeAppPayResponse = AlipayTradeAppPayResponseImpl & AlipayResponseTypeMap export interface AlipayTradeAppPayResponseImpl { code: AlipayNormalResponseCode, msg: string, app_id: string, out_trade_no: string, trade_no: string, total_amount: number, seller_id: string, charset: string, timestamp: string } // App支付同步通知状态码 export enum AlipayPaymentResponseCode { SUCCESS = '9000', PROCESSING = '8000', FAILURE = '4000', REPEAT_REQ = '5000', USER_CANCEL = '6001', NETWORK_ERROR = '6002', UNKNOW = '6004' } // 支付宝异步通知参数 export interface AlipayNotifyArgs { notify_time: string, // 通知的发送时间。格式为yyyy-MM-dd HH:mm:ss notify_type: string, // 通知的类型 notify_id: string, // 通知校验ID app_id: string, // 支付宝分配给开发者的应用Id charset: string, // 编码格式,如utf-8、gbk、gb2312等 version: string, // 调用的接口版本,固定为:1.0 sign_type: AlipaySignType, // 签名类型 trade_no: string, // 支付宝交易凭证号 out_trade_no: string, // 原支付请求的商户订单号 sign: string, // 签名 [key: string]: string | number// 可选参数 } // 校验签名的参数 export interface AlipayVerifySignArgs { sign: string, msg?: string, sign_type: string, async_notify_response: object } // 支付宝接口公共请求参数 export interface AlipayPublicArgs { app_id: string, // 支付宝分配给开发者的应用ID method: MethodType, // 接口名称 format?: string, // 仅支持JSON return_url?: string, // HTTP/HTTPS开头字符串 charset: string, // 请求使用的编码格式,如utf-8,gbk,gb2312等 sign_type: AlipaySignType, // 商户生成签名字符串所使用的签名算法类型 sign: string, // 商户请求参数的签名串 timestamp: string, // 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss" version: string, // 调用的接口版本,固定为:1.0 notify_url: string, // 支付宝服务器主动通知商户服务器里指定的页面http/https路径。 biz_content: string // 业务请求参数的集合,最大长度不限 } export type AlipayResponse = AlipayPublicResponse | AlipayTradeAppPayResponse export type AlipayResponseType = | 'alipay_trade_query_response' | 'alipay_trade_refund_response' | 'alipay_trade_cancel_response' | 'alipay_trade_precreate_response' | 'alipay_trade_close_response' | 'alipay_trade_create_response' | 'alipay_trade_order_settle_response' | 'alipay_trade_fastpay_refund_query_response' | 'alipay_trade_app_pay_response' | 'alipay_fund_trans_toaccount_transfer_response' | 'alipay_data_dataservice_bill_downloadurl_query_response' | 'async_notify_response' export type AlipayResponseTypeMap = { [key in AlipayResponseType]: string } export type AlipayPublicResponse = AlipayPublicResponseImpl & AlipayResponseTypeMap // 支付宝接口公共响应参数 export interface AlipayPublicResponseImpl { code: ApiResponseCode, msg: string, sub_code?: string, sub_msg?: string, sign: string } type OrderTotalAmount = string | number // 创建订单参数 export interface AlipayCreateOrderArgs { body?: string, // 对一笔交易的具体描述信息 subject: string, // 商品的标题/交易标题/订单标题/订单关键字等 out_trade_no: string, // 商户网站唯一订单号 total_amount: OrderTotalAmount, // 订单总金额,单位为元 timeout_express?: string, // 该笔订单允许的最晚付款时间,逾期将关闭交易 time_expire?: string, // 绝对超时时间,格式为yyyy-MM-dd HH:mm auth_token?: string, // 针对用户授权接口,获取用户相关数据时,用于标识用户授权关系 product_code?: string, // 销售产品码 goods_type?: GoodsType, // 商品主类型 passback_params?: string, // 公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数 promo_params?: string, // 优惠参数注:仅与支付宝协商后可用 extend_params?: string, // 业务扩展参数 enable_pay_channels?: string, // 可用渠道 disable_pay_channels?: string, // 禁用渠道 quit_url?: string, // 添加该参数后在h5支付收银台会出现返回按钮,可用于用户付款中途退出并返回到该参数指定的商户网站地址 store_id?: string, // 商户门店编号 ext_user_info?: string // 外部指定买家 } // 查询订单参数 export interface AlipayQueryOrderArgs { out_trade_no?: string, // 订单支付时传入的商户订单号,和支付宝交易号不能同时为空 trade_no?: string, // 支付宝交易号,和商户订单号不能同时为空 org_pid?: string, // 银行间联模式下有用,其它场景请不要使用 } // 取消订单 export interface AlipayCancelOrderArgs { out_trade_no?: string, // 订单支付时传入的商户订单号,和支付宝交易号不能同时为空 trade_no?: string // 支付宝交易号,和商户订单号不能同时为空 } // 统一收单交易关闭接口 export interface AlipayTradeCloseArgs { out_trade_no?: string, // 订单支付时传入的商户订单号,和支付宝交易号不能同时为空 trade_no?: string, // 支付宝交易号,和商户订单号不能同时为空 operator_id?: string // 卖家端自定义的的操作员 ID } // 统一收单交易退款接口 export interface AlipayTradeRefundArgs { out_trade_no?: string, // 订单支付时传入的商户订单号,和支付宝交易号不能同时为空 trade_no?: string, // 支付宝交易号,和商户订单号不能同时为空 refund_amount?: number, // 需要退款的金额 refund_currency?: string, // 订单退款币种信息 refund_reason?: string, // 退款的原因说明 out_request_no?: string, // 标识一次退款请求 operator_id?: string, // 商户的操作员编号 store_id?: string, // 商户的门店编号 terminal_id?: string, // 商户的终端编号 goods_detail?: Map[], // 退款包含的商品列表信息 refund_royalty_parameters?: Map[], // 退分账明细信息 org_pid?: string // 银行间联模式下有用,其它场景请不要使用 } // 统一收单交易退款查询 export interface AlipayTradeRefundQueryArgs { out_trade_no?: string, // 订单支付时传入的商户订单号,和支付宝交易号不能同时为空 trade_no?: string, // 支付宝交易号,和商户订单号不能同时为空 out_request_no: string, // 请求退款接口时,传入的退款请求号 org_pid?: string // 银行间联模式下有用,其它场景请不要使用 } // 查询对账单下载地址 export interface AlipayBillQueryArgs { bill_type: string, // 账单类型 bill_date: string // 账单时间 } // 交易预创建接口 export interface AlipayTradePrecreateArgs { out_trade_no: string, // 商户订单号 seller_id?: string, // 卖家支付宝用户ID total_amount: number, // 订单总金额 discountable_amount?: number, // 可打折金额 subject: string, // 订单标题 goods_detail?: Map[], // 订单包含的商品列表信息 body?: string, // 对交易或商品的描述 operator_id?: string, // 商户操作员编号 store_id?: string, // 商户门店编号 disable_pay_channels?: string, // 禁用渠道 enable_pay_channels?: string, // 可用渠道,用户只能在指定渠道范围内支付 terminal_id?: string, // 商户机具终端编号 extend_params?: string, // 业务扩展参数 timeout_express?: string, // 该笔订单允许的最晚付款时间 settle_info?: any, // 描述结算信息 merchant_order_no?: string, // 商户原始订单号 business_params?: string, // 商户传入业务信息 qr_code_timeout_express?: string // 该笔订单允许的最晚付款时间 } // 统一收单交易结算接口 export interface AlipayTradeSettleArgs { out_request_no: string, // 结算请求流水号 trade_no: string, // 支付宝订单号 royalty_parameters: any, // 分账明细信息 operator_id?: string // 操作员id } // 单笔转账到支付宝账户接口 export interface AlipayToaccountTransferArgs { out_biz_no: string, // 商户转账唯一订单号 payee_type: string, // 收款方账户类型 payee_account: string, // 收款方账户 amount: string, // 转账金额 payer_show_name?: string, // 付款方姓名 payee_real_name?: string, // 收款方真实姓名 remark?: string// 转账备注 } export enum AlipayPayType { ALIPAY_USERID = 'ALIPAY_USERID', ALIPAY_LOGONID = 'ALIPAY_LOGONID' } export enum GoodsType { VIRTUAL = 0, PARTICALITY = 1 } export enum PayChannel { balance = 'balance', moneyFund = 'moneyFund', coupon = 'coupon', pcredit = 'pcredit', pcreditpayInstallment = 'pcreditpayInstallment', creditCard = 'creditCard', creditCardExpress = 'creditCardExpress', creditCardCartoon = 'creditCardCartoon', credit_group = 'credit_group', debitCardExpress = 'debitCardExpress', mcard = 'mcard', pcard = 'pcard', promotion = 'promotion', voucher = 'voucher', point = 'point', mdiscount = 'mdiscount', bankPay = 'bankPay' } export enum MethodType { QUERY_ORDER = 'alipay.trade.query', CREATE_APP_ORDER = 'alipay.trade.app.pay', CREATE_WEB_ORDER = 'alipay.trade.wap.pay', CREATE_PAGE_ORDER = 'alipay.trade.page.pay', CANCEL_ORDER = 'alipay.trade.cancel', TRADE_CLOSE = 'alipay.trade.close', TRADE_SETTLE = 'alipay.trade.order.settle', TRADE_REFUND = 'alipay.trade.refund', TRADE_PRECREATE = 'alipay.trade.precreate', TRADE_REFUND_QUERY = 'alipay.trade.fastpay.refund.query', BILL_DOWNLOAD_QUERY = 'alipay.data.dataservice.bill.downloadurl.query', FUND_TRANS_TOACCOUNT_TRANSFER = 'alipay.fund.trans.toaccount.transfer', // self define VERIFY_PAYMENT = 'verify.payment.status', NOTIFY_RESPONSE = 'notify.response' } export type GateWay = string export enum GateWayDefault { ALIPAY_GETWAY = 'https://openapi.alipay.com/gateway.do', ALIPAY_DEV_GETWAY = 'https://openapi.alipaydev.com/gateway.do' } export enum AlipayAPIList { 'alipay.trade.query' = '订单查询', 'alipay.trade.refund' = '交易退款', 'alipay.trade.cancel' = '取消订单', 'alipay.trade.precreate' = '预创建订单', 'alipay.trade.close' = '关闭交易', 'alipay.trade.create' = '创建交易', 'alipay.trade.order.settle' = '交易结算', 'alipay.trade.fastpay.refund.query' = '交易退款查询', 'alipay.trade.app.pay' = '生成创建订单所需参数', 'alipay.fund.trans.toaccount.transfer' = '单笔转账到支付宝账户接口', 'alipay.data.dataservice.bill.downloadurl.query' = '查询账单下载地址接口', 'async.notify' = '异步通知' // 自定义 } export type GetResponseTypeArgs = AlipayResponse | AlipayVerifySignArgs export enum AlipayNotifyResult { SUCCESS = 'success', FAILURE = 'failure' } export enum AlipayAlgorithm { RSA = 'RSA-SHA1', RSA2 = 'RSA-SHA256' } export enum AlipaySignType { RSA2 = 'RSA2', RSA = 'RSA' } export enum AlipayPrivKey { BEGIN = '-----BEGIN RSA PRIVATE KEY-----\n', END = '\n-----END RSA PRIVATE KEY-----' } ================================================ FILE: src/index.ts ================================================ /** * @file alipay implement * @author linchen */ import * as urllib from 'urllib' import { isProd, AlipayOption, MethodType, GateWay, GateWayDefault, AlipayPrivKey, AlipayTradeSettleArgs, AlipayBillQueryArgs, AlipayPublicArgs, AlipayPublicResponse, AlipayToaccountTransferArgs, AlipayTradeRefundArgs, AlipayTradeCloseArgs, AlipayQueryOrderArgs, AlipayCancelOrderArgs, AlipayCreateOrderArgs, AlipayTradePrecreateArgs, alipayResponseMessage, AlipayNormalResponseCode, AlipayTradeRefundQueryArgs, ApiResponse, AlipayNotifyArgs, AlipayVerifySignArgs, AlipayAPIArgs } from './config' import * as utils from './utils' import * as Validator from './validator' export * from './utils' export default class Alipay { public gateWay: GateWay public options: AlipayOption constructor(options: AlipayOption) { this.options = this.normalizeOptions(options) } private normalizeOptions(options: AlipayOption): AlipayOption { let { appPrivKeyFile: privKey, alipayPubKeyFile: publicKey } = options if (publicKey.indexOf('BEGIN PUBLIC KEY') === -1) { publicKey = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----` } if (privKey.indexOf('BEGIN RSA PRIVATE KEY') === -1 && privKey.indexOf('BEGIN PRIVATE KEY') === -1) { privKey = AlipayPrivKey.BEGIN + privKey + AlipayPrivKey.END } this.gateWay = options.gatewayUrl if (!this.gateWay) { this.gateWay = isProd() ? GateWayDefault.ALIPAY_GETWAY : GateWayDefault.ALIPAY_DEV_GETWAY } return { ...options, appPrivKeyFile: privKey, alipayPubKeyFile: publicKey } } get appId(): string { return this.options.appId } get privKey(): string { return this.options.appPrivKeyFile } get publicKey(): string { return this.options.alipayPubKeyFile } get notifyUrl(): string { return this.options.notifyUrl } private validateBasicParams(method: MethodType, publicParams: AlipayPublicArgs): AlipayPublicArgs { const newOptions = { ...this.options } // remove keys from basic parameters delete newOptions.appPrivKeyFile delete newOptions.alipayPubKeyFile const params: AlipayPublicArgs = { ...newOptions, ...publicParams, method } return Validator.validateBasicParams(params) } private validateParams(method: MethodType, apiParams: AlipayAPIArgs, publicParams: AlipayPublicArgs) { const validateBasicParams: AlipayPublicArgs = this.validateBasicParams(method, publicParams) const validateApiParams: AlipayAPIArgs = Validator.validateAPIParams(method, apiParams) validateBasicParams.biz_content = JSON.stringify(validateApiParams) validateBasicParams.sign = utils.makeSign(this.privKey, validateBasicParams) return validateBasicParams } private makeRequest(params: AlipayPublicArgs, options: urllib.RequestOptions = {}) { const httpclient: urllib.HttpClient2 = new urllib.HttpClient2() return httpclient.request(this.gateWay, { data: params, dataType: 'json', dataAsQueryString: true, ...options }) .then((resp: urllib.HttpClientResponse) => utils.makeResponse(resp.data)) } public makeNotifyResponse(params: AlipayNotifyArgs): ApiResponse { // Validator.validateAPIParams(MethodType.NOTIFY_RESPONSE, params) const resp: AlipayVerifySignArgs = { sign: params.sign, async_notify_response: params, sign_type: params.sign_type } const valid = utils.verifySign(this.publicKey, resp, ['sign', 'sign_type'], params) const code = valid ? AlipayNormalResponseCode.OK : AlipayNormalResponseCode.SIGNATURE_ERROR return { code, message: alipayResponseMessage[code], data: params } } public createWebOrderURL(apiParams: AlipayCreateOrderArgs, publicParams?: AlipayPublicArgs): ApiResponse { const result = this.createWebOrder(apiParams, publicParams) result.data = `${this.gateWay}?${String(result.data)}` return result } public createPageOrderURL(apiParams: AlipayCreateOrderArgs, publicParams?: AlipayPublicArgs): ApiResponse { const result = this.createPageOrder(apiParams, publicParams) result.data = `${this.gateWay}?${String(result.data)}` return result } public createPageOrder(apiParams: AlipayCreateOrderArgs, publicParams?: AlipayPublicArgs): ApiResponse { const params = this.validateParams(MethodType.CREATE_PAGE_ORDER, apiParams, publicParams) const { sign } = params const signStr = utils.makeSignStr(params) const value = signStr.split('&') .reduce( (acc, cur) => { const [k, v] = cur.split('=') return `${acc + k}=${encodeURIComponent(v)}&` }, '' ).slice(0, -1) const data = `${value}&sign=${encodeURIComponent(sign)}` return { code: 0, message: alipayResponseMessage[0], data } } public createWebOrder(apiParams: AlipayCreateOrderArgs, publicParams?: AlipayPublicArgs): ApiResponse { const params = this.validateParams(MethodType.CREATE_WEB_ORDER, apiParams, publicParams) const { sign } = params const signStr = utils.makeSignStr(params) const value = signStr.split('&').reduce( (acc, cur) => { const [k, v] = cur.split('=') return `${acc}${k}=${encodeURIComponent(v)}&` }, '' ).slice(0, -1) const data = `${value}&sign=${encodeURIComponent(sign)}` return { code: 0, message: alipayResponseMessage[0], data } } // Compat public createOrder(apiParams: AlipayCreateOrderArgs, publicParams?: AlipayPublicArgs): ApiResponse { return this.createAppOrder(apiParams, publicParams) } public createAppOrder(apiParams: AlipayCreateOrderArgs, publicParams?: AlipayPublicArgs): ApiResponse { const params = this.validateParams(MethodType.CREATE_APP_ORDER, apiParams, publicParams) const { sign } = params const signStr = utils.makeSignStr(params) const value = signStr.split('&').reduce( (acc, cur) => { const [k, v] = cur.split('=') return `${acc + k}=${encodeURIComponent(v)}&` }, '' ).slice(0, -1) const data = `${value}&sign=${encodeURIComponent(sign)}` return { code: AlipayNormalResponseCode.OK, message: alipayResponseMessage[AlipayNormalResponseCode.OK], data } } public queryOrder(apiParams: AlipayQueryOrderArgs, publicParams?: AlipayPublicArgs) { return Promise.resolve() .then(() => { if (!apiParams.out_trade_no && !apiParams.trade_no) { throw new Error('outTradeNo and tradeNo can not both omit.') } const params = this.validateParams(MethodType.QUERY_ORDER, apiParams, publicParams) return this.makeRequest(params) }) } public cancelOrder(apiParams: AlipayCancelOrderArgs, publicParams?: AlipayPublicArgs) { const params = this.validateParams(MethodType.CANCEL_ORDER, apiParams, publicParams) return this.makeRequest(params) } public tradeClose(apiParams: AlipayTradeCloseArgs, publicParams?: AlipayPublicArgs) { const params = this.validateParams(MethodType.TRADE_CLOSE, apiParams, publicParams) return this.makeRequest(params) } public tradeRefund(apiParams: AlipayTradeRefundArgs, publicParams?: AlipayPublicArgs) { const params = this.validateParams(MethodType.TRADE_REFUND, apiParams, publicParams) return this.makeRequest(params) } public tradeRefundQuery(apiParams: AlipayTradeRefundQueryArgs, publicParams?: AlipayPublicArgs) { const params = this.validateParams(MethodType.TRADE_REFUND_QUERY, apiParams, publicParams) return this.makeRequest(params) } public billDownloadQuery(apiParams: AlipayBillQueryArgs, publicParams?: AlipayPublicArgs) { const params = this.validateParams(MethodType.BILL_DOWNLOAD_QUERY, apiParams, publicParams) return this.makeRequest(params) } public tradePrecreate(apiParams: AlipayTradePrecreateArgs, publicParams?: AlipayPublicArgs) { const params = this.validateParams(MethodType.TRADE_PRECREATE, apiParams, publicParams) return this.makeRequest(params) } public tradeSettle(apiParams: AlipayTradeSettleArgs, publicParams?: AlipayPublicArgs) { const params = this.validateParams(MethodType.TRADE_SETTLE, apiParams, publicParams) return this.makeRequest(params) } public toaccountTransfer(apiParams: AlipayToaccountTransferArgs, publicParams?: AlipayPublicArgs) { const params = this.validateParams(MethodType.FUND_TRANS_TOACCOUNT_TRANSFER, apiParams, publicParams) return this.makeRequest(params) } } ================================================ FILE: src/utils.ts ================================================ /** * @file alipay utils * @author linchen */ import { isString } from 'util' import * as crypto from 'crypto' import * as config from './config' import * as Validator from './validator' import { MethodType, ApiResponse, PaymentResult, AlipayResponse, VerifyPamentArgs, alipayResponseMessage } from './config' export function makeSignStr(params: object, omit = ['sign']) { return Object.keys(params) .sort() .filter((key) => params[key] && omit.indexOf(key) === -1) .map((key) => { const value = typeof params[key] === 'object' ? JSON.stringify(params[key]) : params[key] return `${String(key)}=${String(value)}` }) .join('&') .trim() } export function getSignAlgorithm(signType: config.AlipaySignType): config.AlipayAlgorithm { return config.AlipayAlgorithm[signType] } export function makeSign(privKey: string, params: config.AlipayPublicArgs) { const signStr = makeSignStr(params) const algorithm = getSignAlgorithm(params.sign_type) const signer = crypto.createSign(algorithm as string) signer.update(signStr, params.charset) return signer.sign(privKey, 'base64') } export function verifySign( publicKey: string, response: config.AlipayVerifySignArgs, omit: string[], options: config.AlipayNotifyArgs ): boolean { if (!response.async_notify_response || !response.sign) { return false } const { sign } = response const resp = makeSignStr(response.async_notify_response, omit) const algorithm = getSignAlgorithm(options.sign_type) const verify = crypto.createVerify(algorithm) verify.update(resp, options.charset) return verify.verify(publicKey, sign, 'base64') } export function getResponseType(response: config.GetResponseTypeArgs): config.AlipayResponseType { const respType: string = Object.keys(config.AlipayAPIList) .map((name) => name.replace(/\./g, '_')) .find((api) => `${api}_response` in response) if (respType) { return (`${respType}_response`) } throw new Error(`Not Found responseType: ${String(response.msg)}`) } export function makeResponse(response: AlipayResponse): ApiResponse { return { code: response.code, message: alipayResponseMessage[response.code], data: response[getResponseType(response)] } } export function verifyPayment(params: VerifyPamentArgs): ApiResponse { Validator.validateAPIParams(MethodType.VERIFY_PAYMENT, params) const data = isString(params.result) ? (JSON.parse(params.result)) : params.result return makeResponse(data.alipay_trade_app_pay_response) } ================================================ FILE: src/validator/index.ts ================================================ import * as Joi from 'joi' import * as Schema from './schema' import { MethodType, AlipayPublicArgs, AlipayAPIArgs } from '../config' type AlipayArgs = AlipayPublicArgs | AlipayAPIArgs function validate(schema: Joi.ObjectSchema, params: AlipayArgs): AlipayArgs { const result: Joi.ValidationResult = Joi.validate(params, schema) if (result.error) { throw result.error } return result.value } export function validateBasicParams(params: AlipayPublicArgs): AlipayPublicArgs { return validate(Schema.basicSchema.options({ allowUnknown: true }), params) } export function validateAPIParams(method: MethodType, params: AlipayAPIArgs): AlipayAPIArgs { switch (method) { case MethodType.CREATE_WEB_ORDER: { return validate(Schema.createWebOrderSchema, params) } case MethodType.CREATE_APP_ORDER: { return validate(Schema.createAppOrderSchema, params) } case MethodType.CREATE_PAGE_ORDER: { return validate(Schema.createPageOrderSchema, params) } case MethodType.QUERY_ORDER: { return validate(Schema.queryOrderSchema, params) } case MethodType.CANCEL_ORDER: { return validate(Schema.cancelOrderSchema, params) } case MethodType.VERIFY_PAYMENT: { return validate(Schema.verifyPaymentSchema, params) } case MethodType.NOTIFY_RESPONSE: { return validate(Schema.notifySchema, params) } case MethodType.TRADE_CLOSE: { return validate(Schema.tradeCloseSchema, params) } case MethodType.TRADE_REFUND: { return validate(Schema.tradeRefundSchema, params) } case MethodType.TRADE_REFUND_QUERY: { return validate(Schema.tradeRefundQuery, params) } case MethodType.BILL_DOWNLOAD_QUERY: { return validate(Schema.billDownloadSchema, params) } case MethodType.TRADE_PRECREATE: { return validate(Schema.tradePrecreateSchema, params) } case MethodType.TRADE_SETTLE: { return validate(Schema.tradeSettleSchema, params) } case MethodType.FUND_TRANS_TOACCOUNT_TRANSFER: { return validate(Schema.toaccountTransferSchema, params) } default: { throw new Error(`Parser Unknow method type:${method}`) } } } ================================================ FILE: src/validator/schema/bill_download_query.ts ================================================ import * as Joi from 'joi' export const billDownloadSchema: Joi.ObjectSchema = Joi.object({ bill_type: Joi.string().max(10).required(), bill_date: Joi.string().max(15).required() }) ================================================ FILE: src/validator/schema/cancel_order.ts ================================================ import * as Joi from 'joi' export const cancelOrderSchema: Joi.ObjectSchema = Joi.object({ out_trade_no: Joi.string().max(64), trade_no: Joi.string().max(64) }).or('out_trade_no', 'trade_no') ================================================ FILE: src/validator/schema/create_app_order.ts ================================================ import * as Joi from 'joi' import { GoodsType } from '../../config' export const createAppOrderSchema: Joi.ObjectSchema = Joi.object({ body: Joi.string().max(128), subject: Joi.string().max(256).required(), out_trade_no: Joi.string().max(64).required(), timeout_express: Joi.string().max(6), total_amount: Joi.string().max(9).regex(/^[0-9]+(.[0-9]{1,2}?)$/), seller_id: Joi.string().max(16), product_code: Joi.string().max(64).default('QUICK_MSECURITY_PAY'), goods_type: Joi.string().max(2).allow(GoodsType), passback_params: Joi.string().max(512), extend_params: Joi.object({ sys_service_provider_id: Joi.string().max(64), needBuyerRealnamed: Joi.string().max(1), TRANS_MEMO: Joi.string().max(128), hb_fq_num: Joi.string().max(5), hb_fq_seller_percent: Joi.string().max(3) }), enable_pay_channels: Joi.string().max(128), disable_pay_channels: Joi.string().max(128), promo_params: Joi.string().max(512), store_id: Joi.string().max(32) }) ================================================ FILE: src/validator/schema/create_page_order.ts ================================================ import * as Joi from 'joi' import { GoodsType } from '../../config' export const createPageOrderSchema: Joi.ObjectSchema = Joi.object({ body: Joi.string().max(128), subject: Joi.string().max(256).required(), out_trade_no: Joi.string().max(64).required(), timeout_express: Joi.string().max(6), total_amount: Joi.string().max(9).regex(/^[0-9]+(.[0-9]{1,2}?)$/), seller_id: Joi.string().max(16), product_code: Joi.string().max(64).default('FAST_INSTANT_TRADE_PAY'), goods_type: Joi.string().max(2).allow(GoodsType), passback_params: Joi.string().max(512), extend_params: Joi.object({ sys_service_provider_id: Joi.string().max(64), needBuyerRealnamed: Joi.string().max(1), TRANS_MEMO: Joi.string().max(128), hb_fq_num: Joi.string().max(5), hb_fq_seller_percent: Joi.string().max(3) }), enable_pay_channels: Joi.string().max(128), disable_pay_channels: Joi.string().max(128), promo_params: Joi.string().max(512), store_id: Joi.string().max(32), qr_pay_mode: Joi.string().max(2), qrcode_width: Joi.string().max(4) }) ================================================ FILE: src/validator/schema/create_web_order.ts ================================================ import * as Joi from 'joi' import { GoodsType } from '../../config' export const createWebOrderSchema: Joi.ObjectSchema = Joi.object({ body: Joi.string().max(128), subject: Joi.string().max(256).required(), out_trade_no: Joi.string().max(64).required(), timeout_express: Joi.string().max(6), total_amount: Joi.string().max(9).regex(/^[0-9]+(.[0-9]{1,2}?)$/), seller_id: Joi.string().max(16), product_code: Joi.string().max(64).default('QUICK_WAP_PAY'), goods_type: Joi.string().max(2).allow(GoodsType), passback_params: Joi.string().max(512), extend_params: Joi.object({ sys_service_provider_id: Joi.string().max(64), needBuyerRealnamed: Joi.string().max(1), TRANS_MEMO: Joi.string().max(128), hb_fq_num: Joi.string().max(5), hb_fq_seller_percent: Joi.string().max(3) }), enable_pay_channels: Joi.string().max(128), disable_pay_channels: Joi.string().max(128), promo_params: Joi.string().max(512), store_id: Joi.string().max(32) }) ================================================ FILE: src/validator/schema/index.ts ================================================ /** * @alipay api validator schema * @author linchen */ import * as Joi from 'joi' import * as moment from 'moment' import { AlipaySignType } from '../../config' export * from './notify' export * from './trade_close' export * from './trade_settle' export * from './trade_refund' export * from './query_order' export * from './create_app_order' export * from './create_web_order' export * from './create_page_order' export * from './cancel_order' export * from './verify_payment' export * from './trade_precreate' export * from './trade_refund_query' export * from './bill_download_query' export * from './toaccount_transfer' export const basicSchema: Joi.ObjectSchema = Joi.object({ app_id: Joi.string().max(32).required(), method: Joi.string().max(128).required(), format: Joi.string().max(40), return_url: Joi.string().max(256), charset: Joi.string().max(10).default('utf-8', '请求使用的编码格式'), sign_type: Joi.string().allow( Object.keys(AlipaySignType) ).default('RSA2', '商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2'), timestamp: Joi.string().max(19).default(() => moment().format('YYYY-MM-DD HH:mm:ss'), '时间戳'), version: Joi.string().max(3).default('1.0', '调用的接口版本,固定为:1.0'), notify_url: Joi.string().max(256) }) ================================================ FILE: src/validator/schema/notify.ts ================================================ import * as Joi from 'joi' export const notifySchema: Joi.ObjectSchema = Joi.object({ notify_time: Joi.string().required(), notify_type: Joi.string().required(), notify_id: Joi.string().required(), app_id: Joi.string().required(), version: Joi.string().required(), sign_type: Joi.string().required(), sign: Joi.string().required(), trade_no: Joi.string().required(), out_trade_no: Joi.string().required() }) ================================================ FILE: src/validator/schema/query_order.ts ================================================ import * as Joi from 'joi' export const queryOrderSchema: Joi.ObjectSchema = Joi.object({ out_trade_no: Joi.string().max(64), trade_no: Joi.string().max(64) }).or('out_trade_no', 'trade_no') ================================================ FILE: src/validator/schema/toaccount_transfer.ts ================================================ import * as Joi from 'joi' import { AlipayPayType } from '../../config' export const toaccountTransferSchema: Joi.ObjectSchema = Joi.object({ out_biz_no: Joi.string().required(), payee_type: Joi.string().allow(Object.keys(AlipayPayType)).required(), payee_account: Joi.string().max(100).required(), amount: Joi.string().max(16).required(), payer_show_name: Joi.string().max(100), payee_real_name: Joi.string().max(100), remark: Joi.string().max(200) }) ================================================ FILE: src/validator/schema/trade_close.ts ================================================ import * as Joi from 'joi' export const tradeCloseSchema: Joi.ObjectSchema = Joi.object({ out_trade_no: Joi.string().max(64), trade_no: Joi.string().max(64), operator_id: Joi.string().max(28) }).or('out_trade_no', 'trade_no') ================================================ FILE: src/validator/schema/trade_precreate.ts ================================================ import * as Joi from 'joi' export const tradePrecreateSchema: Joi.ObjectSchema = Joi.object({ out_trade_no: Joi.string().max(64).required(), seller_id: Joi.string().max(128), total_amount: Joi.string().max(11).regex(/^[0-9]+(.[0-9]{1,2}?)$/), discountable_amount: Joi.string().max(11).regex(/^[0-9]+(.[0-9]{1,2}?)$/), undiscountable_amount: Joi.string().max(11).regex(/^[0-9]+(.[0-9]{1,2}?)$/), buyer_logon_id: Joi.string().max(100), subject: Joi.string().max(256).required(), body: Joi.string().max(128), operator_id: Joi.string().max(28), store_id: Joi.string().max(32), disable_pay_channels: Joi.string().max(128), enable_pay_channels: Joi.string().max(128), terminal_id: Joi.string().max(32), extend_params: Joi.object(), timeout_express: Joi.string().max(6), settle_info: Joi.any(), merchant_order_no: Joi.string().max(32), business_params: Joi.string().max(512), qr_code_timeout_express: Joi.string().max(6) }) ================================================ FILE: src/validator/schema/trade_refund.ts ================================================ import * as Joi from 'joi' export const tradeRefundSchema: Joi.ObjectSchema = Joi.object({ out_trade_no: Joi.string().max(64), trade_no: Joi.string().max(64), refund_amount: Joi.number(), refund_reason: Joi.string().max(256), out_request_no: Joi.string().max(64), operator_id: Joi.string().max(30), store_id: Joi.string().max(32), terminal_id: Joi.string().max(32) }).or('trade_no', 'out_trade_no') ================================================ FILE: src/validator/schema/trade_refund_query.ts ================================================ import * as Joi from 'joi' export const tradeRefundQuery: Joi.ObjectSchema = Joi.object({ out_trade_no: Joi.string().max(64), trade_no: Joi.string().max(64), out_request_no: Joi.string().max(64).required(), org_pid: Joi.string().max(16) }).or('trade_no', 'out_trade_no') ================================================ FILE: src/validator/schema/trade_settle.ts ================================================ import * as Joi from 'joi' export const tradeSettleSchema: Joi.ObjectSchema = Joi.object({ out_request_no: Joi.string().max(64).required(), trade_no: Joi.string().max(64).required(), royalty_parameters: Joi.any().required(), operator_id: Joi.string().max(64) }) ================================================ FILE: src/validator/schema/verify_payment.ts ================================================ import * as Joi from 'joi' import { AlipayPaymentResponseCode } from '../../config' export const verifyPaymentSchema: Joi.ObjectSchema = Joi.object({ memo: Joi.string().required(), result: Joi.any().required(), resultStatus: Joi.string().allow(Object.keys(AlipayPaymentResponseCode)) }) ================================================ FILE: tests/index.spec.ts ================================================ import * as fs from 'fs' import * as path from 'path' import Alipay from '../src/index' import { AlipayOption } from '../src/config' const read = (filename) => fs.readFileSync(path.resolve(__dirname, filename)) function createService(optons?: AlipayOption) { const defaultOptions = { app_id: '2016080100137766', appPrivKeyFile: read('./keys/app_priv_key.pem'), alipayPubKeyFile: read('./keys/alipay_public_key.pem') } return new Alipay({ ...defaultOptions, ...optons }) } function testCreateAppOrderOk(options) { const data = { subject: '辣条', out_trade_no: '1232423', total_amount: '100' } const result = createService(options).createAppOrder(data) expect(result.code).toEqual(0) expect(result.message).toEqual('请求成功') } describe('ALIPAY unit test', () => { it('should use private key with ras header', () => { const vOptions = { app_id: '2016080100137766', appPrivKeyFile: read('./keys/app_priv_key_with_rsa_head.pem'), alipayPubKeyFile: read('./keys/alipay_public_key.pem') } return testCreateAppOrderOk(vOptions) }) it('should use private key with header', () => { const vOptions = { app_id: '2016080100137766', appPrivKeyFile: read('./keys/app_priv_key_with_head.pem'), alipayPubKeyFile: read('./keys/alipay_public_key.pem') } return testCreateAppOrderOk(vOptions) }) it('should allow create order', () => { const data = { subject: '辣条', out_trade_no: '1232423', total_amount: '100' } const result = createService().createAppOrder(data) expect(result.code).toEqual(0) expect(result.message).toEqual('请求成功') }) }) ================================================ FILE: tests/keys/alipay_public_key.pem ================================================ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsJLMw7HyJbTab9xBBw9jzw3F37obRwx4DVsZ0AihqcWJicUCLhPIyPmzC0A6f3FjoBcncn0a2w5neLBu0tpYm7qrFeY/QSy92WUzX6PmzFsotxxtq0SnWyMqfzlxkpSZPGoxxDB5vl40S8QEMceWH5ANzL+CT6VB8tNJ3ImCos2DeanjAiq5QBC/n2ZnCm8QUBAJ4hCTJwI850sSkwSETlHrtbVka4h02eY3Nmm120se5zhBc2MPaPMdDZWRiFeyXPWiFkijI4wXrEtSIwEWVECzqhnBuUHAq+0qfzvu+EejyTjGmp0u5+yJccDG6ES/LHFRjk54/9PbNngMEmxFMwIDAQAB ================================================ FILE: tests/keys/app_priv_key.pem ================================================ MIIEogIBAAKCAQEAw9ju1PnQEysiWxopYQbPIHiNRcbYwV2AYTz+WhRMVaTagmyGNYANmskjixQ+pKvvrl0KPa8CVX47h2AhUp3L2tSygh6C2bEpo7oaB77zZ84XmnUKotfwFUekUZUHjDhX91kVgun0LAIezWMDdRkZ9KC/0/8YnCfHUz9ecfYFd4fu17UlA9JJeEBQuyXDUzvdCtv7VEc9/KZ4sShyQoigaxG4HfRb8Iqmr+J2rARGwBAsjkH+bV609TVPTLCrpZT/rj0ua//1G7cv7Nhh2NcWK7nA2zksLvt/W9vLRYl7/3QXD96qUe2/vARzPp0L/QH3QtIv9NE87xVLmGk+TmxIAQIDAQABAoIBADok8Js4Y4cdWkDT3o8Y12ubfs3FgFrAKLo1won51sPigqEQGTRPBTr8FlpXM1XuWeVZaA3yJG4/YOsLJjmB8aDRXwH0jwKUb3lVah78mQUrkaRgtTytgXC/6U/zP99oZu1ffFx2mvBp3L07dS5Runv/MZR9+s95m9riSGbrFKpsorTtOIEd8Uck8O3ble/iB1G9hmX2qaMEqnppiY0gh7iTl5a9ji+TvZc+4Kfmv4jVKuQ1vGpVtDVNlgzq8RkuWxnm4UHFY2dZ1kU8gNBOImuVT4qfFAvpbBcyB69817kM8vh5/JBJEL+3JyJIvWq7v24T0w/Q1Fx7uqsH5Bb/9VUCgYEA8Mp2QkA99pZvehycabgHPfRdtMHcAqv1fLLHykKF4RPcKqhowKkBzSCFV5s0tOaMoGtkum6b5KDDibO7JDuZSG+MFgRGE3NOr/ixzN8kDfIPswmud8Ukkas/OVoBotH7IZsaPbgURJaMFXSFWBNv8OniqRds052PBbtsdiUJMAMCgYEA0De+kiiOHxt3w1VcygqsiJ5N2vPfo40Gv+4P+gbMfTKq35GpEQjoPMq8clqQm1CyJAgD7St0WaN0yxqOzBR0kwUJfyDQOFqsFIzlVapV7213wJYnttWhZ6Ywguadtn8haRAg1woPJh04zkqCOkEn5YJgppdzK+V9SNcFFR6jEqsCgYAPZfMgE3mi7kXcuj2qaRFVfe4MJCWMqjB9s/Ug0xY6qYl31OM2BtjNpFnCm3NIakHt9FAxt+cSPNGkWbELBSCtSCzeL5HHpqUOjcnY/yAQID2uxly43guToS7e3QmjvuffY0rPTZlGNZZpvNTWKBANiPdstsnH0piV1kF+YKnwvwKBgA9i/y831gDQe9IwfbHkhgl2gVRbzzv70PLf+chFNllOJDhvQColGVxcsv5UWPlRRkjJWtjs7CUvohLcPI8yN8chiSke2KRBdxPxsYTaGI2f7kiKEBc/xDSvoEgcGK2CyYpFCdg6QxJS+H6uHN+HLxaTwuasrHMi+1YlpZeA8xYPAoGAMOb9j0EHaVsAr2OyTSxfRho4ZfxZBlN05Q4NX3Fjl+eSBNzyJhz0+MxvSG9Dp6DSAnVJd5lpB8m3PYSHHyTqbq21VJm2+8TcysjQhhpFm3/KErGxLpgb9YFTPbsLGmOeSOL7QHHo3bczZe3NLJnzj55r1ITntoAsGpl3gQD5JS0= ================================================ FILE: tests/keys/app_priv_key_with_head.pem ================================================ -----BEGIN PRIVATE KEY----- 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 wW6XS6BtZ1EOxnJbXGbR1C8ly1nH7hCW8Q== -----END PRIVATE KEY----- ================================================ FILE: tests/keys/app_priv_key_with_rsa_head.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAw9ju1PnQEysiWxopYQbPIHiNRcbYwV2AYTz+WhRMVaTagmyGNYANmskjixQ+pKvvrl0KPa8CVX47h2AhUp3L2tSygh6C2bEpo7oaB77zZ84XmnUKotfwFUekUZUHjDhX91kVgun0LAIezWMDdRkZ9KC/0/8YnCfHUz9ecfYFd4fu17UlA9JJeEBQuyXDUzvdCtv7VEc9/KZ4sShyQoigaxG4HfRb8Iqmr+J2rARGwBAsjkH+bV609TVPTLCrpZT/rj0ua//1G7cv7Nhh2NcWK7nA2zksLvt/W9vLRYl7/3QXD96qUe2/vARzPp0L/QH3QtIv9NE87xVLmGk+TmxIAQIDAQABAoIBADok8Js4Y4cdWkDT3o8Y12ubfs3FgFrAKLo1won51sPigqEQGTRPBTr8FlpXM1XuWeVZaA3yJG4/YOsLJjmB8aDRXwH0jwKUb3lVah78mQUrkaRgtTytgXC/6U/zP99oZu1ffFx2mvBp3L07dS5Runv/MZR9+s95m9riSGbrFKpsorTtOIEd8Uck8O3ble/iB1G9hmX2qaMEqnppiY0gh7iTl5a9ji+TvZc+4Kfmv4jVKuQ1vGpVtDVNlgzq8RkuWxnm4UHFY2dZ1kU8gNBOImuVT4qfFAvpbBcyB69817kM8vh5/JBJEL+3JyJIvWq7v24T0w/Q1Fx7uqsH5Bb/9VUCgYEA8Mp2QkA99pZvehycabgHPfRdtMHcAqv1fLLHykKF4RPcKqhowKkBzSCFV5s0tOaMoGtkum6b5KDDibO7JDuZSG+MFgRGE3NOr/ixzN8kDfIPswmud8Ukkas/OVoBotH7IZsaPbgURJaMFXSFWBNv8OniqRds052PBbtsdiUJMAMCgYEA0De+kiiOHxt3w1VcygqsiJ5N2vPfo40Gv+4P+gbMfTKq35GpEQjoPMq8clqQm1CyJAgD7St0WaN0yxqOzBR0kwUJfyDQOFqsFIzlVapV7213wJYnttWhZ6Ywguadtn8haRAg1woPJh04zkqCOkEn5YJgppdzK+V9SNcFFR6jEqsCgYAPZfMgE3mi7kXcuj2qaRFVfe4MJCWMqjB9s/Ug0xY6qYl31OM2BtjNpFnCm3NIakHt9FAxt+cSPNGkWbELBSCtSCzeL5HHpqUOjcnY/yAQID2uxly43guToS7e3QmjvuffY0rPTZlGNZZpvNTWKBANiPdstsnH0piV1kF+YKnwvwKBgA9i/y831gDQe9IwfbHkhgl2gVRbzzv70PLf+chFNllOJDhvQColGVxcsv5UWPlRRkjJWtjs7CUvohLcPI8yN8chiSke2KRBdxPxsYTaGI2f7kiKEBc/xDSvoEgcGK2CyYpFCdg6QxJS+H6uHN+HLxaTwuasrHMi+1YlpZeA8xYPAoGAMOb9j0EHaVsAr2OyTSxfRho4ZfxZBlN05Q4NX3Fjl+eSBNzyJhz0+MxvSG9Dp6DSAnVJd5lpB8m3PYSHHyTqbq21VJm2+8TcysjQhhpFm3/KErGxLpgb9YFTPbsLGmOeSOL7QHHo3bczZe3NLJnzj55r1ITntoAsGpl3gQD5JS0= -----END RSA PRIVATE KEY----- ================================================ FILE: tests/keys/app_public_key.pem ================================================ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw9ju1PnQEysiWxopYQbPIHiNRcbYwV2AYTz+WhRMVaTagmyGNYANmskjixQ+pKvvrl0KPa8CVX47h2AhUp3L2tSygh6C2bEpo7oaB77zZ84XmnUKotfwFUekUZUHjDhX91kVgun0LAIezWMDdRkZ9KC/0/8YnCfHUz9ecfYFd4fu17UlA9JJeEBQuyXDUzvdCtv7VEc9/KZ4sShyQoigaxG4HfRb8Iqmr+J2rARGwBAsjkH+bV609TVPTLCrpZT/rj0ua//1G7cv7Nhh2NcWK7nA2zksLvt/W9vLRYl7/3QXD96qUe2/vARzPp0L/QH3QtIv9NE87xVLmGk+TmxIAQIDAQAB ================================================ FILE: tsconfig.eslint.json ================================================ { "extends": "./tsconfig.json", "files": [ ".eslintrc.js", "jest.config.js", "tests/index.spec.ts" ] } ================================================ FILE: tsconfig.json ================================================ { "compileOnSave": false, "compilerOptions": { "baseUrl": ".", "types": ["node", "jest"], "lib":["es2015"], "module": "commonjs", "moduleResolution": "node", "sourceMap":true, "removeComments": true, "declaration": true, "target": "es5", "noUnusedLocals":true, "noUnusedParameters": true, "outDir": "./dist/" }, "include": [ "src/" ], "exclude": [ "node_modules", "**/*.spec.ts" ] }