diff --git a/app/PaymentDrivers/Stripe/Connect/Account.php b/app/PaymentDrivers/Stripe/Connect/Account.php new file mode 100644 index 0000000000..36432c047c --- /dev/null +++ b/app/PaymentDrivers/Stripe/Connect/Account.php @@ -0,0 +1,242 @@ +accounts->create([ +// 'type' => 'custom', +// 'country' => 'US', +// 'email' => 'jenny.rosen@example.com', +// 'capabilities' => [ +// 'card_payments' => ['requested' => true], +// 'transfers' => ['requested' => true], +// ], +// ]); +/// + + +//response + +/** + * { + "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" +} + + */ + + + +//then create the account link + +// https://stripe.com/docs/api/account_links/create?lang=php +} \ No newline at end of file diff --git a/app/PaymentDrivers/StripeConnectPaymentDriver.php b/app/PaymentDrivers/StripeConnectPaymentDriver.php new file mode 100644 index 0000000000..6162e1556f --- /dev/null +++ b/app/PaymentDrivers/StripeConnectPaymentDriver.php @@ -0,0 +1,436 @@ + CreditCard::class, + GatewayType::BANK_TRANSFER => ACH::class, + GatewayType::ALIPAY => Alipay::class, + GatewayType::SOFORT => SOFORT::class, + GatewayType::APPLE_PAY => 1, // TODO + GatewayType::SEPA => 1, // TODO + ]; + + const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE; + + /** + * Initializes the Stripe API. + * @return void + */ + public function init(): void + { + $this->stripe = new StripeClient( + $this->company_gateway->getConfigField('apiKey') + ); + + Stripe::setApiKey($this->company_gateway->getConfigField('apiKey')); + } + + public function setPaymentMethod($payment_method_id) + { + $class = self::$methods[$payment_method_id]; + + $this->payment_method = new $class($this); + + return $this; + } + + /** + * Returns the gateway types. + */ + public function gatewayTypes(): array + { + $types = [ + GatewayType::CREDIT_CARD, + GatewayType::CRYPTO, +// GatewayType::SEPA, // TODO: Missing implementation +// GatewayType::APPLE_PAY, // TODO:: Missing implementation + ]; + + if ($this->client + && isset($this->client->country) + && in_array($this->client->country->iso_3166_3, ['AUT', 'BEL', 'DEU', 'ITA', 'NLD', 'ESP'])) { + $types[] = GatewayType::SOFORT; + } + + if ($this->client + && isset($this->client->country) + && in_array($this->client->country->iso_3166_3, ['USA'])) { + $types[] = GatewayType::BANK_TRANSFER; + } + + if ($this->client + && isset($this->client->country) + && in_array($this->client->country->iso_3166_3, ['AUS', 'DNK', 'DEU', 'ITA', 'LUX', 'NOR', 'SVN', 'GBR', 'AUT', 'EST', 'GRC', 'JPN', 'MYS', 'PRT', 'ESP', 'USA', 'BEL', 'FIN', 'HKG', 'LVA', 'NLD', 'SGP', 'SWE', 'CAN', 'FRA', 'IRL', 'LTU', 'NZL', 'SVK', 'CHE'])) { + $types[] = GatewayType::ALIPAY; + } + + return $types; + } + + public function viewForType($gateway_type_id) + { + switch ($gateway_type_id) { + case GatewayType::CREDIT_CARD: + return 'gateways.stripe.credit_card'; + break; + case GatewayType::SOFORT: + return 'gateways.stripe.sofort'; + break; + case GatewayType::BANK_TRANSFER: + return 'gateways.stripe.ach'; + break; + case GatewayType::SEPA: + return 'gateways.stripe.sepa'; + break; + case GatewayType::CRYPTO: + case GatewayType::ALIPAY: + case GatewayType::APPLE_PAY: + return 'gateways.stripe.other'; + break; + + default: + break; + } + } + + public function getClientRequiredFields(): array + { + $fields = [ + ['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required'], + ]; + + if ($this->company_gateway->require_client_name) { + $fields[] = ['name' => 'client_name', 'label' => ctrans('texts.client_name'), 'type' => 'text', 'validation' => 'required']; + } + + if ($this->company_gateway->require_client_phone) { + $fields[] = ['name' => 'client_phone', 'label' => ctrans('texts.client_phone'), 'type' => 'tel', 'validation' => 'required']; + } + + if ($this->company_gateway->require_contact_name) { + $fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required']; + } + + if ($this->company_gateway->require_contact_email) { + $fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc']; + } + + if ($this->company_gateway->require_billing_address) { + $fields[] = ['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required']; + } + + if ($this->company_gateway->require_shipping_address) { + $fields[] = ['name' => 'client_shipping_address_line_1', 'label' => ctrans('texts.shipping_address1'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_shipping_address_line_2', 'label' => ctrans('texts.shipping_address2'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_shipping_city', 'label' => ctrans('texts.shipping_city'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_shipping_state', 'label' => ctrans('texts.shipping_state'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_shipping_postal_code', 'label' => ctrans('texts.shipping_postal_code'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_shipping_country_id', 'label' => ctrans('texts.shipping_country'), 'type' => 'text', 'validation' => 'required']; + } + + return $fields; + } + + /** + * Proxy method to pass the data into payment method authorizeView(). + * + * @param array $data + * @return \Illuminate\Http\RedirectResponse|mixed + */ + public function authorizeView(array $data) + { + return $this->payment_method->authorizeView($data); + } + + /** + * Processes the gateway response for credit card authorization. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse|mixed + */ + public function authorizeResponse($request) + { + return $this->payment_method->authorizeResponse($request); + } + + /** + * Process the payment with gateway. + * + * @param array $data + * @return \Illuminate\Http\RedirectResponse|mixed + */ + public function processPaymentView(array $data) + { + return $this->payment_method->paymentView($data); + } + + public function processPaymentResponse($request) + { + return $this->payment_method->paymentResponse($request); + } + + /** + * Creates a new String Payment Intent. + * + * @param array $data The data array to be passed to Stripe + * @return PaymentIntent The Stripe payment intent object + * @throws ApiErrorException + */ + public function createPaymentIntent($data): ?PaymentIntent + { + $this->init(); + + return PaymentIntent::create($data); + } + + /** + * Returns a setup intent that allows the user + * to enter card details without initiating a transaction. + * + * @return SetupIntent + * @throws ApiErrorException + */ + public function getSetupIntent(): SetupIntent + { + $this->init(); + + return SetupIntent::create(); + } + + /** + * Returns the Stripe publishable key. + * @return null|string The stripe publishable key + */ + public function getPublishableKey(): ?string + { + return $this->company_gateway->getPublishableKey(); + } + + /** + * Finds or creates a Stripe Customer object. + * + * @return null|Customer A Stripe customer object + * @throws \Laracasts\Presenter\Exceptions\PresenterException + * @throws ApiErrorException + */ + public function findOrCreateCustomer(): ?Customer + { + $customer = null; + + $this->init(); + + $client_gateway_token = ClientGatewayToken::whereClientId($this->client->id)->whereCompanyGatewayId($this->company_gateway->id)->first(); + + if ($client_gateway_token && $client_gateway_token->gateway_customer_reference) { + $customer = Customer::retrieve($client_gateway_token->gateway_customer_reference); + } else { + $data['name'] = $this->client->present()->name(); + $data['phone'] = $this->client->present()->phone(); + + if (filter_var($this->client->present()->email(), FILTER_VALIDATE_EMAIL)) { + $data['email'] = $this->client->present()->email(); + } + + $customer = Customer::create($data); + } + + if (!$customer) { + throw new Exception('Unable to create gateway customer'); + } + + return $customer; + } + + public function refund(Payment $payment, $amount, $return_client_response = false) + { + $this->init(); + + /** Response from Stripe SDK/API. */ + $response = null; + + try { + $response = $this->stripe + ->refunds + ->create(['charge' => $payment->transaction_reference, 'amount' => $this->convertToStripeAmount($amount, $this->client->currency()->precision)]); + + if ($response->status == $response::STATUS_SUCCEEDED) { + SystemLogger::dispatch(['server_response' => $response, 'data' => request()->all(),], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->client); + + return [ + 'transaction_reference' => $response->charge, + 'transaction_response' => json_encode($response), + 'success' => $response->status == $response::STATUS_SUCCEEDED ? true : false, + 'description' => $response->metadata, + 'code' => $response, + ]; + } + + SystemLogger::dispatch(['server_response' => $response, 'data' => request()->all(),], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->client); + + return [ + 'transaction_reference' => null, + 'transaction_response' => json_encode($response), + 'success' => false, + 'description' => $response->failure_reason, + 'code' => 422, + ]; + } catch (Exception $e) { + SystemLogger::dispatch(['server_response' => $response, 'data' => request()->all(),], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->client); + + nlog($e->getMessage()); + + return [ + 'transaction_reference' => null, + 'transaction_response' => json_encode($response), + 'success' => false, + 'description' => $e->getMessage(), + 'code' => 422, + ]; + } + } + + public function verificationView(ClientGatewayToken $payment_method) + { + return $this->payment_method->verificationView($payment_method); + } + + public function processVerification(Request $request, ClientGatewayToken $payment_method) + { + return $this->payment_method->processVerification($request, $payment_method); + } + + public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment) + { + if ($request->type == 'source.chargeable') { + $payment->status_id = Payment::STATUS_COMPLETED; + $payment->save(); + } + + return response([], 200); + } + + public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) + { + return (new Charge($this))->tokenBilling($cgt, $payment_hash); + } + + /** + * Attach Stripe payment method to Stripe client. + * + * @param string $payment_method + * @param mixed $customer + * + * @return void + */ + public function attach(string $payment_method, $customer): void + { + try { + $stripe_payment_method = $this->getStripePaymentMethod($payment_method); + $stripe_payment_method->attach(['customer' => $customer->id]); + } catch (ApiErrorException | Exception $e) { + $this->processInternallyFailedPayment($this, $e); + } + } + + /** + * Detach payment method from the Stripe. + * https://stripe.com/docs/api/payment_methods/detach + * + * @param ClientGatewayToken $token + * @return void + */ + public function detach(ClientGatewayToken $token) + { + $stripe = new StripeClient( + $this->company_gateway->getConfigField('apiKey') + ); + + try { + $stripe->paymentMethods->detach($token->token); + } catch (Exception $e) { + SystemLogger::dispatch([ + 'server_response' => $e->getMessage(), 'data' => request()->all(), + ], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->client); + } + } + + public function getCompanyGatewayId(): int + { + return $this->company_gateway->id; + } + + /** + * Retrieve payment method from Stripe. + * + * @param string $source + * + * @return PaymentMethod|void + */ + public function getStripePaymentMethod(string $source) + { + try { + return PaymentMethod::retrieve($source); + } catch (ApiErrorException | Exception $e) { + return $this->processInternallyFailedPayment($this, $e); + } + } +} diff --git a/database/migrations/2021_04_12_095424_stripe_connect_gateway.php b/database/migrations/2021_04_12_095424_stripe_connect_gateway.php new file mode 100644 index 0000000000..110c36582d --- /dev/null +++ b/database/migrations/2021_04_12_095424_stripe_connect_gateway.php @@ -0,0 +1,49 @@ + 56, + 'name' => 'Stripe Connect', + 'provider' => 'StripeConnect', + 'sort_order' => 1, + 'key' => 'd14dd26a47cecc30fdd65700bfb67b34', + 'fields' => '{"apiKey":"", "publishableKey":""}' + ]; + + Gateway::create($gateway); + + if(Ninja::isNinja()) + { + Gateway::where('id', 20)->update(['visible' => 0]); + Gateway::where('id', 56)->update(['visible' => 1]); + } + + Model::guard(); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +}