Skip to content

Commit

Permalink
feat: support fiat payments (#6710)
Browse files Browse the repository at this point in the history
- remove standard bounties code from details2
- Alter BountyFulfillment fields to allow null values
- bounty creation: add fiat support showing alert + hiding token
- start work: add disclaimer notifying contributor that payment is via Paypal
- submit work: hide blockchain text + hide wallet address & ask paypal email id
- payout: add new payout flow for fiat payments
- add new activity called worker_paid so that activity is cleaner
  • Loading branch information
thelostone-mc authored May 27, 2020
1 parent 3826ae6 commit 00b19c4
Show file tree
Hide file tree
Showing 23 changed files with 425 additions and 165 deletions.
2 changes: 2 additions & 0 deletions app/app/local.env
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,5 @@ YOUTUBE_API_KEY=
VIEW_BLOCK_API_KEY=
FORTMATIC_LIVE_KEY=
FORTMATIC_TEST_KEY=

PYPL_CLIENT_ID=
1 change: 1 addition & 0 deletions app/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
VIEW_BLOCK_API_KEY = env('VIEW_BLOCK_API_KEY', default='YOUR-VIEW-BLOCK-KEY')
FORTMATIC_LIVE_KEY = env('FORTMATIC_LIVE_KEY', default='YOUR-SupEr-SecRet-LiVe-FoRtMaTiC-KeY')
FORTMATIC_TEST_KEY = env('FORTMATIC_TEST_KEY', default='YOUR-SupEr-SecRet-TeSt-FoRtMaTiC-KeY')
PYPL_CLIENT_ID = env('PYPL_CLIENT_ID', default='YOUR-SupEr-SecRet-TeSt-PYPL-KeY')

# Ratelimit
RATELIMIT_ENABLE = env.bool('RATELIMIT_ENABLE', default=True)
Expand Down
66 changes: 66 additions & 0 deletions app/assets/v2/js/pages/bounty_detail/PYPL.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@

const payWithPYPL = (fulfillment_id, fulfiller_identifier, ele, vm, modal) => {
const amount = vm.fulfillment_context.amount;

paypal.Buttons(
{
style: {
color: 'blue'
},
createOrder: function(data, actions) {
return actions.order.create({
application_context : {
shipping_preference: "NO_SHIPPING"
},
purchase_units: [{
payee: {
email_address: fulfiller_identifier
},
amount: {
value: amount
}
}]
});
},
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
console.log('Bounty Paid via', details.payer.name);

const payload = {
payout_type: 'fiat',
tenant: 'PYPL',
amount: amount,
token_name: 'USD',
funder_identifier: details.payer && details.payer.email_address,
payout_tx_id: details.id,
payout_status: details.status == 'COMPLETED' ? 'done' : 'expired'
};

const apiUrlBounty = `/api/v1/bounty/payout/${fulfillment_id}`;

fetchData(apiUrlBounty, 'POST', payload).then(response => {
if (200 <= response.status && response.status <= 204) {
console.log('success', response);

vm.fetchBounty();
modal.closeModal();
$(ele).html('');
_alert('Payment Successful');

} else {
_alert('Unable to make payout bounty. Please try again later', 'error');
console.error(`error: bounty payment failed with status: ${response.status} and message: ${response.message}`);
}
}).catch(function(error) {
_alert('Unable to make payout bounty. Please try again later', 'error');
console.log(error);
});

}).catch(function(error) {
_alert('Unable to make payout bounty. Please try again later', 'error');
console.log(error)
});
}
}
).render(ele);
}
34 changes: 26 additions & 8 deletions app/assets/v2/js/pages/bounty_details2.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,18 @@ Vue.mixin({
_alert('Unable to make payout bounty. Please try again later', 'error');
});
},
nextStepAndLoadPYPLButton: function(fulfillment_id, fulfiller_identifier) {
let vm = this;

Promise.resolve(vm.goToStep('submit_transaction', 'payout_amount')).then(() => {
const ele = '#payout-with-pypl';

$(ele).html('');
const modal = this.$refs['payout-modal'][0];

payWithPYPL(fulfillment_id, fulfiller_identifier, ele, vm, modal);
});
},
closeBounty: function() {

let vm = this;
Expand Down Expand Up @@ -388,10 +400,6 @@ Vue.mixin({
return [];
}

if (vm.is_bounties_network) {
return vm.bounty.fulfillments.filter(fulfillment => fulfillment.accepted);
}

return vm.bounty.fulfillments.filter(fulfillment =>
fulfillment.accepted &&
fulfillment.payout_status == 'done'
Expand Down Expand Up @@ -452,6 +460,15 @@ Vue.mixin({
}
vm.fulfillment_context.referrer = currentStep;
vm.fulfillment_context.active_step = nextStep;
},
initFulfillmentContext: function(fulfillment) {
let vm = this;

if (fulfillment.payout_type == 'fiat') {
vm.fulfillment_context.active_step = 'payout_amount';
} else if (fulfillment.payout_type == 'qr') {
vm.fulfillment_context.active_step = 'check_wallet_owner';
}
}
},
computed: {
Expand All @@ -466,9 +483,11 @@ Vue.mixin({
let activities = this.bounty.activities.sort((a, b) => new Date(b.created) - new Date(a.created));

if (decimals) {
activities.forEach(activity => {
activities.forEach((activity, index) => {
if (activity.metadata) {
if (activity.metadata.new_bounty) {
if (activity.metadata.token_name == 'USD' && activity.activity_type == 'worker_paid') {
activity.metadata['token_value'] = activity.metadata.payout_amount;
} else {
activity.metadata['token_value'] = activity.metadata.value_in_token / 10 ** decimals;
}
}
Expand All @@ -491,12 +510,11 @@ if (document.getElementById('gc-bounty-detail')) {
cb_address: cb_address,
isOwner: false,
isOwnerAddress: false,
is_bounties_network: is_bounties_network,
fulfillment_context: {
active_step: 'check_wallet_owner',
amount: 0
},
decimals: 18, // TODO: UPDATE BASED ON TOKEN
decimals: 18,
inputBountyOwnerAddress: bounty.bounty_owner_address,
contxt: document.contxt,
quickLinks: []
Expand Down
21 changes: 7 additions & 14 deletions app/assets/v2/js/pages/create_bounty/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,16 @@ createBounty = data => {
bountyNeverExpires :
new Date(data.expirationTimeDelta).getTime() / 1000;

let is_featured = data.is_featured ? 'True' : 'False';
let coupon_code = $('#coupon_code').val();
let fee_amount;
let fee_tx_id;
let network;
const is_featured = isQRToken(metadata.tokenName) ? 'True' : 'False';
const coupon_code = $('#coupon_code').val();
const fee_amount = 0;
const fee_tx_id = null;
const network = 'mainnet';

const tokenAddress = data.denomination;
const token = tokenAddressToDetails(tokenAddress);

if (qr_tokens.includes(metadata.tokenName)) {
is_featured = 'True';
coupon_code = null;
fee_amount = 0;
fee_tx_id = null;
network = 'mainnet';
}

const web3_type = isQRToken(metadata.tokenName) ? 'qr' : 'fiat';

const params = {
'title': metadata.issueTitle,
Expand Down Expand Up @@ -74,7 +67,7 @@ createBounty = data => {
'attached_job_description': hiring.jobDescription,
'eventTag': metadata.eventTag,
'auto_approve_workers': data.auto_approve_workers ? 'True' : 'False',
'web3_type': 'qr',
'web3_type': web3_type,
'activity': data.activity,
'bounty_owner_address': data.funderAddress
};
Expand Down
13 changes: 9 additions & 4 deletions app/assets/v2/js/pages/fulfill_bounty/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ fulfillBounty = data => {
return;
}

if (!data.payoutAddress) {
_alert({ message: gettext('Add address you would want to be paid out upon payout') }, 'error');
if (web3_type == 'fiat' && !data.fulfiller_identifier) {
_alert({ message: gettext('Add valid email you would want the bounty to be sent to') }, 'error');
} else if (web3_type != 'fiat' && !data.payoutAddress) {
_alert({ message: gettext('Add valid address you would want the bounty to be sent to') }, 'error');
return;
}

Expand All @@ -27,15 +29,18 @@ fulfillBounty = data => {
}
},
'accepted': false,
'fulfiller': data.payoutAddress
'fulfiller': data.payoutAddress,
'fulfiller_identifier': data.fulfiller_identifier
};

const params = {
'issueURL': data.issueURL,
'githubPRLink': data.githubPRLink,
'hoursWorked': data.hoursWorked,
'metadata': JSON.stringify(metadata),
'fulfiller_address': data.payoutAddress
'fulfiller_address': data.payoutAddress,
'fulfiller_identifier': data.fulfiller_identifier,
'payout_type': web3_type
};

$.post(url, params, function(response) {
Expand Down
50 changes: 27 additions & 23 deletions app/assets/v2/js/pages/new_bounty.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,51 @@ document.web3network = 'mainnet';
load_tokens();

const qr_tokens = [ 'ETC', 'cGLD', 'cUSD', 'ZIL' ];
const fiat_tokens = ['USD'];

const isQRToken = tokenName => qr_tokens.includes(tokenName);
const isFiatToken = tokenName => fiat_tokens.includes(tokenName);

const updateOnNetworkOrTokenChange = () => {
const tokenName = $('select[name=denomination]').select2('data')[0] &&
$('select[name=denomination]').select2('data')[0].text;

if (!tokenName) {
// tokens haven't loaded yet
} else if (qr_tokens.includes(tokenName)) {
} else if (isQRToken(tokenName) || isFiatToken(tokenName)) {
document.web3network = 'mainnet';

$('#navbar-network-banner').hide();
$('.navbar-network').hide();
$('.eth-chain').hide();
$('.web3-alert').hide();

$('.funder-address-container').show();
$('#funderAddress').attr('required', true);
FEE_PERCENTAGE = 0;

$('.web3-alert').hide();
if (isQRToken(tokenName)) {
$('.funder-address-container').show();
$('#funderAddress').attr('required', true);
$('#fiat_text').hide();
} else {
$('.funder-address-container').hide();
$('#funderAddress').removeAttr('required');
$('#funderAddress').val('');
$('#fiat_text').show();
}

} else {
listen_for_web3_changes();

$('.eth-chain').show();
FEE_PERCENTAGE = document.FEE_PERCENTAGE / 100.0;

$('#navbar-network-banner').show();
$('.navbar-network').show();

$('.funder-address-container').hide();
$('#funderAddress').removeAttr('required');
$('#funderAddress').val('');
$('#fiat_text').hide();

$('.web3-alert').show();
if (!document.web3network) {
Expand Down Expand Up @@ -284,19 +302,6 @@ const tokenAuthAlert = (isTokenAuthed, tokenName) => {
}
};


const updateViewForToken = (token_name) => {

if (qr_tokens.includes(token_name)) {
$('.eth-chain').hide();
FEE_PERCENTAGE = 0;
} else {
$('.eth-chain').show();
FEE_PERCENTAGE = document.FEE_PERCENTAGE / 100.0;
}

};

$(function() {

$('#last-synced').hide();
Expand Down Expand Up @@ -366,16 +371,15 @@ $(function() {
updateOnNetworkOrTokenChange();

const token_address = $('select[name=denomination]').val();
const tokenName = $('select[name=denomination]').select2('data')[0].text;
const tokenName = $('select[name=denomination]').select2('data')[0] &&
$('select[name=denomination]').select2('data')[0].text;

const tokendetails = qr_tokens.includes(tokenName) ?
const tokendetails = isQRToken(tokenName) || isFiatToken(tokenName) ?
tokenAddressToDetailsByNetwork(token_address, 'mainnet') :
tokenAddressToDetails(token_address);

const token = tokendetails['name'];

updateViewForToken(token);

$('#summary-bounty-token').html(token);
$('#summary-fee-token').html(token);
populateBountyTotal();
Expand Down Expand Up @@ -584,10 +588,10 @@ $('#submitBounty').validate({
});
}

const token = $('#summary-bounty-token').html();
const tokenName = $('#summary-bounty-token').html();
const data = transformBountyData(form);

if (qr_tokens.includes(token)) {
if (isQRToken(tokenName) || isFiatToken(tokenName)) {
createBounty(data);
} else {
ethCreateBounty(data);
Expand Down
8 changes: 4 additions & 4 deletions app/assets/v2/js/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -1077,15 +1077,15 @@ var setUsdAmount = function() {

getUSDEstimate(amount, denomination, function(estimate) {
if (estimate['value']) {
$('#usd-amount-wrapper').css('visibility', 'visible');
$('#usd_amount_text').css('visibility', 'visible');
$('#usd-amount-wrapper').show();
$('#usd_amount_text').show();

$('#usd_amount').val(estimate['value_unrounded']);
$('#usd_amount_text').html(estimate['rate_text']);
$('#usd_amount').removeAttr('disabled');
} else {
$('#usd-amount-wrapper').css('visibility', 'hidden');
$('#usd_amount_text').css('visibility', 'hidden');
$('#usd-amount-wrapper').hide();
$('#usd_amount_text').hide();

$('#usd_amount_text').html('');
$('#usd_amount').prop('disabled', true);
Expand Down
3 changes: 2 additions & 1 deletion app/assets/v2/js/vue-filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ Vue.filter('stringReplace', function(activity_type) {
bounty_removed_slashed_by_staff: gettext('Dinged and Removed from Bounty by Staff'),
bounty_removed_by_staff: gettext('Removed from Bounty by Staff'),
bounty_removed_by_funder: gettext('Removed from Bounty by Funder'),
new_kudos: gettext('New Kudos')
new_kudos: gettext('New Kudos'),
worker_paid: gettext('Bounty paid to')
};

return activity_names[activity_type];
Expand Down
3 changes: 2 additions & 1 deletion app/dashboard/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,8 @@ def get_fulfillment_data_for_activity(fulfillment):
'killed_bounty': 'cancel_bounty',
'work_submitted': 'submit_work',
'stop_work': 'stop_work',
'work_done': 'payout_bounty'
'work_done': 'payout_bounty',
'worker_paid': 'worker_paid'
}


Expand Down
6 changes: 4 additions & 2 deletions app/dashboard/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ class Meta:
model = BountyFulfillment
fields = ('pk', 'fulfiller_email', 'fulfiller_address',
'fulfiller_github_username', 'fulfiller_metadata',
'fulfillment_id', 'accepted', 'profile', 'created_on', 'accepted_on', 'fulfiller_github_url',
'payout_tx_id', 'payout_amount', 'token_name', 'payout_status')
'fulfillment_id', 'accepted', 'profile', 'created_on',
'accepted_on', 'fulfiller_github_url', 'payout_tx_id',
'payout_amount', 'token_name', 'payout_status', 'tenant',
'payout_type', 'fulfiller_identifier', 'funder_identifier')


class HackathonEventSerializer(serializers.ModelSerializer):
Expand Down
Loading

0 comments on commit 00b19c4

Please sign in to comment.