The vue components relevant to stripe were replaced with payeer's components. A function was added to the user's controller to ease transaction completion logic. Storing the paying state in the database is more provider agnostic and avoids having to learn the request parameters of payeer's redirects.master
@@ -91,15 +91,9 @@ class BillingController extends Controller | |||||
$event = \Stripe\Event::constructFrom($request->all()); | $event = \Stripe\Event::constructFrom($request->all()); | ||||
$charge = $event->data->object; | $charge = $event->data->object; | ||||
$transaction = Transaction::where('intent_id', $charge->payment_intent)->first(); | $transaction = Transaction::where('intent_id', $charge->payment_intent)->first(); | ||||
$user = $transaction->user; | |||||
if ($event->type == 'charge.succeeded') { | if ($event->type == 'charge.succeeded') { | ||||
$user->credits = $user->credits + $transaction->credits + $transaction->credits_extra; | |||||
$transaction->status = 'completed'; | |||||
$transaction->completed = true; | |||||
$user->save(); | |||||
$transaction->save(); | |||||
$this->creditUser($transaction->id) | |||||
} else { | } else { | ||||
$transaction->status = $charge->status; | $transaction->status = $charge->status; | ||||
$transaction->save(); | $transaction->save(); | ||||
@@ -159,6 +153,54 @@ class BillingController extends Controller | |||||
$transaction->charge/100, 'description' => $description ]; | $transaction->charge/100, 'description' => $description ]; | ||||
} | } | ||||
//This needs to check the ip of the sender | |||||
public function processPayeer(Request $request) { | public function processPayeer(Request $request) { | ||||
$secret = config('services.payeer.secret'); | |||||
$arHash = [$request->m_operation_id, | |||||
$request->m_operation_ps, | |||||
$request->m_operation_day, | |||||
$request->m_operation_pay_date, | |||||
$request->m_shop, | |||||
$request->m_orderid, | |||||
$request->m_amount, | |||||
$request->m_curr, | |||||
$request->m_desc, | |||||
$request->m_status | |||||
]; | |||||
if (isset($request->m_params)) { | |||||
$arHash[] = $request->m_params; | |||||
} | |||||
$arHash[] = $secret; | |||||
$signature = strtoupper(hash('sha256', implode(':', $arHash))); | |||||
if ($signature == $request->m_sign && $request->m_status == 'success') | |||||
{ | |||||
$this->creditUser((int) $request->m_orderid) | |||||
return $request->m_orderid.'|success'; | |||||
} else { | |||||
return $request->m_orderid.'|error'; | |||||
$transaction = Transaction::find($request->orderid); | |||||
$transaction->status = 'error'; | |||||
$transaction->save(); | |||||
} | |||||
} | |||||
//Credits the user of a given transaction id | |||||
public function creditUser($transaction_id) { | |||||
$transaction = Transaction::find($transaction_id); | |||||
if ($transaction->completed) { | |||||
return; | |||||
} | |||||
$user = $transaction->user; | |||||
$user->credits = $user->credits + $transaction->credits + | |||||
$transaction->credits_extra; | |||||
$transaction->status = 'completed'; | |||||
$transaction->completed = true; | |||||
$user->save(); | |||||
$transaction->save(); | |||||
} | } | ||||
} | } |
@@ -77,6 +77,7 @@ class UserController extends Controller | |||||
//This should probably be changed to not return a page | //This should probably be changed to not return a page | ||||
if (Auth::attempt($credentials)) { | if (Auth::attempt($credentials)) { | ||||
$request->session()->regenerate(); | $request->session()->regenerate(); | ||||
$this->clearPaying(); | |||||
} else { | } else { | ||||
abort(401); | abort(401); | ||||
} | } | ||||
@@ -155,4 +156,13 @@ class UserController extends Controller | |||||
$user->password = Hash::make($request->password); | $user->password = Hash::make($request->password); | ||||
$user->save(); | $user->save(); | ||||
} | } | ||||
public function clearPaying() { | |||||
$user = Auth::user(); | |||||
if ($user->paying) { | |||||
$user->paying = false; | |||||
$user->save(); | |||||
} | |||||
} | |||||
} | } |
@@ -1,5 +1,5 @@ | |||||
<template> | <template> | ||||
<section v-if="!complete" class="select-credits"> | |||||
<section class="select-credits"> | |||||
<div class="credits-pane"><h2>10 Credits</h2> | <div class="credits-pane"><h2>10 Credits</h2> | ||||
<h3>$10.99</h3><div><span>Qty</span><input min="0" max="1000" v-model="packs.credits10" type="number"></div> | <h3>$10.99</h3><div><span>Qty</span><input min="0" max="1000" v-model="packs.credits10" type="number"></div> | ||||
</div> | </div> | ||||
@@ -19,7 +19,7 @@ | |||||
<div id="credits-errors"></div> | <div id="credits-errors"></div> | ||||
</section> | </section> | ||||
<section v-if="!complete" id="payment-section"> | |||||
<section id="payment-section"> | |||||
<h4>Select a payment method</h4> | <h4>Select a payment method</h4> | ||||
<div class="sliding-menu"> | <div class="sliding-menu"> | ||||
@@ -42,12 +42,12 @@ | |||||
<div id="agreement-check"> | <div id="agreement-check"> | ||||
<input v-model="agreed" type="checkbox"><label>I have read and agree to the <a | <input v-model="agreed" type="checkbox"><label>I have read and agree to the <a | ||||
href="/terms-and-policy">Terms and Policy</a> and will not pursue a fraudulent dispute or chargeback.</label> | |||||
href="/terms-and-policy">Terms and Policy</a> and will not pursue a dispute or chargeback.</label> | |||||
<div id="payment-error"></div> | <div id="payment-error"></div> | ||||
</div> | </div> | ||||
</section> | </section> | ||||
<section v-if="!complete" class="credits-confirm"> | |||||
<section class="credits-confirm"> | |||||
<button @click="pay" :disabled="!ready" | <button @click="pay" :disabled="!ready" | ||||
class="brand-btn">Buy<loading v-if="loading"></loading></button> | class="brand-btn">Buy<loading v-if="loading"></loading></button> | ||||
</section> | </section> | ||||
@@ -151,7 +151,7 @@ export default { | |||||
data() { | data() { | ||||
return {packs: {credits10: 0, credits50: 0, | return {packs: {credits10: 0, credits50: 0, | ||||
credits100: 0, credits1000: 0}, loading: false, method: 'payeer', | credits100: 0, credits1000: 0}, loading: false, method: 'payeer', | ||||
complete: false, agreed: false | |||||
agreed: false | |||||
} | } | ||||
}, | }, | ||||
computed: {total, ready}, | computed: {total, ready}, | ||||
@@ -37,7 +37,15 @@ | |||||
<a href="/logout">Logout</a> | <a href="/logout">Logout</a> | ||||
</section> | </section> | ||||
</div> | </div> | ||||
<settings :token="token" :user="user" class="settings-page" id="main" v-else-if="active === '#settings'"></settings> | |||||
<settings :token="token" :user="user" class="settings-page" id="main" | |||||
v-else-if="active === '#settings'"> | |||||
</settings> | |||||
<transaction-end :token="token" :user="user" :active="active" v-else-if="active == | |||||
'#transaction-complete' || active == '#transaction-failed'"> | |||||
</transaction-end> | |||||
</transition> | </transition> | ||||
</template> | </template> | ||||
</template> | </template> | ||||
@@ -47,6 +55,7 @@ import Sidebar from './sidebar.vue' | |||||
import Settings from './settings.vue' | import Settings from './settings.vue' | ||||
import PastOrders from './orders.vue' | import PastOrders from './orders.vue' | ||||
import NewOrder from './services.vue' | import NewOrder from './services.vue' | ||||
import TransactionEnd from './transaction-endpoint.vue' | |||||
function getServices() { | function getServices() { | ||||
return fetch("/panel/services", { | return fetch("/panel/services", { | ||||
@@ -87,7 +96,8 @@ function getOrders() { | |||||
export default { | export default { | ||||
components: { | components: { | ||||
Sidebar, Settings, PastOrders, NewOrder, | |||||
Sidebar, Settings, PastOrders, NewOrder, | |||||
TransactionEnd | |||||
}, | }, | ||||
data() { | data() { | ||||
return {active: window.location.hash, user: null, | return {active: window.location.hash, user: null, | ||||
@@ -1,9 +0,0 @@ | |||||
<template> | |||||
<div class="status-dialog"> | |||||
<img class="icon" src="../../images/checked2.svg" alt=""/> | |||||
<h3>Purchase complete. A receipt will be emailed to you.</h3> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
</script> |
@@ -14,9 +14,14 @@ | |||||
</div> | </div> | ||||
<ServicePane :site="'youtube'" :services="services" | <ServicePane :site="'youtube'" :services="services" | ||||
@select="select"></ServicePane> <ServicePane :site="'instagram'" | |||||
:services="services" @select="select"></ServicePane> <ServicePane | |||||
:site="'twitter'" :services="services" @select="select"></ServicePane> | |||||
@select="select"></ServicePane> | |||||
<ServicePane :site="'instagram'" :services="services" | |||||
@select="select"></ServicePane> | |||||
<ServicePane :site="'twitter'" :services="services" | |||||
@select="select"></ServicePane> | |||||
<ServicePane :site="'tiktok'" :services="services" | <ServicePane :site="'tiktok'" :services="services" | ||||
@select="select"></ServicePane> | @select="select"></ServicePane> | ||||
@@ -68,7 +73,7 @@ | |||||
<div><input required type="url" id="url" v-model="url"></div> | <div><input required type="url" id="url" v-model="url"></div> | ||||
<button @click="buyService" :disabled="paying">Submit<loading | <button @click="buyService" :disabled="paying">Submit<loading | ||||
v-if="paying"></loading></button> | v-if="paying"></loading></button> | ||||
<p id="overlay-error"></p> | |||||
<p id="overlay-error">{{errorText}}</p> | |||||
</div> | </div> | ||||
<div class="overlay-item" v-else-if="completed"> | <div class="overlay-item" v-else-if="completed"> | ||||
@@ -100,8 +105,9 @@ function select(service) { | |||||
if (this.amount > service.maximum){ | if (this.amount > service.maximum){ | ||||
this.amount = service.maximum; | this.amount = service.maximum; | ||||
} | } | ||||
this.selected = service | this.selected = service | ||||
document.getElementById('overlay-error').textContent = '' | |||||
this.errorText = '' | |||||
} | } | ||||
function cost() { | function cost() { | ||||
@@ -110,15 +116,13 @@ function cost() { | |||||
function buyService() { | function buyService() { | ||||
if (!this.url) { | if (!this.url) { | ||||
document.getElementById('overlay-error').textContent = "You must provide a URL." | |||||
this.errorText = "You must provide a URL." | |||||
return | return | ||||
} else if (Math.ceil(this.cost > this.credits)) { | } else if (Math.ceil(this.cost > this.credits)) { | ||||
document.getElementById('overlay-error').textContent = | |||||
'Insuficient Credits' | |||||
this.errorText = 'Insuficient Credits' | |||||
return | return | ||||
} else if (this.amount < this.selected.minimum || this.amount > this.selected.maximum) { | } else if (this.amount < this.selected.minimum || this.amount > this.selected.maximum) { | ||||
document.getElementById('overlay-error').textContent = | |||||
'Invalid amount' | |||||
this.errorText = 'Invalid amount' | |||||
return | return | ||||
} | } | ||||
@@ -142,16 +146,15 @@ function buyService() { | |||||
'quantity': this.amount, 'url': this.url, 'note': note}), }).then( | 'quantity': this.amount, 'url': this.url, 'note': note}), }).then( | ||||
response => { | response => { | ||||
if (response.ok) { | if (response.ok) { | ||||
document.getElementById('overlay-error').textContent = `Success!` | |||||
this.errorText = `Success!` | |||||
this.completed = true | this.completed = true | ||||
this.$emit('updateUser') | this.$emit('updateUser') | ||||
this.$emit('updateOrders') | this.$emit('updateOrders') | ||||
} else if (response.status == 520) { | } else if (response.status == 520) { | ||||
document.getElementById('overlay-error').textContent = | |||||
'Insuficient Credits' | |||||
this.errorText = 'Insuficient Credits' | |||||
} else { | } else { | ||||
document.getElementById('overlay-error').textContent = `Error | |||||
${response.status}: ${response.statusText}` | |||||
this.errorText = `Error ${response.status}: | |||||
${response.statusText}` | |||||
} | } | ||||
this.paying = false | this.paying = false | ||||
@@ -172,7 +175,7 @@ function page() { | |||||
export default { | export default { | ||||
data() { | data() { | ||||
return {servicePane: true, services: null, selected: null, amount: 0, | return {servicePane: true, services: null, selected: null, amount: 0, | ||||
paying: false, url: '', completed: false} | |||||
paying: false, url: '', completed: false, errorText: ''} | |||||
}, | }, | ||||
components: {ServicePane, Loading, Credits}, | components: {ServicePane, Loading, Credits}, | ||||
props: ['token', 'credits', 'active', 'preferred'], | props: ['token', 'credits', 'active', 'preferred'], | ||||
@@ -0,0 +1,28 @@ | |||||
<template> | |||||
<div id="main"> | |||||
<div v-once v-if="active == '#transaction-complete' && user.paying" class="status-dialog"> | |||||
<img class="icon" src="../../images/checked2.svg" alt=""/> | |||||
<h3>Purchase complete.</h3> | |||||
</div> | |||||
<div v-once v-if="active == '#transaction-failed' && user.paying" class="status-dialog"> | |||||
<img class="icon" src="../../images/warning-colored.svg" alt=""/> | |||||
<h3>Purchase failed.</h3> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
props: ['token', 'user', 'active'], | |||||
//Should check that user is actualling in the paying state. If so, send get | |||||
//request to panel/transaction-end. It's then() should emit a payment | |||||
//complete to panel for a user state refresh. | |||||
mounted() { | |||||
} | |||||
} | |||||
</script> |
@@ -1267,8 +1267,11 @@ div#card-errors { | |||||
} | } | ||||
.status-dialog { | .status-dialog { | ||||
position: relative; | |||||
top: 15%; | |||||
margin: auto; | margin: auto; | ||||
max-width: 20em; | max-width: 20em; | ||||
text-align: center; | |||||
img { | img { | ||||
margin: auto; | margin: auto; | ||||
@@ -193,8 +193,8 @@ | |||||
</p></div></div> | </p></div></div> | ||||
<div class="collapsible"><button>How can I pay?</button> | <div class="collapsible"><button>How can I pay?</button> | ||||
<div class="content"><p> | <div class="content"><p> | ||||
We accept Bitcoin, Litecoin, and other cryptocurrencies through our | |||||
Payeer and Perfect Money payment providers. | |||||
We accept Bitcoin, Litecoin, and other cryptocurrencies, as well as USD | |||||
through our Payeer and Perfect Money payment providers. | |||||
</p></div></div> | </p></div></div> | ||||
</div> | </div> | ||||