Skip to content

Instantly share code, notes, and snippets.

@dhonig
Created September 19, 2017 15:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dhonig/b804d38d784e619afd527c33c2fe0c02 to your computer and use it in GitHub Desktop.
Save dhonig/b804d38d784e619afd527c33c2fe0c02 to your computer and use it in GitHub Desktop.
Meteor Method to create a customer and charge
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