Skip to content

Commit

Permalink
Redirect to a second checkout on subscriptions with platform tip (#587)
Browse files Browse the repository at this point in the history
* Redirect to a second checkout on subscriptions with donation

* Update currencies in stripe checkout

* Add metadata to stripe price

* Fix stripe webhook controller

* Fix typo in variable call
  • Loading branch information
subiabre authored Dec 22, 2023
1 parent 0d4c217 commit e8544e7
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 43 deletions.
6 changes: 6 additions & 0 deletions src/Goteo/Controller/InvestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use Goteo\Payment\Payment;
use Goteo\Util\Monolog\Processor\WebProcessor;
use Omnipay\Common\Message\ResponseInterface;
use Omnipay\Stripe\Subscription\Message\DonationResponse;
use RuntimeException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
Expand Down Expand Up @@ -523,6 +524,11 @@ public function completePaymentAction($project_id, $invest_id, Request $request)
if (!$response instanceof ResponseInterface) {
throw new RuntimeException('This response does not implements ResponseInterface.');
}

if ($response instanceof DonationResponse) {
return $this->dispatch(AppEvents::INVEST_INIT_REDIRECT, new FilterInvestRequestEvent($method, $response))->getHttpResponse();
}

if ($response->isSuccessful()) {
// Goto User data fill
Message::info(Text::get('invest-payment-success'));
Expand Down
33 changes: 20 additions & 13 deletions src/Goteo/Controller/StripeSubscriptionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
use Goteo\Payment\Method\StripeSubscriptionPaymentMethod;
use Goteo\Repository\InvestRepository;
use Stripe\Event;
use Stripe\Invoice;
use Stripe\StripeClient;
use Stripe\Webhook;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class StripeSubscriptionController extends Controller
{
Expand All @@ -46,21 +48,19 @@ public function subscriptionsWebhook(Request $request)

switch ($event->type) {
case Event::TYPE_INVOICE_PAYMENT_SUCCEEDED:
$response = $this->createInvest($event->data->object->id);
return $this->processInvoice($event->data->object->id);
case Event::CHARGE_REFUNDED:
$response = $this->chargeRefunded($event);
case Event::TYPE_INVOICE_PAYMENT_FAILED:
break;
case Event::TYPE_CUSTOMER_SUBSCRIPTION_DELETED:
break;
return $this->processRefund($event);
default:
return new JsonResponse(
['data' => sprintf("The event %s is not supported.", $event->type)],
Response::HTTP_BAD_REQUEST
);
break;
}

return new JsonResponse($response);
}

private function chargeRefunded(Event $event): array
private function processRefund(Event $event): JsonResponse
{
$object = $event->data->object;
if (!$object || !$object->invoice) {
Expand All @@ -76,17 +76,24 @@ private function chargeRefunded(Event $event): array
$invest->save();
}

return $invests;
return new JsonResponse(['data' => $invests], Response::HTTP_OK);
}

private function createInvest(string $invoiceId): Invest
private function processInvoice(string $invoiceId): JsonResponse
{
$invoice = $this->stripe->invoices->retrieve($invoiceId);
$subscription = $this->stripe->subscriptions->retrieve($invoice->subscription);
if ($invoice->billing_reason === Invoice::BILLING_REASON_SUBSCRIPTION_CREATE) {
return new JsonResponse([
'data' => Invest::get($invoice->lines->data[0]->price->metadata->invest),
Response::HTTP_OK
]);
}

/** @var User */
$user = User::getByEmail($invoice->customer_email);

$subscription = $this->stripe->subscriptions->retrieve($invoice->subscription);

$invest = new Invest([
'amount' => $invoice->amount_paid / 100,
'donate_amount' => 0,
Expand All @@ -105,6 +112,6 @@ private function createInvest(string $invoiceId): Invest
throw new \RuntimeException(Text::get('invest-create-error') . '<br />' . implode('<br />', $errors));
}

return $invest;
return new JsonResponse(['data' => $invest], Response::HTTP_CREATED);
}
}
3 changes: 2 additions & 1 deletion src/Omnipay/Stripe/Subscription/Gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Omnipay\Common\AbstractGateway;
use Omnipay\Common\Http\ClientInterface;
use Omnipay\Common\Message\ResponseInterface;
use Omnipay\Stripe\Subscription\Message\SubscriptionRequest;
use Omnipay\Stripe\Subscription\Message\SubscriptionResponse;
use Symfony\Component\HttpFoundation\Request as HttpRequest;
Expand Down Expand Up @@ -31,7 +32,7 @@ public function purchase($options = array())
return new SubscriptionRequest($options);
}

public function completePurchase($options = array()): SubscriptionResponse
public function completePurchase($options = array()): ResponseInterface
{
$request = new SubscriptionRequest($options);

Expand Down
40 changes: 40 additions & 0 deletions src/Omnipay/Stripe/Subscription/Message/DonationResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Omnipay\Stripe\Subscription\Message;

use Goteo\Application\Config;
use Omnipay\Common\Message\AbstractResponse;
use Omnipay\Common\Message\RedirectResponseInterface;
use Omnipay\Common\Message\RequestInterface;
use Stripe\Checkout\Session as StripeSession;
use Stripe\StripeClient;

class DonationResponse extends AbstractResponse implements RedirectResponseInterface
{
private StripeClient $stripe;

private StripeSession $checkout;

public function __construct(RequestInterface $request, string $checkoutSessionId)
{
parent::__construct($request, $checkoutSessionId);

$this->stripe = new StripeClient(Config::get('payments.stripe.secretKey'));
$this->checkout = $this->stripe->checkout->sessions->retrieve($checkoutSessionId);
}

public function isSuccessful()
{
return $this->checkout->status === StripeSession::STATUS_COMPLETE;
}

public function isRedirect()
{
return true;
}

public function getRedirectUrl()
{
return $this->checkout->url;
}
}
108 changes: 79 additions & 29 deletions src/Omnipay/Stripe/Subscription/Message/SubscriptionRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
use Goteo\Application\Config;
use Goteo\Library\Text;
use Goteo\Model\Invest;
use Goteo\Model\Project;
use Goteo\Model\User;
use Omnipay\Common\Message\AbstractRequest;
use Stripe\Checkout\Session as CheckoutSession;
use Stripe\Customer;
use Stripe\Product;
use Stripe\StripeClient;

class SubscriptionRequest extends AbstractRequest
{
private array $data;

private StripeClient $stripe;

public function __construct(array $data)
Expand All @@ -35,39 +38,37 @@ public function sendData($data)
$user = $data['user'];
$invest = $data['invest'];

/** @var Project */
$project = $invest->getProject();

$price = $this->stripe->prices->create([
'unit_amount' => ($invest->amount + $invest->donate_amount) * 100,
'currency' => 'eur',
'recurring' => ['interval' => 'month'],
'product' => $this->getStripeProduct($invest)->id
]);
$customer = $this->getStripeCustomer($user)->id;
$metadata = $this->getMetadata($project, $invest, $user);

$successUrl = sprintf('%s?session_id={CHECKOUT_SESSION_ID}', $this->getRedirectUrl(
'invest',
$project->id,
$invest->id,
'complete'
));

$session = $this->stripe->checkout->sessions->create([
'customer' => $this->getStripeCustomer($data['user'])->id,
'success_url' => sprintf('%s?session_id={CHECKOUT_SESSION_ID}', $this->getRedirectUrl(
'invest',
$project->id,
$invest->id,
'complete'
)),
'cancel_url' => $this->getRedirectUrl(
'project',
$project->id
),
'mode' => 'subscription',
'customer' => $customer,
'success_url' => $successUrl,
'cancel_url' => $this->getRedirectUrl('project', $project->id),
'mode' => CheckoutSession::MODE_SUBSCRIPTION,
'line_items' => [
[
'price' => $price->id,
'price' => $this->stripe->prices->create([
'unit_amount' => $invest->amount * 100,
'currency' => $project->currency,
'recurring' => ['interval' => 'month'],
'product' => $this->getStripeProduct($invest)->id,
'metadata' => $metadata
])->id,
'quantity' => 1
]
],
'metadata' => [
'project' => $project->id,
'reward' => $this->getInvestReward($invest, ''),
'user' => $user->id,
]
'metadata' => $metadata
]);

return new SubscriptionResponse($this, $session->id);
Expand All @@ -78,13 +79,51 @@ public function completePurchase(array $options = [])
// Dirty sanitization because something is double concatenating the ?session_id query param
$sessionId = explode('?', $_REQUEST['session_id'])[0];
$session = $this->stripe->checkout->sessions->retrieve($sessionId);
$metadata = $session->metadata->toArray();

$this->stripe->subscriptions->update(
$session->subscription, [
'metadata' => $session->metadata->toArray()
]);
if ($session->subscription) {
$this->stripe->subscriptions->update(
$session->subscription,
[
'metadata' => $metadata
]
);

if ($metadata['donate_amount'] < 1) {
return new SubscriptionResponse($this, $session->id);
}

$donation = $this->stripe->checkout->sessions->create([
'customer' => $this->getStripeCustomer(User::get($metadata['user']))->id,
'success_url' => sprintf('%s?session_id={CHECKOUT_SESSION_ID}', $this->getRedirectUrl(
'invest',
$metadata['project'],
$metadata['invest'],
'complete'
)),
'cancel_url' => $this->getRedirectUrl('project', $metadata['project']->id),
'mode' => CheckoutSession::MODE_PAYMENT,
'line_items' => [
[
'price' => $this->stripe->prices->create([
'unit_amount' => $metadata['donate_amount'] * 100,
'currency' => Config::get('currency'),
'product_data' => [
'name' => Text::get('donate-meta-description')
]
])->id,
'quantity' => 1
]
],
'metadata' => $metadata
]);

return new DonationResponse($this, $donation->id);
}

return new SubscriptionResponse($this, $session->id);
if ($session->payment_intent) {
return new SubscriptionResponse($this, $session->id);
}
}

private function getRedirectUrl(...$args): string
Expand Down Expand Up @@ -157,4 +196,15 @@ private function getStripeProduct(Invest $invest): Product
]);
}
}

private function getMetadata(Project $project, Invest $invest, User $user): array
{
return [
'donate_amount' => $invest->donate_amount,
'project' => $project->id,
'invest' => $invest->id,
'reward' => $this->getInvestReward($invest, ''),
'user' => $user->id,
];
}
}

0 comments on commit e8544e7

Please sign in to comment.