Skip to content

Commit

Permalink
Handle cc declines (#1119)
Browse files Browse the repository at this point in the history
* v1

* Added tests

* migration test dbs

* remote processedAt from tests

* fix tests for putting pm behind login check

* rename methods in subscriptions library

* add more tests for updating status

* Few other tweaks and cleanup

* fix bug and add ability to copy to dev heroku

* reduce db connections by default

* migrate test dbs

* tweak
  • Loading branch information
asood123 authored Feb 21, 2018
1 parent 32688ce commit 935b65c
Show file tree
Hide file tree
Showing 34 changed files with 1,041 additions and 406 deletions.
4 changes: 2 additions & 2 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"host": "127.0.0.1",
"dialect": "postgres",
"pool": {
"min": 10,
"max": 50,
"min": 5,
"max": 10,
"acquire": 1200000
},
"logging": false
Expand Down
42 changes: 42 additions & 0 deletions migrations/20180205193041-enable-order-histories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use strict';

module.exports = {
up: (queryInterface, DataTypes) => {
return queryInterface.createTable('OrderHistories', {
id: DataTypes.INTEGER,
CreatedByUserId: DataTypes.INTEGER,
FromCollectiveId: DataTypes.INTEGER,
CollectiveId: DataTypes.INTEGER,
TierId: DataTypes.INTEGER,
quantity: DataTypes.INTEGER,
currency: DataTypes.STRING(3),
totalAmount: DataTypes.INTEGER,
description: DataTypes.STRING,
publicMessage: DataTypes.STRING,
privateMessage: DataTypes.STRING,
SubscriptionId: DataTypes.INTEGER,
PaymentMethodId: DataTypes.INTEGER,
MatchingPaymentMethodId: DataTypes.INTEGER,
ReferralCollectiveId: DataTypes.INTEGER,
processedAt: DataTypes.DATE,
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
deletedAt: DataTypes.DATE,
hid: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true,
unique: true
},
archivedAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
},

down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('OrderHistories');
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict';

const Promise = require('bluebird');


module.exports = {
up: (queryInterface, sequelize) => {
// find all post-migration credit cards that don't have a collective id
return queryInterface.sequelize.query(`
SELECT * FROM "PaymentMethods"
WHERE "service" ilike 'stripe'
AND "type" ilike 'creditcard'
AND "deletedAt" is null
AND "CollectiveId" is null
AND "archivedAt" is null
AND "name" is not null
`, { type: sequelize.QueryTypes.SELECT })
.then(paymentMethods => {

console.log("PaymentMethods found: ", paymentMethods.length);

return Promise.map(paymentMethods, pm => {
return queryInterface.sequelize.query(`
SELECT distinct("FromCollectiveId") FROM "Orders"
WHERE "deletedAt" is null
AND "PaymentMethodId" = ${pm.id}
`, { type: sequelize.QueryTypes.SELECT })
.then(fromCollectiveIds => {

if (fromCollectiveIds.length === 0) {
console.log("No fromCollectiveId found for pm.id", pm.id, "skipping");
return Promise.resolve();
}
if (fromCollectiveIds.length > 1) {
throw new Error('Found more than 1 fromCollectiveId for paymentMethodId', pm.id)
}

return queryInterface.sequelize.query(`
UPDATE "PaymentMethods"
SET "CollectiveId" = :collectiveId
WHERE "id" = :pmId
`, {
replacements: {
collectiveId: fromCollectiveIds[0].FromCollectiveId,
pmId: pm.id
}
})
})
})
})
},

down: (queryInterface, Sequelize) => {
return Promise.resolve(); // No way to revert this
}
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@
"db:seed": "sequelize db:seed --config config/sequelize_cli.json --env $SEQUELIZE_ENV",
"db:copy:prod": "./scripts/db_copy_to_localhost.sh prod && PG_DATABASE=opencollective_prod_snapshot npm run db:sanitize",
"db:copy:staging": "./scripts/db_copy_to_localhost.sh staging",
"db:copy:prod:staging": "./scripts/db_copy_prod_to_staging.sh && heroku run npm run db:sanitize --app opencollective-staging-api",
"db:copy:prod:staging": "./scripts/db_copy_prod_to_staging.sh opencollective-staging-api && heroku run npm run db:sanitize --app opencollective-staging-api",
"db:copy:prod:staging-dev": "./scripts/db_copy_prod_to_staging.sh $DEV && heroku run npm run db:sanitize --app $DEV",
"db:sanitize": "babel-node scripts/replace_stripe_accounts",
"db:reset": "babel-node scripts/reset",
"migration:create": "sequelize migration:create --config config/sequelize_cli.json",
Expand Down
4 changes: 0 additions & 4 deletions scripts/charge_subscriptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ import fs from 'fs';
import json2csv from 'json2csv';
import { ArgumentParser } from 'argparse';

import * as payments from '../server/lib/payments';
import emailLib from '../server/lib/email';
import { promiseSeq } from '../server/lib/utils';
import { sequelize } from '../server/models';
import {
ordersWithPendingCharges,
processOrderWithSubscription,
updateNextChargeDate,
updateChargeRetryCount,
handleRetryStatus,
} from '../server/lib/subscriptions';

const REPORT_EMAIL = 'ops@opencollective.com';
Expand Down
10 changes: 6 additions & 4 deletions scripts/db_copy_prod_to_staging.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@
# Usage: npm run db:copyprodtostaging (from the root of the opencollective-api repo)
#
# The staging version of OpenCollective is on https://staging.opencollective.com

ENV="${1}"
PROD_PG_URL=`heroku config:get PG_URL -a opencollective-prod-api`

echo "Copying prod to ${ENV}"

# See https://devcenter.heroku.com/articles/heroku-postgres-import-export
heroku pg:copy $PROD_PG_URL DATABASE_URL --app opencollective-staging-api --confirm opencollective-staging-api
heroku pg:copy $PROD_PG_URL DATABASE_URL --app ${ENV} --confirm ${ENV}

echo ""

echo " All done"
echo ""
echo "https://staging.opencollective.com is now synced with the production database"
echo "${ENV} is now synced with the production database"
echo ""
echo "You may need to run a migration if the schema has changed"
echo "To do so, follow those steps:"
echo "$> heroku run bash -a opencollective-staging-api"
echo "$> heroku run bash -a ${ENV}"
echo "heroku> npm run db:migrate"
72 changes: 0 additions & 72 deletions server/controllers/subscriptions.js

This file was deleted.

26 changes: 26 additions & 0 deletions server/graphql/CollectiveInterface.js
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,12 @@ export const CollectiveInterfaceType = new GraphQLInterfaceType({
}
},
orders: { type: new GraphQLList(OrderType) },
ordersFromCollective: {
type: new GraphQLList(OrderType),
args: {
subscriptionsOnly: { type: GraphQLBoolean }
}
},
stats: { type: CollectiveStatsType },
transactions: {
type: new GraphQLList(TransactionInterfaceType),
Expand Down Expand Up @@ -786,6 +792,26 @@ const CollectiveFields = () => {
});
}
},
ordersFromCollective: {
type: new GraphQLList(OrderType),
args: {
subscriptionsOnly: { type: GraphQLBoolean }
},
resolve(collective, args) {
const query = {
where: { }, // TODO: might need a filter of 'processedAt'
order: [ ['createdAt', 'DESC']]
};

if (args.subscriptionsOnly) {
query.include = [{
model: models.Subscription,
required: true
}]
}
return collective.getOutgoingOrders(query)
}
},
transactions: {
type: new GraphQLList(TransactionInterfaceType),
args: {
Expand Down
15 changes: 13 additions & 2 deletions server/graphql/TransactionInterface.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,11 @@ const TransactionFields = () => {
},
uuid: {
type: GraphQLString,
resolve(transaction) {
return transaction.uuid;
resolve(transaction, args, req) {
if (!req.remoteUser) {
return null;
}
return transaction.getDetailsForUser(req.remoteUser);
}
},
type: {
Expand Down Expand Up @@ -163,6 +166,7 @@ const TransactionFields = () => {
type: PaymentMethodType,
resolve(transaction, args, req) {
if (!transaction.PaymentMethodId) return null;
// TODO: put behind a login check
return req.loaders.paymentMethods.findById.load(transaction.PaymentMethodId);
}
}
Expand Down Expand Up @@ -219,9 +223,16 @@ export const TransactionOrderType = new GraphQLObjectType({
privateMessage: {
type: GraphQLString,
resolve(transaction) {
// TODO: Put behind a login check
return transaction.getOrder().then(order => order && order.privateMessage);
}
},
publicMessage: {
type: GraphQLString,
resolve(transaction) {
return transaction.getOrder().then(order => order && order.publicMessage);
}
},
order: {
type: OrderType,
resolve(transaction) {
Expand Down
25 changes: 23 additions & 2 deletions server/graphql/mutations.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createCollective, editCollective, deleteCollective, approveCollective } from './mutations/collectives';
import { createOrder } from './mutations/orders';
import { createOrder, cancelSubscription, updateSubscription } from './mutations/orders';
import { createMember, removeMember } from './mutations/members';
import { editTiers } from './mutations/tiers';
import { editConnectedAccount } from './mutations/connectedAccounts';
Expand Down Expand Up @@ -36,7 +36,8 @@ import {
ExpenseInputType,
UpdateInputType,
UpdateAttributesInputType,
ConnectedAccountInputType
ConnectedAccountInputType,
PaymentMethodInputType
} from './inputTypes';

const mutations = {
Expand Down Expand Up @@ -237,6 +238,26 @@ const mutations = {
resolve(_, args, req) {
return updateMutations.deleteUpdate(_, args, req);
}
},
cancelSubscription: {
type: OrderType,
args: {
id: { type: new GraphQLNonNull(GraphQLInt)}
},
resolve(_, args, req) {
return cancelSubscription(req.remoteUser, args.id);
}
},

updateSubscription: {
type: OrderType,
args: {
id: { type: new GraphQLNonNull(GraphQLInt)},
paymentMethod: { type: PaymentMethodInputType}
},
resolve(_, args, req) {
return updateSubscription(req.remoteUser, args)
}
}
}

Expand Down
Loading

0 comments on commit 935b65c

Please sign in to comment.