Changeset View
Changeset View
Standalone View
Standalone View
src/app/Providers/Payment/Coinbase.php
Show First 20 Lines • Show All 135 Lines • ▼ Show 20 Lines | class Coinbase extends \App\Providers\PaymentProvider | ||||
* - methodId: Payment method | * - methodId: Payment method | ||||
* | * | ||||
* @return array Provider payment data: | * @return array Provider payment data: | ||||
* - id: Operation identifier | * - id: Operation identifier | ||||
* - redirectUrl: the location to redirect to | * - redirectUrl: the location to redirect to | ||||
*/ | */ | ||||
public function payment(Wallet $wallet, array $payment): ?array | public function payment(Wallet $wallet, array $payment): ?array | ||||
{ | { | ||||
if ($payment['type'] == self::TYPE_RECURRING) { | if ($payment['type'] == Payment::TYPE_RECURRING) { | ||||
throw new \Exception("not supported"); | throw new \Exception("not supported"); | ||||
} | } | ||||
$amount = $payment['amount'] / 100; | $amount = $payment['amount'] / 100; | ||||
$post = [ | $post = [ | ||||
'json' => [ | 'json' => [ | ||||
"name" => \config('app.name'), | "name" => \config('app.name'), | ||||
Show All 17 Lines | public function payment(Wallet $wallet, array $payment): ?array | ||||
if ($code !== 201) { | if ($code !== 201) { | ||||
$this->logError("Failed to create coinbase charge", $response); | $this->logError("Failed to create coinbase charge", $response); | ||||
throw new \Exception("Failed to create coinbase charge: {$code}"); | throw new \Exception("Failed to create coinbase charge: {$code}"); | ||||
} | } | ||||
$json = json_decode($response->getBody(), true); | $json = json_decode($response->getBody(), true); | ||||
// Store the payment reference in database | // Store the payment reference in database | ||||
$payment['status'] = self::STATUS_OPEN; | $payment['status'] = Payment::STATUS_OPEN; | ||||
//We take the code instead of the id because it fits into our current db schema and the id doesn't | //We take the code instead of the id because it fits into our current db schema and the id doesn't | ||||
$payment['id'] = $json['data']['code']; | $payment['id'] = $json['data']['code']; | ||||
//We store in satoshis (the database stores it as INTEGER type) | //We store in satoshis (the database stores it as INTEGER type) | ||||
$payment['currency_amount'] = $json['data']['pricing']['bitcoin']['amount'] * self::SATOSHI_MULTIPLIER; | $payment['currency_amount'] = $json['data']['pricing']['bitcoin']['amount'] * self::SATOSHI_MULTIPLIER; | ||||
$payment['currency'] = 'BTC'; | $payment['currency'] = 'BTC'; | ||||
$this->storePayment($payment, $wallet->id); | $this->storePayment($payment, $wallet->id); | ||||
Show All 37 Lines | class Coinbase extends \App\Providers\PaymentProvider | ||||
* @return bool True on success, False on failure | * @return bool True on success, False on failure | ||||
*/ | */ | ||||
public function cancel(Wallet $wallet, $paymentId): bool | public function cancel(Wallet $wallet, $paymentId): bool | ||||
{ | { | ||||
$response = $this->client()->request('POST', "/charges/{$paymentId}/cancel"); | $response = $this->client()->request('POST', "/charges/{$paymentId}/cancel"); | ||||
if ($response->getStatusCode() == 200) { | if ($response->getStatusCode() == 200) { | ||||
$db_payment = Payment::find($paymentId); | $db_payment = Payment::find($paymentId); | ||||
$db_payment->status = self::STATUS_CANCELED; | $db_payment->status = Payment::STATUS_CANCELED; | ||||
$db_payment->save(); | $db_payment->save(); | ||||
} else { | } else { | ||||
$this->logError("Failed to cancel payment", $response); | $this->logError("Failed to cancel payment", $response); | ||||
return false; | return false; | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | public function webhook(): int | ||||
} | } | ||||
$payment = Payment::find($payment_id); | $payment = Payment::find($payment_id); | ||||
if (empty($payment)) { | if (empty($payment)) { | ||||
return 200; | return 200; | ||||
} | } | ||||
$newStatus = self::STATUS_PENDING; | $newStatus = Payment::STATUS_PENDING; | ||||
// Even if we receive the payment delayed, we still have the money, and therefore credit it. | // Even if we receive the payment delayed, we still have the money, and therefore credit it. | ||||
if ($type == 'charge:resolved' || $type == 'charge:delayed') { | if ($type == 'charge:resolved' || $type == 'charge:delayed') { | ||||
// The payment is paid. Update the balance | // The payment is paid. Update the balance | ||||
if ($payment->status != self::STATUS_PAID && $payment->amount > 0) { | if ($payment->status != Payment::STATUS_PAID && $payment->amount > 0) { | ||||
$credit = true; | $credit = true; | ||||
} | } | ||||
$newStatus = self::STATUS_PAID; | $newStatus = Payment::STATUS_PAID; | ||||
} elseif ($type == 'charge:failed') { | } elseif ($type == 'charge:failed') { | ||||
// Note: I didn't find a way to get any description of the problem with a payment | // Note: I didn't find a way to get any description of the problem with a payment | ||||
\Log::info(sprintf('Coinbase payment failed (%s)', $payment->id)); | \Log::info(sprintf('Coinbase payment failed (%s)', $payment->id)); | ||||
$newStatus = self::STATUS_FAILED; | $newStatus = Payment::STATUS_FAILED; | ||||
} | } | ||||
DB::beginTransaction(); | DB::beginTransaction(); | ||||
// This is a sanity check, just in case the payment provider api | // This is a sanity check, just in case the payment provider api | ||||
// sent us open -> paid -> open -> paid. So, we lock the payment after | // sent us open -> paid -> open -> paid. So, we lock the payment after | ||||
// recivied a "final" state. | // recivied a "final" state. | ||||
$pending_states = [self::STATUS_OPEN, self::STATUS_PENDING, self::STATUS_AUTHORIZED]; | $pending_states = [Payment::STATUS_OPEN, Payment::STATUS_PENDING, Payment::STATUS_AUTHORIZED]; | ||||
if (in_array($payment->status, $pending_states)) { | if (in_array($payment->status, $pending_states)) { | ||||
$payment->status = $newStatus; | $payment->status = $newStatus; | ||||
$payment->save(); | $payment->save(); | ||||
} | } | ||||
if (!empty($credit)) { | if (!empty($credit)) { | ||||
self::creditPayment($payment); | $payment->credit('Coinbase'); | ||||
} | } | ||||
DB::commit(); | DB::commit(); | ||||
return 200; | return 200; | ||||
} | } | ||||
/** | /** | ||||
* Apply the successful payment's pecunia to the wallet | |||||
*/ | |||||
protected static function creditPayment($payment) | |||||
{ | |||||
// TODO: Localization? | |||||
$description = 'Payment'; | |||||
$description .= " transaction {$payment->id} using Coinbase"; | |||||
$payment->wallet->credit($payment, $description); | |||||
} | |||||
/** | |||||
* List supported payment methods. | * List supported payment methods. | ||||
* | * | ||||
* @param string $type The payment type for which we require a method (oneoff/recurring). | * @param string $type The payment type for which we require a method (oneoff/recurring). | ||||
* @param string $currency Currency code | * @param string $currency Currency code | ||||
* | * | ||||
* @return array Array of array with available payment methods: | * @return array Array of array with available payment methods: | ||||
* - id: id of the method | * - id: id of the method | ||||
* - name: User readable name of the payment method | * - name: User readable name of the payment method | ||||
* - minimumAmount: Minimum amount to be charged in cents | * - minimumAmount: Minimum amount to be charged in cents | ||||
* - currency: Currency used for the method | * - currency: Currency used for the method | ||||
* - exchangeRate: The projected exchange rate (actual rate is determined during payment) | * - exchangeRate: The projected exchange rate (actual rate is determined during payment) | ||||
* - icon: An icon (icon name) representing the method | * - icon: An icon (icon name) representing the method | ||||
*/ | */ | ||||
public function providerPaymentMethods(string $type, string $currency): array | public function providerPaymentMethods(string $type, string $currency): array | ||||
{ | { | ||||
$availableMethods = []; | $availableMethods = []; | ||||
if ($type == self::TYPE_ONEOFF) { | if ($type == Payment::TYPE_ONEOFF) { | ||||
$availableMethods['bitcoin'] = [ | $availableMethods['bitcoin'] = [ | ||||
'id' => 'bitcoin', | 'id' => 'bitcoin', | ||||
'name' => "Bitcoin", | 'name' => "Bitcoin", | ||||
'minimumAmount' => 0.001, | 'minimumAmount' => 0.001, | ||||
'currency' => 'BTC' | 'currency' => 'BTC' | ||||
]; | ]; | ||||
} | } | ||||
Show All 26 Lines |