Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite placeOrder and support multiple payments for an order #4908

Merged
merged 53 commits into from
Jan 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
1e3d394
feat: support multiple payments, part 1
aldeed Dec 19, 2018
2695ed0
fix: catch errors in appEvents consumers
aldeed Dec 19, 2018
71ac587
refactor: update marketplace checkout to use placeOrder mutation
aldeed Dec 19, 2018
c6f5cb9
refactor: update example IOU checkout to use placeOrder mutation
aldeed Dec 19, 2018
8ad5107
refactor: adjustments for order payments schema changes
aldeed Dec 19, 2018
b334f5c
refactor: more adjustments for order payments schema changes
aldeed Dec 20, 2018
4b4dd12
refactor: GraphQL updates for multiple payments
aldeed Dec 21, 2018
13d9e85
fix: marketplace Stripe should have its own functions
aldeed Dec 21, 2018
d9eea3b
fix: mark charge as captured if Stripe errors that it already is
aldeed Dec 21, 2018
10449cc
refactor: update cancelOrder method for multiple payment handling
aldeed Dec 21, 2018
cd373d4
refactor: update "orders/refunds/create" method for multi payment
aldeed Dec 21, 2018
2fb30bd
feat: port "orders/capturePayments" Meteor method
aldeed Dec 21, 2018
bbcd3a1
refactor: operator orders UI cleanup for multi payments
aldeed Dec 21, 2018
57e97fe
refactor: use OrderSummary component without Blaze wrapper
aldeed Dec 21, 2018
7dd2d41
refactor: use OrderInvoice component without Blaze wrapper
aldeed Dec 21, 2018
2cfc769
feat: move all shipping info to the order shipping panel
aldeed Dec 21, 2018
78b033f
refactor: remove billingAddressId
aldeed Jan 1, 2019
c599c65
refactor: remove _id from order shipping address
aldeed Jan 1, 2019
3870ba9
feat: display multiple payments in order summary
aldeed Jan 1, 2019
c1a2ecf
feat: show payment approval button for each payment
aldeed Jan 1, 2019
b0c4e02
feat: show capture button for each payment
aldeed Jan 1, 2019
28d2e6c
feat: allow refunding per payment
aldeed Jan 1, 2019
35e2dff
refactor: move and rewire "cancel order" button
aldeed Jan 2, 2019
93085a5
refactor: remove createOrder mutation
aldeed Jan 2, 2019
9fa0ac4
refactor: split out some placeOrder code to separate func
aldeed Jan 2, 2019
d37a845
feat: throw if payment method isn't enabled for shop
aldeed Jan 2, 2019
d6d5995
refactor: move payment method tracking to payments plugin
aldeed Jan 2, 2019
82a6dd1
fix: Fix sendOrderEmail error listing refunds
aldeed Jan 2, 2019
a1cdcc6
feat: require each order payment input to specify an amount
aldeed Jan 2, 2019
7c579f1
feat: remove unused "payment provider
aldeed Jan 2, 2019
e9db5ce
chore: migrate order schema changes
aldeed Jan 3, 2019
6867d0b
Merge branch 'develop' into feat-aldeed-place-order-rewrite
aldeed Jan 3, 2019
4d319e3
refactor: GraphQL schema adjustments for multiple payments
aldeed Jan 7, 2019
f4c15ff
Merge branch 'develop' into feat-aldeed-place-order-rewrite
aldeed Jan 8, 2019
abca858
fix: svf of payment capture bug
aldeed Jan 8, 2019
3e90014
chore: change currency exchange logging level from warn to debug
aldeed Jan 8, 2019
ee91966
Merge branch 'develop' into feat-aldeed-place-order-rewrite
aldeed Jan 9, 2019
9d3edc2
test: update import paths for test mocks
aldeed Jan 10, 2019
604be55
chore: updates for non-Meteor NodeApp
aldeed Jan 10, 2019
3cc07ec
chore: fix lint
aldeed Jan 10, 2019
9b8aad7
Merge branch 'develop' into feat-aldeed-place-order-rewrite
aldeed Jan 11, 2019
5f103a9
Merge branch 'develop' into feat-aldeed-place-order-rewrite
aldeed Jan 15, 2019
7293bfa
fix: pull out verifyPaymentsMatchOrderTotal func
aldeed Jan 15, 2019
21a268a
fix: allow canceling order before capturing its payments
aldeed Jan 16, 2019
c286c43
fix: show all payments in order emails
aldeed Jan 16, 2019
9c7e40d
fix: fix error in sendOrderEmail when there is no address for a payment
aldeed Jan 16, 2019
ffe1f57
fix: correct inventory updating
aldeed Jan 16, 2019
ab36e55
feat: update example IOU payment method to support refunds
aldeed Jan 16, 2019
6365ffd
feat: support non-refundable payment methods
aldeed Jan 16, 2019
f5528e0
Merge remote-tracking branch 'origin/develop' into feat-aldeed-place-…
kieckhafer Jan 17, 2019
5fe0e26
fix: fix minor error in Meteor publication
aldeed Jan 17, 2019
54d918f
test: update paymentMethods tests
aldeed Jan 17, 2019
9402179
fix: properly format money in alerts
aldeed Jan 17, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/modules/i18n/currency.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function findCurrency(defaultCurrency, useDefaultShopCurrency) {

/**
* @name formatPriceString
* @deprecated Slowly migrating everything to use `formatMoney` from "/imports/utils/formatMoney" instead
* @summary Return shop/locale specific formatted price. Also accepts a range formatted with " - ".
* @memberof i18n
* @method
Expand Down
15 changes: 14 additions & 1 deletion imports/collections/schemas/orders.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,6 @@ export const OrderFulfillmentGroup = new SimpleSchema({
},
"items.$": OrderItem,
"itemIds": [String],
"payment": Payment,
"shipmentMethod": SelectedFulfillmentOption,
"shippingLabelUrl": {
type: String,
Expand Down Expand Up @@ -372,6 +371,7 @@ export const OrderFulfillmentGroup = new SimpleSchema({
* @property {String} _id required
* @property {String} accountId Account ID for account orders, or null for anonymous
* @property {String} anonymousAccessToken Token for accessing anonymous carts, null for account carts
* @property {Address} [billingAddress] Optional billing address
* @property {String} cartId optional For tracking which cart created this order
* @property {Date} createdAt required
* @property {String} currencyCode required
Expand All @@ -380,6 +380,7 @@ export const OrderFulfillmentGroup = new SimpleSchema({
* @property {Object[]} exportHistory optional
* @property {History[]} history optional
* @property {Notes[]} notes optional
* @property {Payment[]} payments Array of payments
* @property {Shipment[]} shipping Array of fulfillment groups
* @property {String} shopId required The owner shop
* @property {OrderTransaction[]} transactions optional
Expand All @@ -401,6 +402,13 @@ export const Order = new SimpleSchema({
index: 1,
optional: true
},
// Although billing address is typically needed only by the payment plugin,
// some tax services require it to calculate taxes for digital items. Thus
// it should be provided here in order to be added to the CommonOrder if possible.
"billingAddress": {
type: Address,
optional: true
},
"cartId": {
type: String,
optional: true
Expand Down Expand Up @@ -438,6 +446,11 @@ export const Order = new SimpleSchema({
optional: true
},
"notes.$": Notes,
"payments": {
type: Array,
optional: true
},
"payments.$": Payment,
"referenceId": String,
"shipping": [OrderFulfillmentGroup],
"shopId": {
Expand Down
11 changes: 10 additions & 1 deletion imports/collections/schemas/payments.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Address } from "./address";
* @property {Number} total Grand total
*/
export const Invoice = new SimpleSchema({
currencyCode: String,
discounts: {
type: Number,
min: 0
Expand Down Expand Up @@ -79,6 +80,7 @@ registerSchema("CurrencyExchangeRate", CurrencyExchangeRate);
* @property {String} [cardBrand] The brand of card, if the payment method was a credit card
* @property {CurrencyExchangeRate} [currency] The exchange rate, if the user's currency is different from shop's
* @property {Object} [data] Arbitrary data that the payment method needs
* @property {String} mode "authorize" if still needs to be captured, or "capture" if captured. "cancel" if auth was canceled.
* @property {Invoice} invoice A summary of the totals that make up the full charge amount. Created when the payment is added to an order.
* @property {String} shopId The ID of the shop that is being paid. This might be a merchant shop in a marketplace setup.
*/
Expand All @@ -92,6 +94,14 @@ export const Payment = new SimpleSchema({
optional: true
},
"amount": Number,
"captureErrorCode": {
type: String,
optional: true
},
"captureErrorMessage": {
type: String,
optional: true
},
"cardBrand": {
type: String,
optional: true
Expand All @@ -108,7 +118,6 @@ export const Payment = new SimpleSchema({
blackbox: true
},
"displayName": String,
"invoice": Invoice,
"method": String,
"mode": String,
"name": String,
Expand Down
19 changes: 15 additions & 4 deletions imports/node-app/core/util/appEvents.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Logger from "@reactioncommerce/logger";

/**
* This is a temporary events solution on our path to
* event streams and services. For now, some code relies
Expand All @@ -7,16 +9,25 @@

/**
* @summary calls each function in an array with args, one at a time
* @param {String} name Event name
* @param {Function[]} funcs List of functions to call
* @param {Array} args Arguments to pass to each function
* @returns {undefined} Promise that resolves with undefined after all
* functions in the list have been called
*/
async function synchronousPromiseLoop(funcs, args) {
async function synchronousPromiseLoop(name, funcs, args) {
const func = funcs.shift();
await func(...args);

// One function failing should not prevent others from running,
// so catch and log
try {
await func(...args);
} catch (error) {
Logger.error(`Error in "${name}" consumer`, error);
}

if (funcs.length) {
await synchronousPromiseLoop(funcs, args);
await synchronousPromiseLoop(name, funcs, args);
}
}

Expand All @@ -28,7 +39,7 @@ class AppEvents {

// Can't use forEach or map because we want each func to wait
// until the previous func promise resolves
await synchronousPromiseLoop(this.handlers[name].slice(0), args);
await synchronousPromiseLoop(name, this.handlers[name].slice(0), args);
}

on(name, func) {
Expand Down
19 changes: 18 additions & 1 deletion imports/node-app/devserver/mutations.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import { merge } from "lodash";
// CORE
import accounts from "/imports/plugins/core/accounts/server/no-meteor/mutations";
import cart from "/imports/plugins/core/cart/server/no-meteor/mutations";
import catalog from "/imports/plugins/core/catalog/server/no-meteor/mutations";
import navigation from "/imports/plugins/core/navigation/server/no-meteor/mutations";
import orders from "/imports/plugins/core/orders/server/no-meteor/mutations";
import payments from "/imports/plugins/core/payments/server/no-meteor/mutations";
import shipping from "/imports/plugins/core/shipping/server/no-meteor/mutations";
import taxes from "/imports/plugins/core/taxes/server/no-meteor/mutations";
// INCLUDED
import shippingRates from "/imports/plugins/included/shipping-rates/server/no-meteor/mutations";

export default merge({}, accounts, cart, catalog, orders, taxes);
export default merge(
{},
accounts,
cart,
catalog,
navigation,
orders,
payments,
shipping,
taxes,
shippingRates
);
17 changes: 16 additions & 1 deletion imports/node-app/devserver/queries.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import { merge } from "lodash";
// CORE
import accounts from "/imports/plugins/core/accounts/server/no-meteor/queries";
import address from "/imports/plugins/core/address/server/no-meteor/queries";
import cart from "/imports/plugins/core/cart/server/no-meteor/queries";
import catalog from "/imports/plugins/core/catalog/server/no-meteor/queries";
import core from "/imports/plugins/core/core/server/no-meteor/queries";
import navigation from "/imports/plugins/core/navigation/server/no-meteor/queries";
import orders from "/imports/plugins/core/orders/server/no-meteor/queries";
import taxes from "/imports/plugins/core/taxes/server/no-meteor/queries";
// INCLUDED
import shippingRates from "/imports/plugins/included/shipping-rates/server/no-meteor/queries";

export default merge({}, accounts, address, cart, catalog, core, orders, taxes);
export default merge(
{},
accounts,
address,
cart,
catalog,
core,
navigation,
orders,
taxes,
shippingRates
);
10 changes: 4 additions & 6 deletions imports/node-app/devserver/resolvers.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// CORE
import { merge } from "lodash";
import accounts from "/imports/plugins/core/accounts/server/no-meteor/resolvers";
import address from "/imports/plugins/core/address/server/no-meteor/resolvers";
import cart from "/imports/plugins/core/cart/server/no-meteor/resolvers";
import catalog from "/imports/plugins/core/catalog/server/no-meteor/resolvers";
import core from "/imports/plugins/core/core/server/no-meteor/resolvers";
import navigation from "/imports/plugins/core/navigation/server/no-meteor/resolvers";
import orders from "/imports/plugins/core/orders/server/no-meteor/resolvers";
import payments from "/imports/plugins/core/payments/server/no-meteor/resolvers";
import product from "/imports/plugins/core/product/server/no-meteor/resolvers";
import shipping from "/imports/plugins/core/shipping/server/no-meteor/resolvers";
import taxes from "/imports/plugins/core/taxes/server/no-meteor/resolvers";
import marketplace from "/imports/plugins/included/marketplace/server/no-meteor/resolvers";
import paymentsExample from "/imports/plugins/included/payments-example/server/no-meteor/resolvers";
import paymentsStripe from "/imports/plugins/included/payments-stripe/server/no-meteor/resolvers";
// INCLUDED
import shippingRates from "/imports/plugins/included/shipping-rates/server/no-meteor/resolvers";

export default merge(
Expand All @@ -21,13 +21,11 @@ export default merge(
cart,
catalog,
core,
navigation,
orders,
payments,
product,
shipping,
taxes,
marketplace,
paymentsExample,
paymentsStripe,
shippingRates
);
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ input AddressInput {
"Optional second line"
address2: String

"""
Optionally, a name for this address (e.g. "Home") to easily identify it in the future
"""
addressName: String

"City"
city: String!

Expand Down Expand Up @@ -74,9 +79,9 @@ input AddressInput {
}

"Represents a physical or mailing address somewhere on Earth"
type Address implements Node {
type Address {
"The address ID"
_id: ID!
_id: ID

"The street address / first line"
address1: String!
Expand Down Expand Up @@ -119,15 +124,15 @@ type Address implements Node {
}

"Wraps a list of `Addresses`, providing pagination cursors and information."
type AddressConnection implements NodeConnection {
type AddressConnection {
edges: [AddressEdge]
nodes: [Address]
pageInfo: PageInfo!
totalCount: Int!
}

"A connection edge in which each node is an `Address` object"
type AddressEdge implements NodeEdge {
type AddressEdge {
cursor: ConnectionCursor!
node: Address
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { encodeFulfillmentGroupOpaqueId } from "@reactioncommerce/reaction-graphql-xforms/cart";
import { resolveShopFromShopId } from "@reactioncommerce/reaction-graphql-utils";

export default {
_id: (node) => encodeFulfillmentGroupOpaqueId(node._id)
_id: (node) => encodeFulfillmentGroupOpaqueId(node._id),
shop: resolveShopFromShopId
};
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ type FulfillmentGroup implements Node {
"The fulfillment method selected by a shopper for this group, with its associated price"
selectedFulfillmentOption: FulfillmentOption

"The shipping address collected for this group, if relevant"
shippingAddress: Address

"The shop that owns the items in this group and is responsible for fulfillment"
shop: Shop!

"""
The fulfillment type. Any valid type that has been registered by a fulfillment plugin. Examples: "shipping", "digital"
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,5 @@
import accounting from "accounting-js";
import getDisplayPrice from "./getDisplayPrice";

/**
* A wrapper around accounting.formatMoney that handles minor differences between Reaction
* API and accounting.js API.
* @param {Number} price - A price (float)
* @param {Object} [currencyInfo] - A currency object in Reaction schema
* @returns {String} Formatted currency string such as "$15.99". If `currencyInfo` is not provided,
* returns `accounting.toFixed(price, 2)`.
*/
export function formatMoney(price, currencyInfo) {
// Implementation of toFixed() that treats floats more like decimal values than binary,
// fixing inconsistent precision rounding in JavaScript (where some .05 values round up,
// while others round down):
if (!currencyInfo) return accounting.toFixed(price, 2);

// If there are no decimal places, in the case of the Japanese Yen, we adjust it here.
let priceToFormat = price;
if (currencyInfo.scale === 0) {
priceToFormat = price * 100;
}

const currencyFormatSettings = { ...currencyInfo };

// Precision is mis-used in accounting js. Scale is the proper term for number
// of decimal places. Let's adjust it here so accounting.js does not break.
if (typeof currencyInfo.scale === "number") {
currencyFormatSettings.precision = currencyInfo.scale;
}

return accounting.formatMoney(priceToFormat, currencyFormatSettings);
}

/**
*
* @method getPriceRange
Expand Down
10 changes: 0 additions & 10 deletions imports/plugins/core/core/server/Reaction/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
customPublishedProductVariantFields,
functionsByType,
mutations,
paymentMethods,
queries,
resolvers,
schemas
Expand Down Expand Up @@ -118,15 +117,6 @@ export default {
});
}

if (packageInfo.paymentMethods) {
for (const paymentMethod of packageInfo.paymentMethods) {
paymentMethods[paymentMethod.name] = {
...paymentMethod,
pluginName: packageInfo.name
};
}
}

if (packageInfo.catalog) {
const { publishedProductFields, publishedProductVariantFields } = packageInfo.catalog;
if (Array.isArray(publishedProductFields)) {
Expand Down
16 changes: 2 additions & 14 deletions imports/plugins/core/core/server/fixtures/packages.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ export const getPkgData = (pkgName) => {
* @property {Object} settings - Object
* @property {Boolean} settings.mode - `false`
* @property {String} settings.apikey - `""`
* @property {Object} example - Object
* @property {Boolean} example.enabled - `false`
* @property {Object} example-paymentmethod - Object
* @property {Boolean} example-paymentmethod.enabled - `true`
* @property {Array} example-paymentmethod.support - `["Authorize", "Capture", "Refund"]`
* @property {Array} registry - `[]`
* @property {Object} layout - `null`
*/
Expand All @@ -44,15 +39,8 @@ export function examplePaymentMethod() {
shopId: getShopId(),
enabled: true,
settings: {
"mode": false,
"apikey": "",
"example": {
enabled: false
},
"example-paymentmethod": {
enabled: true,
support: ["Authorize", "Capture", "Refund"]
}
mode: false,
apikey: ""
},
registry: [],
layout: null
Expand Down
Loading