This repository has been archived by the owner on Dec 11, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Added support for creating and deleting connect accounts. - Removed special connect account `acct_invalid` because any account that has not been created is invalid.
- Loading branch information
jeff
committed
Sep 24, 2019
1 parent
add454b
commit a29a323
Showing
16 changed files
with
364 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,16 @@ | ||
import * as stripe from "stripe"; | ||
|
||
export interface AdditionalStripeErrorMembers { | ||
charge?: string; | ||
decline_code?: string; | ||
doc_url?: string; | ||
} | ||
|
||
export default class StripeError extends Error { | ||
export class StripeError extends Error { | ||
|
||
constructor(public statusCode: number, public error: stripe.IStripeError & AdditionalStripeErrorMembers) { | ||
constructor(public statusCode: number, public error: stripe.IStripeError & StripeError.AdditionalStripeErrorMembers) { | ||
super(error.message); | ||
} | ||
} | ||
|
||
export namespace StripeError { | ||
export interface AdditionalStripeErrorMembers { | ||
charge?: string; | ||
decline_code?: string; | ||
doc_url?: string; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
import * as stripe from "stripe"; | ||
import log = require("loglevel"); | ||
import {generateId} from "./utils"; | ||
import {StripeError} from "./StripeError"; | ||
|
||
export namespace accounts { | ||
|
||
const accounts: {[accountId: string]: stripe.accounts.IAccount} = {}; | ||
|
||
export function create(accountId: string, params: stripe.accounts.IAccountCreationOptions): stripe.accounts.IAccount { | ||
log.debug("accounts.create", accountId, params); | ||
|
||
if (accountId !== "acct_default") { | ||
throw new StripeError(400, { | ||
message: "You can only create new accounts if you've signed up for Connect, which you can learn how to do at https://stripe.com/docs/connect.", | ||
type: "invalid_request_error" | ||
}); | ||
} | ||
if (!params.type) { | ||
throw new StripeError(400, { | ||
code: "parameter_missing", | ||
doc_url: "https://stripe.com/docs/error-codes/parameter-missing", | ||
message: "Missing required param: type.", | ||
param: "type", | ||
type: "invalid_request_error" | ||
}); | ||
} | ||
|
||
const connectedAccountId = `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", | ||
business_profile: { | ||
mcc: (params.business_profile && params.business_profile.mcc) || null, | ||
name: (params.business_profile && params.business_profile.name) || "Stripe.com", | ||
product_description: (params.business_profile && params.business_profile.product_description) || null, | ||
support_address: (params.business_profile && params.business_profile.support_address) || null, | ||
support_email: (params.business_profile && params.business_profile.support_email) || null, | ||
support_phone: (params.business_profile && params.business_profile.support_phone) || null, | ||
support_url: (params.business_profile && params.business_profile.support_url) || null, | ||
url: (params.business_profile && params.business_profile.url) || null | ||
}, | ||
business_type: params.business_type || null, | ||
capabilities: {}, | ||
charges_enabled: false, | ||
country: params.country || "US", | ||
created: (now.getTime() / 1000) | 0, | ||
default_currency: params.default_currency || "usd", | ||
details_submitted: false, | ||
email: params.email || "[email protected]", | ||
external_accounts: { | ||
object: "list", | ||
data: [], | ||
has_more: false, | ||
total_count: 0, | ||
url: `/v1/accounts/${connectedAccountId}/external_accounts` | ||
}, | ||
metadata: params.metadata || {}, | ||
payouts_enabled: false, | ||
requirements: { | ||
current_deadline: null, | ||
currently_due: [ | ||
"business_type", | ||
"business_url", | ||
"company.address.city", | ||
"company.address.line1", | ||
"company.address.postal_code", | ||
"company.address.state", | ||
"person_8UayFKIMRJklog.dob.day", | ||
"person_8UayFKIMRJklog.dob.month", | ||
"person_8UayFKIMRJklog.dob.year", | ||
"person_8UayFKIMRJklog.first_name", | ||
"person_8UayFKIMRJklog.last_name", | ||
"product_description", | ||
"support_phone", | ||
"tos_acceptance.date", | ||
"tos_acceptance.ip" | ||
], | ||
disabled_reason: "requirements.past_due", | ||
eventually_due: [ | ||
"business_url", | ||
"product_description", | ||
"support_phone", | ||
"tos_acceptance.date", | ||
"tos_acceptance.ip" | ||
], | ||
past_due: [], | ||
pending_verification: [] | ||
}, | ||
settings: { | ||
branding: { | ||
icon: (params.settings && params.settings.branding && params.settings.branding.icon) || null, | ||
logo: (params.settings && params.settings.branding && params.settings.branding.logo) || null, | ||
primary_color: (params.settings && params.settings.branding && params.settings.branding.primary_color) || null | ||
}, | ||
card_payments: { | ||
decline_on: { | ||
avs_failure: true, | ||
cvc_failure: false | ||
}, | ||
statement_descriptor_prefix: null | ||
}, | ||
dashboard: { | ||
display_name: "Stripe.com", | ||
timezone: "US/Pacific" | ||
}, | ||
payments: { | ||
statement_descriptor: "", | ||
statement_descriptor_kana: null, | ||
statement_descriptor_kanji: null | ||
}, | ||
payouts: { | ||
debit_negative_balances: true, | ||
schedule: { | ||
delay_days: 7, | ||
interval: "daily" | ||
}, | ||
statement_descriptor: null | ||
} | ||
}, | ||
tos_acceptance: { | ||
date: (params.tos_acceptance && params.tos_acceptance.date) || null, | ||
ip: (params.tos_acceptance && params.tos_acceptance.ip) || null, | ||
user_agent: (params.tos_acceptance && params.tos_acceptance.user_agent) || null | ||
}, | ||
type: params.type | ||
}; | ||
accounts[connectedAccountId] = account; | ||
return account; | ||
} | ||
|
||
export function retrieve(accountId: string, connectedAccountId: string, censoredAccessToken: string): stripe.accounts.IAccount { | ||
log.debug("accounts.retrieve", accountId, connectedAccountId); | ||
|
||
if (accountId !== "acct_default" && accountId !== connectedAccountId) { | ||
throw new StripeError(400, { | ||
message: "The account specified in the path of /v1/accounts/:account does not match the account specified in the Stripe-Account header.", | ||
type: "invalid_request_error" | ||
}); | ||
} | ||
if (!accounts[connectedAccountId]) { | ||
throw new StripeError(403, { | ||
code: "account_invalid", | ||
doc_url: "https://stripe.com/docs/error-codes/account-invalid", | ||
message: `The provided key '${censoredAccessToken}' does not have access to account '${connectedAccountId}' (or that account does not exist). Application access may have been revoked.`, | ||
type: "invalid_request_error" | ||
}); | ||
} | ||
return accounts[connectedAccountId]; | ||
} | ||
|
||
export function del(accountId: string, connectedAccountId: string): stripe.IDeleteConfirmation { | ||
log.debug("accounts.delete", accountId, connectedAccountId); | ||
|
||
delete accounts[connectedAccountId]; | ||
return { | ||
id: connectedAccountId, | ||
object: "account", | ||
"deleted": true | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,45 @@ | ||
import express from "express"; | ||
import basicAuthParser = require("basic-auth"); | ||
import {getRequestAccountId} from "../routes"; | ||
|
||
export function authRoute(req: express.Request, res: express.Response, next: express.NextFunction): void { | ||
let token = null; | ||
export namespace auth { | ||
export function authRoute(req: express.Request, res: express.Response, next: express.NextFunction): void { | ||
const token = getAccessTokenFromRequest(req); | ||
|
||
const authorizationHeader = req.header("authorization"); | ||
const basicAuth = basicAuthParser(req); | ||
if (authorizationHeader && authorizationHeader.startsWith("Bearer ")) { | ||
token = /^Bearer (.*)/.exec(authorizationHeader)[1]; | ||
} else if (basicAuth) { | ||
token = basicAuth.name; | ||
if (!token) { | ||
res.status(401).send({ | ||
error: { | ||
message: "You did not provide an API key. You need to provide your API key in the Authorization header, using Bearer auth (e.g. 'Authorization: Bearer YOUR_SECRET_KEY'). See https://stripe.com/docs/api#authentication for details, or we can help at https://support.stripe.com/.", | ||
type: "invalid_request_error" | ||
} | ||
}); | ||
} else if (!/^sk_test_/.test(token)) { | ||
res.status(401).send({ | ||
error: { | ||
message: `Invalid API Key provided: ${censorAccessToken(token)}`, | ||
type: "invalid_request_error" | ||
} | ||
}); | ||
} else { | ||
next(); | ||
} | ||
} | ||
|
||
if (!token) { | ||
res.status(401).send({ | ||
error: { | ||
message: "You did not provide an API key. You need to provide your API key in the Authorization header, using Bearer auth (e.g. 'Authorization: Bearer YOUR_SECRET_KEY'). See https://stripe.com/docs/api#authentication for details, or we can help at https://support.stripe.com/.", | ||
type: "invalid_request_error" | ||
} | ||
}); | ||
} else if (!/^sk_test_/.test(token)) { | ||
res.status(401).send({ | ||
error: { | ||
message: `Invalid API Key provided: ${censorAccessToken(token)}`, | ||
type: "invalid_request_error" | ||
} | ||
}); | ||
} else if (getRequestAccountId(req) === "acct_invalid") { | ||
res.status(403).send({ | ||
error: { | ||
code: "account_invalid", | ||
doc_url: "https://stripe.com/docs/error-codes/account-invalid", | ||
message: `The provided key '${censorAccessToken(token)}' does not have access to account '${getRequestAccountId(req)}' (or that account does not exist). Application access may have been revoked.`, | ||
type: "invalid_request_error" | ||
} | ||
}); | ||
} else { | ||
next(); | ||
export function getAccessTokenFromRequest(req: express.Request): string { | ||
const authorizationHeader = req.header("authorization"); | ||
const basicAuth = basicAuthParser(req); | ||
if (authorizationHeader && authorizationHeader.startsWith("Bearer ")) { | ||
return /^Bearer (.*)/.exec(authorizationHeader)[1]; | ||
} else if (basicAuth) { | ||
return basicAuth.name; | ||
} | ||
return null; | ||
} | ||
|
||
export function getCensoredAccessTokenFromRequest(req: express.Request): string { | ||
return censorAccessToken(getAccessTokenFromRequest(req)); | ||
} | ||
} | ||
|
||
function censorAccessToken(token: string): string { | ||
return `${token.substr(0, Math.min(token.length, 11))}${new Array(token.length - Math.min(token.length, 15)).fill("*").join("")}${token.substr(token.length - Math.min(token.length, 4))}`; | ||
export function censorAccessToken(token: string): string { | ||
return `${token.substr(0, Math.min(token.length, 11))}${new Array(token.length - Math.min(token.length, 15)).fill("*").join("")}${token.substr(token.length - Math.min(token.length, 4))}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.