Merge branch 'master' into hotFix-balance-compensation
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
commit
6533b92ad3
|
@ -82,8 +82,6 @@
|
|||
}
|
||||
&[type=time],
|
||||
&[type=date] {
|
||||
clip-path: inset(0 20px 0 0);
|
||||
|
||||
&::-webkit-inner-spin-button,
|
||||
&::-webkit-clear-button {
|
||||
display: none;
|
||||
|
@ -99,7 +97,7 @@
|
|||
}
|
||||
&[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
const crypto = require('crypto');
|
||||
const UserError = require('vn-loopback/util/user-error');
|
||||
const base64url = require('base64url');
|
||||
|
||||
|
@ -31,9 +30,6 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Source: https://github.com/santiperez/node-redsys-api
|
||||
*/
|
||||
Self.confirm = async(signatureVersion, merchantParameters, signature) => {
|
||||
const $ = Self.app.models;
|
||||
|
||||
|
@ -56,19 +52,11 @@ module.exports = Self => {
|
|||
fields: ['id', 'secretKey']
|
||||
});
|
||||
|
||||
const secretKey = Buffer.from(merchant.secretKey, 'base64');
|
||||
const iv = Buffer.alloc(8, 0);
|
||||
|
||||
const cipher = crypto.createCipheriv('des-ede3-cbc', secretKey, iv);
|
||||
cipher.setAutoPadding(false);
|
||||
const orderKey = Buffer.concat([
|
||||
cipher.update(zeroPad(orderId, 8)),
|
||||
cipher.final()
|
||||
]);
|
||||
|
||||
const base64hmac = crypto.createHmac('sha256', orderKey)
|
||||
.update(merchantParameters)
|
||||
.digest('base64');
|
||||
const base64hmac = Self.createSignature(
|
||||
orderId,
|
||||
merchant.secretKey,
|
||||
merchantParameters
|
||||
);
|
||||
|
||||
if (base64hmac !== base64url.toBase64(signature))
|
||||
throw new UserError('Invalid signature');
|
||||
|
@ -81,14 +69,8 @@ module.exports = Self => {
|
|||
params['Ds_Currency'],
|
||||
params['Ds_Response'],
|
||||
params['Ds_ErrorCode']
|
||||
]
|
||||
);
|
||||
]);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
function zeroPad(buf, blocksize) {
|
||||
const buffer = typeof buf === 'string' ? Buffer.from(buf, 'utf8') : buf;
|
||||
const pad = Buffer.alloc((blocksize - (buffer.length % blocksize)) % blocksize, 0);
|
||||
return Buffer.concat([buffer, pad]);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('end', {
|
||||
description: 'Ends electronic payment transaction',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'orderId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
}, {
|
||||
arg: 'status',
|
||||
type: 'string',
|
||||
required: true,
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: `/end`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.end = async(ctx, orderId, status) => {
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const transaction = await Self.findById(orderId, {
|
||||
fields: ['id', 'clientFk']
|
||||
});
|
||||
|
||||
if (transaction?.clientFk != userId)
|
||||
throw new UserError('Transaction not owned by user');
|
||||
|
||||
await Self.rawSql(
|
||||
'CALL hedera.tpvTransaction_end(?, ?)', [
|
||||
orderId,
|
||||
status
|
||||
]);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,85 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('start', {
|
||||
description: 'Starts electronic payment transaction',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'amount',
|
||||
type: 'Number',
|
||||
required: true,
|
||||
}, {
|
||||
arg: 'companyId',
|
||||
type: 'Number',
|
||||
required: false,
|
||||
}, {
|
||||
arg: 'urlOk',
|
||||
type: 'String',
|
||||
required: false,
|
||||
}, {
|
||||
arg: 'urlKo',
|
||||
type: 'String',
|
||||
required: false,
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/start`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.start = async(ctx, amount, companyId, urlOk, urlKo) => {
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const [[row]] = await Self.rawSql(
|
||||
'CALL hedera.tpvTransaction_start(?, ?, ?)', [
|
||||
amount,
|
||||
companyId,
|
||||
userId
|
||||
]);
|
||||
|
||||
if (!row)
|
||||
throw new UserError('Transaction error');
|
||||
|
||||
const orderId = row.transactionId.padStart(12, '0');
|
||||
const merchantUrl = row.merchantUrl ? row.merchantUrl : '';
|
||||
urlOk = urlOk ? urlOk.replace('_transactionId_', orderId) : '';
|
||||
urlKo = urlKo ? urlKo.replace('_transactionId_', orderId) : '';
|
||||
|
||||
const params = {
|
||||
'Ds_Merchant_Amount': amount,
|
||||
'Ds_Merchant_Order': orderId,
|
||||
'Ds_Merchant_MerchantCode': row.merchant,
|
||||
'Ds_Merchant_Currency': row.currency,
|
||||
'Ds_Merchant_TransactionType': row.transactionType,
|
||||
'Ds_Merchant_Terminal': row.terminal,
|
||||
'Ds_Merchant_MerchantURL': merchantUrl,
|
||||
'Ds_Merchant_UrlOK': urlOk,
|
||||
'Ds_Merchant_UrlKO': urlKo
|
||||
};
|
||||
for (const param in params)
|
||||
params[param] = encodeURIComponent(params[param]);
|
||||
|
||||
const json = JSON.stringify(params);
|
||||
const merchantParameters = Buffer.from(json).toString('base64');
|
||||
|
||||
const signature = Self.createSignature(
|
||||
orderId,
|
||||
row.secretKey,
|
||||
merchantParameters
|
||||
);
|
||||
|
||||
return {
|
||||
url: row.url,
|
||||
postValues: {
|
||||
'Ds_SignatureVersion': 'HMAC_SHA256_V1',
|
||||
'Ds_MerchantParameters': merchantParameters,
|
||||
'Ds_Signature': signature
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
|
@ -1,3 +1,29 @@
|
|||
const crypto = require('crypto');
|
||||
|
||||
module.exports = Self => {
|
||||
require('../methods/tpv-transaction/confirm')(Self);
|
||||
require('../methods/tpv-transaction/start')(Self);
|
||||
require('../methods/tpv-transaction/end')(Self);
|
||||
|
||||
Self.createSignature = function(orderId, secretKey, merchantParameters) {
|
||||
secretKey = Buffer.from(secretKey, 'base64');
|
||||
const iv = Buffer.alloc(8, 0);
|
||||
|
||||
const cipher = crypto.createCipheriv('des-ede3-cbc', secretKey, iv);
|
||||
cipher.setAutoPadding(false);
|
||||
const orderKey = Buffer.concat([
|
||||
cipher.update(zeroPad(orderId, 8)),
|
||||
cipher.final()
|
||||
]);
|
||||
|
||||
return crypto.createHmac('sha256', orderKey)
|
||||
.update(merchantParameters)
|
||||
.digest('base64');
|
||||
};
|
||||
|
||||
function zeroPad(buf, blocksize) {
|
||||
const buffer = typeof buf === 'string' ? Buffer.from(buf, 'utf8') : buf;
|
||||
const pad = Buffer.alloc((blocksize - (buffer.length % blocksize)) % blocksize, 0);
|
||||
return Buffer.concat([buffer, pad]);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -72,4 +72,45 @@ describe('upsertFixedPrice()', () => {
|
|||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it(`should recalculate rate2 if change rate3`, async() => {
|
||||
const tx = await models.FixedPrice.beginTransaction({});
|
||||
|
||||
const tomorrow = new Date(now);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
const rate2 = 2;
|
||||
const firstRate3 = 1;
|
||||
const secondRate3 = 2;
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
const ctx = {args: {
|
||||
id: undefined,
|
||||
itemFk: 1,
|
||||
warehouseFk: 1,
|
||||
started: tomorrow,
|
||||
ended: tomorrow,
|
||||
rate2: rate2,
|
||||
rate3: firstRate3,
|
||||
minPrice: 0,
|
||||
hasMinPrice: false
|
||||
}};
|
||||
|
||||
// create new fixed price
|
||||
const newFixedPrice = await models.FixedPrice.upsertFixedPrice(ctx, options);
|
||||
|
||||
// change rate3 to same fixed price id
|
||||
ctx.args.id = newFixedPrice.id;
|
||||
ctx.args.rate3 = secondRate3;
|
||||
|
||||
const result = await models.FixedPrice.upsertFixedPrice(ctx, options);
|
||||
|
||||
expect(result.rate2).not.toEqual(rate2);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -72,6 +72,16 @@ module.exports = Self => {
|
|||
|
||||
try {
|
||||
delete args.ctx; // removed unwanted data
|
||||
|
||||
if (args.id) {
|
||||
const beforeFixedPrice = await models.FixedPrice.findById(args.id, {fields: ['rate3']}, myOptions);
|
||||
const [result] = await Self.rawSql(`SELECT vn.priceFixed_getRate2(?, ?) as rate2`,
|
||||
[args.id, args.rate3], myOptions);
|
||||
|
||||
if (beforeFixedPrice.rate3 != args.rate3 && result && result.rate2)
|
||||
args.rate2 = result.rate2;
|
||||
}
|
||||
|
||||
const fixedPrice = await models.FixedPrice.upsert(args, myOptions);
|
||||
const targetItem = await models.Item.findById(args.itemFk, null, myOptions);
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@
|
|||
class="dense"
|
||||
vn-focus
|
||||
ng-model="price.rate3"
|
||||
on-change="$ctrl.upsertPrice(price); $ctrl.recalculateRate2(price)"
|
||||
on-change="$ctrl.upsertPrice(price);"
|
||||
step="0.01"s>
|
||||
</vn-input-number>
|
||||
</field>
|
||||
|
|
|
@ -113,24 +113,6 @@ export default class Controller extends Section {
|
|||
return {[param]: value};
|
||||
}
|
||||
}
|
||||
|
||||
recalculateRate2(price) {
|
||||
if (!price.id || !price.rate3) return;
|
||||
|
||||
const query = 'FixedPrices/getRate2';
|
||||
const params = {
|
||||
fixedPriceId: price.id,
|
||||
rate3: price.rate3
|
||||
};
|
||||
this.$http.get(query, {params})
|
||||
.then(res => {
|
||||
const rate2 = res.data.rate2;
|
||||
if (rate2) {
|
||||
price.rate2 = rate2;
|
||||
this.upsertPrice(price);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.vnComponent('vnFixedPrice', {
|
||||
|
|
|
@ -85,25 +85,5 @@ describe('fixed price', () => {
|
|||
expect(controller.$.model.remove).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('recalculateRate2()', () => {
|
||||
it(`should rate2 recalculate`, () => {
|
||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||
const price = {
|
||||
id: 1,
|
||||
itemFk: 1,
|
||||
rate2: 2,
|
||||
rate3: 2
|
||||
};
|
||||
const response = {rate2: 1};
|
||||
controller.recalculateRate2(price);
|
||||
|
||||
const query = `FixedPrices/getRate2?fixedPriceId=${price.id}&rate3=${price.rate3}`;
|
||||
$httpBackend.expectGET(query).respond(response);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(price.rate2).toEqual(response.rate2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,6 +26,11 @@ module.exports = Self => {
|
|||
type: 'string',
|
||||
required: true,
|
||||
description: `The old version number`
|
||||
}, {
|
||||
arg: 'description',
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: `The description of changes`
|
||||
}, {
|
||||
arg: 'unlock',
|
||||
type: 'boolean',
|
||||
|
@ -42,8 +47,7 @@ module.exports = Self => {
|
|||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.upload = async(ctx, appName, toVersion, branch, fromVersion, unlock, options) => {
|
||||
Self.upload = async(ctx, options) => {
|
||||
const models = Self.app.models;
|
||||
const myOptions = {};
|
||||
const $t = ctx.req.__; // $translate
|
||||
|
@ -51,6 +55,12 @@ module.exports = Self => {
|
|||
const AccessContainer = models.AccessContainer;
|
||||
const fileOptions = {};
|
||||
let tx;
|
||||
const appName = ctx.args.appName;
|
||||
const toVersion = ctx.args.toVersion;
|
||||
const branch = ctx.args.branch;
|
||||
const fromVersion = ctx.args.fromVersion;
|
||||
let description = ctx.args.description;
|
||||
const unlock = ctx.args.unlock;
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
@ -132,13 +142,46 @@ module.exports = Self => {
|
|||
await fs.symlink(rootRelative, destinationRoot);
|
||||
}
|
||||
}
|
||||
if (description) {
|
||||
let formatDesc;
|
||||
const mainBranches = new Set(['master', 'test', 'dev']);
|
||||
if (mainBranches.has(branch))
|
||||
formatDesc = `> :branch_${branch}: `;
|
||||
else
|
||||
formatDesc = `> :branch: `;
|
||||
|
||||
formatDesc += `*${appName.toUpperCase()}* v.${toVersion} `;
|
||||
|
||||
const oldVersion = await models.MdbVersionTree.findOne({
|
||||
where: {version: fromVersion},
|
||||
fields: ['branchFk']
|
||||
}, myOptions);
|
||||
|
||||
if (branch == oldVersion.branchFk)
|
||||
formatDesc += `[*${branch}*]: `;
|
||||
else
|
||||
formatDesc += `[*${oldVersion.branchFk}* » *${branch}*]: `;
|
||||
|
||||
const params = await models.MdbConfig.findOne(myOptions);
|
||||
const issueTrackerUrl = params.issueTrackerUrl;
|
||||
const issueNumberRegex = params.issueNumberRegex;
|
||||
const chatDestination = params.chatDestination;
|
||||
|
||||
const regex = new RegExp(issueNumberRegex, 'g');
|
||||
formatDesc += description.replace(regex, (match, issueId) => {
|
||||
const newUrl = issueTrackerUrl.replace('{index}', issueId);
|
||||
return `[#${issueId}](${newUrl})`;
|
||||
});
|
||||
|
||||
await models.Chat.send(ctx, chatDestination, formatDesc, myOptions);
|
||||
}
|
||||
await models.MdbVersionTree.create({
|
||||
app: appName,
|
||||
version: toVersion,
|
||||
branchFk: branch,
|
||||
fromVersion,
|
||||
userFk: userId
|
||||
userFk: userId,
|
||||
description,
|
||||
}, myOptions);
|
||||
|
||||
await models.MdbVersion.upsert({
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
"MdbVersionTree": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"MdbConfig": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"AccessContainer": {
|
||||
"dataSource": "accessStorage"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "MdbConfig",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "mdbConfig"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"id": true
|
||||
},
|
||||
"issueTrackerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"issueNumberRegex": {
|
||||
"type": "string"
|
||||
},
|
||||
"chatDestination": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,9 @@
|
|||
},
|
||||
"userFk": {
|
||||
"type": "number"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
|
|
Loading…
Reference in New Issue