-
-
Save dhonig/b804d38d784e619afd527c33c2fe0c02 to your computer and use it in GitHub Desktop.
Meteor Method to create a customer and charge
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
export const methods = { | |
"stripe/payment/createCharges": async function (transactionType, cardData, cartId) { | |
check(transactionType, String); | |
check(cardData, { | |
name: String, | |
number: String, | |
expire_month: String, // eslint-disable-line camelcase | |
expire_year: String, // eslint-disable-line camelcase | |
cvv2: String, | |
type: String | |
}); | |
check(cartId, String); | |
const primaryShopId = Reaction.getPrimaryShopId(); | |
const stripePkg = Reaction.getPackageSettingsWithOptions({ | |
shopId: primaryShopId, | |
name: "reaction-stripe" | |
}); | |
const card = parseCardData(cardData); | |
if (!stripePkg || !stripePkg.settings || !stripePkg.settings.api_key) { | |
// Fail if we can't find a Stripe API key | |
throw new Meteor.Error("Attempted to create multiple stripe charges, but stripe was not configured properly."); | |
} | |
const capture = transactionType === "capture"; | |
// Must have an email | |
const cart = Cart.findOne({ _id: cartId }); | |
const customerAccount = Accounts.findOne({ _id: cart.userId }); | |
let customerEmail; | |
if (!customerAccount || !Array.isArray(customerAccount.emails)) { | |
// TODO: Is it okay to create random email here if anonymous? | |
Logger.Error("cart email missing!"); | |
throw new Meteor.Error("Email is required for marketplace checkouts."); | |
} | |
const defaultEmail = customerAccount.emails.find((email) => email.provides === "default"); | |
if (defaultEmail) { | |
customerEmail = defaultEmail.address; | |
} else { | |
throw new Meteor.Error("Customer does not have default email"); | |
} | |
// Initialize stripe api lib | |
const stripeApiKey = stripePkg.settings.api_key; | |
const stripe = stripeNpm(stripeApiKey); | |
// get array of shopIds that exist in this cart | |
const shopIds = cart.items.reduce((uniqueShopIds, item) => { | |
if (uniqueShopIds.indexOf(item.shopId) === -1) { | |
uniqueShopIds.push(item.shopId); | |
} | |
return uniqueShopIds; | |
}, []); | |
const transactionsByShopId = {}; | |
// TODO: If there is only one transactionsByShopId and the shopId is primaryShopId - | |
// Create a standard charge and bypass creating a customer for this charge | |
const primaryShop = Shops.findOne({ _id: primaryShopId }); | |
const currency = primaryShop.currency; | |
try { | |
// Creates a customer object, adds a source via the card data | |
// and waits for the promise to resolve | |
const customer = Promise.await(stripe.customers.create({ | |
email: customerEmail | |
}).then(function (cust) { | |
const customerCard = stripe.customers.createSource(cust.id, { source: { ...card, object: "card" } }); | |
return customerCard; | |
})); | |
// Get cart totals for each Shop | |
const cartTotals = cart.cartTotalByShop(); | |
// Loop through all shopIds represented in cart | |
shopIds.forEach((shopId) => { | |
// TODO: If shopId is primaryShopId - create a non-connect charge with the | |
// stripe customer object | |
const isPrimaryShop = shopId === primaryShopId; | |
let merchantStripePkg; | |
// Initialize options - this is where idempotency_key | |
// and, if using connect, stripe_account go | |
const stripeOptions = {}; | |
const stripeCharge = { | |
amount: formatForStripe(cartTotals[shopId]), | |
capture: capture, | |
currency: currency | |
// TODO: add product metadata to stripe charge | |
}; | |
if (isPrimaryShop) { | |
// If this is the primary shop, we can make a direct charge to the | |
// customer object we just created. | |
stripeCharge.customer = customer.customer; | |
} else { | |
// If this is a merchant shop, we need to tokenize the customer | |
// and charge the token with the merchant id | |
merchantStripePkg = Reaction.getPackageSettingsWithOptions({ | |
shopId: shopId, | |
name: "reaction-stripe" | |
}); | |
// If this merchant doesn't have stripe setup, fail. | |
// We should _never_ get to this point, because | |
// this will not roll back the entire transaction | |
if (!merchantStripePkg || | |
!merchantStripePkg.settings || | |
!merchantStripePkg.settings.connectAuth || | |
!merchantStripePkg.settings.connectAuth.stripe_user_id) { | |
throw new Meteor.Error(`Error processing payment for merchant with shopId ${shopId}`); | |
} | |
// get stripe account for this shop | |
const stripeUserId = merchantStripePkg.settings.connectAuth.stripe_user_id; | |
stripeOptions.stripe_account = stripeUserId; // eslint-disable-line camelcase | |
// Create token from our customer object to use with merchant shop | |
const token = Promise.await(stripe.tokens.create({ | |
customer: customer.customer | |
}, stripeOptions)); | |
// TODO: Add description to charge in Stripe | |
stripeCharge.source = token.id; | |
// Demo 20% application fee | |
stripeCharge.application_fee = formatForStripe(cartTotals[shopId] * 0.2); // eslint-disable-line camelcase | |
} | |
// We should only do this once per shop per cart | |
stripeOptions.idempotency_key = `${shopId}${cart._id}${Random.id()}`; // eslint-disable-line camelcase | |
// Create a charge with the options set above | |
const charge = Promise.await(stripe.charges.create(stripeCharge, stripeOptions)); | |
transactionsByShopId[shopId] = charge; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment