Skip to content

Commit 93adb00

Browse files
committed
hacking payments
1 parent 876775d commit 93adb00

27 files changed

Lines changed: 315 additions & 269 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,6 @@ app/stylesheets/sprites/*
3636
bower_components/
3737

3838
# Passwords and other secret stuff
39-
config/secret.js
39+
modules/config/secret.js
40+
modules/config/certs/*
41+

hmvc/payments/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ exports.createTransactionForm = function* (order, method) {
4949

5050
yield transaction.persist();
5151

52-
return paymentModules[method].renderForm(transaction);
52+
var form = yield* paymentModules[method].renderForm(transaction);
53+
54+
yield transaction.log('form', form);
55+
56+
return form;
5357

5458
};
5559

hmvc/payments/models/transaction.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,22 @@ schema.methods.getStatusDescription = function() {
9595
throw new Error("неподдерживаемый статус транзакции");
9696
};
9797

98+
schema.methods.logRequest = function*(event, request) {
99+
yield this.log(event, {url: request.originalUrl, body: request.body});
100+
};
101+
98102
// log anything related to the transaction
99-
schema.methods.log = function*(options) {
103+
schema.methods.log = function*(event, data) {
100104

101-
console.log(options);
105+
if (typeof event != "string") {
106+
throw new Error("event name must be a string");
107+
}
102108

103-
options.transaction = this._id;
109+
var options = {
110+
transaction: this._id,
111+
event: event,
112+
data: data
113+
};
104114

105115
// for complex objects -> prior to logging make them simple (must be jsonable)
106116
// e.g for HTTP response (HTTP.IncomingMessage)

hmvc/payments/models/transactionLog.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ var schema = new Schema({
55

66
transaction: {
77
type: Schema.Types.ObjectId,
8-
ref: 'Transaction'
8+
ref: 'Transaction',
9+
index: true
10+
},
11+
event: {
12+
type: String,
13+
index: true
914
},
10-
event: String,
1115
data: Schema.Types.Mixed,
1216

1317
created: {

hmvc/payments/payanyway/controller/callback.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@ exports.post = function* (next) {
1515
this.throw(403, "wrong signature");
1616
}
1717

18-
yield this.transaction.log({
19-
event: 'callback',
20-
data: {url: this.request.originalUrl, body: this.request.body}
21-
});
18+
yield this.transaction.logRequest('callback', this.request);
2219

20+
// signature is valid, so everything MUST be fine
2321
if (this.transaction.amount != parseFloat(this.request.body.MNT_AMOUNT) ||
2422
this.request.body.MNT_ID != config.payments.modules.payanyway.id) {
25-
this.throw(404, 'transaction with given params not found');
23+
yield this.transaction.persist({
24+
status: Transaction.STATUS_FAIL,
25+
statusMessage: "данные транзакции не совпадают с базой, свяжитесь с поддержкой"
26+
});
27+
this.throw(404, "transaction data doesn't match the POST body");
2628
}
2729

2830
yield this.transaction.persist({

hmvc/payments/payanyway/index.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
const jade = require('jade');
2-
const path = require('path');
3-
var config = require('config');
4-
var payment = require('payment');
51

62
var router = require('./router');
73

hmvc/payments/payanyway/renderForm.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ const jade = require('jade');
22
const config = require('config');
33
const path = require('path');
44

5-
module.exports = function (transaction) {
5+
module.exports = function* (transaction) {
66

77
return jade.renderFile(path.join(__dirname, 'templates/form.jade'), {
88
amount: transaction.amount,
99
number: transaction.number,
10+
currency: config.payments.currency,
1011
id: config.payments.modules.payanyway.id
1112
});
1213

hmvc/payments/payanyway/router.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
var Router = require('koa-router');
2-
var payment = require('payment');
32

43
var router = module.exports = new Router();
54

@@ -9,11 +8,10 @@ var success = require('./controller/success');
98
var inprogress = require('./controller/inprogress');
109
var cancel = require('./controller/cancel');
1110

12-
// webmoney server posts here (in background)
1311
router.post('/callback', callback.post);
1412

15-
// webmoney server redirects here if payment successful
1613
router.get('/success', success.get);
14+
router.get('/inprogress', inprogress.get);
1715

1816
router.get('/cancel', cancel.get);
1917

hmvc/payments/payanyway/templates/form.jade

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
form(method="POST" action="https://www.moneta.ru/assistant.htm" accept-charset="UTF-8")
33
input(type="hidden",name="MNT_ID",value=id)
44
input(type="hidden",name="MNT_TRANSACTION_ID",value=number)
5-
input(type="hidden",name="MNT_CURRENCY_CODE",value="RUB")
5+
input(type="hidden",name="MNT_CURRENCY_CODE",value=currency)
66
input(type="hidden",name="MNT_AMOUNT",value=amount)
77
input(type="hidden",name="paymentSystem.limitIds",value="843858,248362,822360,545234,1028,499669")
88
input(type="submit",value="Оплатить")
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
const config = require('config')
2+
const paypalConfig = config.payments.modules.paypal;
3+
const Order = require('../../models/order');
4+
const Transaction = require('../../models/transaction');
5+
const TransactionLog = require('../../models/transactionLog');
6+
const log = require('js-log')();
7+
const request = require('koa-request');
8+
9+
// docs:
10+
//
11+
// https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNIntro/
12+
13+
log.debugOn();
14+
15+
/* jshint -W106 */
16+
exports.post = function* (next) {
17+
18+
yield* this.loadTransaction('invoice', {skipOwnerCheck: true});
19+
20+
yield this.transaction.logRequest('ipn-unverified', this.request);
21+
22+
var qs = {
23+
'cmd': '_notify-validate'
24+
};
25+
26+
for (var field in this.request.body) {
27+
qs[field] = this.request.body[field];
28+
}
29+
30+
// request oauth token
31+
var options = {
32+
method: 'GET',
33+
qs: qs,
34+
url: 'https://www.paypal.com/cgi-bin/webscr'
35+
};
36+
37+
38+
yield this.transaction.log('request ipn verify', options);
39+
40+
var response;
41+
try {
42+
response = yield request(options);
43+
} catch(e) {
44+
yield this.transaction.log('request ipn verify failed', e.message);
45+
this.throw(403, "Couldn't verify ipn");
46+
}
47+
48+
if (response.body != "VERIFIED") {
49+
this.throw(403, "Invalid IPN");
50+
}
51+
52+
// ipn is verified now! But we check if it's data matches the transaction (as recommended in docs)
53+
if (this.transaction.amount != parseFloat(this.request.body.mc_gross) ||
54+
this.request.body.receiver_email != paypalConfig.email ||
55+
this.request.body.mc_currency != config.payments.currency) {
56+
57+
yield this.transaction.persist({
58+
status: Transaction.STATUS_FAIL,
59+
statusMessage: "данные транзакции не совпадают с базой, свяжитесь с поддержкой"
60+
});
61+
this.throw(404, "transaction data doesn't match the POST body");
62+
}
63+
64+
// match agains latest ipn in logs as recommended:
65+
// if there was an IPN about the same transaction, and it's state is the same
66+
// => then the current one is a duplicate
67+
68+
var previousIpn = yield TransactionLog.findOne({
69+
event: "ipn",
70+
transaction: this.transaction._id
71+
}).sort({created: -1}).exec();
72+
73+
if (previousIpn && previousIpn.data.payment_status == this.request.body.payment_status) {
74+
yield this.transaction.logRequest("ipn duplicate", this.request);
75+
// ignore duplicate
76+
this.body = '';
77+
return;
78+
}
79+
80+
// now we have a valid non-duplicate IPN, let's update the transaction
81+
82+
83+
this.body = '';
84+
};

0 commit comments

Comments
 (0)