Skip to content
This repository has been archived by the owner on Dec 11, 2022. It is now read-only.

Commit

Permalink
Merge pull request #3 from Giftbit/ListImprovements
Browse files Browse the repository at this point in the history
Release 0.0.11.
  • Loading branch information
Jeffery Grajkowski authored Oct 18, 2019
2 parents bf6731d + 5416aea commit acc2a0c
Show file tree
Hide file tree
Showing 15 changed files with 505 additions and 331 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "stripe-stateful-mock",
"version": "0.0.10",
"version": "0.0.11",
"description": "A half-baked, stateful Stripe mock server",
"main": "dist/index.js",
"scripts": {
Expand Down
6 changes: 4 additions & 2 deletions src/api/AccountData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export class AccountData<T extends {id: string}> {
export class AccountData<T extends {id: string, created: number}> {

private data: {[accountId: string]: {[id: string]: T}} = {};

Expand All @@ -12,7 +12,9 @@ export class AccountData<T extends {id: string}> {
if (!this.data[accountId]) {
return [];
}
return Object.keys(this.data[accountId]).map(key => this.data[accountId][key]);
return Object.keys(this.data[accountId])
.map(key => this.data[accountId][key])
.sort((a, b) => b.created - a.created);
}

contains(accountId: string, objectId: string): boolean {
Expand Down
10 changes: 7 additions & 3 deletions src/api/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as stripe from "stripe";
import log = require("loglevel");
import {generateId} from "./utils";
import {applyListOptions, generateId} from "./utils";
import {StripeError} from "./StripeError";

export namespace accounts {
Expand All @@ -27,7 +27,6 @@ export namespace accounts {
}

const connectedAccountId = (params as any).id || `acct_${generateId(16)}`;
const now = new Date();
const account: stripe.accounts.IAccount & any = { // The d.ts is out of date on this object and I don't want to bother.
id: connectedAccountId,
object: "account",
Expand All @@ -45,7 +44,7 @@ export namespace accounts {
capabilities: {},
charges_enabled: false,
country: params.country || "US",
created: (now.getTime() / 1000) | 0,
created: (Date.now() / 1000) | 0,
default_currency: params.default_currency || "usd",
details_submitted: false,
email: params.email || "[email protected]",
Expand Down Expand Up @@ -169,6 +168,11 @@ export namespace accounts {
return accounts[connectedAccountId];
}

export function list(accountId: string, params: stripe.IListOptions): stripe.IList<stripe.accounts.IAccount> {
let data = Object.values(accounts);
return applyListOptions(data, params, (id, paramName) => retrieve(accountId, id, paramName));
}

export function del(accountId: string, connectedAccountId: string): stripe.IDeleteConfirmation {
log.debug("accounts.delete", accountId, connectedAccountId);

Expand Down
145 changes: 12 additions & 133 deletions src/api/charges.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as stripe from "stripe";
import log = require("loglevel");
import {StripeError} from "./StripeError";
import {generateId, stringifyMetadata} from "./utils";
import {applyListOptions, generateId, stringifyMetadata} from "./utils";
import {getEffectiveSourceTokenFromChain, isSourceTokenChain} from "./sourceTokenChains";
import {cards} from "./cards";
import {AccountData} from "./AccountData";
import {customers} from "./customers";
import {disputes} from "./disputes";
import {refunds} from "./refunds";

export namespace charges {

Expand Down Expand Up @@ -156,6 +157,14 @@ export namespace charges {
return charge;
}

export function list(accountId: string, params: stripe.charges.IChargeListOptions): stripe.IList<stripe.charges.ICharge> {
let data = accountCharges.getAll(accountId);
if (params.customer) {
data = data.filter(d => d.customer === params.customer);
}
return applyListOptions(data, params, (id, paramName) => retrieve(accountId, id, paramName));
}

export function update(accountId: string, chargeId: string, params: stripe.charges.IChargeUpdateOptions): stripe.charges.ICharge {
log.debug("charges.update", accountId, chargeId, params);

Expand Down Expand Up @@ -224,7 +233,7 @@ export namespace charges {

if (captureAmount < charge.amount) {
charge.captured = true;
createRefund(accountId, {
refunds.create(accountId, {
amount: charge.amount - captureAmount,
charge: charge.id
});
Expand All @@ -236,137 +245,7 @@ export namespace charges {
return charge;
}

export function createRefund(accountId: string, params: stripe.refunds.IRefundCreationOptionsWithCharge): stripe.refunds.IRefund {
log.debug("charges.createRefund", accountId, params);

if (params.hasOwnProperty("amount")) {
if (params.amount < 1) {
throw new StripeError(400, {
code: "parameter_invalid_integer",
doc_url: "https://stripe.com/docs/error-codes/parameter-invalid-integer",
message: "Invalid positive integer",
param: "amount",
type: "invalid_request_error"
});
}
if (params.amount > 99999999) {
throw new StripeError(400, {
code: "amount_too_large",
doc_url: "https://stripe.com/docs/error-codes/amount-too-large",
message: "Amount must be no more than $999,999.99",
param: "amount",
type: "invalid_request_error"
});
}
}

const charge = retrieve(accountId, params.charge, "id");
if (charge.amount_refunded >= charge.amount) {
throw new StripeError(400, {
code: "charge_already_refunded",
doc_url: "https://stripe.com/docs/error-codes/charge-already-refunded",
message: `Charge ${charge.id} has already been refunded.`,
type: "invalid_request_error"
});
}
if (charge.dispute) {
const dispute = disputes.retrieve(accountId, charge.dispute as string, "dispute");
if (!dispute.is_charge_refundable) {
throw new StripeError(400, {
code: "charge_disputed",
doc_url: "https://stripe.com/docs/error-codes/charge-disputed",
message: `Charge ${charge.id} has been charged back; cannot issue a refund.`,
type: "invalid_request_error"
});
}
}

let refundAmount = params.hasOwnProperty("amount") ? +params.amount : charge.amount - charge.amount_refunded;
if (refundAmount > charge.amount - charge.amount_refunded) {
throw new StripeError(400, {
message: `Refund amount (\$${refundAmount / 100}) is greater than unrefunded amount on charge (\$${(charge.amount - charge.amount_refunded) / 100})`,
param: "amount",
type: "invalid_request_error"
});
}

if (!charge.captured && charge.amount !== refundAmount) {
throw new StripeError(400, {
message: "You cannot partially refund an uncaptured charge. Instead, capture the charge for an amount less than the original amount",
param: "amount",
type: "invalid_request_error"
});
}

const now = new Date();
const refund: stripe.refunds.IRefund = {
id: "re_" + generateId(24),
object: "refund",
amount: refundAmount,
balance_transaction: "txn_" + generateId(24),
charge: charge.id,
created: (now.getTime() / 1000) | 0,
currency: charge.currency.toLowerCase(),
metadata: stringifyMetadata(params.metadata),
reason: params.reason || null,
receipt_number: null,
source_transfer_reversal: null,
status: "succeeded",
transfer_reversal: null
};
charge.refunds.data.unshift(refund);
charge.refunds.total_count++;
charge.amount_refunded += refundAmount;
charge.refunded = charge.amount_refunded === charge.amount;
return refund;
}

export function retrieveRefund(accountId: string, refundId: string, paramName: string): stripe.refunds.IRefund {
log.debug("charges.retrieveRefund", accountId, refundId);

for (const charge of accountCharges.getAll(accountId)) {
const refund = charge.refunds.data.find(refund => refund.id === refundId);
if (refund) {
return refund;
}
}

throw new StripeError(404, {
code: "resource_missing",
doc_url: "https://stripe.com/docs/error-codes/resource-missing",
message: `No such refund: ${refundId}`,
param: paramName,
type: "invalid_request_error"
});
}

export function listRefunds(accountId: string, params: stripe.refunds.IRefundListOptions): stripe.IList<stripe.refunds.IRefund> {
if (params.charge) {
return listChargeRefunds(accountId, params.charge, params);
}

log.debug("charges.listRefunds", accountId, params);
const refunds: stripe.refunds.IRefund[] = accountCharges.getAll(accountId).reduce((refunds, charge) => [...refunds, ...charge.refunds.data], [] as stripe.refunds.IRefund[]);
return {
object: "list",
data: refunds,
has_more: false,
url: "/v1/refunds",
total_count: refunds.length
};
}

export function listChargeRefunds(accountId: string, chargeId: string, params: stripe.IListOptions): stripe.IList<stripe.refunds.IRefund> {
log.debug("charges.listChargeRefunds", accountId, chargeId, params);
const charge = retrieve(accountId, chargeId, "charge");
return {
...charge.refunds,
total_count: undefined // For some reason this isn't on this endpoint.
};
}

function getChargeFromCard(params: stripe.charges.IChargeCreationOptions, source: stripe.cards.ICard): stripe.charges.ICharge {
const now = new Date();
const chargeId = "ch_" + generateId();
return {
id: chargeId,
Expand All @@ -391,7 +270,7 @@ export namespace charges {
phone: null
},
captured: params.capture as any !== "false",
created: (now.getTime() / 1000) | 0,
created: (Date.now() / 1000) | 0,
currency: params.currency.toLowerCase(),
customer: null,
description: params.description || null,
Expand Down
13 changes: 10 additions & 3 deletions src/api/customers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as stripe from "stripe";
import log = require("loglevel");
import {StripeError} from "./StripeError";
import {generateId, stringifyMetadata} from "./utils";
import {applyListOptions, generateId, stringifyMetadata} from "./utils";
import {cards} from "./cards";
import {AccountData} from "./AccountData";

Expand All @@ -22,14 +22,13 @@ export namespace customers {
}

const customerId = (params as any).id || `cus_${generateId(14)}`;
const now = new Date();
const customer: stripe.customers.ICustomer = {
id: customerId,
object: "customer",
account_balance: +params.account_balance || +params.balance || 0,
address: params.address || null,
balance: +params.balance || +params.account_balance || 0,
created: (now.getTime() / 1000) | 0,
created: (Date.now() / 1000) | 0,
currency: null,
default_source: null,
delinquent: false,
Expand Down Expand Up @@ -101,6 +100,14 @@ export namespace customers {
return customer;
}

export function list(accountId: string, params: stripe.customers.ICustomerListOptions): stripe.IList<stripe.customers.ICustomer> {
let data = accountCustomers.getAll(accountId);
if (params.email) {
data = data.filter(d => d.email === params.email);
}
return applyListOptions(data, params, (id, paramName) => retrieve(accountId, id, paramName));
}

export function update(accountId: string, customerId: string, params: stripe.customers.ICustomerUpdateOptions) {
log.debug("customers.update", accountId, customerId, params);

Expand Down
5 changes: 2 additions & 3 deletions src/api/disputes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export namespace disputes {
transactionAvailableDate.setHours(17);
transactionAvailableDate.setMinutes(0, 0, -1);

const now = new Date();
const disputeId = `dp_${generateId(24)}`;
const disputeTxnId = `txn_${generateId(24)}`;
const dispute: stripe.disputes.IDispute = {
Expand All @@ -34,7 +33,7 @@ export namespace disputes {
object: "balance_transaction",
amount: -charge.amount,
available_on: (transactionAvailableDate.getTime() / 1000) | 0,
created: (now.getTime() / 1000) | 0,
created: (Date.now() / 1000) | 0,
currency: charge.currency,
description: `Chargeback withdrawal for ${charge.id}`,
exchange_rate: null,
Expand All @@ -55,7 +54,7 @@ export namespace disputes {
}
],
charge: charge.id,
created: (now.getTime() / 1000) | 0,
created: (Date.now() / 1000) | 0,
currency: charge.currency,
evidence: {
access_activity_log: null,
Expand Down
2 changes: 2 additions & 0 deletions src/api/idempotency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {getRequestAccountId} from "../routes";

interface StoredRequest {
id: string;
created: number;
requestId: string;
requestBody: any;
responseCode: number;
Expand Down Expand Up @@ -46,6 +47,7 @@ export function idempotencyRoute(req: express.Request, res: express.Response, ne
} else {
const storedRequest: StoredRequest = {
id: storedRequestKey,
created: (Date.now() / 1000) | 0,
requestId: "req_" + generateId(14),
requestBody: req.body,
responseCode: 0,
Expand Down
Loading

0 comments on commit acc2a0c

Please sign in to comment.