Open lwillems opened 2 years ago
Hello @lwillems,
I'm also using this library with the Sylius plugin to provide subscriptions on our shop.
ConvertSubscriptionAction
(it will be nearly the same action as this one), it will add subscription_data
to the array
used to create a Stripe Session
object. Tips : your payment object is available on the $request
object : $request->getSource()
allowing you to know what is the products you will have to add to the subscription_data
array.checkout.session.completed
, checkout.session.async_payment_failed
and checkout.session.async_payment_succeeded
are already setup into this library, you just have to setup the webhook into Stripe dashboard (the webhook url is always the notify unsafe one describe in the doc). Async events are required for sepa_debit
or other async payment methods.invoice.payment_failed
and invoice.payment_succeeded
by creating two new Payum actions : InvoicePaymentFailedWebhookEventAction
and InvoicePaymentSucceededWebhookEventAction
extending \FluxSE\PayumStripe\Action\Api\WebhookEvent\AbstractWebhookEventAction
. Example : <?php
declare(strict_types=1);
namespace App\PayumStripe\Action\StripeCheckoutSession\Api\WebhookEvent;
use FluxSE\PayumStripe\Wrapper\EventWrapperInterface;
use Payum\Core\Exception\RequestNotSupportedException;
use FluxSE\PayumStripe\Action\Api\WebhookEvent\AbstractWebhookEventAction;
use Stripe\Event;
final class InvoicePaymentSucceededWebhookEventAction extends AbstractWebhookEventAction
{
private foo $handler;
public function __construct(foo $handler)
{
$this->handler = $handler;
}
protected function getSupportedEventTypes(): array
{
return [
Event::INVOICE_PAYMENT_SUCCEEDED,
];
}
public function execute($request): void
{
RequestNotSupportedException::assertSupports($this, $request);
/** @var EventWrapperInterface $eventWrapper */
$eventWrapper = $request->getModel();
$event = $eventWrapper->getEvent();
$this->handler->handle($event);
}
}
The service $this->handler
will be in charge of making what you need to do with you own doctrine entities.
Tips: you can also execute other Payum requests by implementing the Payum\Core\GatewayAwareInterface
using the Payum\Core\GatewayAwareTrait
and give the $this->gateway
to $this->handler->handle($this->gateway, $event)
.
customer.subscription.updated
event to update my database when the subscription is created in Stripe, when it expires or status changes.Hope it helps you 😉
Hi @Prometee
It was so helpful for my understanding of payum architecture. You're awesome as your bundle thanks. About subscription auto-renewal, i am not able to test, can you confirm that when it's occurs, invoice.payment_succeeded event is sent with invoice billing_reason to subscription_cycle and with initial metadata from subscription creation (token_hash) ?
Regards,
@lwillems Yes those events are triggered, to test them I made a 2 days cycle sub.
You can store some info into the metadata
of the created subscription :
# ConvertSubscriptionAction.php (#1 of my previous comment)
$details->offsetSet('subscription_data', [
// ... other attributes like items etc
'metadata' => [
'my_metadata_id' => $something,
// 'token_hash' will be set automatically by this library, so you don't have to worry about
// it and this token hash won't be consumed and deleted after the first use because the
// notify unsafe which will receive it is not aware of it, only a notify safe is using a token
// and delete it after use.
],
]);
// dont forget to `setResult` after adding what you needed to the `$details` array
$request->setResult($details);
About the billing reason, I have several checks like this into my InvoicePaymentSucceededHandler
to avoid processing invoice of the first cycle for example:
use Stripe\Invoice as StripeInvoice;
if (
// skip the first created invoice
$stripeInvoice->billing_reason === StripeInvoice::BILLING_REASON_SUBSCRIPTION_CREATE
) {
return null;
}
@Prometee Nice tips for 2 days cycle and metadata, will give it a try. So as far as i understand, you are using InvoicePaymentSucceededWebhookEventAction only for renewal and CheckoutSessionCompletedAction for 1st created subscription ? I guess you have overrided native CheckoutSessionCompletedAction ? because bundle native one is only updating payum storage entity details and you probably need to update your own entity at this step too ?
Regards
So as far as i understand, you are using InvoicePaymentSucceededWebhookEventAction only for renewal and CheckoutSessionCompletedAction for 1st created subscription ?
Yes, because the first order is already created by Sylius in my case, then each new Stripe invoices are synced to Sylius Order for accounting reasons (we are not using Stripe invoices for our accounting).
I guess you have overrided native CheckoutSessionCompletedAction ? because bundle native one is only updating payum storage entity details and you probably need to update your own entity at this step too ?
If you are using doctrine then Payum is triggering the update of the Payment
entity if needed. If your entity is linked to this entity then Doctrine will also save any update of it. If not you will have to use your doctrine manager carefully, to update your subscription entity.
@Prometee : What is the most effective method for tracking all payments associated with subscriptions? The initial payment entries are created when a user subscribes, but how can we generate additional payment entries when the subscription is renewed? Is there a more efficient approach to handle this process?
@oulfr if you use Checkout Session with subscription
type, the initial payment will create a Stripe Subscription and it will be saved in the Payment
details
field. Then Stripe will handle the subscription payments on each cycles (month for ex).
You can handle subscriptions with very different maners, you can ask for a Stripe SetupIntent
for the first initialization of the sub and then handle each payments by yourself. You can even let Stripe manage the subscription but it will require to sync all new Orders and Payments if you already have an accounting software of want your shop to deliver the invoice.
On my side, since my company is only selling software licences, I let Stripe handle all the cycles and sync the invoices to orders, payments into my shop. And I also manage the subscription from within the shop to pause, cancel, change the default payment etc.
Thank you for your response. Do you have an example of how you have synced invoices with orders and payments in your shop? or what do you have implemented ? In my side I use Checkout Session to handle the subscription
@oulfr unfortunately this part is on closed sources, I simply make some transformer services which create an order and a payment when the webhook event Stripe\Event::INVOICE_PAYMENT_SUCCEEDED
is received. You will also listen for :
Stripe\Event::INVOICE_PAYMENT_FAILED
Stripe\Event::CUSTOMER_SUBSCRIPTION_UPDATED
Stripe\Event::CUSTOMER_SUBSCRIPTION_DELETED
to be able to cover all the differents flow happening during a Subscription life cycle.
@Prometee: It's exactly what i planned to do, Thank you for your support
Hi
Currently using stripe and this package (symfony bundle) to build as subscription on stripe : According stripe documentation Webhooks should be used to provision and renewal (checkout.session.completed and invoice.paid), invoice.paid is not implement but i can register an extra action. I have implemented webhook with dedicated (unsafe) route provided, i am new to payum and i am a bit confused on how to hook my own logical. As far as i understand, when webhook is received, payment is retrieved and detail is updated (stored in doctrine in my case), but i need to perform extra steps on my customer account (another entity linked to payment), such as update subscription date.
Thanks for your help Regards