diff --git a/README.md b/README.md index 0ed7e82..57f3c95 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ `$ npm install wechatpay-axios-plugin` +## Requirements + +`Rsa.encrypt` and `Rsa.decrypt` with `oaepHash` was added on Node v12.9.0. So that the Node minimum version should be 12.9.0(I'm not very sure). + ## Examples ### Initialization @@ -54,6 +58,17 @@ client.post('/v3/combine-transactions/jsapi', {}).then(response => { }) ``` +#### POST `/v3/smartguide/guides/{guide_id}/assign` mixed `RESTful` parameter with `JSON` body payload, response as `204` status code. + +```js +client.post(`/v3/smartguide/guides/${guide_id}/assign`, { + sub_mchid, + out_trade_no, +}).catch(error => { + console.error(error) +}) +``` + ### Async/Await style #### GET `/v3/merchant-service/complaints` with `query` parameters @@ -71,7 +86,7 @@ client.post('/v3/combine-transactions/jsapi', {}).then(response => { }) console.info(res.data) } catch (error) { - console.info(error) + console.error(error) } })() ``` @@ -96,7 +111,7 @@ client.post('/v3/combine-transactions/jsapi', {}).then(response => { }) console.info(res.data.code_url) } catch (error) { - console.info(error) + console.error(error) } })() ``` @@ -139,7 +154,7 @@ client.post('/v3/combine-transactions/jsapi', {}).then(response => { }) console.info(res.data) } catch (error) { - console.info(error) + console.error(error) } })() ``` diff --git a/lib/aes.js b/lib/aes.js index abc4b5c..6b805e7 100644 --- a/lib/aes.js +++ b/lib/aes.js @@ -7,8 +7,21 @@ const base64 = `base64` const BLOCK_SIZES = 16 const algorithm = `aes-256-gcm` +/** + * Aes encrypt/decrypt using `aes-256-gcm` algorithm with `AAD`. + */ class Aes { + /** + * Encrypts plaintext. + * + * @param {string} iv - The initialization vector, 16 bytes string. + * @param {string} key - The secret key, 32 bytes string. + * @param {string} plaintext - Text to encode. + * @param {string} aad - The additional authenticated data, maybe empty string. + * + * @returns {object} + */ static encrypt(iv, key, plaintext, aad = '') { const cipher = crypto.createCipheriv( algorithm, key, iv @@ -21,6 +34,16 @@ class Aes { ]).toString(base64) } + /** + * Decrypts ciphertext. + * + * @param {string} iv - The initialization vector, 16 bytes string. + * @param {string} key - The secret key, 32 bytes string. + * @param {string} ciphertext - Base64-encoded ciphertext. + * @param {string} aad - The additional authenticated data, maybe empty string. + * + * @returns {string} + */ static decrypt(iv, key, ciphertext, aad = '') { const buf = Buffer.from(ciphertext, base64) const tag = buf.slice(-BLOCK_SIZES) diff --git a/lib/formatter.js b/lib/formatter.js index 270e7d8..2e9a50f 100644 --- a/lib/formatter.js +++ b/lib/formatter.js @@ -1,24 +1,69 @@ - +/** + * Provides easy used methods using in this project. + */ class Formatter { + /** + * Generate a random string aka `nonce`, similar as `crypto.randomBytes`. + * + * @param {number} size - Nonce string length, default is 32 bytes. + * + * @returns {string} + */ static nonce(size = 32) { const chars = `0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ` return `0`.repeat(size).replace(/0/g, _ => chars[Math.random()*62|0]) } + /** + * Retrieve the current `Unix` timestamp. + * + * @returns {number} + */ static timestamp() { return +(new Date)/1000|0 } + /** + * Formatting for the heading `Authorization` value. + * + * @param {string} mchid - The merchant ID. + * @param {string} nonce_str - The Nonce string. + * @param {string} signature - The base64-encoded `Rsa.sign` ciphertext. + * @param {string} timestamp - The `Unix` timestamp. + * @param {string} serial_no - The serial number of the merchant public certification. + * + * @returns {string} + */ static authorization(mchid, nonce_str, signature, timestamp, serial_no) { return `WECHATPAY2-SHA256-RSA2048 mchid="${mchid}",nonce_str="${nonce_str}",signature="${signature}",timestamp="${timestamp}",serial_no="${serial_no}"` } + /** + * Formatting this `HTTP.request` for `Rsa.sign` input. + * + * @param {string} method - The merchant ID. + * @param {string} uri - Combined string with `URL.pathname` and `URL.search`. + * @param {string} timestamp - The `Unix` timestamp, should be the one used in `authorization`. + * @param {string} nonce - The `Nonce` string, should be the one used in `authorization`. + * @param {string} body - The playload string, HTTP `GET` should be an empty string. + * + * @returns {string} + */ static request(method, uri, timestamp, nonce, body = '') { return `${method}\n${uri}\n${timestamp}\n${nonce}\n${body}\n` } + /** + * Formatting this `HTTP.response` for `Rsa.verify` input. + * + * @param {string} timestamp - The `Unix` timestamp, should be the one from `response.headers[wechatpay-timestamp]`. + * @param {string} nonce - The `Nonce` string, should be the one from `response.headers[wechatpay-nonce]`. + * @param {string} body - The response payload string, HTTP status(`204`) should be an empty string. + * + * @returns {string} + */ static response(timestamp, nonce, body = '') { return `${timestamp}\n${nonce}\n${body}\n` } diff --git a/lib/rsa.js b/lib/rsa.js index c28b0de..840321b 100644 --- a/lib/rsa.js +++ b/lib/rsa.js @@ -7,8 +7,20 @@ const base64 = `base64` const sha256WithRSAEncryption = `sha256WithRSAEncryption` const RSA_PKCS1_OAEP_PADDING = crypto.constants.RSA_PKCS1_OAEP_PADDING +/** + * Provides some methods for the RSA `sha256WithRSAEncryption` with `RSA_PKCS1_OAEP_PADDING`. + */ class Rsa { + /** + * Encrypts text with sha256WithRSAEncryption/RSA_PKCS1_OAEP_PADDING. + * Node Limits >= 12.9.0 (`oaepHash` was added) + * + * @param {string} plaintext - Cleartext to encode. + * @param {string|Buffer} publicCertificate - A PEM encoded public certification. + * + * @returns {string} + */ static encrypt(plaintext, publicCertificate) { return crypto.publicEncrypt({ oaepHash : sha1, @@ -17,6 +29,15 @@ class Rsa { }, Buffer.from(plaintext, utf8)).toString(base64) } + /** + * Decrypts base64 encoded string with `privateKeyCertificate`. + * Node Limits >= 12.9.0 (`oaepHash` was added) + * + * @param {string} ciphertext - Was previously encrypted string using the corresponding public certication. + * @param {string|Buffer} privateKeyCertificate - A PEM encoded private key certification. + * + * @returns {string} + */ static decrypt(ciphertext, privateKeyCertificate) { return crypto.privateDecrypt({ oaepHash : sha1, @@ -25,6 +46,14 @@ class Rsa { }, Buffer.from(ciphertext, base64)).toString(utf8) } + /** + * Creates and returns a `Sign` string that uses `sha256WithRSAEncryption`. + * + * @param {string} message - Content will be `crypto.Sign`. + * @param {string|Buffer} privateKeyCertificate - A PEM encoded private key certification. + * + * @returns {string} + */ static sign(message, privateKeyCertificate) { return crypto.createSign( sha256WithRSAEncryption @@ -34,6 +63,15 @@ class Rsa { ) } + /** + * Verifying the `message` with given `signature` string that uses `sha256WithRSAEncryption`. + * + * @param {string} message - Content will be `crypto.Verify`. + * @param {string} signature - The base64-encoded ciphertext. + * @param {string|Buffer} publicCertificate - A PEM encoded public certification. + * + * @returns {string} + */ static verify(message, signature, publicCertificate) { return crypto.createVerify( sha256WithRSAEncryption