Skip to content

Commit

Permalink
Merge branch 'develop' into feature/issue-111
Browse files Browse the repository at this point in the history
  • Loading branch information
Aleserche committed Apr 2, 2018
2 parents 3ae991b + 47ef826 commit 04c02a4
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 118 deletions.
24 changes: 20 additions & 4 deletions apiary.apib
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,22 @@ Authentication attempts with not verified token will result in 401 response.
"status": "pending",
"type": "eth_transfer",
"direction": "in"
},
{
"address": '0x99eb89a5D15A6D487da3f3C1fC4fc2378eE227aa',
"confirmsNeeded": 0,
"currency": 'BTC',
"expiredOn": 1234567890,
"id": '563143b280d2387c91807933',
"qrcodeUrl": 'qrcode_url',
"receivedAmount": '0.1',
"receivedConfirms": '0',
"status": '100',
"statusUrl": 'status_url',
"totalAmount": 1,
"txnId": 'abc123432',
"type": 'gateway_transaction',
"timeout": 3600
}
]
Expand Down Expand Up @@ -812,12 +828,12 @@ Authentication attempts with not verified token will result in 401 response.
{
"address": "mhocbNymvkn9sP7FRAionoif2SQqJBuibx",
"amount": 5,
"confirms_needed": "0",
"confirmsNeeded": "0",
"currency1": "ETH",
"currency2": "BTC",
"email": "[email protected]",
"qrcode_url": "https://www.coinpayments.net/qrgen.php?id=CPCC41OJBXM5TKQO3HNMDR3F0W&key=8877e51b87aee89e256336e7e955ec53",
"status_url": "https://www.coinpayments.net/index.php?cmd=status&id=CPCC41OJBXM5TKQO3HNMDR3F0W&key=8877e51b87aee89e256336e7e955ec53",
"qrcodeUrl": "https://www.coinpayments.net/qrgen.php?id=CPCC41OJBXM5TKQO3HNMDR3F0W&key=8877e51b87aee89e256336e7e955ec53",
"statusUrl": "https://www.coinpayments.net/index.php?cmd=status&id=CPCC41OJBXM5TKQO3HNMDR3F0W&key=8877e51b87aee89e256336e7e955ec53",
"timeout": 3600,
"txn_id": "CPCC4YOJBXMI1VQOAHNMDR330W"
"txnId": "CPCC4YOJBXMI1VQOAHNMDR330W"
}
148 changes: 82 additions & 66 deletions docs/index.html

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions src/controllers/gateway.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import config from '../config';
import { getConnection } from 'typeorm';
import { PaymentGateTransaction } from '../entities/payment.gate.transaction';

import { Logger } from '../logger';

const IPN_RESPONSE_STATUS_COMPLETE = 100;
const IPN_RESPONSE_STATUS_QUEUED_PAYOUT = 2;

Expand All @@ -23,6 +25,8 @@ const cpMiddleware = CoinPayments.ipn({
'/gateway'
)
export class GatewayController {
private logger = Logger.getInstance('DASHBOARD_CONTROLLER');

constructor(
@inject(VerificationClientType) private verificationClient: VerificationClientInterface,
@inject(CoinpaymentsClientType) private coinpaimentsClient: CoinpaymentsClientInterface,
Expand All @@ -48,6 +52,8 @@ export class GatewayController {
'AuthMiddleware'
)
async createTransaction(req: AuthorizedRequest, res: Response): Promise<void> {
const logger = this.logger.sub({ email: req.user.email }, '[createTransaction] ');

try {
const tx = await this.paymentsService.initiateBuyEths(
req.user,
Expand All @@ -58,6 +64,7 @@ export class GatewayController {

res.json(tx.buyCoinpaymentsData);
} catch (error) {
logger.exception(error);
res.json(error);
}
}
Expand All @@ -80,6 +87,7 @@ export class GatewayController {
(req, res, next) => cpMiddleware(req, {end: () => {}}, next)
)
async ipn(req: Request, res: Response, next): Promise<void> {
const logger = this.logger.sub({ ipnId: req.body.ipn_id }, '[IPN Handler] ');
try {
if (req.body.status >= IPN_RESPONSE_STATUS_COMPLETE) {
// complete
Expand All @@ -94,6 +102,7 @@ export class GatewayController {

res.end('IPN OK');
} catch (error) {
logger.exception(error);
res.end('IPN Error: ' + error);
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/controllers/specs/dashboard.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,25 @@ describe('Dashboard', () => {
qrcodeUrl: 'qrcode_url',
receivedAmount: '0.1',
receivedConfirms: '0',
status: '100',
statusUrl: 'status_url',
totalAmount: 1,
txnId: 'abc123432',
type: 'gateway_transaction',
timeout: 3600
},
{
address: '0x99eb89a5D15A6D487da3f3C1fC4fc2378eE227aa',
confirmsNeeded: 0,
currency: 'BTC',
expiredOn: 1234567890,
id: '563143b280d2387c91807f00',
qrcodeUrl: 'qrcode_url',
status: '-1',
statusUrl: 'status_url',
timeout: 3600,
totalAmount: 1,
txnId: 'abc124',
type: 'gateway_transaction'
}
]);
Expand Down
3 changes: 2 additions & 1 deletion src/entities/ipn.response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class IPNResponse {

// API fields
@Column()
staus: number; // see: https://www.coinpayments.net/merchant-tools-ipn#statuses
status: number; // see: https://www.coinpayments.net/merchant-tools-ipn#statuses

@Column()
statusText: string;
Expand Down Expand Up @@ -98,6 +98,7 @@ export class IPNResponse {
ipnResponse.receivedAmount = data.received_amount;
ipnResponse.receivedConfirms = data.received_amount;
ipnResponse.statusText = data.status_text;
ipnResponse.status = data.status;
ipnResponse.timestamp = data.timestamp;
ipnResponse.txnId = data.txn_id;

Expand Down
3 changes: 0 additions & 3 deletions src/entities/payment.gate.transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ export class PaymentGateTransaction {
@Column()
buyIpns: Array<IPNResponse>;

@Column()
convertIpns: Array<any>;

@Column()
timestamp: number;
}
2 changes: 1 addition & 1 deletion src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,6 @@ declare interface PaymentGateTransactionInterface {
buyCoinpaymentsData: any;
convertCoinpaymentsData: null;
buyIpns: Array<any>;
convertIpns: Array<any>;
}

declare interface PaymentsServiceInterface {
Expand Down Expand Up @@ -403,6 +402,7 @@ declare interface PaymentGateTransactionView {
qrcodeUrl: string;
address: string;
timestamp: number;
timeout: number;
expiredOn: number;
txnId: string;
statusUrl: string;
Expand Down
25 changes: 19 additions & 6 deletions src/services/ipn.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,23 @@ import { IPNResponse } from '../entities/ipn.response';
import { CoinpaymentsClient, CoinpaymentsClientType } from './coinpayments/coinpayments.client';
import config from '../config';
import { Investor } from '../entities/investor';
import { Logger } from '../logger';

@injectable()
export class IPNService implements IPNServiceInterface {
private logger = Logger.getInstance('IPN_SERVICE');

constructor(
@inject(CoinpaymentsClientType) private cpClient: CoinpaymentsClientInterface
) { }

async processFail(data: any): Promise<PaymentGateTransactionInterface> {
async processFail(data: IPNApiTypeResponse): Promise<PaymentGateTransactionInterface> {
const logger = this.logger.sub({ ipn_id: data.ipn_id }, '[processFail] ');

const txRepository = getConnection().mongoManager.getMongoRepository(PaymentGateTransaction);

logger.debug('Find transaction', { txId: data.txn_id } );

const tx: PaymentGateTransaction = await txRepository.findOne({where: {
'buyCoinpaymentsData.txnId': data.txn_id
}});
Expand All @@ -32,15 +39,18 @@ export class IPNService implements IPNServiceInterface {
tx.status = PAYMENT_GATE_TRANSACTION_STATUS_FAILED;
if (tx.type === PAYMENT_GATE_TRANSACTION_TYPE_BUY) {
tx.buyIpns.push({...ipnResponse});
} else {
tx.convertIpns.push({...ipnResponse});
}

return getConnection().mongoManager.save(tx);
}

async processPending(data: any): Promise<PaymentGateTransactionInterface> {
async processPending(data: IPNApiTypeResponse): Promise<PaymentGateTransactionInterface> {
const logger = this.logger.sub({ ipn_id: data.ipn_id }, '[processPending] ');

const txRepository = getConnection().mongoManager.getMongoRepository(PaymentGateTransaction);

logger.debug('Find transaction', { txn_id: data.txn_id });

const tx: PaymentGateTransaction = await txRepository.findOne({where: {
'buyCoinpaymentsData.txnId': data.txn_id
}});
Expand All @@ -58,17 +68,19 @@ export class IPNService implements IPNServiceInterface {
tx.status = PAYMENT_GATE_TRANSACTION_STATUS_PENDING;
if (tx.type === PAYMENT_GATE_TRANSACTION_TYPE_BUY) {
tx.buyIpns.push({...ipnResponse});
} else {
tx.convertIpns.push({...ipnResponse});
}

return getConnection().mongoManager.save(tx);
}

async processComplete(data: IPNApiTypeResponse): Promise<PaymentGateTransactionInterface> {
const logger = this.logger.sub({ ipnId: data.ipn_id }, '[processComplete] ');

const txRepository = getConnection().mongoManager.getMongoRepository(PaymentGateTransaction);
const investorRepository = getConnection().mongoManager.getMongoRepository(Investor);

logger.debug('Find transaction', { txn_id: data.txn_id });

const tx: PaymentGateTransaction = await txRepository.findOne({where: {
'buyCoinpaymentsData.txnId': data.txn_id
}});
Expand All @@ -87,6 +99,7 @@ export class IPNService implements IPNServiceInterface {
const investor = await investorRepository.findOne({where: {email: tx.userEmail}});

try {
logger.debug('Convert Coins');
tx.convertCoinpaymentsData = await this.cpClient.convertCoinsTransaction({
amount: data.net,
from: tx.buyCoinpaymentsData.currency2,
Expand Down
1 change: 0 additions & 1 deletion src/services/payments.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export class PaymentsService implements PaymentsServiceInterface {
const tx = new PaymentGateTransaction();
tx.buyCoinpaymentsData = txCoinpaymentsData;
tx.buyIpns = [];
tx.convertIpns = [];
tx.convertCoinpaymentsData = null;
tx.expiredOn = txCoinpaymentsData.timeout + Date.now();
tx.status = PAYMENT_GATE_TRANSACTION_STATUS_STARTED;
Expand Down
1 change: 0 additions & 1 deletion src/services/specs/ipn.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ describe('IPN Service', () => {

expect(tx.type).to.eq(PAYMENT_GATE_TRANSACTION_TYPE_CONVERT);
expect(tx.status).to.eq(PAYMENT_GATE_TRANSACTION_STATUS_STARTED);
expect(tx.convertIpns.length).to.eq(0);
expect(tx.buyIpns.length).to.eq(1);
expect(tx.convertCoinpaymentsData).to.contain(converResult);
expect(tx.buyIpns[0]).to.contain(ipnResponse);
Expand Down
84 changes: 49 additions & 35 deletions src/services/transaction.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,63 +75,45 @@ export class TransactionService implements TransactionServiceInterface {
async getTransactionsOfUser(user: Investor): Promise<GenericTransaction[]> {
const data: Array<GenericTransaction> = [];

const txs = await getMongoManager().createEntityCursor(Transaction, {
'$and': [
{
'$or': [
{
'from': user.ethWallet.address
},
{
'to': user.ethWallet.address
}
]
},
{
'type': {
'$ne': REFERRAL_TRANSFER
}
}
]
}).toArray() as ExtendedTransaction[];
data.push(...await this.getExtendedTransactions(user));
data.push(...await this.getPaymentGateTransactions(user));

for (let transaction of txs) {
if (transaction.from === user.ethWallet.address) {
transaction.direction = DIRECTION_OUT;
} else {
transaction.direction = DIRECTION_IN;
}
}

data.push(...txs);
return data;
}

// coinpayments transaction
private async getPaymentGateTransactions(user: Investor): Promise<Array<GenericTransaction>> {
const data: Array<GenericTransaction> = [];
const paymentGateTransactionRepository = getConnection().mongoManager.getMongoRepository(PaymentGateTransaction);
const cpTxs = await paymentGateTransactionRepository.find({
const txs = await paymentGateTransactionRepository.find({
where: {userEmail: user.email}
});

cpTxs.forEach(item => {
txs.forEach(item => {
if (item.buyIpns.length > 0) {
let tx = {} as PaymentGateTransactionView;

// get latest actual ipn response.
let latestIPN = item.buyIpns[item.buyIpns.length - 1];
item.buyIpns.sort((a, b) => {
return a.status - b.status;
});

const actualIPN = item.buyIpns[0].status < 0 ? item.buyIpns[0] : item.buyIpns[item.buyIpns.length - 1];

tx.address = item.buyCoinpaymentsData.address;
tx.confirmsNeeded = item.buyCoinpaymentsData.confirmsNeeded;
tx.currency = item.buyCoinpaymentsData.currency2;
tx.expiredOn = item.expiredOn;
tx.id = item.id.toHexString();
tx.qrcodeUrl = item.buyCoinpaymentsData.qrcodeUrl;
tx.receivedAmount = latestIPN.receivedAmount;
tx.receivedConfirms = latestIPN.receivedConfirms;
tx.status = latestIPN.staus;
tx.receivedAmount = actualIPN.receivedAmount;
tx.receivedConfirms = actualIPN.receivedConfirms;
tx.status = actualIPN.status;
tx.statusUrl = item.buyCoinpaymentsData.statusUrl;
tx.totalAmount = item.buyCoinpaymentsData.amount;
tx.txnId = item.buyCoinpaymentsData.txnId;
tx.type = 'gateway_transaction';
tx.timestamp = item.timestamp;
tx.timeout = item.buyCoinpaymentsData.timeout;

data.push(tx);
}
Expand All @@ -140,6 +122,38 @@ export class TransactionService implements TransactionServiceInterface {
return data;
}

private async getExtendedTransactions(user: Investor): Promise<Array<GenericTransaction>> {
const txs = await getMongoManager().createEntityCursor(Transaction, {
'$and': [
{
'$or': [
{
'from': user.ethWallet.address
},
{
'to': user.ethWallet.address
}
]
},
{
'type': {
'$ne': REFERRAL_TRANSFER
}
}
]
}).toArray() as ExtendedTransaction[];

for (let transaction of txs) {
if (transaction.from === user.ethWallet.address) {
transaction.direction = DIRECTION_OUT;
} else {
transaction.direction = DIRECTION_IN;
}
}

return txs;
}

async getReferralIncome(user: Investor): Promise<ReferralResult> {
const referrals = await getMongoManager().createEntityCursor(Investor, {
referral: user.email
Expand Down
Binary file not shown.

0 comments on commit 04c02a4

Please sign in to comment.