-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Fix Braintree discounts and refunds #1265
Merged
Merged
Changes from 27 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
b4f50c1
add Braintree Payment error to en.json
kieckhafer 49b2634
enable discounts for Braintree payments
kieckhafer 927ffb9
removed else statement after an if containing a return
kieckhafer a348662
added testing for Braintree refunds
kieckhafer e9bafcf
remove no longer needed commented code
kieckhafer e2cd723
remove no longer needed commented code
kieckhafer 993b608
added field to expected response for testing
kieckhafer 43d61c0
moved braintree/refund/list methods into BraintreeApi wrapper
kieckhafer 381a6a6
Merge branch 'development' into ek-refunds-braintree
kieckhafer cefb012
move braintree methods into new file
kieckhafer edd6deb
reconfigure all braintree payment code to no longer use ValidateMethod
kieckhafer af86321
fixed lint issues
kieckhafer e1475b6
removed code used to skip over 24 hour braintree delay (for testing)
kieckhafer 5821c95
Rename braintreeapi.js to braintreeApi.js
kieckhafer 4099285
display absolute number of adjustedTotal
kieckhafer 7a141f3
updated schema to match supported payment methods
kieckhafer b81b61c
min -> max
kieckhafer 753ab11
removed comments
kieckhafer 8dec94f
test testing
kieckhafer 8eda45b
update braintree test
kieckhafer e2127a3
update exports of braintreeApi functions
kieckhafer a621be5
Merge branch 'development' into ek-refunds-braintree
kieckhafer d75a4c9
fixed callback error when action has no callback
kieckhafer 13572e8
Updated error message to make more sense to a human user
kieckhafer c9042bf
linter fixes
kieckhafer 71f3625
Merge branch 'development' into ek-refunds-braintree
949e32b
updated 'Logger.info' to Logger.debug
kieckhafer a5c8e5d
removed unused test
kieckhafer 12748c0
removed commented callback
kieckhafer fd364af
Merge branch 'development' into ek-refunds-braintree
a936672
don't log full order details on transaction error
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
244 changes: 5 additions & 239 deletions
244
imports/plugins/included/braintree/server/methods/braintree.js
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,243 +1,9 @@ | ||
import moment from "moment"; | ||
import * as BraintreeMethods from "./braintreeMethods"; | ||
import { Meteor } from "meteor/meteor"; | ||
import Future from "fibers/future"; | ||
import Braintree from "braintree"; | ||
import { Reaction, Logger } from "/server/api"; | ||
import { Packages } from "/lib/collections"; | ||
import { PaymentMethod } from "/lib/collections/schemas"; | ||
|
||
function getSettings(settings, ref, valueName) { | ||
if (settings !== null) { | ||
return settings[valueName]; | ||
} else if (ref !== null) { | ||
return ref[valueName]; | ||
} | ||
return undefined; | ||
} | ||
|
||
|
||
function getAccountOptions() { | ||
let environment; | ||
let settings = Packages.findOne({ | ||
name: "reaction-braintree", | ||
shopId: Reaction.getShopId(), | ||
enabled: true | ||
}).settings; | ||
if (typeof settings !== "undefined" && settings !== null ? settings.mode : undefined === true) { | ||
environment = "production"; | ||
} else { | ||
environment = "sandbox"; | ||
} | ||
|
||
let ref = Meteor.settings.braintree; | ||
let options = { | ||
environment: environment, | ||
merchantId: getSettings(settings, ref, "merchant_id"), | ||
publicKey: getSettings(settings, ref, "public_key"), | ||
privateKey: getSettings(settings, ref, "private_key") | ||
}; | ||
if (!options.merchantId) { | ||
throw new Meteor.Error("invalid-credentials", "Invalid Braintree Credentials"); | ||
} | ||
return options; | ||
} | ||
|
||
|
||
function getGateway() { | ||
let accountOptions = getAccountOptions(); | ||
if (accountOptions.environment === "production") { | ||
accountOptions.environment = Braintree.Environment.Production; | ||
} else { | ||
accountOptions.environment = Braintree.Environment.Sandbox; | ||
} | ||
let gateway = Braintree.connect(accountOptions); | ||
return gateway; | ||
} | ||
|
||
function getPaymentObj() { | ||
return { | ||
amount: "", | ||
options: {submitForSettlement: true} | ||
}; | ||
} | ||
|
||
function parseCardData(data) { | ||
return { | ||
cardholderName: data.name, | ||
number: data.number, | ||
expirationMonth: data.expirationMonth, | ||
expirationYear: data.expirationYear, | ||
cvv: data.cvv | ||
}; | ||
} | ||
|
||
Meteor.methods({ | ||
/** | ||
* braintreeSubmit | ||
* Authorize, or authorize and capture payments from Brinatree | ||
* https://developers.braintreepayments.com/reference/request/transaction/sale/node | ||
* @param {String} transactionType - either authorize or capture | ||
* @param {Object} cardData - Object containing everything about the Credit card to be submitted | ||
* @param {Object} paymentData - Object containing everything about the transaction to be settled | ||
* @return {Object} results - Object containing the results of the transaction | ||
*/ | ||
"braintreeSubmit": function (transactionType, cardData, paymentData) { | ||
check(transactionType, String); | ||
check(cardData, { | ||
name: String, | ||
number: String, | ||
expirationMonth: String, | ||
expirationYear: String, | ||
cvv2: String, | ||
type: String | ||
}); | ||
check(paymentData, { | ||
total: String, | ||
currency: String | ||
}); | ||
let gateway = getGateway(); | ||
let paymentObj = getPaymentObj(); | ||
if (transactionType === "authorize") { | ||
paymentObj.options.submitForSettlement = false; | ||
} | ||
paymentObj.creditCard = parseCardData(cardData); | ||
paymentObj.amount = paymentData.total; | ||
let fut = new Future(); | ||
gateway.transaction.sale(paymentObj, Meteor.bindEnvironment(function (error, result) { | ||
if (error) { | ||
fut.return({ | ||
saved: false, | ||
error: error | ||
}); | ||
} else if (!result.success) { | ||
fut.return({ | ||
saved: false, | ||
response: result | ||
}); | ||
} else { | ||
fut.return({ | ||
saved: true, | ||
response: result | ||
}); | ||
} | ||
}, function (error) { | ||
Reaction.Events.warn(error); | ||
})); | ||
return fut.wait(); | ||
}, | ||
|
||
|
||
/** | ||
* braintree/payment/capture | ||
* Capture payments from Braintree | ||
* https://developers.braintreepayments.com/reference/request/transaction/submit-for-settlement/node | ||
* @param {Object} paymentMethod - Object containing everything about the transaction to be settled | ||
* @return {Object} results - Object containing the results of the transaction | ||
*/ | ||
"braintree/payment/capture": function (paymentMethod) { | ||
check(paymentMethod, PaymentMethod); | ||
let transactionId = paymentMethod.transactions[0].transaction.id; | ||
let amount = paymentMethod.transactions[0].transaction.amount; | ||
let gateway = getGateway(); | ||
const fut = new Future(); | ||
this.unblock(); | ||
gateway.transaction.submitForSettlement(transactionId, amount, Meteor.bindEnvironment(function (error, result) { | ||
if (error) { | ||
fut.return({ | ||
saved: false, | ||
error: error | ||
}); | ||
} else { | ||
fut.return({ | ||
saved: true, | ||
response: result | ||
}); | ||
} | ||
}, function (e) { | ||
Logger.warn(e); | ||
})); | ||
return fut.wait(); | ||
}, | ||
/** | ||
* braintree/refund/create | ||
* Refund BrainTree payment | ||
* https://developers.braintreepayments.com/reference/request/transaction/refund/node | ||
* @param {Object} paymentMethod - Object containing everything about the transaction to be settled | ||
* @param {Number} amount - Amount to be refunded if not the entire amount | ||
* @return {Object} results - Object containing the results of the transaction | ||
*/ | ||
"braintree/refund/create": function (paymentMethod, amount) { | ||
check(paymentMethod, PaymentMethod); | ||
check(amount, Number); | ||
let transactionId = paymentMethod.transactions[0].transaction.id; | ||
let gateway = getGateway(); | ||
const fut = new Future(); | ||
gateway.transaction.refund(transactionId, amount, Meteor.bindEnvironment(function (error, result) { | ||
if (error) { | ||
fut.return({ | ||
saved: false, | ||
error: error | ||
}); | ||
} else if (!result.success) { | ||
if (result.errors.errorCollections.transaction.validationErrors.base[0].code === "91506") { | ||
fut.return({ | ||
saved: false, | ||
error: "Cannot refund transaction until it\'s settled. Please try again later" | ||
}); | ||
} else { | ||
fut.return({ | ||
saved: false, | ||
error: result.message | ||
}); | ||
} | ||
} else { | ||
fut.return({ | ||
saved: true, | ||
response: result | ||
}); | ||
} | ||
}, function (e) { | ||
Logger.fatal(e); | ||
})); | ||
return fut.wait(); | ||
}, | ||
|
||
/** | ||
* braintree/refund/list | ||
* List all refunds for a transaction | ||
* https://developers.braintreepayments.com/reference/request/transaction/find/node | ||
* @param {Object} paymentMethod - Object containing everything about the transaction to be settled | ||
* @return {Array} results - An array of refund objects for display in admin | ||
*/ | ||
"braintree/refund/list": function (paymentMethod) { | ||
check(paymentMethod, Object); | ||
let transactionId = paymentMethod.transactionId; | ||
let gateway = getGateway(); | ||
this.unblock(); | ||
let braintreeFind = Meteor.wrapAsync(gateway.transaction.find, gateway.transaction); | ||
let findResults = braintreeFind(transactionId); | ||
let result = []; | ||
if (findResults.refundIds.length > 0) { | ||
for (let refund of findResults.refundIds) { | ||
let refundDetails = getRefundDetails(refund); | ||
result.push({ | ||
type: "refund", | ||
amount: parseFloat(refundDetails.amount), | ||
created: moment(refundDetails.createdAt).unix() * 1000, | ||
currency: refundDetails.currencyIsoCode, | ||
raw: refundDetails | ||
}); | ||
} | ||
} | ||
return result; | ||
} | ||
"braintreeSubmit": BraintreeMethods.paymentSubmit, | ||
"braintree/payment/capture": BraintreeMethods.paymentCapture, | ||
"braintree/refund/create": BraintreeMethods.createRefund, | ||
"braintree/refund/list": BraintreeMethods.listRefunds | ||
}); | ||
|
||
getRefundDetails = function (refundId) { | ||
check(refundId, String); | ||
let gateway = getGateway(); | ||
let braintreeFind = Meteor.wrapAsync(gateway.transaction.find, gateway.transaction); | ||
let findResults = braintreeFind(refundId); | ||
return findResults; | ||
}; | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What CC uses 19 digits? Just curious. Is this something that should be changed across all payment methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Maestro debit card was the most popular one I could find that used 19 digits, but I made this change based on Error Code 81716.
Yes, I think It probably is something that needs to be changed for the other payment methods, if nothing else because Amex is only 15 digits, so we at least need to make the minimum less. I will update these in the other PR's currently open for the other providers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maestro (min 12, max 19) is accepted by PayPal, Auth.net, and Braintree in SOME countries, not all countries. Do we want to use 12/19 to meet this standard where it's accepted, or do we use 13/16 (Visa can be 13 OR 16) and let those using Maestro update it themselves?