Skip to content

Commit

Permalink
Merge pull request #75 from ifanrx/qq-payment
Browse files Browse the repository at this point in the history
添加 QQ 小程序支付、QQ 网页扫码支付、QQ 加密信息解密等方法的支持
  • Loading branch information
zzzze authored Aug 21, 2019
2 parents 56f6080 + fa7b385 commit 3f74727
Show file tree
Hide file tree
Showing 14 changed files with 1,960 additions and 24 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.4.0 (2019-8-21)
- [A] QQ SDK 支持 QQ 小程序支付
- [A] web 端支持 QQ 扫码支付
- [A] QQ SDK 支持解密 QQ 返回的加密信息

## 2.3.0 (2019-7-19)
- [A] 支持手机号登录
- [A] web 端支持微信 JSAPI 支付
Expand Down
1 change: 1 addition & 0 deletions core/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const API = {
AUTHENTICATE: '/hserve/v2.0/idp/qq/authenticate/',
USER_ASSOCIATE: '/hserve/v2.0/idp/qq/user-association/',
TEMPLATE_MESSAGE: '/hserve/v2.0/template-message-ticket/',
DECRYPT: '/hserve/v2.0/qq/decrypt/',
},

ALIPAY: {
Expand Down
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "minapp-sdk",
"version": "2.3.0",
"version": "2.4.0",
"main": "./lib/index.js",
"browser": "./lib/web.js",
"miniprogram": "lib",
Expand All @@ -9,11 +9,12 @@
"/lib"
],
"scripts": {
"test": "npm run test-core && npm run test-wechat && npm run test-web && npm run test-alipay",
"test": "npm run test-core && npm run test-wechat && npm run test-web && npm run test-alipay && npm run test-qq",
"test-core": "mocha test/core-test-new/*.test.js",
"test-wechat": "mocha test/wechat/index.js",
"test-alipay": "mocha test/alipay/index.js",
"test-web": "./test/web/test.sh",
"test-qq": "ava test/qq/*.test.js",
"web-dev-server": "anywhere -p 40034 -h localhost -d test/web-dev-server",
"dev": "export NODE_ENV=development && webpack --config sdk-file/webpack.config.js --mode=development -w",
"build": "export NODE_ENV=production && webpack --mode=production --config sdk-file/webpack.config.js",
Expand All @@ -33,8 +34,8 @@
},
"homepage": "https://github.com/ifanrx/repository#readme",
"versions": {
"alipay": "2.3.0",
"web": "2.3.0"
"alipay": "2.4.0",
"web": "2.4.0"
},
"devDependencies": {
"@babel/core": "^7.2.2",
Expand Down Expand Up @@ -63,6 +64,7 @@
"webpack-cli": "^3.2.1"
},
"dependencies": {
"ava": "^2.3.0",
"axios": "^0.18.0",
"console-log-level": "^1.4.1",
"core-js": "^3.0.1"
Expand Down
2 changes: 1 addition & 1 deletion sdk-file/src/alipay/pay.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class PayError extends HError {
}
}

const createPayFn = BaaS => (params) => {
const createPayFn = BaaS => params => {
const API = BaaS._config.API
let paramsObj = {}

Expand Down
36 changes: 36 additions & 0 deletions sdk-file/src/qq/decryptData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const HError = require('core-module/HError')

const createDecryptDataFn = BaaS => (...params) => {
const API = BaaS._config.API

if (!validateParams(params)) {
throw new HError(605)
}

let paramsObj = {
encryptedData: params[0],
iv: params[1]
}

return BaaS._baasRequest({
url: API.QQ.DECRYPT + params[2] + '/',
method: 'POST',
data: paramsObj,
}).then(res => {
return res.data
}, err => {
let code = err.code
if (code === 403) throw new HError(403, 'QQ 解密插件未开启')
throw err
})
}

const validateParams = (params) => {
if (!(params instanceof Array) || params.length < 3) return false
const requiredDataKeys = ['open-gid']
return requiredDataKeys.indexOf(params[2]) !== -1
}

module.exports = function (BaaS) {
BaaS.decryptData = createDecryptDataFn(BaaS)
}
4 changes: 4 additions & 0 deletions sdk-file/src/qq/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const polyfill = require('./polyfill')
const auth = require('./auth')
const reportTicket = require('./reportTicket')
const reportTemplateMsgAnalytics = require('./reportTemplateMsgAnalytics')
const pay = require('./pay')
const decryptData = require('./decryptData')

BaaS._config.VERSION = __VERSION_QQ__

Expand All @@ -12,6 +14,8 @@ BaaS.use(polyfill)
BaaS.use(auth)
BaaS.use(reportTicket)
BaaS.use(reportTemplateMsgAnalytics)
BaaS.use(pay)
BaaS.use(decryptData)
BaaS.request = require('./request')
BaaS._baasRequest = require('./baasRequest')
BaaS.uploadFile = require('./uploadFile')
Expand Down
52 changes: 52 additions & 0 deletions sdk-file/src/qq/pay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const BaaS = require('core-module/baas')
const HError = require('core-module/HError')

const API = BaaS._config.API

const keysMap = {
merchandiseSchemaID: 'merchandise_schema_id', // optional
merchandiseRecordID: 'merchandise_record_id', // optional
merchandiseSnapshot: 'merchandise_snapshot', // optional
merchandiseDescription: 'merchandise_description', // required
totalCost: 'total_cost', // required
}

const createPayFn = BaaS => params => {
const API = BaaS._config.API
let paramsObj = {}

for (let key in params) {
paramsObj[keysMap[key]] = params[key]
}

paramsObj.gateway_type = 'qpay'

return BaaS._baasRequest({
url: API.PAY,
method: 'POST',
data: paramsObj,
}).then(function (res) {
let data = res.data || {}
return new Promise((resolve, reject) => {
qq.requestPayment({
package: data.package,
success: function (res) {
res.transaction_no = data.transaction_no
res.trade_no = data.trade_no
return resolve(res)
},
fail: function (err) {
if (err.errMsg == 'requestPayment:fail 用户取消') {
reject(new HError(607))
} else {
reject(new HError(608, err.errMsg))
}
},
})
})
})
}

module.exports = function (BaaS) {
BaaS.pay = createPayFn(BaaS)
}
12 changes: 12 additions & 0 deletions sdk-file/src/web/payment.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ let ALIPAY_GATEWAY_TYPE = {
PAGE: 'alipay_page',
}

let QQ_GATEWAY_TYPE = {
NATIVE: 'qpay_native',
}

const pay = (BaaS, options) => {
let API = BaaS._config.API
return BaaS._baasRequest({
Expand Down Expand Up @@ -73,9 +77,17 @@ const createPayWithAlipayFn = BaaS => options => {
return pay(BaaS, options)
}

const createPayWithQQFn = BaaS => options => {
if (options.gatewayType !== QQ_GATEWAY_TYPE.NATIVE) {
return Promise.reject(new HError(608, 'incorrect gateway type'))
}
return pay(BaaS, options)
}

module.exports = function (BaaS) {
BaaS.payment = {
payWithWechat: createPayWithWechatFn(BaaS),
payWithAlipay: createPayWithAlipayFn(BaaS),
payWithQQ: createPayWithQQFn(BaaS),
}
}
69 changes: 69 additions & 0 deletions test/qq/decryptData.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import test from 'ava';
import sinon from 'sinon'

const moduleAlias = require('module-alias')
moduleAlias.addAlias('core-module', __dirname + '../../../core')
const decryptDataModule = require('../../sdk-file/src/qq/decryptData')
const HError = require('core-module/HError')

test.beforeEach(t => {
t.context.BaaS = {
_baasRequest: sinon.stub(),
_config: {
API: {
QQ: {
DECRYPT: 'decrypt-url/',
},
},
},
}
})

test('invoke encryptedData success', t => {
decryptDataModule(t.context.BaaS)
let encryptedData = 'encryptedData'
let iv = 'iv'
let type = 'open-gid'
let data = {
foo: 'foo',
bar: 'bar',
baz: 'baz',
}
t.context.BaaS._baasRequest.resolves({
status: 200,
data,
})
return t.context.BaaS.decryptData(encryptedData, iv, type).then(res => {
t.deepEqual(res, data)
t.is(t.context.BaaS._baasRequest.callCount, 1)
t.deepEqual(t.context.BaaS._baasRequest.getCall(0).args, [{
data: {
encryptedData: 'encryptedData',
iv: 'iv',
},
method: 'POST',
url: 'decrypt-url/open-gid/',
}])
})
})

test('invoke encryptedData fail (605)', t => {
decryptDataModule(t.context.BaaS)
let encryptedData = 'encryptedData'
let iv = 'iv'
let type = 'anyone'
t.throws(() => {
t.context.BaaS.decryptData(encryptedData, iv, type)
}, new HError(605))
})

test('invoke encryptedData fail (403)', t => {
decryptDataModule(t.context.BaaS)
let encryptedData = 'encryptedData'
let iv = 'iv'
let type = 'open-gid'
t.context.BaaS._baasRequest.rejects({code: 403})
return t.context.BaaS.decryptData(encryptedData, iv, type).catch(err => {
t.deepEqual(err, new HError(403, 'QQ 解密插件未开启'))
})
})
58 changes: 58 additions & 0 deletions test/qq/pay.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import test from 'ava';
import sinon from 'sinon'

const moduleAlias = require('module-alias')
moduleAlias.addAlias('core-module', __dirname + '../../../core')
const payModule = require('../../sdk-file/src/qq/pay')

test('qq.pay', t => {
let BaaS = {
_baasRequest: sinon.stub().resolves({
data: {
package: 'test-package',
transaction_no: 'foo',
trade_no: 'bar',
},
}),
_config: {
API: {
PAY: 'payment-url',
},
},
}
global.qq = {
requestPayment: sinon.stub().callsFake(({success}) => {
success({test: 'test-result'})
})
}
payModule(BaaS)
let params = {
merchandiseSchemaID: 'merchandise_schema_id', // optional
merchandiseRecordID: 'merchandise_record_id', // optional
merchandiseSnapshot: 'merchandise_snapshot', // optional
merchandiseDescription: 'merchandise_description', // required
totalCost: 'total_cost', // required
}
return BaaS.pay(params).then(res => {
t.deepEqual(res, {
test: 'test-result',
transaction_no: 'foo',
trade_no: 'bar',
})
t.is(global.qq.requestPayment.callCount, 1)
t.is(BaaS._baasRequest.callCount, 1)
t.is(global.qq.requestPayment.getCall(0).args[0].package, 'test-package')
t.deepEqual(BaaS._baasRequest.getCall(0).args, [{
data: {
gateway_type: 'qpay',
merchandise_schema_id: 'merchandise_schema_id',
merchandise_record_id: 'merchandise_record_id',
merchandise_snapshot: 'merchandise_snapshot',
merchandise_description: 'merchandise_description',
total_cost: 'total_cost',
},
method: 'POST',
url: 'payment-url',
}])
})
});
8 changes: 8 additions & 0 deletions test/web-dev-server/payment/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ <h5 class="card-title">支付宝 H5 支付</h5>
<button type="button" class="btn btn-primary" @click="payWithAlipayWap">支付</button>
</div>
</div>

<div class="card">
<div class="card-body">
<h5 class="card-title">QQ 扫码支付</h5>
<img v-if="qqQrCode" :src="qqQrCode" alt="">
<button type="button" class="btn btn-primary" @click="payWithQQ">支付</button>
</div>
</div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcode-generator/1.4.3/qrcode.min.js"></script>
Expand Down
18 changes: 18 additions & 0 deletions test/web-dev-server/payment/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ function init() {
data() {
return {
qrCode: '',
qqQrCode: '',
}
},
watch: {
Expand Down Expand Up @@ -97,6 +98,23 @@ function init() {
console.log('支付成功')
}).catch(err => console.log(err))
},

payWithQQ() {
BaaS.payment.payWithQQ({
gatewayType: 'qpay_native',
totalCost: 0.01,
merchandiseDescription: 'test-04',
}).then(res => {
var qr = qrcode(0, 'M')
qr.addData(res.data.code_url)
qr.make()
this.qqQrCode = qr.createDataURL()
return new Promise((resolve, reject) => this.checkPaymentStatus(res.data.trade_no, resolve, reject))
}).then(() => {
alert('支付成功')
console.log('支付成功')
}).catch(err => console.log(err))
},
},


Expand Down
Loading

0 comments on commit 3f74727

Please sign in to comment.