We Pay signup

This commit is contained in:
David Bomba 2021-05-05 14:29:58 +10:00
parent d55a5df148
commit 8c00bce71b
17 changed files with 683 additions and 163 deletions

View File

@ -34,7 +34,6 @@ class StripeConnectController extends BaseController
if(!is_array($request->getTokenContent()))
abort(400, 'Invalid token');
MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
$data = [

View File

@ -0,0 +1,62 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Libraries\MultiDB;
use App\Models\CompanyGateway;
use App\Models\User;
use App\PaymentDrivers\WePayPaymentDriver;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class WePayController extends BaseController
{
use MakesHash;
/**
* Initialize WePay Signup.
*/
public function signup(string $token)
{
// $hash = [
// 'user_id' => auth()->user()->id,
// 'company_key'=> auth()->user()->company()->company_key,
// 'context' => $request->input('context'),
// ];
$hash = Cache::get($token);
//temporarily comment this out
// if(!$hash)
// abort(400, 'Link expired');
// MultiDB::findAndSetDbByCompanyKey($hash['company_key']);
// $data['user_id'] = $this->encodePrimaryKey($hash['user_id']);
// $data['company_key'] = $hash['company_key'];
$data['user_id'] = 1;
$user = User::first();
$data['company_key'] = $user->account->companies()->first()->company_key;
$wepay_driver = new WePayPaymentDriver(new CompanyGateway, null, null);
return $wepay_driver->setup($data);
}
public function processSignup(Request $request)
{
}
}

View File

@ -0,0 +1,73 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Livewire;
use App\Models\Company;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class WepaySignup extends Component
{
public $user;
public $user_id;
public $company_key;
public $first_name;
public $last_name;
public $email;
public $terms;
public $privacy_policy;
public $saved;
protected $rules = [
'first_name' => ['sometimes'],
'last_name' => ['sometimes'],
'email' => ['required', 'email'],
];
public function mount()
{
$user = User::find($this->user_id);
$company = Company::where('company_key', $this->company_key)->first();
$this->fill([
'user' => $user,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'email' => $user->email,
'company_name' => $company->present()->name(),
'saved' => ctrans('texts.confirm'),
'terms' => '<a href="https://go.wepay.com/terms-of-service" target="_blank">'.ctrans('texts.terms_of_service').'</a>',
'privacy_policy' => '<a href="https://go.wepay.com/privacy-policy" target="_blank">'.ctrans('texts.privacy_policy').'</a>',
]);
}
public function render()
{
return render('gateways.wepay.signup.wepay-signup');
}
public function submit()
{
$data = $this->validate($this->rules);
// $this->user
// ->fill($data)
// ->save();
$this->saved = ctrans('texts.saved_at', ['time' => now()->toTimeString()]);
}
}

View File

@ -65,6 +65,7 @@ class SystemLog extends Model
const TYPE_CHECKOUT = 304;
const TYPE_AUTHORIZE = 305;
const TYPE_CUSTOM = 306;
const TYPE_WEPAY = 309;
const TYPE_QUOTA_EXCEEDED = 400;
const TYPE_UPSTREAM_FAILURE = 401;

View File

@ -47,164 +47,5 @@ class Account
]);
}
/*** If this is a new account (ie there is no account_id in company_gateways.config, the we need to create an account as below.
///
// $stripe = new \Stripe\StripeClient(
// 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
// );
// $stripe->accounts->create([
// 'type' => 'standard',
// 'country' => 'US', //if we have it - inject
// 'email' => 'jenny.rosen@example.com', //if we have it - inject
// ]);
///
//response
//******************* We should store the 'id' as a property in the config with the key `account_id`
/**
* {
"id": "acct_1032D82eZvKYlo2C",
"object": "account",
"business_profile": {
"mcc": null,
"name": "Stripe.com",
"product_description": null,
"support_address": null,
"support_email": null,
"support_phone": null,
"support_url": null,
"url": null
},
"capabilities": {
"card_payments": "active",
"transfers": "active"
},
"charges_enabled": false,
"country": "US",
"default_currency": "usd",
"details_submitted": false,
"email": "site@stripe.com",
"metadata": {},
"payouts_enabled": false,
"requirements": {
"current_deadline": null,
"currently_due": [
"business_profile.product_description",
"business_profile.support_phone",
"business_profile.url",
"external_account",
"tos_acceptance.date",
"tos_acceptance.ip"
],
"disabled_reason": "requirements.past_due",
"errors": [],
"eventually_due": [
"business_profile.product_description",
"business_profile.support_phone",
"business_profile.url",
"external_account",
"tos_acceptance.date",
"tos_acceptance.ip"
],
"past_due": [],
"pending_verification": []
},
"settings": {
"bacs_debit_payments": {},
"branding": {
"icon": null,
"logo": null,
"primary_color": null,
"secondary_color": null
},
"card_issuing": {
"tos_acceptance": {
"date": null,
"ip": null
}
},
"card_payments": {
"decline_on": {
"avs_failure": true,
"cvc_failure": false
},
"statement_descriptor_prefix": null
},
"dashboard": {
"display_name": "Stripe.com",
"timezone": "US/Pacific"
},
"payments": {
"statement_descriptor": null,
"statement_descriptor_kana": null,
"statement_descriptor_kanji": null
},
"payouts": {
"debit_negative_balances": true,
"schedule": {
"delay_days": 7,
"interval": "daily"
},
"statement_descriptor": null
},
"sepa_debit_payments": {}
},
"type": "standard"
}
*/
//At this stage we have an account, so we need to generate the account link
//then create the account link
// now we start the stripe onboarding flow
// https://stripe.com/docs/api/account_links/object
//
/**
* $stripe = new \Stripe\StripeClient(
'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
);
$stripe->accountLinks->create([
'account' => 'acct_1032D82eZvKYlo2C',
'refresh_url' => 'https://example.com/reauth',
'return_url' => 'https://example.com/return',
'type' => 'account_onboarding',
]);
*/
/**
* Response =
* {
"object": "account_link",
"created": 1618869558,
"expires_at": 1618869858,
"url": "https://connect.stripe.com/setup/s/9BhFaPdfseRF"
}
*/
//The users account may not be active yet, we need to pull the account back and check for the property `charges_enabled`
//
//
// What next?
//
// Now we need to create a superclass of the StripePaymentDriver, i believe the only thing we need to change is the way we initialize the gateway..
/**
*
\Stripe\Stripe::setApiKey("{{PLATFORM_SECRET_KEY}}"); <--- platform secret key = Invoice Ninja secret key
\Stripe\Customer::create(
["email" => "person@example.edu"],
["stripe_account" => "{{CONNECTED_STRIPE_ACCOUNT_ID}}"] <------ company_gateway.config.account_id
);
*/
}

View File

@ -0,0 +1,31 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\WePay;
use App\PaymentDrivers\WePayPaymentDriver;
class CreditCard
{
public $wepay;
public function __construct(WePayPaymentDriver $wepay)
{
$this->wepay = $wepay;
}
public function authorizeView($data)
{
}
}

View File

@ -0,0 +1,206 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\WePay;
use App\PaymentDrivers\WePayPaymentDriver;
use Illuminate\Http\Request;
class Setup
{
public $wepay;
public function __construct(WePayPaymentDriver $wepay)
{
$this->wepay = $wepay;
}
public function boot($data)
{
/*
'user_id',
'company_key',
*/
return render('gateways.wepay.signup.index', $data);
}
public function processSignup(Request $request)
{
}
}
/*
protected function setupWePay($accountGateway, &$response)
{
$user = Auth::user();
$account = $user->account;
$rules = [
'company_name' => 'required',
'tos_agree' => 'required',
'first_name' => 'required',
'last_name' => 'required',
'email' => 'required|email',
'country' => 'required|in:US,CA,GB',
];
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('gateways/create')
->withErrors($validator)
->withInput();
}
if (! $user->email) {
$user->email = trim(Input::get('email'));
$user->first_name = trim(Input::get('first_name'));
$user->last_name = trim(Input::get('last_name'));
$user->save();
}
try {
$wepay = Utils::setupWePay();
$userDetails = [
'client_id' => WEPAY_CLIENT_ID,
'client_secret' => WEPAY_CLIENT_SECRET,
'email' => Input::get('email'),
'first_name' => Input::get('first_name'),
'last_name' => Input::get('last_name'),
'original_ip' => \Request::getClientIp(true),
'original_device' => \Request::server('HTTP_USER_AGENT'),
'tos_acceptance_time' => time(),
'redirect_uri' => URL::to('gateways'),
'scope' => 'manage_accounts,collect_payments,view_user,preapprove_payments,send_money',
];
$wepayUser = $wepay->request('user/register/', $userDetails);
$accessToken = $wepayUser->access_token;
$accessTokenExpires = $wepayUser->expires_in ? (time() + $wepayUser->expires_in) : null;
$wepay = new WePay($accessToken);
$accountDetails = [
'name' => Input::get('company_name'),
'description' => trans('texts.wepay_account_description'),
'theme_object' => json_decode(WEPAY_THEME),
'callback_uri' => $accountGateway->getWebhookUrl(),
'rbits' => $account->present()->rBits,
'country' => Input::get('country'),
];
if (Input::get('country') == 'CA') {
$accountDetails['currencies'] = ['CAD'];
$accountDetails['country_options'] = ['debit_opt_in' => boolval(Input::get('debit_cards'))];
} elseif (Input::get('country') == 'GB') {
$accountDetails['currencies'] = ['GBP'];
}
$wepayAccount = $wepay->request('account/create/', $accountDetails);
try {
$wepay->request('user/send_confirmation/', []);
$confirmationRequired = true;
} catch (\WePayException $ex) {
if ($ex->getMessage() == 'This access_token is already approved.') {
$confirmationRequired = false;
} else {
throw $ex;
}
}
$accountGateway->gateway_id = GATEWAY_WEPAY;
$accountGateway->setConfig([
'userId' => $wepayUser->user_id,
'accessToken' => $accessToken,
'tokenType' => $wepayUser->token_type,
'tokenExpires' => $accessTokenExpires,
'accountId' => $wepayAccount->account_id,
'state' => $wepayAccount->state,
'testMode' => WEPAY_ENVIRONMENT == WEPAY_STAGE,
'country' => Input::get('country'),
]);
if ($confirmationRequired) {
Session::flash('message', trans('texts.created_wepay_confirmation_required'));
} else {
$updateUri = $wepay->request('/account/get_update_uri', [
'account_id' => $wepayAccount->account_id,
'redirect_uri' => URL::to('gateways'),
]);
$response = Redirect::to($updateUri->uri);
return true;
}
$response = Redirect::to("gateways/{$accountGateway->public_id}/edit");
return true;
} catch (\WePayException $e) {
Session::flash('error', $e->getMessage());
$response = Redirect::to('gateways/create')
->withInput();
return false;
}
}
*/
/*
rbits
private function createRBit($type, $source, $properties)
{
$data = new stdClass();
$data->receive_time = time();
$data->type = $type;
$data->source = $source;
$data->properties = new stdClass();
foreach ($properties as $key => $val) {
$data->properties->$key = $val;
}
return $data;
}
public function rBits()
{
$account = $this->entity;
$user = $account->users()->first();
$data = [];
$data[] = $this->createRBit('business_name', 'user', ['business_name' => $account->name]);
$data[] = $this->createRBit('industry_code', 'user', ['industry_detail' => $account->present()->industry]);
$data[] = $this->createRBit('comment', 'partner_database', ['comment_text' => 'Logo image not present']);
$data[] = $this->createRBit('business_description', 'user', ['business_description' => $account->present()->size]);
$data[] = $this->createRBit('person', 'user', ['name' => $user->getFullName()]);
$data[] = $this->createRBit('email', 'user', ['email' => $user->email]);
$data[] = $this->createRBit('phone', 'user', ['phone' => $user->phone]);
$data[] = $this->createRBit('website_uri', 'user', ['uri' => $account->website]);
$data[] = $this->createRBit('external_account', 'partner_database', ['is_partner_account' => 'yes', 'account_type' => 'Invoice Ninja', 'create_time' => time()]);
return $data;
}
*/

View File

@ -0,0 +1,110 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\SystemLog;
use App\PaymentDrivers\WePay\CreditCard;
use App\PaymentDrivers\WePay\Setup;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
use WePay;
class WePayPaymentDriver extends BaseDriver
{
use MakesHash;
public $refundable = true; //does this gateway support refunds?
public $token_billing = true; //does this gateway support token billing?
public $can_authorise_credit_card = true; //does this gateway support authorizations?
public $wepay; //initialized gateway
public $payment_method; //initialized payment method
public static $methods = [
GatewayType::CREDIT_CARD => CreditCard::class, //maps GatewayType => Implementation class
];
const SYSTEM_LOG_TYPE = SystemLog::TYPE_WEPAY;
public function init()
{
if (WePay::getEnvironment() == 'none') {
if(config('ninja.wepay.environment') == 'staging')
WePay::useStaing(config('ninja.wepay.client_id'), config('ninja.wepay.client_secret'));
else
WePay::useProduction(config('ninja.wepay.client_id'), config('ninja.wepay.client_secret'));
}
if ($this->company_gateway)
$this->wepay = new WePay($this->company_gateway->getConfig()->accessToken);
$this->wepay = new WePay(null);
}
public function setup(array $data)
{
return (new Setup($this))->boot($data);
}
public function processSetup(Request $request)
{
return (new Setup($this))->processSignup($request);
}
public function setPaymentMethod($payment_method_id)
{
$class = self::$methods[$payment_method_id];
$this->payment_method = new $class($this);
return $this;
}
public function authorizeView(array $data)
{
return $this->payment_method->authorizeView($data); //this is your custom implementation from here
}
public function authorizeResponse($request)
{
return $this->payment_method->authorizeResponse($request); //this is your custom implementation from here
}
public function processPaymentView(array $data)
{
return $this->payment_method->paymentView($data); //this is your custom implementation from here
}
public function processPaymentResponse($request)
{
return $this->payment_method->paymentResponse($request); //this is your custom implementation from here
}
public function refund(Payment $payment, $amount, $return_client_response = false)
{
return $this->payment_method->yourRefundImplementationHere(); //this is your custom implementation from here
}
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
{
return $this->payment_method->yourTokenBillingImplmentation(); //this is your custom implementation from here
}
}

View File

@ -70,6 +70,7 @@
"turbo124/beacon": "^1.0",
"turbo124/laravel-gmail": "^5",
"webpatser/laravel-countries": "dev-master#75992ad",
"wepay/php-sdk": "^0.3",
"wildbit/swiftmailer-postmark": "^3.3"
},
"require-dev": {
@ -136,4 +137,4 @@
},
"minimum-stability": "dev",
"prefer-stable": true
}
}

53
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "38a79899673526624db4d62a76dd9a5e",
"content-hash": "8ebeefd50035c907152aebf54d1dbd21",
"packages": [
{
"name": "asm/php-ansible",
@ -10155,6 +10155,57 @@
},
"time": "2019-07-12T14:06:05+00:00"
},
{
"name": "wepay/php-sdk",
"version": "0.3.1",
"source": {
"type": "git",
"url": "https://github.com/wepay/PHP-SDK.git",
"reference": "2a89ceb2954d117d082f869d3bfcb7864e6c2a7d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wepay/PHP-SDK/zipball/2a89ceb2954d117d082f869d3bfcb7864e6c2a7d",
"reference": "2a89ceb2954d117d082f869d3bfcb7864e6c2a7d",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.2.x-dev"
}
},
"autoload": {
"classmap": [
"wepay.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "WePay",
"email": "api@wepay.com"
}
],
"description": "WePay APIv2 SDK for PHP",
"keywords": [
"payment",
"sdk",
"wepay"
],
"support": {
"issues": "https://github.com/wepay/PHP-SDK/issues",
"source": "https://github.com/wepay/PHP-SDK/tree/master"
},
"time": "2017-01-21T07:03:26+00:00"
},
{
"name": "wildbit/swiftmailer-postmark",
"version": "3.3.0",

View File

@ -148,4 +148,9 @@ return [
'disable_auto_update' => env('DISABLE_AUTO_UPDATE', false),
'invoiceninja_hosted_pdf_generation' => env('NINJA_HOSTED_PDF', false),
'ninja_stripe_key' => env('NINJA_STRIPE_KEY', null),
'wepay' => [
'environment' => env('WEPAY_ENVIRONMENT', 'staging'),
'client_id' => env('WEPAY_CLIENT_ID', ''),
'client_secret' => env('WEPAY_CLIENT_SECRET',''),
]
];

View File

@ -0,0 +1,30 @@
<?php
use App\Models\Gateway;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ActivateWePay extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if(Gateway::count() >=1)
Gateway::whereIn('id', [49])->update(['visible' => true]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -95,7 +95,7 @@ class PaymentLibrariesSeeder extends Seeder
Gateway::query()->update(['visible' => 0]);
Gateway::whereIn('id', [1,15,20,39,55])->update(['visible' => 1]);
Gateway::whereIn('id', [1,15,20,39,55,49])->update(['visible' => 1]);
Gateway::all()->each(function ($gateway) {
$gateway->site_url = $gateway->getHelp();

View File

@ -0,0 +1,14 @@
@extends('portal.ninja2020.layout.clean')
@section('meta_title', ctrans('texts.sign_up_with_wepay'))
@section('body')
@livewire('wepay-signup', ['user_id' => $user_id, 'company_key' => $company_key])
@endsection
@push('footer')
<script>
</script>
@endpush

View File

@ -0,0 +1,93 @@
<div class="flex flex-col justify-center items-center mt-10">
<form wire:submit.prevent="submit">
@csrf
@method('POST')
<div class="shadow overflow-hidden rounded">
<div class="px-4 py-5 bg-white sm:p-6">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<label for="first_name" class="input-label">@lang('texts.first_name')</label>
<input id="first_name" class="input w-full" name="first_name" wire:model.defer="first_name" />
@error('first_name')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-3">
<label for="last_name" class="input-label">@lang('texts.last_name')</label>
<input id="last_name" class="input w-full" name="last_name" wire:model.defer="last_name" />
@error('last_name')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-4">
<label for="email_address" class="input-label">@lang('texts.email_address')</label>
<input id="email_address" class="input w-full" type="email" name="email" wire:model.defer="email" disabled="true" />
@error('email')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-4">
<label for="company_name" class="input-label">@lang('texts.company_name')</label>
<input id="company_name" class="input w-full" name="company_name" wire:model.defer="company_name" />
@error('company_name')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-4">
<label for="country" class="input-label">@lang('texts.country')</label>
<div class="radio">
<input class="form-radio mr-2" type="radio" value="US" name="country" checked>
<span>{{ ctrans('texts.country_United States') }}</span>
</div>
<div class="radio">
<input class="form-radio mr-2" type="radio" value="CA" name="country">
<span>{{ ctrans('texts.country_Canada') }}</span>
</div>
<div class="radio">
<input class="form-radio mr-2" type="radio" value="GB" name="country">
<span>{{ ctrans('texts.country_United Kingdom') }}</span>
</div>
</div>
<div class="col-span-6 sm:col-span-4">
<label for="country" class="input-label">@lang('texts.ach')</label>
<div class="checkbox">
<input class="switch-input" type="checkbox" name="ach">
<span>{{ ctrans('texts.enable_ach')}}</span>
</div>
</div>
<div class="col-span-6 sm:col-span-4">
<label for="country" class="input-label"></label>
<div class="checkbox">
<input class="switch-input" type="checkbox" name="wepay_payment_tos_agree">
<span>{!! ctrans('texts.wepay_payment_tos_agree', ['terms' => $terms, 'privacy_policy' => $privacy_policy]) !!}</span>
</div>
</div>
</div>
</div>
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
<button class="button button-primary bg-primary">{{ $saved }}</button>
</div>
</div>
</form>
</div>

View File

@ -199,6 +199,8 @@ Route::get('webcron', 'WebCronController@index');
Route::group(['middleware' => ['locale']], function () {
Route::get('stripe_connect/{token}', 'StripeConnectController@initialize')->name('stripe_connect.initialization');
Route::get('stripe_connect/completed', 'StripeConnectController@completed')->name('stripe_connect.return');
Route::get('wepay/signup/{token}', 'WePayController@signup')->name('wepay.signup');
Route::post('wepay/processSignup', 'WePayController@processSignup')->name('wepay.process_signup');
});
Route::fallback('BaseController@notFound');

View File

@ -79,6 +79,7 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence
Route::post('upload', 'ClientPortal\UploadController')->name('upload.store');
Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout');
});
Route::get('client/subscription/{subscription}/purchase/', 'ClientPortal\SubscriptionPurchaseController@index')->name('client.subscription.purchase');