Skip to content
This repository has been archived by the owner on Mar 28, 2023. It is now read-only.

Order details dispute #529

Merged
merged 41 commits into from
Jul 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c0afedd
WIP dispute order form
rmisio Jun 15, 2017
eba2fd7
seperated out the order details moderator fragment into its own view
rmisio Jun 16, 2017
8fcd8a6
removing some files
rmisio Jun 16, 2017
ca54ecc
WIP - functionlity to open a dispute
rmisio Jun 16, 2017
b5c5f8c
removed file from git
rmisio Jun 16, 2017
41432a1
dispute open form
rmisio Jun 16, 2017
1fee72e
wiring in updating order details based on the disputeOpen notification
rmisio Jun 16, 2017
87b9420
WIP - starting on the Resolve Dispute form
rmisio Jun 16, 2017
daaae5f
additional work on the resolve dispute form
rmisio Jun 19, 2017
66e7c02
Completed the resolve dispute form
rmisio Jun 20, 2017
1198c4b
implemented the resolve dispute section
rmisio Jun 21, 2017
1d6f164
handling issue where payout amount not set for a party if the other p…
rmisio Jun 21, 2017
c6f3af0
tweak to the paymount amount logic in the disputePayout template
rmisio Jun 21, 2017
376dc9d
cleanup related to the dispute payout section
rmisio Jun 21, 2017
b2704f9
removing the pending filter from the cases tab
rmisio Jun 21, 2017
8e21de9
managaing the complet order form when a diuspute is opened and closed
rmisio Jun 22, 2017
a3ed869
adjusting the accepted event state when a dispute is opened
rmisio Jun 22, 2017
b0d6f6e
removing unused import
rmisio Jun 22, 2017
4e1d3c6
merging in master
rmisio Jun 22, 2017
8ba03e6
tweaked the purchase payment view to accept data in a more generic way
rmisio Jun 22, 2017
66be08d
integrated the Purchase - Payment view into the Order Detail overlay
rmisio Jun 22, 2017
f2ef463
fixing issue where the dispute icon remains in the middle of the prog…
rmisio Jun 22, 2017
28586f5
updating the state in the transactions tables based on state change i…
rmisio Jun 23, 2017
0f816ab
remove the crowd-funding listing type
rmisio Jun 23, 2017
9f56475
update based on changed function name
rmisio Jun 26, 2017
2ca22df
using payment amount for total on order details
rmisio Jun 27, 2017
2c47a04
removed some debugging code
rmisio Jun 27, 2017
cb6c758
removed some debugging code
rmisio Jun 27, 2017
3f3e9ab
handling dispute payout payment amounts
rmisio Jun 27, 2017
e6a3b98
WIP of the dispute closed section
rmisio Jun 27, 2017
bd66b86
added in a comment
rmisio Jun 27, 2017
34dd176
updated handling of profile fetcher to avoid odd race condition
rmisio Jun 28, 2017
0481894
showing the complete order form for the buyer when a dispute payout i…
rmisio Jun 28, 2017
f799e38
finished up the dispute complete section
rmisio Jun 28, 2017
f291f3d
Merge branch 'master' into order-details-dispute
rmisio Jun 28, 2017
069b2d0
fixing bug with anonymous flag in the complete order form
rmisio Jun 30, 2017
7302852
code review tweaks
rmisio Jun 30, 2017
864464e
handling a locl pickup item in the fulfillment related views
rmisio Jun 30, 2017
fd2b4a4
changing sequence of where page state is updated upon purchase
rmisio Jun 30, 2017
3ea43bf
fixing bug where shipping info was lost when fulfilling a physical order
rmisio Jul 5, 2017
c6264c5
merging in master
rmisio Jul 5, 2017
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
81 changes: 74 additions & 7 deletions js/languages/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,7 @@
"formats": {
"PHYSICAL_GOOD": "Physical Good",
"DIGITAL_GOOD": "Digital Good",
"SERVICE": "Service",
"CROWD_FUND": "Crowd fund"
"SERVICE": "Service"
},
"conditionTypes": {
"NEW": "New",
Expand Down Expand Up @@ -1073,7 +1072,7 @@
"noteFromLabel": "Note from %{store}:",
"copyLink": "Copy",
"digitalReadyForDlHeading": "Digital files are ready for download!",
"digitalReadyForDlText": "The files have been delivered. DOwnload when you're ready.",
"digitalReadyForDlText": "The files have been delivered. Download when you're ready.",
"urlLabel": "File URL",
"passwordLabel": "Password"
},
Expand All @@ -1095,6 +1094,39 @@
"heading": "Order Complete",
"reviewLabel": "%{name}'s Review:"
},
"disputeStarted": {
"heading": "Dispute Started",
"partyIsDisputing": "%{name} is disputing the order:",
"resolveBtn": "Resolve Dispute",
"genericIsDisputed": "The order is being disputed:",
"noReasonProvided": "No reason was provided."
},
"disputePayout": {
"heading": "Dispute Payout",
"buyerHeading": "Buyer",
"buyerHeadingWithName": "%{name} (buyer)",
"vendorHeading": "Vendor",
"vendorHeadingWithName": "%{name} (vendor)",
"moderatorHeading": "Moderator",
"moderatorHeadingWithName": "%{name} (moderator)",
"noteFromHeading": "Note from moderator:",
"noteFromHeadingWithName": "Note from %{name}:",
"btnAcceptPayout": "Accept Payout",
"acceptPayoutConfirm": {
"title": "Are you sure?",
"body": "Once accepted, the payout will process immediately",
"btnCancel": "Cancel",
"btnConfirm": "Yes, Accept"
}
},
"disputeAcceptance": {
"heading": "Dispute Closed",
"genericBuyerAcceptedPayout": "The buyer accepted the dispute payout",
"genericVendorAcceptedPayout": "The vendor accepted the dispute payout",
"userAcceptedPayout": "%{name} accepted the dispute payout",
"orderCompleteWhenYouReview": "The order will be complete when you leave a review.",
"orderCompleteWhenBuyerReviews": "The order will be complete when the buyer leaves a review."
},
"orderDetails": {
"progressBarStates": {
"paid": "Paid",
Expand All @@ -1116,9 +1148,6 @@
"moderatorHeading": "Moderator",
"copyAddress": "Copy",
"viewOnMap": "View on map"
},
"payForOrder": {
"heading": "Pay for your order"
}
},
"fulfillOrderTab": {
Expand All @@ -1136,6 +1165,33 @@
"noteHelperTextDigital": "The buyer will receive a notification containing the file information",
"btnCancel": "Cancel",
"btnSubmit": "Submit"
},
"resolveDisputeTab": {
"heading": "Resolve Dispute",
"buyerAmountLabel": "Buyer Amount",
"vendorAmountLabel": "Vendor Amount",
"commentLabel": "Comment",
"commentPlaceholder": "Explain your decision...",
"btnCancel": "Cancel",
"btnSubmit": "Submit",
"resolveConfirm": {
"title": "Are you sure?",
"body": "Please double check everything looks good",
"btnCancel": "Cancel",
"btnSubmit": "Submit"
}
},
"disputeOrderTab": {
"heading": "Dispute Order",
"moderatorLabel": "Moderator",
"reasonLabel": "Reason",
"reasonPlaceholder": "Please explain your reason for opening a dispute…",
"reasonHelperText": "Opening a dispute will notify and invite the moderator into the order to help resolve any conflicts",
"btnCancel": "Cancel",
"btnSubmit": "Submit"
},
"actionBar": {
"disputeOrderBtn": "Dispute Order"
}
},
"orderUtil": {
Expand All @@ -1144,7 +1200,10 @@
"failedCancelHeading": "There was an error canceling the order.",
"failedFulfillHeading": "There was an error fulfilling the order.",
"failedRefundHeading": "There was an error refunding the order.",
"failedCompleteHeading": "There was an error completing the order."
"failedCompleteHeading": "There was an error completing the order.",
"failedOpenDisputeHeading": "There was an error completing the order.",
"failedResolveHeading": "There was an error resolving the order.",
"failedAcceptPayoutHeading": "There was an error accepting the payout."
},
"exchangeRatesSyncer": {
"fetchingRatesStatusMsg": "Fetching exchange rates…",
Expand Down Expand Up @@ -1304,6 +1363,14 @@
"provideReview": "Please provide a review.",
"provideRating": "Please select a rating."
},
"resolveDisputeModelErrors": {
"provideAmount": "Please provide an amount.",
"percentageOutOfRange": "The amount must be between 0 and 100.",
"providePercentageAsNumber": "Please provide a vendor amount as a number.",
"totalPercentageOutOfRange": "The sum of the buyer and vendor amounts cannot exceed 100.",
"totalPercentageTooLow": "The sum of the buyer and vendor amounts must add up to 100.",
"provideResolution": "Please explain your decision."
},
"bitcoinCurrencyUnits": {
"BTC": "BTC",
"MBTC": "mBTC",
Expand Down
1 change: 0 additions & 1 deletion js/models/listing/Metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export default class extends BaseModel {
'PHYSICAL_GOOD',
'DIGITAL_GOOD',
'SERVICE',
'CROWD_FUND',
];
}

Expand Down
32 changes: 32 additions & 0 deletions js/models/order/Case.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,38 @@ export default class extends BaseModel {
// convert price fields
response.vendorContract.buyerOrder.payment.amount =
integerToDecimal(response.vendorContract.buyerOrder.payment.amount, true);

if (response.resolution) {
response.resolution.payout.buyerOutput =
response.resolution.payout.buyerOutput || {};
response.resolution.payout.vendorOutput =
response.resolution.payout.vendorOutput || {};
response.resolution.payout.moderatorOutput =
response.resolution.payout.moderatorOutput || {};

// Temporary to account for server bug:
// https://github.com/OpenBazaar/openbazaar-go/issues/548
// Sometimes the payment amounts are coming back as enormously inflated strings.
// For now, we'll just make them dummy values.
if (typeof response.resolution.payout.buyerOutput.amount === 'string') {
response.resolution.payout.buyerOutput.amount = 25000;
}

if (typeof response.resolution.payout.vendorOutput.amount === 'string') {
response.resolution.payout.vendorOutput.amount = 12000;
}

if (typeof response.resolution.payout.moderatorOutput.amount === 'string') {
response.resolution.payout.moderatorOutput.amount = 6000;
}

response.resolution.payout.buyerOutput.amount =
integerToDecimal(response.resolution.payout.buyerOutput.amount || 0, true);
response.resolution.payout.vendorOutput.amount =
integerToDecimal(response.resolution.payout.vendorOutput.amount || 0, true);
response.resolution.payout.moderatorOutput.amount =
integerToDecimal(response.resolution.payout.moderatorOutput.amount || 0, true);
}
}

return response;
Expand Down
11 changes: 11 additions & 0 deletions js/models/order/Contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ export default class extends BaseModel {
.get('contractType');
}

get isLocalPickup() {
const buyerOrder = this.get('buyerOrder');

if (buyerOrder && buyerOrder.items && buyerOrder.items[0] &&
buyerOrder.items[0].shippingOption) {
return buyerOrder.items[0].shippingOption.service === '';
}

return false;
}

parse(response) {
return {
...response,
Expand Down
35 changes: 35 additions & 0 deletions js/models/order/Order.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,41 @@ export default class extends BaseModel {
// convert price fields
response.contract.buyerOrder.payment.amount =
integerToDecimal(response.contract.buyerOrder.payment.amount, true);

if (response.contract.disputeResolution) {
response.contract.disputeResolution.payout.buyerOutput =
response.contract.disputeResolution.payout.buyerOutput || {};
response.contract.disputeResolution.payout.vendorOutput =
response.contract.disputeResolution.payout.vendorOutput || {};
response.contract.disputeResolution.payout.moderatorOutput =
response.contract.disputeResolution.payout.moderatorOutput || {};

// Temporary to account for server bug:
// https://github.com/OpenBazaar/openbazaar-go/issues/548
// Sometimes the payment amounts are coming back as enormously inflated strings.
// For now, we'll just make them dummy values.
if (typeof response.contract.disputeResolution.payout.buyerOutput.amount === 'string') {
response.contract.disputeResolution.payout.buyerOutput.amount = 25000;
}

if (typeof response.contract.disputeResolution.payout.vendorOutput.amount === 'string') {
response.contract.disputeResolution.payout.vendorOutput.amount = 12000;
}

if (typeof response.contract.disputeResolution.payout.moderatorOutput.amount === 'string') {
response.contract.disputeResolution.payout.moderatorOutput.amount = 6000;
}

response.contract.disputeResolution.payout.buyerOutput.amount =
integerToDecimal(
response.contract.disputeResolution.payout.buyerOutput.amount || 0, true);
response.contract.disputeResolution.payout.vendorOutput.amount =
integerToDecimal(
response.contract.disputeResolution.payout.vendorOutput.amount || 0, true);
response.contract.disputeResolution.payout.moderatorOutput.amount =
integerToDecimal(
response.contract.disputeResolution.payout.moderatorOutput.amount || 0, true);
}
}

response.paymentAddressTransactions = response.paymentAddressTransactions || [];
Expand Down
26 changes: 26 additions & 0 deletions js/models/order/OrderDispute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import app from '../../app';
import BaseModel from '../BaseModel';

export default class extends BaseModel {
defaults() {
return {
claim: '',
};
}

url() {
return app.getServerUrl('ob/opendispute/');
}

get idAttribute() {
return 'orderId';
}

sync(method, model, options) {
if (method === 'create' || method === 'update') {
options.type = 'POST';
}

return super.sync(method, model, options);
}
}
83 changes: 83 additions & 0 deletions js/models/order/ResolveDispute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import app from '../../app';
import BaseModel from '../BaseModel';

export default class extends BaseModel {
defaults() {
return {
resolution: '',
};
}

url() {
return app.getServerUrl('ob/closedispute/');
}

get idAttribute() {
return 'orderId';
}

validate(attrs) {
const errObj = {};

const addError = (fieldName, error) => {
errObj[fieldName] = errObj[fieldName] || [];
errObj[fieldName].push(error);
};

let vendorPercentageOk = false;
let buyerPercentageOk = false;

if (typeof attrs.vendorPercentage === 'undefined' || attrs.vendorPercentage === '') {
addError('vendorPercentage',
app.polyglot.t('resolveDisputeModelErrors.provideAmount'));
} else if (typeof attrs.vendorPercentage !== 'number') {
addError('vendorPercentage',
app.polyglot.t('resolveDisputeModelErrors.providePercentageAsNumber'));
} else if (attrs.vendorPercentage < 0 || attrs.vendorPercentage > 100) {
addError('vendorPercentage',
app.polyglot.t('resolveDisputeModelErrors.vendorPercentageOutOfRange'));
} else {
vendorPercentageOk = true;
}

if (typeof attrs.buyerPercentage === 'undefined' || attrs.buyerPercentage === '') {
addError('buyerPercentage',
app.polyglot.t('resolveDisputeModelErrors.provideAmount'));
} else if (typeof attrs.buyerPercentage !== 'number') {
addError('buyerPercentage',
app.polyglot.t('resolveDisputeModelErrors.providePercentageAsNumber'));
} else if (attrs.buyerPercentage < 0 || attrs.buyerPercentage > 100) {
addError('buyerPercentage',
app.polyglot.t('resolveDisputeModelErrors.buyerPercentageOutOfRange'));
} else {
buyerPercentageOk = true;
}

if (vendorPercentageOk && buyerPercentageOk) {
if (attrs.buyerPercentage + attrs.vendorPercentage > 100) {
addError('buyerPercentage',
app.polyglot.t('resolveDisputeModelErrors.totalPercentageOutOfRange'));
} else if (attrs.buyerPercentage + attrs.vendorPercentage < 100) {
addError('buyerPercentage',
app.polyglot.t('resolveDisputeModelErrors.totalPercentageTooLow'));
}
}

if (!attrs.resolution) {
addError('resolution',
app.polyglot.t('resolveDisputeModelErrors.provideResolution'));
}

if (Object.keys(errObj).length) return errObj;

return undefined;
}

sync(method, model, options) {
if (method === 'create' || method === 'update') {
options.type = 'POST';
}

return super.sync(method, model, options);
}
}
14 changes: 10 additions & 4 deletions js/models/order/orderFulfillment/OrderFulfillment.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,33 @@ export default class extends BaseModel {
throw new Error('Please provide the contract type.');
}

if (typeof options.isLocalPickup !== 'boolean') {
throw new Error('Please provide a boolean indicating whether the item is to ' +
'be picked up locally.');
}

// Since the contract type is not available on this when
// the defaults are initially called, we need to set the
// initial contract type dependant attributes here. We also
// set them in defaults, so if the model is reset, they'll
// be restored properly.
if (options.contractType === 'DIGITAL_GOOD') {
attrs.digitalDelivery = new DigitalDelivery();
} else if (options.contractType === 'PHYSICAL_GOOD') {
attrs.physicalDelivery = new PhysicalDelivery();
attrs.digitalDelivery = new DigitalDelivery(attrs.digitalDelivery || {});
} else if (options.contractType === 'PHYSICAL_GOOD' && !options.isLocalPickup) {
attrs.physicalDelivery = new PhysicalDelivery(attrs.physicalDelivery || {});
}

super(attrs, options);
this.contractType = options.contractType;
this.isLocalPickup = options.isLocalPickup;
}

defaults() {
const defaults = {};

if (this.contractType === 'DIGITAL_GOOD') {
defaults.digitalDelivery = new DigitalDelivery();
} else if (this.contractType === 'PHYSICAL_GOOD') {
} else if (this.contractType === 'PHYSICAL_GOOD' && !this.isLocalPickup) {
defaults.physicalDelivery = new PhysicalDelivery();
}

Expand Down
6 changes: 6 additions & 0 deletions js/templates/modals/orderDetail/actionBar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<% if (ob.showDisputeOrderButton) { %>
<%= ob.processingButton({
className: 'flex btn clrErr clrBrDec1 clrTOnEmph js-openDispute',
btnText: ob.polyT('orderDetail.actionBar.disputeOrderBtn'),
}) %>
<% } %>
Loading