Commit de39c72f authored by Mark Harding's avatar Mark Harding

(refactor): Add new Stripe Managers and refactor Wire to be abstracted from Paywall

1 merge request!254WIP: &37 - Multi-currency
Pipeline #71063620 failed with stages
in 4 minutes and 29 seconds
<?php
/**
* Converts a static class to use instances
*/
namespace Minds\Common;
class StaticToInstance
{
/** @var $class */
private $class;
public function __construct($class)
{
$this->setClass($class);
}
/**
* Set the class in question
* @return StripeStaticToOO
*/
public function setClass($class)
{
$this->class = new \ReflectionClass($class);
return clone $this;
}
/**
* Call the static functions as OO style
* @param string $method
* @param array $arguments
* @return midex
*/
public function __call($method, $arguments)
{
$instance = $this->class->newInstanceWithoutConstructor();
return $instance::$method($arguments);
}
}
\ No newline at end of file
......@@ -299,14 +299,6 @@ class channel implements Interfaces\Api
$channel->enabled = 'no';
$channel->save();
$customer = (new Core\Payments\Customer())
->setUser($channel);
$stripe = Core\Di\Di::_()->get('StripePayments');
$customer = $stripe->getCustomer($customer);
if ($customer) {
$stripe->deleteCustomer($customer);
}
(new Core\Data\Sessions())->destroyAll($channel->guid);
}
......
<?php
/**
* Minds Payments API:: braintree
*
* @version 1
* @author Mark Harding
*/
namespace Minds\Controllers\api\v1\payments;
use Minds\Core;
use Minds\Helpers;
use Minds\Interfaces;
use Minds\Api\Factory;
use Minds\Core\Payments;
class braintree implements Interfaces\Api
{
/**
* Returns merchant information
* @param array $pages
*
* API:: /v1/merchant/:slug
*/
public function get($pages)
{
$response = array();
switch ($pages[0]) {
case "token":
$gateway = isset($pages[1]) ? $pages[1] : 'default';
$response['token'] = Payments\Factory::build('braintree', ['gateway'=>$gateway])->getToken();
break;
}
return Factory::response($response);
}
public function post($pages)
{
$response = array();
switch ($pages[0]) {
case "charge":
$amount = $_POST['amount'];
$fee = $amount * 0.05 + 0.30; //5% + $.30
if (!isset($_POST['merchant'])) {
$merchant = Core\Session::getLoggedInUser();
}
$sale = (new Payments\Sale())
->setAmount($amount)
->setMerchant($merchant)
->setFee($fee)
->setCustomerId(Core\Session::getLoggedInUser()->guid)
->setNonce($_POST['nonce']);
try {
$result = Payments\Factory::build('braintree', ['gateway'=>'merchants'])->setSale($sale);
} catch (\Exception $e) {
$response['status'] = "error";
$response['message'] = $e->getMessage();
}
break;
case "charge-master":
$amount = $_POST['amount'];
$sale = (new Payments\Sale())
->setAmount($amount)
->setCustomerId(Core\Session::getLoggedInUser()->guid)
->setSettle(true)
->setFee(0)
->setNonce($_POST['nonce']);
try {
$result = Payments\Factory::build('braintree', ['gateway'=>'merchants'])->setSale($sale);
} catch (\Exception $e) {
$response['status'] = "error";
$response['message'] = $e->getMessage();
}
break;
}
return Factory::response($response);
}
public function put($pages)
{
return Factory::response(array());
}
public function delete($pages)
{
return Factory::response(array());
}
}
<?php
/**
* Minds Payments Plans
*
* @version 1
* @author Mark Harding
*/
namespace Minds\Controllers\api\v1\payments;
use Minds\Api\Factory;
use Minds\Core;
use Minds\Entities;
use Minds\Core\Entities as CoreEntities;
use Minds\Core\Payments;
use Minds\Interfaces;
class plans implements Interfaces\Api
{
/**
* Returns plan information or whether a plan exists
* @param array $pages
*
* API:: /v1/playments/plans/:slug
*/
public function get($pages)
{
$response = [];
switch ($pages[0]) {
case "payment-methods":
//return if the customer has any payment methods
$customer = (new Payments\Customer())
->setUser(Core\Session::getLoggedInUser());
$response['payment_methods'] = $customer->getPaymentMethods();
break;
case "exclusive":
$stripe = Core\Di\Di::_()->get('StripePayments');
$entity = Entities\Factory::build($pages[1]);
if (!$entity) {
return Factory::response([
'status' => 'error',
'message' => 'Post not found'
]);
}
$owner = $entity->getOwnerEntity();
$repo = new Payments\Plans\Repository();
$plan = $repo->setEntityGuid($entity->owner_guid)
->setUserGuid(Core\Session::getLoggedInUser()->guid)
->getSubscription('exclusive');
$isPlus = Core\Session::getLoggedInUser()->isPlus() && $entity->owner_guid == '730071191229833224';
if ($plan->getStatus() == 'active' || Core\Session::isAdmin() || $isPlus) {
$response['subscribed'] = true;
$entity->paywall = false;
$response['entity'] = $entity->export();
} else {
$owner = new Entities\User($entity->owner_guid, false);
$response['subscribed'] = false;
$plan = $stripe->getPlan("exclusive", $owner->getMerchant()['id']);
if ($plan) {
$response['amount'] = $plan->amount / 100;
} else {
$response = [
'status' => 'error',
'message' => "We couldn't find the plan"
];
}
}
break;
}
return Factory::response($response);
}
public function post($pages)
{
$response = [];
$stripe = Core\Di\Di::_()->get('StripePayments');
$lu = Core\Di\Di::_()->get('Database\Cassandra\Lookup');
switch ($pages[0]) {
case "subscribe":
$entity = Entities\Factory::build($pages[1]);
if (!$entity) {
return Factory::response([
'status' => 'error',
'message' => 'Entity not found'
]);
}
$customer = (new Payments\Customer())
->setUser(Core\Session::getLoggedInUser());
if (!$stripe->getCustomer($customer) || !$customer->getId()) {
//create the customer on stripe
$customer->setPaymentToken($_POST['nonce']);
$customer = $stripe->createCustomer($customer);
}
try {
$subscription = (new Payments\Subscriptions\Subscription())
->setCustomer($customer)
->setMerchant($entity)
->setPlanId($pages[2]);
$subscription_id = $stripe->createSubscription($subscription);
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
'message' => $e->getMessage()
]);
}
$plan = (new Payments\Plans\Plan)
->setName($pages[2])
->setEntityGuid($pages[1])
->setUserGuid(Core\Session::getLoggedInUser()->guid)
->setSubscriptionId($subscription_id)
->setStatus('active')
->setExpires(-1); //indefinite
$repo = new Payments\Plans\Repository();
$repo->add($plan);
$response['subscribed'] = true;
$entity->paywall = false;
$response['entity'] = $entity->export();
break;
}
return Factory::response($response);
}
public function put($pages)
{
return Factory::response([]);
}
public function delete($pages)
{
$response = [];
$stripe = Core\Di\Di::_()->get('StripePayments');
switch ($pages[0]) {
case 'exclusive':
$user = new Entities\User($pages[1]);
if (!$user || !$user->guid) {
return Factory::response([
'status' => 'error',
'message' => 'User not found'
]);
}
$merchant = (new Payments\Merchant())
->setId($user->getMerchant()['id']);
$repo = new Payments\Plans\Repository();
$repo
->setEntityGuid($user->guid)
->setUserGuid(Core\Session::getLoggedInUser()->guid);
try {
$plan = $repo->getSubscription('exclusive');
$subscription = (new Payments\Subscriptions\Subscription())
->setMerchant($merchant)
->setId($plan->getSubscriptionId());
$stripe->cancelSubscription($subscription);
$repo->cancel('exclusive');
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
'message' => $e->getMessage()
]);
}
break;
case 'wire':
$entity = CoreEntities::get([ 'guids' => [$pages[1]]])[0];
$user_guid = $entity->type == 'user' ? $entity->guid : $entity->ownerObj['guid'];
if (!$user_guid) {
return Factory::response([
'status' => 'error',
'message' => 'User not found'
]);
}
/** @var Core\Wire\Manager $manager */
$manager = Core\Di\Di::_()->get('Wire\Manager');
$wires = $manager->get(['type' => 'sent', 'user_guid' => $user_guid, 'order' => 'DESC']);
$wire = null;
foreach($wires as $w) {
if($w->isActive() && $w->isRecurring() && $w->getMethod('usd')){
$wire = $w;
}
}
if(!$wire) {
return Factory::response([
'status' => 'error',
'message' => 'Wire not found'
]);
}
$merchant = (new Payments\Merchant())
->setId($wire->getFrom()->getMerchant()['id']);
$repo = new Payments\Plans\Repository();
$repo->setEntityGuid($wire->getEntity()->getGuid())
->setUserGuid($wire->getFrom()->guid);
try {
$plan = $repo->getSubscription('wire');
$subscription = (new Payments\Subscriptions\Subscription())
->setMerchant($merchant)
->setId($plan->getSubscriptionId());
$stripe->cancelSubscription($subscription);
$repo->cancel('wire');
$wire->setActive(0)->save();
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
'message' => $e->getMessage()
]);
}
break;
}
return Factory::response($response);
}
}
<?php
/**
* USD Wallet Controller
*
* @version 1
* @author Mark Harding
*/
namespace Minds\Controllers\api\v2\wallet;
use Minds\Core;
use Minds\Helpers;
use Minds\Interfaces;
use Minds\Api\Factory;
use Minds\Core\Payments;
use Minds\Entities;
class usd implements Interfaces\Api
{
/**
* Returns merchant information
* @param array $pages
*
* API:: /v1/merchant/:slug
*/
public function get($pages)
{
Factory::isLoggedIn();
$response = [];
switch ($pages[0]) {
case "status":
$merchants = Core\Di\Di::_()->get('Monetization\Merchants');
$merchants->setUser(Core\Sandbox::user(Core\Session::getLoggedInUser(), 'merchant'));
$isMerchant = (bool) $merchants->getId();
$canBecomeMerchant = !$merchants->isBanned();
return Factory::response([
'isMerchant' => $isMerchant,
'canBecomeMerchant' => $canBecomeMerchant,
]);
break;
case "settings":
$stripeConnectManager = Core\Di\Di::_()->get('Stripe\Connect\Manager');
try {
$account = $stripeConnectManager->getByUser(Core\Session::getLoggedInUser());
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
'message' => $e->getMessage()
]);
}
if (!$account) {
return Factory::response([
'status' => 'error',
'message' => 'Not a merchant account'
]);
}
$response['merchant'] = $merchant->export();
break;
}
return Factory::response($response);
}
public function post($pages)
{
Factory::isLoggedIn();
$response = array();
switch ($pages[0]) {
case "onboard":
$account = (new Payments\Stripe\Connect\Account())
->setUserGuid(Core\Session::getLoggedInUser()->guid)
->setUser(Core\Session::getLoggedInUser())
->setDestination('bank')
->setCountry($_POST['country'])
->setSSN($_POST['ssn'] ? str_pad((string) $_POST['ssn'], 4, '0', STR_PAD_LEFT) : '')
->setPersonalIdNumber($_POST['personalIdNumber'])
->setFirstName($_POST['firstName'])
->setLastName($_POST['lastName'])
->setGender($_POST['gender'])
->setDateOfBirth($_POST['dob'])
->setStreet($_POST['street'])
->setCity($_POST['city'])
->setState($_POST['state'])
->setPostCode($_POST['postCode'])
->setPhoneNumber($_POST['phoneNumber'])
->setIp($_SERVER['HTTP_X_FORWARDED_FOR']);
try {
$stripeConnectManager = Core\Di\Di::_()->get('Stripe\Connect\Manager');
$id = $stripeConnectManager->add($account);
} catch (\Exception $e) {
$response['status'] = "error";
$response['message'] = $e->getMessage();
}
break;
case "verification":
try {
$stripe = Core\Di\Di::_()->get('StripePayments');
$stripe->verifyMerchant(Core\Session::getLoggedInUser()->getMerchant()['id'], $_FILES['file']);
} catch (\Exception $e) {
$response['status'] = "error";
$response['message'] = $e->getMessage();
}
break;
case "update":
$account = (new Payments\Stripe\Connect\Account())
->setId(Core\Session::getLoggedInUser()->getMerchant()['id'])
->setFirstName($_POST['firstName'])
->setLastName($_POST['lastName'])
->setGender($_POST['gender'])
->setDateOfBirth($_POST['dob'])
->setStreet($_POST['street'])
->setCity($_POST['city'])
->setState($_POST['state'])
->setPostCode($_POST['postCode'])
->setPhoneNumber($_POST['phoneNumber']);
if ($_POST['ssn']) {
$account->setSSN($_POST['ssn'] ? str_pad((string) $_POST['ssn'], 4, '0', STR_PAD_LEFT) : '');
}
if ($_POST['personalIdNumber']) {
$account->setPersonalIdNumber($_POST['personalIdNumber']);
}
if ($_POST['accountNumber']) {
$account->setAccountNumber($_POST['accountNumber']);
}
if ($_POST['routingNumber']) {
$account->setRoutingNumber($_POST['routingNumber']);
}
try {
$stripeConnectManager = Core\Di\Di::_()->get('Stripe\Connect\Manager');
$result = $stripeConnectManager->update($account);
} catch (\Exception $e) {
$response['status'] = "error";
$response['message'] = $e->getMessage();
}
break;
}
return Factory::response($response);
}
public function put($pages)
{
return Factory::response(array());
}
public function delete($pages)
{
return Factory::response(array());
}
}
......@@ -43,6 +43,13 @@ class Minds extends base
$modules = [];
foreach ($this->modules as $module) {
$modules[] = new $module();
// Submodules han be registered with the ->submodules[] property
if (property_exists($module, 'submodules')) {
foreach ($modules->submodules as $submodule) {
$modules[] = $submodule;
}
}
}
/*
......
This diff is collapsed.
<?php
/**
* Braintree webhooks
*/
namespace Minds\Core\Payments\Braintree;
use Minds\Core;
use Minds\Core\Guid;
use Minds\Core\Payments;
use Minds\Entities;
use Minds\Helpers\Wallet as WalletHelper;
use Minds\Core\Di\Di;
use Minds\Core\Blockchain\Transactions\Transaction;
use Braintree_ClientToken;
use Braintree_Configuration;
use Braintree_MerchantAccount;
use Braintree_Transaction;
use Braintree_TransactionSearch;
use Braintree_Customer;
use Braintree_CustomerSearch;
use Braintree_PaymentMethod;
use Braintree_Subscription;
use Braintree_Test_MerchantAccount;
use Braintree_WebhookNotification;
use Minds\Core\Payments\Subscriptions\Subscription;
class Webhooks
{
protected $payload;
protected $signature;
protected $notification;
protected $aliases = [
Braintree_WebhookNotification::SUB_MERCHANT_ACCOUNT_APPROVED => 'subMerchantApproved',
Braintree_WebhookNotification::SUB_MERCHANT_ACCOUNT_DECLINED => 'subMerchantDeclined',
'subscription_charged_successfully' => 'subscriptionCharged',
'subscription_went_active' => 'subscriptionActive',
Braintree_WebhookNotification::SUBSCRIPTION_EXPIRED => 'subscriptionExpired',
Braintree_WebhookNotification::SUBSCRIPTION_WENT_PAST_DUE => 'subscriptionOverdue',
Braintree_WebhookNotification::SUBSCRIPTION_CANCELED => 'subscriptionCanceled',
'check' => 'check'
];
protected $hooks;
public function __construct($hooks = null, $braintree = null)
{
$this->hooks = $hooks ?: new Payments\Hooks();
$this->braintree = $braintree;
}
/**
* Set the request payload
* @param string $payload
* @return $this
*/
public function setPayload($payload)
{
$this->payload = $payload;
return $this;
}
/**
* Set the request signature
* @param string $signature
* @return $this
*/
public function setSignature($signature)
{
$this->signature = $signature;
return $this;
}
/**
* Run the notification hook
* @return $this
*/
public function run()
{
$this->buildNotification();
$this->routeAlias();
return $this;
}
protected function buildNotification()
{
$this->notification = Braintree_WebhookNotification::parse($this->signature, $this->payload);
}
protected function routeAlias()
{
if (method_exists($this, $this->aliases[$this->notification->kind])) {
$method = $this->aliases[$this->notification->kind];
$this->$method();
}
}
/**
* @return void
*/
protected function subMerchantApproved()
{
$message = "Congrats, you are now a Minds Merchant";
Core\Events\Dispatcher::trigger('notification', 'elgg/hook/activity', [
'to'=>[$notification->merchantAccount->id],
'from' => 100000000000000519,
'notification_view' => 'custom_message',
'params' => ['message'=>$message],
'message'=>$message
]);
}
/**
* @return void
*/
protected function subMerchantDeclined()
{
$reason = $notification->message;
$message = "Sorry, we could not approve your Merchant Account: $reason";
Core\Events\Dispatcher::trigger('notification', 'elgg/hook/activity', [
'to'=>[$notification->merchantAccount->id],
'from' => 100000000000000519,
'notification_view' => 'custom_message',
'params' => ['message'=>$message],
'message'=>$message
]);
}
/**
* @return void
*/
protected function subscriptionCharged()
{
$subscription = (new Subscription())
->setId($this->notification->subscription->id)
->setBalance($this->notification->subscription->balance)
->setPrice($this->notification->subscription->price);
$db = new Core\Data\Call('user_index_to_guid');
//find the customer
$user_guids = $db->getRow("subscription:" . $subscription->getId());
$user = Entities\Factory::build($user_guids[0]);
//WalletHelper::createTransaction($user->guid, ($subscription->getPrice() * 1000) * 1.1, null, "Purchase (Recurring)");
//$this->hooks->onCharged($subscription);
$transaction = new Transaction();
$transaction
->setUserGuid($user->guid)
->setWalletAddress('offchain')
->setTimestamp(time())
->setTx('cc:bt-' . Guid::build())
->setAmount(($subscription->getPrice()) * 1.1 * 10 ** 18)
->setContract('offchain:points')
->setCompleted(true);
Di::_()->get('Blockchain\Transactions\Repository')
->add($transaction);
}
/**
* @return void
*/
protected function subscriptionActive()
{
$subscription = (new Subscription())
->setId($this->notification->subscription->id)
->setBalance($this->notification->subscription->balance)
->setPrice($this->notification->subscription->price);
$this->hooks->onActive($subscription);
}
/**
* @return void
*/
protected function subscriptionExpired()
{
$subscription = (new Subscription())
->setId($this->notification->subscription->id);
$this->hooks->onExpired($subscription);
}
/**
* @return void
*/
protected function subscriprionOverdue()
{
$subscription = (new Subscription())
->setId($this->notification->subscription->id);
$this->hooks->onOverdue($subscription);
}
/**
* @return void
*/
protected function subscriptionCanceled()
{
$subscription = (new Subscription())
->setId($this->notification->subscription->id);
$this->hooks->onCanceled($subscription);
}
/**
* @return void
*/
protected function check()
{
error_log("[webook]:: check is OK!");
}
}
<?php
/**
* Stripe Checkout Session
*/
namespace Minds\Core\Payments\Stripe\Checkout;
use Minds\Traits\MagicAttributes;
class CheckoutSession
{
use MagicAttributes;
/** @var string $userGuid */
private $sessionId;
/**
* Expose to the public apis
* @param array $extend
* @return array
*/
public function export($extend = [])
{
return [
'sessionId' => (string) $this->sessionId,
];
}
}
<?php
namespace Minds\Core\Payments\Stripe\Checkout;
use Stripe\Checkout\Session;
class Manager
{
/**
* Get a checkout session id
* @param Order $order
* @return CheckoutSession
*/
public function checkout(Order $order): CheckoutSession
{
$lineItems = [];
$lineItems[] = [
'name' => $this->getName(),
'amount' => $this->getAmount(),
'currency' => $this->getCurrency(),
'quantity' => $this->getQuantity(),
];
$session = Session::create([
'payment_method_types' => [
'card',
],
'line_items' => $lineItems,
'payment_intent_data' => [
'application_fee_amount' => $order->getServiceFee(),
'transfer_data' => [
'destination' => $order->getStripeAccountId(),
],
],
'success_url' => 'https://example.com/success',
'cancel_url' => 'https://example.com/cancel',
]);
return $session;
}
}
\ No newline at end of file
<?php
/**
* Stripe Order
*/
namespace Minds\Core\Payments\Stripe\Checkout;
use Minds\Traits\MagicAttributes;
class Order
{
use MagicAttributes;
/** @var string $name */
private $name;
/** @var int $amount */
private $amount = 0;
/** @var int $quantity */
private $quantity = 1;
/** @var string $currency */
private $currency = 'usd';
/** @var int $serviceFeePct */
private $serviceFeePct = 0;
/** @var string $customerId */
private $customerId;
/** @var string $stripeAccountId */
private $stripeAccountId;
/**
* Return the service
* @return int
*/
public function getServiceFee(): int
{
return $this->amount * ($this->serviceFeePct / 100);
}
/**
* Expose to the public apis
* @param array $extend
* @return array
*/
public function export($extend = [])
{
return [
'name' => (string) $this->name,
'amount' => $this->getAmount(),
'quantity' => $this->getQuantity(),
'currency' => $this->getCurrency(),
'service_fee_pct' => $this->getServiceFeePct(),
'service_fee' => $this->getServiceFee(),
'stripe_account_id' => $this->getStripeAccount(),
];
}
}
<?php
/**
* Stripe Connect Account
*/
namespace Minds\Core\Payments\Stripe\Connect;
use Minds\Entities\User;
use Minds\Traits\MagicAttributes;
/**
* @method Account getDateOfBirth(): string
* @method Account getCountry(): string
* @method Account getFirstName(): string
* @method Account getLastName(): string
* @method Account getCity(): string
* @method Account getStreet(): string
* @method Account getPostCode(): string
* @method Account getState(): string
* @method Account getDateOfBirth(): string
* @method Account getIp(): string
* @method Account getGender(): string
* @method Account getPhoneNumber(): string
* @method Account getSSN(): string
* @method Account getPersonalIdNumber(): string
* @method Account getUser(): User
*/
class Account
{
use MagicAttributes;
/** @var string $id */
private $id;
/** @var string $userGuid */
private $userGuid;
/** @var string $gener */
private $gender;
/** @var string $firstName */
private $firstName;
/** @var string $lastName */
private $lastName;
/** @var string $email */
private $email;
/** @var string $dob */
private $dob;
/** @var string $ssn */
private $ssn;
/** @var string $personalIdNumber */
private $personalIdNumber;
/** @var string $street */
private $street;
/** @var string $city */
private $city;
/** @var string $region */
private $region;
/** @var string $state */
private $state;
/** @var string $country */
private $country;
/** @var string $postCode */
private $postCode;
/** @var string $phoneNumber */
private $phoneNumber;
/** @var string $bankAccount */
private $bankAccount;
/** @var int $accountNumber */
private $accountNumber;
/** @var int $routingNumber */
private $routingNumber;
/** @var string $destination */
private $destination;
/** @var boolean $verified */
private $verified = false;
/** @var string $status */
private $status = "processing";
/** @var array $exportable */
private $exportable = [
'guid',
'gender',
'firstName',
'lastName',
'email',
'dob',
'ssn',
'personalIdNumber',
'street',
'city',
'region',
'state',
'country',
'postCode',
'phoneNumber',
'accountNumber',
'routingNumber',
'destination',
'status',
'verified',
];
/**
* Conditions on setting $destination
* @param string $destination
* @return $this
*/
public function setDestination($destination)
{
if (!in_array($destination, array('bank', 'email'))) {
throw new \Exception("$destination is not a valid payout method");
}
$this->destination = $destination;
return $this;
}
/**
* Expose to public API
* @return array
*/
public function export()
{
$export = [];
foreach ($this->exportable as $field) {
$export[$field] = $this->$field;
}
return $export;
}
}
<?php
namespace Minds\Core\Payments\Stripe\Connect\Delegates;
use Minds\Core\Payments\Stripe\Connect\Account;
use Minds\Core\Di\Di;
class NotificationDelegate
{
public function __construct($eventsDispatcher = null)
{
$this->eventsDispatcher = $eventsDispatcher ?: Di::_()->get('EventsDispatcher');
}
/**
* @param Account $account
* @return void
*/
public function onAccepted(Account $account)
{
$this->eventsDispatcher->trigger('notification', 'program', [
'to'=> [ $account->getUserGuid() ],
'from' => 100000000000000519,
'notification_view' => 'program_accepted',
'params' => [ 'program' => 'monetization' ],
]);
}
}
<?php
namespace Minds\Core\Payments\Stripe\Connect;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\Payments\Stripe\Connect\Delegates\NotificationDelegate;
use Minds\Core\Payments\Stripe\Currencies;
use Minds\Core\Payments\Stripe\Instances\AccountInstance;
use Minds\Entities\User;
class Manager
{
/** @var Save $save */
private $save;
/** @var NotificationDelegate */
private $notificationDelegate;
/** @var AccountInstance $accountInstance */
private $accountInstance;
public function __construct(
Save $save = null,
NotificationDelegate $notificationDelegate = null,
AccountInstance $accountInstance
)
{
$this->save = $save ?: new Save();
$this->notificationDelegate = $notificationDelegate ?: new NotificationDelegate();
$this->accountInstance = $accountInstance ?: new AccountInstance();
}
/**
* Add a conenct account to stripe
* @param Account $account
* @return string
*/
public function add(Account $account): string
{
$dob = explode('-', $account->getDateOfBirth());
$data = [
'managed' => true,
'country' => $account->getCountry(),
'legal_entity' => [
'type' => 'individual',
'first_name' => $account->getFirstName(),
'last_name' => $account->getLastName(),
'address' => [
'city' => $account->getCity(),
'line1' => $account->getStreet(),
'postal_code' => $account->getPostCode(),
'state' => $account->getState(),
],
'dob' => [
'day' => $dob[2],
'month' => $dob[1],
'year' => $dob[0]
],
],
'tos_acceptance' => [
'date' => time(),
'ip' => $account->getIp(),
]
];
// Required for JP only
if ($account->getGender()) {
$data['legal_entity']['gender'] = $account->getGender();
}
if ($account->getPhoneNumber()) {
$data['legal_entity']['phone_number'] = $account->getPhoneNumber();
}
// US 1099 requires SSN
if ($account->getSSN()) {
$data['legal_entity']['ssn_last_4'] = $account->getSSN();
}
if ($account->getPersonalIdNumber()) {
$data['legal_entity']['personal_id_number'] = $account->getPersonalIdNumber();
}
$result = $this->accountInstance->create($data);
if (!$result->id) {
throw new \Exception($result->message);
}
$id = $result->id;
// Save reference directly to user entity
$user = $account->getUser();
$user->setMerchant([
'service' => 'stripe',
'id' => $id
]);
$this->save->setEntity($user)
->save();
// Send a notification
$this->notificationDelegate->onAccepted($account);
return $id;
}
/**
* Updates a stripe connect account
* @param $account
* @return string Account id
* @throws \Exception
*/
public function update(Account $account)
{
try {
$stripeAccount = $this->accountInstance->retrieve($account->getId());
if ($stripeAccount->legal_entity->verification->status !== 'verified') {
$stripeAccount->legal_entity->first_name = $account->getFirstName();
$stripeAccount->legal_entity->last_name = $account->getLastName();
$stripeAccount->legal_entity->address->city = $account->getCity();
$stripeAccount->legal_entity->address->line1 = $account->getStreet();
$stripeAccount->legal_entity->address->postal_code = $account->getPostCode();
$stripeAccount->legal_entity->address->state = $account->getState();
$dob = explode('-', $account->getDateOfBirth());
$stripeAccount->legal_entity->dob->day = $dob[2];
$stripeAccount->legal_entity->dob->month = $dob[1];
$stripeAccount->legal_entity->dob->year = $dob[0];
if ($account->getGender()) {
$stripeAccount->legal_entity->gender = $account->getGender();
}
if ($account->getPhoneNumber()) {
$stripeAccount->legal_entity->phone_number = $account->getPhoneNumber();
}
} else {
if (!$stripeAccount->legal_entity->ssn_last_4_provided && $account->getSSN()) {
$stripeAccount->legal_entity->ssn_last_4 = $account->getSSN();
}
if (!$account->legal_entity->personal_id_number_provided && $account->getPersonalIdNumber()) {
$account->legal_entity->personal_id_number = $account->getPersonalIdNumber();
}
}
if ($account->getAccountNumber()) {
$stripeAccount->external_account->account_number = $account->getAccountNumber();
}
if ($account->getRoutingNumber()) {
$stripeAccount->external_account->routing_number = $account->getRoutingNumber();
}
$stripeAccount->save();
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
return $stripeAccount->id;
}
/**
* Add a bank account to stripe account
* @param Account $account
* @return boolean
*/
public function addBankAccount(Account $account)
{
$stripeAccount = $this->accountInstance->retrieve($account->getId());
$stripeAccount->external_account = [
'object' => 'bank_account',
'account_number' => $account->getAccountNumber(),
'country' => $account->getCountry(),
'currency' => Currencies::byCountry($account->getCountry())
];
if ($account->getRoutingNumber()) {
$stripeAccount->external_account['routing_number'] = $account->getRoutingNumber();
}
$stripeAccount->save();
return true;
}
/**
* Return a stripe account
* @param string $id
* @return Account
*/
public function getByAccountId($id)
{
try {
$result = $this->accountInstance->retrieve($id);
$account = (new Account())
->setId($result->id)
->setStatus('active')
->setCountry($result->country)
->setFirstName($result->legal_entity->first_name)
->setLastName($result->legal_entity->last_name)
->setGender($result->legal_entity->gender)
->setDateOfBirth($result->legal_entity->dob->year . '-' . str_pad($result->legal_entity->dob->month, 2, '0', STR_PAD_LEFT) . '-' . str_pad($result->legal_entity->dob->day, 2, '0', STR_PAD_LEFT))
->setStreet($result->legal_entity->address->line1)
->setCity($result->legal_entity->address->city)
->setPostCode($result->legal_entity->address->postal_code)
->setState($result->legal_entity->address->state)
->setPhoneNumber($result->legal_entity->phone_number)
->setSSN($result->legal_entity->ssn_last_4)
->setPersonalIdNumber($result->legal_entity->personal_id_number)
->setBankAccount($result->external_accounts->data[0])
->setAccountNumber($result->external_accounts->data[0]['last4'])
->setRoutingNumber($result->external_accounts->data[0]['routing_number'])
->setDestination('bank');
//verifiction check
if ($result->legal_entity->verification->status === 'verified') {
$account->setVerified(true);
}
if ($result->verification->disabled_reason == 'fields_needed') {
if ($result->verification->fields_needed[0] == 'legal_entity.verification.document') {
$account->setStatus('awaiting-document');
}
}
return $account;
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
/**
* Get account from user
* @param User $user
* @return Account
*/
public function getByUser(User $user): ?Account
{
$merchant = $user->getMerchant();
if (!$merchant || $merchant['service'] !== 'stripe') {
return null;
}
return $this->getByAccountId($merchant['id']);
}
/**
* Delete a merchant accont
* @param Account $account
* @return boolean
*/
public function delete(Account $account)
{
$stripeAccount = $this->accountInstance->retrieve($account->getId());
$result = $stripeAccount->delete();
if (!$result->deleted) {
return false;
}
// Delete id from user entity
$user = $account->getUser();
$user->setMerchant([]);
$this->save->setEntity($user)
->save();
return true;
}
}
<?php
namespace Minds\Core\Payments\Stripe;
class Currencies
{
/**
* Return the currency by country code
* @param string $country
* @return string
*/
public static function byCountry($country): string
{
$countryToCurrency = [
'AU' => 'AUD',
'CA' => 'CAD',
'GB' => 'GBP',
'HK' => 'HKD',
'JP' => 'JPY',
'SG' => 'SGD',
'US' => 'USD',
'NZ' => 'NZD',
];
if (!isset($countryToCurrency[$country])) {
return 'EUR';
}
return $countryToCurrency[$country];
}
}
<?php
/**
* Stripe Customer
*/
namespace Minds\Core\Payments\Stripe\Customers;
use Minds\Traits\MagicAttributes;
class Customer
{
use MagicAttributes;
/** @var string $id */
private $id;
/** @var string $userGuid */
private $userGuid;
/** @var User $user */
private $user;
/** @var string $paymentSource */
private $paymentSource;
/**
* Expose to the public apis
* @param array $extend
* @return array
*/
public function export($extend = [])
{
return [
'user_guid' => (string) $this->userGuid,
'user' => $this->user ? $this->user->export() : null,
];
}
}
<?php
namespace Minds\Core\Payments\Stripe\Customers;
use Minds\Core\Di\Di;
class Manager
{
/** @var Lookup $lookup */
private $lookup;
public function __construct($lookup = null)
{
$this->lookup = $lookup ?: Di::_()->get('Database\Cassandra\Lookup');
}
/**
* Return a Customer from user guid
* @param string $userGuid
* @return Customer
*/
public function getFromUserGuid($userGuid): Customer
{
$customerId = $this->lookup->get("{$userGuid}:payments")['customer_id'];
if (!$customerId) {
return null;
}
$stripeCustomer = \Stripe\Customer::retrieve($customerId);
if (!$stripeCustomer) {
return null;
}
$customer = new Customer();
$customer->setPaymentSource($stripeCustomer->default_source)
->setUserGuid($userGuid)
->setId($customerId);
return $customer;
}
/**
* Add a customer to stripe
* @param Customer $customer
* @return boolean
*/
public function add(Customer $customer)
{
$customer = \Stripe\Customer::create([
'source' => $customer->getPaymentSource(),
'email' => $customer->getUser()->getEmail(),
]);
$this->lu->set("{$customer->getUserGuid()}:payments", [
'customer_id' => (string) $customer->id
]);
return (bool) $customer->id;
}
}
\ No newline at end of file
<?php
namespace Minds\Core\Payments\Stripe\Instances;
use Minds\Common\StaticToInstance;
/**
* @method AccountInstance create()
* @method AccountInstance retrieve()
*/
class AccountInstance extends StaticToInstance
{
public function __construct()
{
$this->setClass(new \Stripe\Account);
}
}
\ No newline at end of file
<?php
/**
* Stripe Payment Intent
*/
namespace Minds\Core\Payments\Stripe\Intents;
use Minds\Traits\MagicAttributes;
class Intent
{
use MagicAttributes;
/** @var string $id */
private $id;
/** @var int $amount */
private $amount = 0;
/** @var int $quantity */
private $quantity = 1;
/** @var string $currency */
private $currency = 'usd';
/** @var int $serviceFeePct */
private $serviceFeePct = 0;
/** @var string $customerId */
private $customerId;
/** @var string $stripeAccountId */
private $stripeAccountId;
/**
* Return the service
* @return int
*/
public function getServiceFee(): int
{
return $this->amount * ($this->serviceFeePct / 100);
}
/**
* Expose to the public apis
* @param array $extend
* @return array
*/
public function export($extend = [])
{
return [
];
}
}
<?php
namespace Minds\Core\Payments\Stripe\Intents;
class Manager
{
/**
* Add a payment intent to stripe
* @param Intent $intent
* @return string
*/
public function add(Intent $intent): string
{
$params = [
'amount' => $intent->getAmount(),
'currency' => $intent->getCurrency(),
'setup_future_usage' => 'off_session',
'payment_method_types' => [
'card',
],
'application_fee_amount' => $intent->getServiceFee(),
'transfer_data' => [
'destination' => $order->getStripeAccountId(),
],
];
$stripeIntent = \Stripe\PaymentIntent::create($params);
return $stripeIntent->id;
}
}
\ No newline at end of file
<?php
/**
* Stripe service controller
* DEPRECATED. Use managers
*/
namespace Minds\Core\Payments\Stripe;
......@@ -18,12 +19,15 @@ use Minds\Core\Payments\Subscriptions\SubscriptionPaymentServiceInterface;
use Minds\Core\Payments\Transfers\Transfer;
use Stripe as StripeSDK;
class Stripe implements PaymentServiceInterface, SubscriptionPaymentServiceInterface
class Stripe implements SubscriptionPaymentServiceInterface
{
private $config;
public function __construct(Config $config)
public function __construct(Config $config = null)
{
if (!$config) {
$config = new Config;
}
$this->config = $config;
if ($config->payments && isset($config->payments['stripe'])) {
$this->setConfig($config->payments['stripe']);
......@@ -505,199 +509,6 @@ class Stripe implements PaymentServiceInterface, SubscriptionPaymentServiceInter
return $totals;
}
/**
* Add a merchant to Stripe
* @param Merchant $merchant
* @return string - the ID of the merchant
* @throws \Exception error message from failed account creation
*/
public function addMerchant(Merchant $merchant)
{
$dob = explode('-', $merchant->getDateOfBirth());
$data = [
'managed' => true,
'country' => $merchant->getCountry(),
'legal_entity' => [
'type' => 'individual',
'first_name' => $merchant->getFirstName(),
'last_name' => $merchant->getLastName(),
'address' => [
'city' => $merchant->getCity(),
'line1' => $merchant->getStreet(),
'postal_code' => $merchant->getPostCode(),
'state' => $merchant->getState(),
],
'dob' => [
'day' => $dob[2],
'month' => $dob[1],
'year' => $dob[0]
],
],
'tos_acceptance' => [
'date' => time(),
'ip' => '0.0.0.0' // @todo: Should we set the actual IP?
]
];
if ($merchant->getGender()) {
$data['legal_entity']['gender'] = $merchant->getGender();
}
if ($merchant->getPhoneNumber()) {
$data['legal_entity']['phone_number'] = $merchant->getPhoneNumber();
}
if ($merchant->getSSN()) {
$data['legal_entity']['ssn_last_4'] = $merchant->getSSN();
}
if ($merchant->getPersonalIdNumber()) {
$data['legal_entity']['personal_id_number'] = $merchant->getPersonalIdNumber();
}
$result = StripeSDK\Account::create($data);
if ($result->id) {
$merchant->setGuid($result->id);
return $result;
}
throw new \Exception($result->message);
}
/**
* Return a merchant from an id
* @param $id
* @return Merchant
* @throws \Exception
*/
public function getMerchant($id)
{
try {
$result = StripeSDK\Account::retrieve($id);
$merchant = (new Merchant())
->setId($result->id)
->setStatus('active')
->setCountry($result->country)
->setFirstName($result->legal_entity['first_name'])
->setLastName($result->legal_entity['last_name'])
->setGender($result->legal_entity['gender'])
->setDateOfBirth($result->legal_entity['dob']['year'] . '-' . str_pad($result->legal_entity['dob']['month'], 2, '0', STR_PAD_LEFT) . '-' . str_pad($result->legal_entity['dob']['day'], 2, '0', STR_PAD_LEFT))
->setStreet($result->legal_entity['address']['line1'])
->setCity($result->legal_entity['address']['city'])
->setPostCode($result->legal_entity['address']['postal_code'])
->setState($result->legal_entity['address']['state'])
->setPhoneNumber($result->legal_entity['phone_number'])
->setSSN($result->legal_entity['ssn_last_4'])
->setPersonalIdNumber($result->legal_entity['personal_id_number'])
->setBankAccount($result->external_accounts->data[0])
->setAccountNumber($result->external_accounts->data[0]['last4'])
->setRoutingNumber($result->external_accounts->data[0]['routing_number'])
->setDestination('bank');
//verifiction check
if ($result->legal_entity->verification->status === 'verified') {
$merchant->markAsVerified();
}
if ($result->verification->disabled_reason == 'fields_needed') {
if ($result->verification->fields_needed[0] == 'legal_entity.verification.document') {
$merchant->setStatus('awaiting-document');
}
}
return $merchant;
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
/**
* Updates a merchant in Stripe
* @param $merchant
* @return string Merchant's id
* @throws \Exception
*/
public function updateMerchant(Merchant $merchant)
{
try {
$account = StripeSDK\Account::retrieve($merchant->getId());
if ($account->legal_entity->verification->status !== 'verified') {
$account->legal_entity->first_name = $merchant->getFirstName();
$account->legal_entity->last_name = $merchant->getLastName();
$account->legal_entity->address->city = $merchant->getCity();
$account->legal_entity->address->line1 = $merchant->getStreet();
$account->legal_entity->address->postal_code = $merchant->getPostCode();
$account->legal_entity->address->state = $merchant->getState();
$dob = explode('-', $merchant->getDateOfBirth());
$account->legal_entity->dob->day = $dob[2];
$account->legal_entity->dob->month = $dob[1];
$account->legal_entity->dob->year = $dob[0];
if ($merchant->getGender()) {
$account->legal_entity->gender = $merchant->getGender();
}
if ($merchant->getPhoneNumber()) {
$account->legal_entity->phone_number = $merchant->getPhoneNumber();
}
} else {
if (!$account->legal_entity->ssn_last_4_provided && $merchant->getSSN()) {
$account->legal_entity->ssn_last_4 = $merchant->getSSN();
}
if (!$account->legal_entity->personal_id_number_provided && $merchant->getPersonalIdNumber()) {
$account->legal_entity->personal_id_number = $merchant->getPersonalIdNumber();
}
}
if ($merchant->getAccountNumber()) {
$account->external_account->account_number = $merchant->getAccountNumber();
}
if ($merchant->getRoutingNumber()) {
$account->external_account->routing_number = $merchant->getRoutingNumber();
}
$account->save();
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
return $account->id;
}
public function updateMerchantAccount($merchant)
{
$account = StripeSDK\Account::retrieve($merchant->getId());
$account->external_account = [
'object' => 'bank_account',
'account_number' => $merchant->getAccountNumber(),
'country' => $merchant->getCountry(),
'currency' => $this->getCurrencyFor($merchant->getCountry())
];
if ($merchant->getRoutingNumber()) {
$account->external_account['routing_number'] = $merchant->getRoutingNumber();
}
$account->save();
return $account;
}
public function deleteMerchantAccount($merchant)
{
$account = StripeSDK\Account::retrieve($merchant->getId());
$result = $account->delete();
return $result->deleted;
}
public function verifyMerchant($id, $file)
{
$result = StripeSDK\FileUpload::create([
......@@ -713,10 +524,6 @@ class Stripe implements PaymentServiceInterface, SubscriptionPaymentServiceInter
return $result->id;
}
public function confirmMerchant(Merchant $merchant)
{
}
/* Subscriptions */
public function createCustomer(Customer $customer)
......@@ -966,4 +773,5 @@ class Stripe implements PaymentServiceInterface, SubscriptionPaymentServiceInter
return $countryToCurrency[$country];
}
}
......@@ -16,53 +16,6 @@ class Events
{
public function register()
{
Dispatcher::register('subscriptions:cancel', 'all', function ($event) {
$params = $event->getParameters();
$user = $params['user'];
if ($user->type != 'user') {
return;
}
/** @var Manager $manager */
$manager = Core\Di\Di::_()->get('Wire\Manager');
$wires = $manager->get(['user_guid' => $user->guid, 'type' => 'sent', 'order' => 'DESC']);
// cancel all wires and subscriptions
foreach ($wires as $wire) {
if ($wire->isRecurring() && $wire->isActive()) {
if ($wire->getMethod() == 'usd') {
// cancel all subscriptions from stripe
$this->cancelSubscriptions($user);
}
$wire->setActive(0)
->save();
}
}
});
/*
* Legcacy compatability for exclusive content
*/
Dispatcher::register('export:extender', 'activity', function ($event) {
$params = $event->getParameters();
$activity = $params['entity'];
if ($activity->type != 'activity') {
return;
}
$export = $event->response() ?: [];
$currentUser = Session::getLoggedInUserGuid();
if ($activity->isPaywall() && !$activity->getWireThreshold()) {
$export['wire_threshold'] = [
'type' => 'money',
'min' => $activity->getOwnerEntity()->getMerchant()['exclusive']['amount'],
];
return $event->setResponse($export);
}
});
// Recurring subscriptions
Dispatcher::register('subscriptions:process', 'wire', function (Core\Events\Event $event) {
......@@ -70,13 +23,14 @@ class Events
/** @var Core\Payments\Subscriptions\Subscription $subscription */
$subscription = $params['subscription'];
$manager = Di::_()->get('Wire\Manager');
$manager = Di::_()->get('Wire\Subscriptions\Manager');
$result = $manager->onRecurring($subscription);
return $event->setResponse($result);
});
// Wire ermails
// Wire emails
Dispatcher::register('wire:email', 'wire', function (Core\Events\Event $event) {
$params = $event->getParameters();
$wire = $params['wire'];
......@@ -101,31 +55,4 @@ class Events
});
}
private function cancelSubscriptions(User $user)
{
/** @var Core\Payments\Subscriptions\Repository $repository */
$repository = Di::_()->get('Payments\Subscriptions\Repository');
/** @var Core\Payments\Subscriptions\Manager $manager */
$manager = Di::_()->get('Payments\Subscriptions\Manager');
$subscriptions = $repository->getList([
'user_guid' => $user->guid,
'plan_id' => 'wire',
]);
foreach ($subscriptions as $subscription) {
if (!$subscription->getId()) {
continue;
}
$subscription->setMerchant($user);
$stripe = Core\Di\Di::_()->get('StripePayments');
$stripe->cancelSubscription($subscription);
//cancel the plan itself
$manager->setSubscription($subscription);
$manager->cancel();
}
}
}
......@@ -190,6 +190,14 @@ class Manager
->setReceiver($this->receiver);
}
$wire = new Wire();
$wire
->setSender($this->sender)
->setReceiver($this->receiver)
->setEntity($this->entity)
->setAmount($this->amount)
->setTimestamp(time());
switch ($this->payload['method']) {
case 'onchain':
//add transaction to the senders transaction log
......@@ -236,6 +244,7 @@ class Manager
'entity_guid' => (string) $this->entity->guid,
];
// Charge offchain wallet
$this->offchainTxs
->setAmount($this->amount)
->setType('wire')
......@@ -243,29 +252,59 @@ class Manager
->setData($txData)
->transferFrom($this->sender);
$wire = new Wire();
$wire
->setSender($this->sender)
->setReceiver($this->receiver)
->setEntity($this->entity)
->setAmount($this->amount)
->setTimestamp(time());
// Save the wire to the Repository
$this->repository->add($wire);
// Notify plus
$this->plusDelegate
->onWire($wire, 'offchain');
// Send notification
$this->sendNotification($wire);
// Clear caches
$this->clearWireCache($wire);
//is this a subscription?
// Register the next subscription, if needed
if ($this->recurring) {
$this->subscriptionsManager
->setAddress('offchain')
->create();
}
break;
case 'erc20':
throw new \Exception("Not implemented ERC20 yet");
break;
case 'eth':
throw new \Exception("Not implemented ETH yet");
break;
case 'money':
$intent = new StripePaymentIntent();
$intent
->setUserGuid($this->sender->getGuid())
->setAmount($this->amount)
->setDestination($this->receiver->getStripeAccount());
// Charge stripe
$this->stripeManager->charge($intent);
// Register the next subscription, if needed
if ($this->recurring) {
$this->subscriptionsManager
->setMethod('money')
->setAddress('stripe')
->create();
}
// Save the wire to the Repository
$this->repository->add($wire);
// Send notification
$this->sendNotification($wire);
// Clear caches
$this->clearWireCache($wire);
break;
}
......@@ -300,14 +339,6 @@ class Manager
->setCompleted(true);
$this->txRepo->add($transaction);
/*Dispatcher::trigger('wire-payment-email', 'object', [
'charged' => false,
'amount' => $wire->getAmount,
'unit' => 'tokens',
'description' => 'Wire',
'user' => $wire->getReceiver(),
]);*/
$this->plusDelegate
->onWire($wire, $data['receiver_address']);
......@@ -318,57 +349,6 @@ class Manager
return $success;
}
/**
* Call when a recurring wire is triggered.
*
* @param Core\Payments\Subscriptions\Subscription $subscription
*/
public function onRecurring($subscription)
{
$sender = $subscription->getUser();
$receiver = new User($subscription->getEntity()->guid);
$amount = $subscription->getAmount();
$id = $subscription->getId();
if (strpos($id, 'urn:', 0) !== 0) {
error_log("[wire][recurring]: $id was expecting a urn");
return false;
}
$urn = new Urn($id);
list ($address, , ,) = explode('-', $urn->getNss());
if ($address === 'offchain') {
$this->setPayload([
'method' => 'offchain',
]);
} else { //onchain
$txHash = $this->client->sendRawTransaction($this->config->get('blockchain')['contracts']['wire']['wallet_pkey'],
[
'from' => $this->config->get('blockchain')['contracts']['wire']['wallet_address'],
'to' => $this->config->get('blockchain')['contracts']['wire']['contract_address'],
'gasLimit' => BigNumber::_(200000)->toHex(true),
'data' => $this->client->encodeContractMethod('wireFromDelegate(address,address,uint256)', [
$address,
$receiver->getEthWallet(),
BigNumber::_($this->token->toTokenUnit($amount))->toHex(true),
]),
]);
$this->setPayload([
'method' => 'onchain',
'address' => $address, //sender address
'receiver' => $receiver->getEthWallet(),
'txHash' => $txHash,
]);
}
$this->setSender($sender)
->setEntity($receiver)
->setAmount($subscription->getAmount());
$this->create();
}
/**
* @param Wire $wire
*/
......
<?php
namespace Minds\Core\Wire;
use Minds\Interfaces\ModuleInterface;
class Module implements ModuleInterface
{
/** @var array $submodules */
public $submodules = [
Paywall\Module::class,
];
/**
* Executed onInit
*/
public function onInit()
{
$events = new Events();
$events->register();
}
}
\ No newline at end of file
......@@ -128,5 +128,27 @@ class Events
return $event->setResponse(false);
});
/*
* Legcacy compatability for exclusive content
*/
Dispatcher::register('export:extender', 'activity', function ($event) {
$params = $event->getParameters();
$activity = $params['entity'];
if ($activity->type != 'activity') {
return;
}
$export = $event->response() ?: [];
$currentUser = Session::getLoggedInUserGuid();
if ($activity->isPaywall() && !$activity->getWireThreshold()) {
$export['wire_threshold'] = [
'type' => 'money',
'min' => $activity->getOwnerEntity()->getMerchant()['exclusive']['amount'],
];
return $event->setResponse($export);
}
});
}
}
<?php
namespace Minds\Core\Wire\Paywall;
use Minds\Interfaces\ModuleInterface;
class Module implements ModuleInterface
{
/**
* Executed onInit
*/
public function onInit()
{
$events = new Events();
$events->register();
}
}
\ No newline at end of file
......@@ -11,6 +11,8 @@ use Minds\Entities\User;
class Manager
{
/** @var Core\Wire\Manager */
protected $wireManager;
/** @var Core\Payments\Subscriptions\Manager $subscriptionsManager */
protected $subscriptionsManager;
......@@ -30,14 +32,19 @@ class Manager
/** @var User $receiver */
protected $receiver;
/** @var string $method */
protected $method = 'tokens';
/** @var string $address */
protected $address = 'offchain';
public function __construct(
$wireManager = null,
$subscriptionsManager = null,
$subscriptionsRepository = null,
$config = null
) {
$this->wireManager = $wireManager ?: Di::_()->get('Wire\Manager');
$this->subscriptionsManager = $subscriptionsManager ?: Di::_()->get('Payments\Subscriptions\Manager');
$this->subscriptionsRepository = $subscriptionsRepository ?: Di::_()->get('Payments\Subscriptions\Repository');
$this->config = $config ?: Di::_()->get('Config');
......@@ -67,6 +74,12 @@ class Manager
return $this;
}
public function setMethod($method)
{
$this->method = $method;
return $this;
}
/**
* @return mixed
* @throws WalletNotSetupException
......@@ -85,7 +98,7 @@ class Manager
$subscription = (new Core\Payments\Subscriptions\Subscription())
->setId($urn)
->setPlanId('wire')
->setPaymentMethod('tokens')
->setPaymentMethod($this->method)
->setAmount($this->amount)
->setUser($this->sender)
->setEntity($this->receiver);
......@@ -96,6 +109,67 @@ class Manager
return $subscription->getId();
}
/**
* Call when a recurring wire is triggered.
*
* @param Core\Payments\Subscriptions\Subscription $subscription
*/
public function onRecurring($subscription)
{
$sender = $subscription->getUser();
$receiver = new User($subscription->getEntity()->guid);
$amount = $subscription->getAmount();
$id = $subscription->getId();
if (strpos($id, 'urn:', 0) !== 0) {
error_log("[wire][recurring]: $id was expecting a urn");
return false;
}
$urn = new Urn($id);
list ($address, , ,) = explode('-', $urn->getNss());
switch ($address) {
case "offchain":
$this->setPayload([
'method' => 'offchain',
]);
break;
case "money":
$this->setPayload([
'method' => 'money',
]);
break;
default:
$txHash = $this->client->sendRawTransaction($this->config->get('blockchain')['contracts']['wire']['wallet_pkey'],
[
'from' => $this->config->get('blockchain')['contracts']['wire']['wallet_address'],
'to' => $this->config->get('blockchain')['contracts']['wire']['contract_address'],
'gasLimit' => BigNumber::_(200000)->toHex(true),
'data' => $this->client->encodeContractMethod('wireFromDelegate(address,address,uint256)', [
$address,
$receiver->getEthWallet(),
BigNumber::_($this->token->toTokenUnit($amount))->toHex(true),
]),
]);
$this->setPayload([
'method' => 'onchain',
'address' => $address, //sender address
'receiver' => $receiver->getEthWallet(),
'txHash' => $txHash,
]);
}
// Manager acts as factory
$this->wireManager
->setSender($sender)
->setEntity($receiver)
->setAmount($subscription->getAmount());
// Create the wire
$this->wireManager->create();
}
protected function cancelSubscription()
{
$subscriptions = $this->subscriptionsRepository->getList([
......
<?php
namespace Spec\Minds\Core\Payments\Braintree;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Minds\Core\Config\Config;
use Braintree_Configuration;
class BraintreeSpec extends ObjectBehavior
{
function it_is_initializable(Braintree_Configuration $btConfig, Config $config)
{
$this->beConstructedWith($btConfig, $config);
$this->shouldHaveType('Minds\Core\Payments\Braintree\Braintree');
}
}
<?php
namespace Spec\Minds\Core\Payments\Braintree;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Braintree_WebhookTesting;
use Braintree_WebhookNotification;
use Minds\Core\Payments\Subscriptions;
use Minds\Core\Payments\Hooks;
use Minds\Core\Payments\HookInterface;
use Minds\Core\Payments\Subscriptions\SubscriptionsHookInterface;
use Minds\Core\Payments\Braintree\Braintree as BT;
class WebhooksSpec extends ObjectBehavior
{
private $hooks;
function it_is_initializable(BT $bt)
{
$this->beConstructedWith(false, $bt);
$this->shouldHaveType('Minds\Core\Payments\Braintree\Webhooks');
}
/*function it_should_call_a_charge_hook(BT $bt)
{
$this->beConstructedWith(false, $bt);
$mock = Braintree_WebhookTesting::sampleNotification(
Braintree_WebhookNotification::SUBSCRIPTION_CHARGED_SUCCESSFULLY,
'charge-001'
);
$this
->setSignature($mock['bt_signature'])
->setPayload($mock['bt_payload'])
->run();
}*/
/*function it_should_call_an_active_hook(Hooks $hooks, SubscriptionsHookInterface $hook)
{
$hooks->__call('onActive', [])->willReturn(true);
$this->beConstructedWith($hooks);
$mock = Braintree_WebhookTesting::sampleNotification(
Braintree_WebhookNotification::SUBSCRIPTION_WENT_ACTIVE,
'active-001'
);
$this->setSignature($mock['bt_signature'])
->setPayload($mock['bt_payload'])
->run();
}*/
}
<?php
namespace Spec\Minds\Core\Payments;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Minds\Core\Config;
class FactorySpec extends ObjectBehavior
{
public function let()
{
}
public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Payments\Factory');
}
public function it_should_build_a_service()
{
$this::build('braintree', ['gateway'=>'merchants'])->shouldImplement('Minds\Core\Payments\PaymentServiceInterface');
}
public function it_should_throw_an_exception_if_service_doesnt_exist()
{
$this->shouldThrow('\Exception')->during('build', ['foobar']);
}
}
<?php
namespace Spec\Minds\Core\Payments\Stripe\Connect\Delegates;
use Minds\Core\Payments\Stripe\Connect\Delegates\NotificationDelegate;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class NotificationDelegateSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(NotificationDelegate::class);
}
}
<?php
namespace Spec\Minds\Core\Payments\Stripe\Connect;
use Minds\Core\Payments\Stripe\Connect\Manager;
use Minds\Core\Payments\Stripe\Connect\Account;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\Payments\Stripe\Connect\Delegates\NotificationDelegate;
use Minds\Core\Payments\Stripe\Instances\AccountInstance;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
private $save;
private $notificationDelegate;
private $accountInstance;
function let(Save $save, NotificationDelegate $notificationDelegate, AccountInstance $accountInstance)
{
$this->beConstructedWith($save, $notificationDelegate, $accountInstance);
$this->save = $save;
$this->notificationDelegate = $notificationDelegate;
$this->accountInstance = $accountInstance;
}
function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
function it_should_add_a_new_account_to_stripe(Account $account)
{
$account->getDateOfBirth()
->willReturn('1943-02-25');
$account->getFirstName()
->willReturn('George');
$account->getLastName()
->willReturn('Harrison');
$account->getCity()
->willReturn('Liverpool');
$account->getCountry()
->willReturn('GB');
$account->getStreet()
->willReturn('12 Arnold Grove');
$account->getState()
->willReturn('Lancashire');
$account->getPostCode()
->willReturn('L15 8HP');
$account->getIp()
->willReturn('45.01.99.99');
$account->getGender()
->willReturn(null);
$account->getPhoneNumber()
->willReturn('01000000000');
$account->getSSN()
->willReturn(null);
$account->getPersonalIdNumber()
->willReturn(null);
$this->accountInstance->create(Argument::any())
->shouldBeCalled()
->willReturn((object) [
'id' => 'account_id',
]);
$user = new User();
$account->getUser()
->willReturn($user);
$this->save
->setEntity($user)
->shouldBeCalled()
->willReturn($this->save);
$this->save
->save()
->shouldBeCalled();
$this->add($account)
->shouldReturn('account_id');
}
public function it_should_get_an_account_by_an_id()
{
$this->accountInstance->retrieve('acc_123')
->shouldBeCalled()
->willReturn((object) [
'id' => 'acc_123',
'country' => 'GB',
'legal_entity' => (object) [
'first_name' => 'George',
'last_name' => 'Harrison',
'gender' => null,
'dob' => (object) [
'year' => 1943,
'month' => 2,
'day' => 25,
],
'address' => (object) [
'line1' => '12 Arnold Grove',
'city' => 'Liverpool',
'state' => 'Lancashire',
'postal_code' => 'L15 8HP',
],
'phone_number' => '0112',
'ssn_last_4' => null,
'personal_id_number' => null,
'verification' => (object) [
'status' => 'verified',
],
],
'external_accounts' => (object) [
'data' => [
[
'last4' => 6789,
'routing_number' => 123456789,
],
],
],
'verification' => (object) [
'disabled_reason' => null,
],
]);
$account = $this->getByAccountId('acc_123');
$account->getId()
->shouldBe('acc_123');
$account->getFirstName()
->shouldBe('George');
$account->getLastName()
->shouldBe('Harrison');
$account->getStreet()
->shouldBe('12 Arnold Grove');
$account->getCity()
->shouldBe('Liverpool');
}
public function it_should_get_an_account_from_a_user_entity()
{
$this->accountInstance->retrieve('acc_123')
->shouldBeCalled()
->willReturn((object) [
'id' => 'acc_123',
'country' => 'GB',
'legal_entity' => (object) [
'first_name' => 'George',
'last_name' => 'Harrison',
'gender' => null,
'dob' => (object) [
'year' => 1943,
'month' => 2,
'day' => 25,
],
'address' => (object) [
'line1' => '12 Arnold Grove',
'city' => 'Liverpool',
'state' => 'Lancashire',
'postal_code' => 'L15 8HP',
],
'phone_number' => '0112',
'ssn_last_4' => null,
'personal_id_number' => null,
'verification' => (object) [
'status' => 'verified',
],
],
'external_accounts' => (object) [
'data' => [
[
'last4' => 6789,
'routing_number' => 123456789,
],
],
],
'verification' => (object) [
'disabled_reason' => null,
],
]);
$user = new User();
$user->setMerchant([
'service' => 'stripe',
'id' => 'acc_123',
]);
$account = $this->getByUser($user);
}
function it_should_not_get_an_account_if_not_stripe_service()
{
$user = new User();
$user->setMerchant([
'service' => 'bitcoin',
'id' => 'acc_123',
]);
$account = $this->getByUser($user);
$account->shouldBe(null);
}
}
<?php
namespace Spec\Minds\Core\Payments\Stripe\Customers;
use Minds\Core\Payments\Stripe\Customers\Manager;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
}
<?php
namespace Spec\Minds\Core\Payments\Stripe\Intents;
use Minds\Core\Payments\Stripe\Intents\Manager;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
}
......@@ -228,146 +228,146 @@ class ManagerSpec extends ObjectBehavior
->shouldReturn(true);
}
public function it_should_charge_a_recurring_onchain_subscription(
User $user,
User $user2,
Core\Payments\Subscriptions\Subscription $subscription
) {
$this->call->getRow(Argument::any(), Argument::any())
->shouldBeCalled()
->willReturn([
'guid' => '1234',
'type' => 'user',
'eth_wallet' => 'wallet',
]);
$this->config->get('blockchain')
->shouldBeCalled()
->willReturn([
'contracts' => [
'wire' => [
'wallet_pkey' => 'key',
'wallet_address' => 'address',
'contract_address' => 'contract_address',
],
],
]);
$subscription->getUser()
->shouldBeCalled()
->willReturn($user);
$subscription->getEntity()
->shouldBeCalled()
->willReturn($user2);
$user2->get('guid')
->shouldBeCalled()
->willReturn('5678');
$subscription->getAmount()
->shouldBeCalled()
->willReturn(1000000000000000000);
$subscription->getId()
->shouldBeCalled()
->willReturn('urn:subscription:0x123-1234-5678');
$this->client->encodeContractMethod('wireFromDelegate(address,address,uint256)', [
'0x123',
'wallet',
Core\Util\BigNumber::_(1000000000000000000)->toHex(true),
])
->shouldBeCalled()
->willReturn('data hash');
$this->token->toTokenUnit(1000000000000000000)
->shouldBeCalled()
->willReturn(1000000000000000000);
$this->client->sendRawTransaction('key', [
'from' => 'address',
'to' => 'contract_address',
'gasLimit' => Core\Util\BigNumber::_(200000)->toHex(true),
'data' => 'data hash',
])
->shouldBeCalled()
->willReturn('0x123asd');
$this->onRecurring($subscription);
}
public function it_should_charge_a_recurring_offchain_subscription(
User $user,
User $user2,
Core\Payments\Subscriptions\Subscription $subscription
) {
$this->call->getRow(Argument::any(), Argument::any())
->shouldBeCalled()
->willReturn([
'guid' => '1234',
'type' => 'user',
'eth_wallet' => 'wallet',
]);
$subscription->getUser()
->shouldBeCalled()
->willReturn($user);
$subscription->getEntity()
->shouldBeCalled()
->willReturn($user2);
$user2->get('guid')
->shouldBeCalled()
->willReturn('5678');
$subscription->getAmount()
->shouldBeCalled()
->willReturn(1000000000000000000);
$subscription->getId()
->shouldBeCalled()
->willReturn('urn:subscription:offchain-1234-5678');
$this->cap->setUser(Argument::any())
->shouldBeCalled()
->willReturn($this->cap);
$this->cap->setContract('wire')
->shouldBeCalled();
$this->cap->isAllowed(1000000000000000000)
->shouldBeCalled()
->willReturn(true);
$this->offchainTxs->setAmount(1000000000000000000)
->shouldBeCalled()
->willReturn($this->offchainTxs);
$this->offchainTxs->setType('wire')
->shouldBeCalled()
->willReturn($this->offchainTxs);
$this->offchainTxs->setUser(Argument::type(User::class))
->shouldBeCalled()
->willReturn($this->offchainTxs);
$this->offchainTxs->setData(Argument::type('array'))
->shouldBeCalled()
->willReturn($this->offchainTxs);
$this->offchainTxs->transferFrom(Argument::type(User::class))
->shouldBeCalled()
->willReturn(true);
$this->queue->setQueue('WireNotification')
->shouldBeCalled()
->willReturn($this->queue);
$this->queue->send(Argument::any())
->shouldBeCalled();
$this->onRecurring($subscription);
}
// public function it_should_charge_a_recurring_onchain_subscription(
// User $user,
// User $user2,
// Core\Payments\Subscriptions\Subscription $subscription
// ) {
// $this->call->getRow(Argument::any(), Argument::any())
// ->shouldBeCalled()
// ->willReturn([
// 'guid' => '1234',
// 'type' => 'user',
// 'eth_wallet' => 'wallet',
// ]);
// $this->config->get('blockchain')
// ->shouldBeCalled()
// ->willReturn([
// 'contracts' => [
// 'wire' => [
// 'wallet_pkey' => 'key',
// 'wallet_address' => 'address',
// 'contract_address' => 'contract_address',
// ],
// ],
// ]);
// $subscription->getUser()
// ->shouldBeCalled()
// ->willReturn($user);
// $subscription->getEntity()
// ->shouldBeCalled()
// ->willReturn($user2);
// $user2->get('guid')
// ->shouldBeCalled()
// ->willReturn('5678');
// $subscription->getAmount()
// ->shouldBeCalled()
// ->willReturn(1000000000000000000);
// $subscription->getId()
// ->shouldBeCalled()
// ->willReturn('urn:subscription:0x123-1234-5678');
// $this->client->encodeContractMethod('wireFromDelegate(address,address,uint256)', [
// '0x123',
// 'wallet',
// Core\Util\BigNumber::_(1000000000000000000)->toHex(true),
// ])
// ->shouldBeCalled()
// ->willReturn('data hash');
// $this->token->toTokenUnit(1000000000000000000)
// ->shouldBeCalled()
// ->willReturn(1000000000000000000);
// $this->client->sendRawTransaction('key', [
// 'from' => 'address',
// 'to' => 'contract_address',
// 'gasLimit' => Core\Util\BigNumber::_(200000)->toHex(true),
// 'data' => 'data hash',
// ])
// ->shouldBeCalled()
// ->willReturn('0x123asd');
// $this->onRecurring($subscription);
// }
// public function it_should_charge_a_recurring_offchain_subscription(
// User $user,
// User $user2,
// Core\Payments\Subscriptions\Subscription $subscription
// ) {
// $this->call->getRow(Argument::any(), Argument::any())
// ->shouldBeCalled()
// ->willReturn([
// 'guid' => '1234',
// 'type' => 'user',
// 'eth_wallet' => 'wallet',
// ]);
// $subscription->getUser()
// ->shouldBeCalled()
// ->willReturn($user);
// $subscription->getEntity()
// ->shouldBeCalled()
// ->willReturn($user2);
// $user2->get('guid')
// ->shouldBeCalled()
// ->willReturn('5678');
// $subscription->getAmount()
// ->shouldBeCalled()
// ->willReturn(1000000000000000000);
// $subscription->getId()
// ->shouldBeCalled()
// ->willReturn('urn:subscription:offchain-1234-5678');
// $this->cap->setUser(Argument::any())
// ->shouldBeCalled()
// ->willReturn($this->cap);
// $this->cap->setContract('wire')
// ->shouldBeCalled();
// $this->cap->isAllowed(1000000000000000000)
// ->shouldBeCalled()
// ->willReturn(true);
// $this->offchainTxs->setAmount(1000000000000000000)
// ->shouldBeCalled()
// ->willReturn($this->offchainTxs);
// $this->offchainTxs->setType('wire')
// ->shouldBeCalled()
// ->willReturn($this->offchainTxs);
// $this->offchainTxs->setUser(Argument::type(User::class))
// ->shouldBeCalled()
// ->willReturn($this->offchainTxs);
// $this->offchainTxs->setData(Argument::type('array'))
// ->shouldBeCalled()
// ->willReturn($this->offchainTxs);
// $this->offchainTxs->transferFrom(Argument::type(User::class))
// ->shouldBeCalled()
// ->willReturn(true);
// $this->queue->setQueue('WireNotification')
// ->shouldBeCalled()
// ->willReturn($this->queue);
// $this->queue->send(Argument::any())
// ->shouldBeCalled();
// $this->onRecurring($subscription);
// }
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment