我正在通过Laravel和Laravel收银员的API实现按比例的订阅退款,我在理解文档中未涵盖的一些细节方面遇到了麻烦。我正在使用Stripe的托管页面进行订阅管理。
最终目标:我想在按比例取消订阅时自动退款给用户。我决定将这个过程分成两部分:1)扩展Stripe的webhook控制器,以调度一个使用收银员的退款方法计算和发出退款的作业;2)单独监听收费。refunddeent from Stripe,仅在退款成功时调整用户在Stripe上的余额。
我已经确定了需要涵盖的三个场景:
。用户通过卡支付订阅费用。B.用户通过其信用余额全额支付。C.用户通过信用卡支付,但金额被信用卡余额减少。
我想确保不会有任何退款问题或缺钱。对于这些场景,我假设我需要将各自的金额退还给原始支付方式(卡或信用卡余额)。
对于场景C,用户通过信用卡支付,金额减少了他们的信用余额,我不确定我是应该将全部退款到卡中还是将部分退款到余额中。有人能确认这种情况下的正确方法吗?
这是我正在进行的工作代码,处理在订阅取消时发出退款:
<?php
namespace AppJobs;
use AppMailAmountProblemRefund;
use AppMailExceptionProcessingRefund;
use AppMailInvoiceProblemRefund;
use AppMailNoSubscriptionForRefund;
use AppMailNotEnoughCreditBalanceForRefund;
use AppMailNoUserForRefund;
use AppMailRefundFailed;
use Exception;
use IlluminateSupportFacadesLog;
use IlluminateSupportFacadesMail;
use LaravelCashierSubscription;
use StripeRefund;
class ProcessStripeRefundJob extends Job
{
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(public array $payload)
{
}
public function run(): void
{
try {
// Find the subscription in the application using the Stripe subscription ID
$subscription = Subscription::where('stripe_id', $this->payload['data']['object']['id'])->first();
// Check if the subscription doesn't exist
if (!$subscription) {
Mail::to('*****@gmail.com')->send(new NoSubscriptionForRefund($this->payload));
Log::error('Subscription not found: ' . $this->payload['data']['object']['id']);
return;
}
$user = $subscription->user;
// Check if the user doesn't exist
if (!$user) {
Mail::to('*****@gmail.com')->send(new NoUserForRefund($this->payload));
Log::error('User not found for subscription: ' . $subscription->id);
return;
}
// Check if the user is currently on trial
if ($user->onTrial()) {
// The user is on trial, so no need to refund, just return a success response
return;
}
// Retrieve the proration cancellation invoice, which should be the latest invoice, and it should be
// available straight after the subscription is canceled, so at this point we should already have it here
$latestInvoice = $user->findInvoice($this->payload['data']['object']['latest_invoice']);
// Retrieve the previous invoice with the original subscription amount
$previousInvoice = $user->findInvoice($latestInvoice->lines->data[0]->proration_details->credited_items->invoice);
// Check if the invoices exist
if (!$latestInvoice || !$previousInvoice) {
Mail::to('*****@gmail.com')->send(new InvoiceProblemRefund(
$this->payload, // stripe event payload
$this->payload['data']['object']['latest_invoice'], // latest invoice ID
));
Log::error('Latest or previous invoice not found');
return;
}
// Refund amount comes from the first line item of the proration invoice, in my system currently this is the
// only line item as I offer only one subscription plan and user can only have one subscription at a time
// that value is in pennies
$refundAmount = abs($latestInvoice->lines->data[0]['amount']);
// Check if the refund amount is valid
if ($refundAmount <= 0) {
Mail::to('*****@gmail.com')->send(new AmountProblemRefund(
$this->payload, // stripe event payload
$refundAmount
));
Log::error('Invalid refund amount: ' . $refundAmount);
return;
}
// Get the user's balance - if this is a negative number, it means the user has a credit balance, meaning they
// have money that they can use to pay for their subscription next month, but we will use that credit balance
// to refund them prorated amount for the current billing cycle, that value is already in pennies
$balanceInPennies = abs($user->rawBalance());
// Determine if the payment was taken from the user's credit balance
$paymentFromCreditBalance = false; //TODO - implement this
$paymentFromOtherMethod = true; //TODO - implement this
// Handle the refund paid via the user's credit balance
if ($paymentFromCreditBalance) {
// TODO: Implement a refund process for cases when the payment was taken from the user's credit balance
}
// Handle the refund paid via other payment methods
if ($paymentFromOtherMethod) { // this means the payment was taken from the user's credit card
// Check if the user has enough credit to cover the refund
if ($balanceInPennies >= $refundAmount) {
// Create a refund in Stripe
$refund = $user->refund($previousInvoice->payment_intent, [
'amount' => $refundAmount,
'reason' => Refund::REASON_REQUESTED_BY_CUSTOMER,
'metadata' => [
'subscription_id' => $subscription->id,
'user_id' => $user->id,
'message' => 'Prorated refund for canceled subscription - adjust credit balance after it is processed'
],
]);
// Check if the refund was successful
if (!$refund) {
Log::error('Refund failed for user: ' . $user->id);
Mail::to('*****@gmail.com')->send(new RefundFailed(
$this->payload,
json_encode($refund)
));
}
} else {
// email myself about the failed refund
Mail::to('*****@gmail.com')->send(new NotEnoughCreditBalanceForRefund($this->payload));
}
}
} catch (Exception $exception) {
// log the error so I have some backup in case the email fails
Log::error('Error processing refund', [
'payload' => $this->payload,
'line' => $exception->getLine(),
'file' => $exception->getFile(),
'stack' => $exception->getTraceAsString(),
'exception' => $exception->getMessage(),
]);
// email myself about the failed refund
Mail::to('*****@gmail.com')->send(new ExceptionProcessingRefund(
$this->payload,
$exception->getLine(),
$exception->getFile(),
$exception->getMessage()
));
}
}
}
您指的是Stripe的客户信用余额吗?如果是这样,那么你就不能为它创建退款。但是,您可以重新调整他们的信用余额,以恢复您试图退款的发票中使用的任何信用。如果订阅发票是通过卡支付的,并且金额减少了信用余额,那么您将为卡支付的金额创建退款。那么,如果您也想重新调整他们的信用余额,那就由您决定了。