From 6e57f1959861853e0052a19b01d61e21c7e7a479 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 13 Sep 2023 09:42:00 +1000 Subject: [PATCH] listen for charge.refunded for stripe --- app/Models/Payment.php | 18 +++ .../Stripe/Jobs/ChargeRefunded.php | 147 ++++++++++++++++++ app/PaymentDrivers/StripePaymentDriver.php | 7 + 3 files changed, 172 insertions(+) create mode 100644 app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 3773e8237d99..6df4fbc0dbaf 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -361,6 +361,24 @@ class Payment extends BaseModel return new PaymentService($this); } + /** + * $data = [ + 'id' => $payment->id, + 'amount' => 10, + 'invoices' => [ + [ + 'invoice_id' => $invoice->id, + 'amount' => 10, + ], + ], + 'date' => '2020/12/12', + 'gateway_refund' => false, + 'email_receipt' => false, + ]; + * + * @param array $data + * @return self + */ public function refund(array $data) :self { return $this->service()->refundPayment($data); diff --git a/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php b/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php new file mode 100644 index 000000000000..d0393dd2e752 --- /dev/null +++ b/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php @@ -0,0 +1,147 @@ +stripe_request = $stripe_request; + $this->company_key = $company_key; + $this->company_gateway_id = $company_gateway_id; + } + + public function handle() + { + MultiDB::findAndSetDbByCompanyKey($this->company_key); + nlog($this->stripe_request); + + $company = Company::query()->where('company_key', $this->company_key)->first(); + + $source = $this->stripe_request['object']; + $charge_id = $source['id']; + $amount_refunded = $source['amount_refunded'] ?? 0; + + $payment_hash_key = $source['metadata']['payment_hash'] ?? null; + + $company_gateway = CompanyGateway::query()->find($this->company_gateway_id); + $payment_hash = PaymentHash::query()->where('hash', $payment_hash_key)->first(); + + $stripe_driver = $company_gateway->driver()->init(); + + $stripe_driver->payment_hash = $payment_hash; + + /** @var \App\Models\Payment $payment **/ + $payment = Payment::query() + ->withTrashed() + ->where('company_id', $company->id) + ->where('transaction_reference', $charge_id) + ->first(); + + //don't touch if already refunded + if(!$payment || in_array($payment->status_id, [Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])) { + return; + } + + $stripe_driver->client = $payment->client; + + $amount_refunded = $stripe_driver->convertFromStripeAmount($amount_refunded, $payment->client->currency()->precision, $payment->client->currency()); + + if ($payment->status_id == Payment::STATUS_PENDING) { + $payment->service()->deletePayment(); + $payment->status_id = Payment::STATUS_FAILED; + $payment->save(); + return; + } + + if($payment->status_id == Payment::STATUS_COMPLETED) { + + $invoice_collection = $payment->paymentables + ->where('paymentable_type','invoices') + ->map(function ($pivot){ + return [ + 'invoice_id' => $pivot->paymentable_id, + 'amount' => $pivot->amount - $pivot->refunded + ]; + }); + + if($invoice_collection->count() == 1 && $invoice_collection->first()['amount'] >= $amount_refunded) { + //If there is only one invoice- and we are refunding _less_ than the amount of the invoice, we can just refund the payment + + $invoice_collection = $payment->paymentables + ->where('paymentable_type', 'invoices') + ->map(function ($pivot) use ($amount_refunded){ + return [ + 'invoice_id' => $pivot->paymentable_id, + 'amount' => $amount_refunded + ]; + }); + + } + elseif($invoice_collection->sum('amount') != $amount_refunded) { + //too many edges cases at this point, return early + return; + } + + $invoices = $invoice_collection->toArray(); + + $data = [ + 'id' => $payment->id, + 'amount' => $amount_refunded, + 'invoices' => $invoices, + 'date' => now()->format('Y-m-d'), + 'gateway_refund' => false, + 'email_receipt' => false, + ]; + + nlog($data); + + $payment->refund($data); + + $payment->private_notes .= 'Refunded via Stripe'; + return; + } + + } + + public function middleware() + { + return [new WithoutOverlapping($this->company_gateway_id)]; + } +} diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index a80c09789dcd..de4f2ac93e14 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -39,6 +39,7 @@ use App\PaymentDrivers\Stripe\FPX; use App\PaymentDrivers\Stripe\GIROPAY; use App\PaymentDrivers\Stripe\iDeal; use App\PaymentDrivers\Stripe\ImportCustomers; +use App\PaymentDrivers\Stripe\Jobs\ChargeRefunded; use App\PaymentDrivers\Stripe\Jobs\PaymentIntentFailureWebhook; use App\PaymentDrivers\Stripe\Jobs\PaymentIntentPartiallyFundedWebhook; use App\PaymentDrivers\Stripe\Jobs\PaymentIntentProcessingWebhook; @@ -790,6 +791,12 @@ class StripePaymentDriver extends BaseDriver } elseif ($request->data['object']['status'] == "pending") { return response()->json([], 200); } + } elseif ($request->type === "charge.refunded") { + + ChargeRefunded::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(5, 10))); + + return response()->json([], 200); + } return response()->json([], 200);