<?php namespace Civi\StripeImport; use Civi\Api4\Contact; use Civi\Api4\Contribution; use Civi\Api4\ContributionRecur; use Civi\Api4\Email; use Civi\Api4\FinancialTrxn; use Civi\Api4\StripeCustomer; use CRM_StripeImport_ExtensionUtil as E; class Charge extends Base { /** * @var string The Stripe charge ID */ private string $stripeChargeID; public function setStripeChargeID(string $ChargeID) { $this->stripeChargeID = $ChargeID; } public function getStripeChargeID(): string { return $this->stripeChargeID; } /** * @var int The CiviCRM Contact ID */ private int $contactID; public function setContactID(int $contactID) { $this->contactID = $contactID; } public function getContactID(): int { return $this->contactID; } public function hasContactID(): bool { return isset($this->contactID); } /** * @var int The CiviCRM Contribution ID */ private int $contributionID; public function setContributionID(int $contributionID) { $this->contributionID = $contributionID; } public function getContributionID(): int { return $this->contributionID; } private string $description = ''; public function setDescription(string $description) { $this->description = $description; } public function getDescription(): string { return $this->description; } private int $paymentInstrumentID; public function setPaymentInstrumentID(int $paymentInstrumentID) { $this->paymentInstrumentID = $paymentInstrumentID; } public function getPaymentInstrumentID(): int { return $this->paymentInstrumentID; } private int $financialTypeID; public function setFinancialTypeID(int $financialTypeID) { $this->financialTypeID = $financialTypeID; } public function getFinancialTypeID(): int { return $this->financialTypeID; } public function importOne(): array { // Retrieve the Stripe charge. $stripeCharge = $this->paymentProcessor->stripeClient->charges->retrieve($this->getStripeChargeID()); // Get the related invoice. $stripeInvoiceID = \CRM_Stripe_Api::getObjectParam('invoice_id', $stripeCharge); if (!empty($stripeInvoiceID)) { $stripeInvoice = $this->paymentProcessor->stripeClient->invoices->retrieve($stripeCharge->invoice); } if (empty($this->getDescription())) { if (isset($stripeInvoice) && !empty(\CRM_Stripe_Api::getObjectParam('description', $stripeInvoice))) { $this->setDescription(\CRM_Stripe_Api::getObjectParam('description', $stripeInvoice) ?? ''); } } if (empty($this->getDescription())) { $this->setDescription(\CRM_Stripe_Api::getObjectParam('description', $stripeCharge) ?? ''); } if (empty($this->getDescription())) { $this->setDescription('Stripe: Manual import via API'); } if (!$this->hasContactID()) { // Derive contact ID from stripe customer if (empty(\CRM_Stripe_Api::getObjectParam('customer_id', $stripeCharge))) { throw new \Exception('Missing customer_id in Stripe Charge. Cannot derive contact ID'); } $stripeCustomer = \Civi\Api4\StripeCustomer::get(FALSE) ->addWhere('customer_id', '=', \CRM_Stripe_Api::getObjectParam('customer_id', $stripeCharge)) ->addWhere('processor_id', '=', $this->getPaymentProcessorID()) ->addClause('OR', ['currency', 'IS EMPTY'], ['currency', '=', \CRM_Stripe_Api::getObjectParam('customer_id', $stripeCharge)]) ->addOrderBy('currency', 'DESC') ->execute() ->first(); if (empty($stripeCustomer)) { throw new \Exception('Stripe Customer: ' . \CRM_Stripe_Api::getObjectParam('customer_id', $stripeCharge) . ' not found in CiviCRM. Cannot derive contact ID'); } $this->setContactID($stripeCustomer['contact_id']); } // Check for a subscription. if (isset($stripeInvoice)) { $subscriptionID = \CRM_Stripe_Api::getObjectParam('subscription_id', $stripeInvoice); $contributionRecurID = NULL; if ($subscriptionID) { // Lookup the contribution_recur_id. $contributionRecur = ContributionRecur::get(FALSE) ->addWhere('trxn_id', '=', $subscriptionID) ->addWhere('is_test', '=', $this->paymentProcessor->getPaymentProcessor()['is_test']) ->execute() ->first(); if (!$contributionRecur) { throw new \Exception(E::ts('The charge has a subscription, but the subscription is not in CiviCRM. Please import the subscription and try again.')); } $contributionRecurID = $contributionRecur['id']; } } // Prepare to either create or update a contribution record in CiviCRM. $contributionParams = []; // We update these parameters regardless if it's a new contribution // or an existing contributions. if (isset($stripeInvoice)) { $contributionParams['receive_date'] = \CRM_Stripe_Api::getObjectParam('receive_date', $stripeInvoice); $contributionParams['total_amount'] = \CRM_Stripe_Api::getObjectParam('total_amount', $stripeInvoice); } else { $contributionParams['receive_date'] = \CRM_Stripe_Api::getObjectParam('receive_date', $stripeCharge); $contributionParams['total_amount'] = \CRM_Stripe_Api::getObjectParam('total_amount', $stripeCharge); } // Check if a contribution already exists. if (empty($this->getContributionID())) { // See if we have a matching payment for this trxn_id and get the linked contribution ID $financialTrxn = FinancialTrxn::get(FALSE) ->addSelect('contribution.id') ->addJoin('Contribution AS contribution', 'LEFT', 'EntityFinancialTrxn') ->addWhere('is_payment', '=', TRUE) ->addWhere('trxn_id', '=', $this->getStripeChargeID()) ->execute() ->first(); if (!empty($financialTrxn['contribution.id'])) { $this->setContributionID($financialTrxn['contribution.id']); } } // If no contribution was found we need to create it if (empty($this->getContributionID())) { // We have to build all the parameters. $contributionParams['contact_id'] = $this->getContactID(); $contributionParams['currency'] = \CRM_Stripe_Api::getObjectParam('currency', $stripeCharge); $contributionParams['trxn_id'] = $this->getStripeChargeID(); $contributionParams['payment_instrument_id'] = $this->getPaymentInstrumentID(); $contributionParams['financial_type_id'] = $this->getFinancialTypeID(); $contributionParams['is_test'] = $this->paymentProcessor->getPaymentProcessor()['is_test']; $contributionParams['contribution_source'] = $this->getDescription(); $contributionParams['contribution_status_id:name'] = 'Pending'; if (!empty($contributionRecurID)) { $contributionParams['contribution_recur_id'] = $contributionRecurID; } $contribution = Contribution::create(FALSE) ->setValues($contributionParams) ->execute() ->first(); $this->setContributionID($contribution['id']); } $webhookEventProcessor = new \Civi\Stripe\Webhook\Events($this->getPaymentProcessorID()); $webhookEventProcessor->setData(\Stripe\StripeObject::constructFrom(['object' => $stripeCharge])); $return = $webhookEventProcessor->getResultObject(); switch (\CRM_Stripe_Api::getObjectParam('status', $stripeCharge)) { case 'succeeded': $webhookEventProcessor->setEventType('charge.succeeded'); $return = $webhookEventProcessor->doChargeSucceeded(); break; case 'failed': $webhookEventProcessor->setEventType('charge.failed'); $return = $webhookEventProcessor->doChargeFailed(); break; case 'pending': // Nothing further to do (we already created the contribution and should not create a payment or failure yet) $return->ok = TRUE; $return->message = 'charge is pending'; } return [ 'contribution_id' => $this->getContributionID(), 'result' => $return, ]; } }