LaravelBitPay enables you and your business to transact in Bitcoin, Bitcoin Cash and 10+ other BitPay-supported cryptocurrencies within your Laravel application.
Requires PHP 7.4+
If upgrading from v4, please follow MIGRATION.md
- ✅ Invoices
- ✅ Refunds
- ✅ Bills
- ✅ Subscriptions
- ✅ Settlements
- ✅ Ledgers
- ✅ Recipients
- ✅ Payouts
- ✅ Currencies
- ✅ Rates
- Installation
- Examples
- Testing
- Changelog
- Contributing
- Security
- Credits
- License
You can install the package via composer:
composer require vrajroham/laravel-bitpay
Publish config file with:
php artisan vendor:publish --provider="Vrajroham\LaravelBitpay\LaravelBitpayServiceProvider"
This will create a laravel-bitpay.php
file inside your config directory.
Add the following keys to your .env
file and update the values to match your
preferences (read more about configuration):
BITPAY_PRIVATE_KEY_PATH=
BITPAY_NETWORK=testnet
BITPAY_KEY_STORAGE_PASSWORD=RandomPasswordForEncryption
BITPAY_ENABLE_MERCHANT=true
BITPAY_ENABLE_PAYOUT=false
BITPAY_MERCHANT_TOKEN=
BITPAY_PAYOUT_TOKEN=
The laravel-bitpay:createkeypair
command generates a BitPay API Token and Pairing Code for each enabled facade:
php artisan laravel-bitpay:createkeypair
ℹ️ By default, the command will use the (valid) existing private key located at
BITPAY_PRIVATE_KEY_PATH
. You may specify the--fresh
or-f
option to explicitly generate a fresh private key, from which tokens are derived.
After successful API Token generation, you will need to approve it by visiting the provided link.
payout
facade must be enabled on your BitPay merchant account before you can approve and use
the related API Token. This means you won't be able to perform actions on the Recipients
and Payouts resources. To enable Payouts
functionality, Contact BitPay Support.
BitPay resource status updates are completely based on webhooks (IPNs). LaravelBitPay is fully capable of automatically
handling webhook requests. Whenever a webhook is received from BitPay's server, BitpayWebhookReceived
event is
dispatched. Take the following steps to configure your application for webhook listening:
Resolve the bitPayWebhook
route macro in your desired route file (web.php
is recommended). The macro accepts a
single, optional argument, which is the URI path at which you want to receive BitPay webhook POST
requests. If none is
provided, it defaults to 'laravel-bitpay/webhook'
:
// ... your other 'web' routes
Route::bitPayWebhook(); // https://example.com/laravel-bitpay/webhook
// OR ...
Route::bitPayWebhook('receive/webhooks/here'); // https://example.com/receive/webhooks/here
ℹ️ To retrieve your newly created webhook route anywhere in your application, use:
route('laravel-bitpay.webhook.capture')
LaravelBitPay also offers the convenience of auto-populating your configured webhook url on applicable resources. Specifically when:
- Creating an Invoice
- Inviting Recipients
- Creating a Payout/Payout Batch
You may enable this feature per-resource by uncommenting the respective entry within the auto_populate_webhook
array
found in the laravel-bitpay.php
config file.
$resource->setNotificationURL('https://...')
during resource
initialization, auto-population is overridden.
Start by generating an event listener:
php artisan make:listener BitPayWebhookListener --event=\Vrajroham\LaravelBitpay\Events\BitpayWebhookReceived
Then, implement your application-specific logic in the handle(...)
function of the generated listener.
In the following example, we assume you have previously created an invoice, storing its token
on your internal Order
model:
/**
* Handle the webhook event, keeping in mind that the server doesn't trust the client (us), so neither should
* we trust the server. Well, trust, but verify.
*
* @param BitpayWebhookReceived $event
* @return void
*/
public function handle(BitpayWebhookReceived $event)
{
// Extract event payload
$payload = $event->payload;
// Verify that webhook is for a BitPay Invoice resource
if (in_array($payload['event']['code'], array_keys(BitPayConstants::INVOICE_WEBHOOK_CODES))) {
try {
// Do not trust the webhook data. Pull the referenced Invoice from BitPay's server
$invoice = LaravelBitpay::getInvoice($payload['data']['id']);
// Now grab our internal Order instance for this supposed Invoice
$order = Order::whereOrderId($invoice->getOrderId())->first();
// Verify Invoice token, previously stored at time of creation
// Learn more at: https://github.com/vrajroham/laravel-bitpay#create-an-invoice
if ($invoice->getToken() !== $order->invoice_token) {
return;
}
$invoice_status = $invoice->getStatus();
// Do something about the new Invoice status
if ($invoice_status === InvoiceStatus::Paid) {
$order->update(['status' => $invoice_status]) && OrderStatusChanged::dispatch($order->refresh());
}
} catch (BitPayException $e) {
Log::error($e);
}
}
}
Finally, map your listener to the BitpayWebhookReceived
event inside the $listen
array of
your EventServiceProvider
:
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
// ... other event-listener mappings
BitpayWebhookReceived::class => [
BitPayWebhookListener::class,
],
]
Invoices are time-sensitive payment requests addressed to specific buyers. An invoice has a fixed price, typically denominated in fiat currency. It also has an equivalent price in the supported cryptocurrencies, calculated by BitPay, at a locked exchange rate with an expiration time of 15 minutes.
In this example we assume you've already created an instance of your equivalent Order
model, to be associated with
this Invoice (referred to as $order
):
// Create instance of Invoice
$invoice = LaravelBitpay::Invoice(449.99, Currency::USD); // Always use the BitPay Currency model to prevent typos
// Set item details (Only 1 item per Invoice)
$invoice->setItemDesc('You "Joe Goldberg" Life-Size Wax Figure');
$invoice->setItemCode('sku-1234');
$invoice->setPhysical(true); // Set to false for digital/virtual items
// Ensure you provide a unique OrderId for each Invoice
$invoice->setOrderId($order->order_id);
// Create Buyer Instance
$buyer = LaravelBitpay::Buyer();
$buyer->setName('John Doe');
$buyer->setEmail('john.doe@example.com');
$buyer->setAddress1('2630 Hegal Place');
$buyer->setAddress2('Apt 42');
$buyer->setLocality('Alexandria');
$buyer->setRegion('VA');
$buyer->setPostalCode(23242);
$buyer->setCountry('US');
$buyer->setNotify(true); // Instructs BitPay to email Buyer about their Invoice
// Attach Buyer to Invoice
$invoice->setBuyer($buyer);
// Set URL that Buyer will be redirected to after completing the payment, via GET Request
$invoice->setRedirectURL(route('your-bitpay-success-url'));
// Set URL that Buyer will be redirected to after closing the invoice or after the invoice expires, via GET Request
$invoice->setCloseURL(route('your-bitpay-cancel-url'));
$invoice->setAutoRedirect(true);
// Optional. Learn more at: https://github.com/vrajroham/laravel-bitpay#1-setup-your-webhook-route
$invoice->setNotificationUrl('https://example.com/your-custom-webhook-url');
// This is the recommended IPN format that BitPay advises for all new implementations
$invoice->setExtendedNotifications(true);
// Create invoice on BitPay's server
$invoice = LaravelBitpay::createInvoice($invoice);
$invoiceId = $invoice->getId();
$invoiceToken = $invoice->getToken();
// You should save Invoice ID and Token, for your reference
$order->update(['invoice_id' => $invoiceId, 'invoice_token' => $invoiceToken]);
// Redirect user to the Invoice's hosted URL to complete payment
$paymentUrl = $invoice->getUrl();
return Redirect::to($paymentUrl);
ℹ️ It is highly recommended you store the Invoice ID and Token on your internal model(s). The token can come in handy when verifying webhooks.
$invoice = LaravelBitpay::getInvoice('invoiceId_sGsdVsgheF');
In this example we retrieve all MTD (Month-To-Date) invoices:
$startDate = date('Y-m-d', strtotime('first day of this month'));
$endDate = date('Y-m-d');
$invoices = LaravelBitpay::getInvoices($startDate, $endDate);
// True if the webhook has been resent for the current invoice status, false otherwise.
$webhookResent = LaravelBitpay::requestInvoiceWebhook('invoiceId_sGsdVsgheF');
Refund requests are full or partial refunds associated to an invoice. Fully paid invoices can be refunded via the merchant's authorization to issue a refund, while underpaid and overpaid invoices are automatically executed by BitPay to issue the underpayment or overpayment amount to the customer.
The item Jane purchased was dead on arrival. Give back the lady her crypto:
$invoice = LaravelBitpay::getInvoice('invoiceId_sGsdVsgheF');
$refundRequested = LaravelBitpay::createRefund($invoice, 'jane.doe@example.com', 0.016, 'ETH');
if ($refundRequested) {
// Don't just sit there. Do something!
}
Let's periodically retrieve (and check the status of) Jane's refund request:
$invoice = LaravelBitpay::getInvoice('invoiceId_sGsdVsgheF');
$refund = LaravelBitpay::getRefund($invoice, 'refundId_pUdhjwGjsg');
In this example we retrieve all refund requests related to Jane's invoice:
$invoice = LaravelBitpay::getInvoice('invoiceId_sGsdVsgheF');
$refundRequests = LaravelBitpay::getRefunds($invoice);
Turns out Jane didn't initially follow the instruction manual. The item works and she no longer wants a refund:
$invoice = LaravelBitpay::getInvoice('invoiceId_sGsdVsgheF');
$refundRequestCancelled = LaravelBitpay::cancelRefund($invoice->getId(), 'refundId_pUdhjwGjsg');
Bills are payment requests addressed to specific buyers. Bill line items have fixed prices, typically denominated in fiat currency.
In the following example, we create a bill that's due in 10 days:
// Initialize Bill
$billData = LaravelBitpay::Bill();
$billData->setNumber('bill1234-EFGH');
$billData->setCurrency(Currency::USD); // Always use the BitPay Currency model to prevent typos
$dueDate = date(BitPayConstants::DATETIME_FORMAT, strtotime('+10 days')); // ISO-8601 formatted date
$billData->setDueDate($dueDate);
$billData->setPassProcessingFee(true); // Let the recipient shoulder BitPay's processing fee
// Prepare Bill recipient's data
$billData->setName('John Doe');
$billData->setAddress1('2630 Hegal Place');
$billData->setAddress2('Apt 42');
$billData->setCity('Alexandria');
$billData->setState('VA');
$billData->setZip(23242);
$billData->setCountry('US');
$billData->setEmail('john.doe@example.com');
$billData->setCc(['jane.doe@example.com']);
$billData->setPhone('555-123-456');
// Prepare Bill's line item(s)
$itemUno = LaravelBitpay::BillItem();
$itemUno->setDescription('Squid Game "Front Man" Costume');
$itemUno->setPrice(49.99);
$itemUno->setQuantity(2);
$itemDos = LaravelBitpay::BillItem();
$itemDos->setDescription('GOT "House Stark" Sterling Silver Pendant');
$itemDos->setPrice(35);
$itemDos->setQuantity(1);
$billData->setItems([$itemUno, $itemDos]);
// Create Bill
$bill = LaravelBitpay::createBill($billData);
// Store the Bill's BitPay ID and URL for future reference
$billId = $bill->getId();
$billPaymentUrl = $bill->getUrl();
// OR
// Redirect the recipient to BitPay's hosted Bill payment page
Redirect::to($billPaymentUrl);
$bill = LaravelBitpay::getBill('bill1234-EFGH');
You can narrow down the retrieved list by specifying a Bill status:
$paidBills = LaravelBitpay::getBills(BillStatus::Paid);
We managed to upsell a product to our client. Let's add an extra line item to their existing Bill:
$existingBill = LaravelBitpay::getBill('bill1234-EFGH');
$existingItems = $existingBill->getItems();
$billData = LaravelBitpay::Bill();
$billData->setId($existingBill->getId());
$itemTres = LaravelBitpay::BillItem();
$itemTres->setDescription('The Tomorrow War "White Spike" Life-Size Wax Figure');
$itemTres->setPrice(189.99);
$itemTres->setQuantity(1);
$billData->setItems(array_merge($existingItems, [$itemTres]));
// Update Bill
$updatedBill = LaravelBitpay::updateBill($billData, $billData->getId());
$bill = LaravelBitpay::getBill('bill1234-EFGH');
$billDelivered = LaravelBitpay::deliverBill($bill->getId(), $bill->getToken());
if ($billDelivered) {
// Bill delivered successfully. Do something about that... or not.
}
Subscriptions are repeat billing agreements with specific buyers. BitPay sends bill emails to buyers identified in active subscriptions according to the specified schedule.
Let's create a subscription that's delivered on the 28th of each month and due on the first of the following month, at 9 AM, respectively:
// Initialize Subscription
$subscriptionData = LaravelBitpay::Subscription();
$subscriptionData->setSchedule(BitPayConstants::SUBSCRIPTION_SCHEDULE_MONTHLY);
// Optional recurring bill data
$billData = [
'number' => 'subscription1234-ABCD',
'name' => 'John Doe',
'address1' => '2630 Hegal Place',
'address2' => 'Apt 42',
'city' => 'Alexandria',
'state' => 'VA',
'zip' => 23242,
'country' => 'US',
'cc' => ['jane.doe@example.com'],
'phone' => '555-123-456',
'passProcessingFee' => true,
];
$dueDate = date(BitPayConstants::DATETIME_FORMAT, strtotime('first day of next month 9 AM'));
$billItems = array(
LaravelBitpay::SubscriptionItem(100.00, 1, 'Web Hosting - 4 CPUs | 16GB Memory | 400GB SSD'),
LaravelBitpay::SubscriptionItem(80.00, 1, 'Basic Website Maintenance'),
);
// Autofill optional bill data
$mapper = new JsonMapper();
$billData = $mapper->map(
$billData,
LaravelBitpay::BillData(
Currency::USD, // Always use the BitPay Currency model to prevent typos
'john.doe@example.com',
$dueDate,
$billItems
)
);
$subscriptionData->setBillData($billData);
// A little wizardry to always get the 28th day of the current month (leap year safe)
$deliveryDate = strtotime('first day of this month 9 AM');
$deliveryDate = new \DateTime("@$deliveryDate");
$deliveryDate = $deliveryDate->modify('+27 days')->getTimestamp();
$deliveryDate = date(BitPayConstants::DATETIME_FORMAT, $deliveryDate);
$subscriptionData->setNextDelivery($deliveryDate);
// Create the Subscription on BitPay
$subscription = LaravelBitpay::createSubscription($subscriptionData);
// You may then store the Subscription ID for future reference
$subscriptionId = $subscription->getId();
$subscription = LaravelBitpay::getSubscription('6gqe8y5mkc5Qx2a9zmspgx');
You can narrow down the retrieved list by specifying a Subscription status:
$activeSubscriptions = LaravelBitpay::getSubscriptions(SubscriptionStatus::Active);
In this example we activate a Subscription by updating its status:
$subscriptionData = LaravelBitpay::Subscription();
$subscriptionData->setId('6gqe8y5mkc5Qx2a9zmspgx');
$subscriptionData->setStatus(SubscriptionStatus::Active);
$activatedSubscription = LaravelBitpay::updateSubscription($subscriptionData, $subscriptionData->getId());
Settlements are transfers of payment profits from BitPay to bank accounts and cryptocurrency wallets owned by merchants, partners, etc.
In this example we retrieve completed YTD (Year-To-Date) settlements denominated in Euros (EUR). We only need 100 records, starting from the 5th one:
$startDate = date('Y-m-d', strtotime('first day of this year'));
$endDate = date('Y-m-d');
$eurSettlements = LaravelBitpay::getSettlements(
Currency::EUR,
$startDate,
$endDate,
BitPayConstants::SETTLEMENT_STATUS_COMPLETED,
100,
4
);
$settlement = LaravelBitpay::getSettlement('settlementId_uidwb3668');
A reconciliation report is a detailed report of the activity within the settlement period, in order to reconcile incoming settlements from BitPay.
$settlement = LaravelBitpay::getSettlement('settlementId_uidwb3668');
$settlementReport = LaravelBitpay::getSettlementReconciliationReport($settlement);
Ledgers are records of money movement.
$accountBalances = LaravelBitpay::getLedgers();
In this example we retrieve MTD (Month-To-Date) ledger entries denominated in United States Dollars (USD).
$startDate = date('Y-m-d', strtotime('first day of this month'));
$endDate = date('Y-m-d');
$usdLedgerEntries = LaravelBitpay::getLedger(Currency::USD, $startDate, $endDate);
The Recipient resource allows a merchant to invite their clients to signup for a BitPay personal account.
// Init individual recipients
$jane = LaravelBitpay::PayoutRecipient('jane.doe@example.com', 'Plain Jane');
$ada = LaravelBitpay::PayoutRecipient('ada@cardano.org', 'Ada Lovelace');
// Optional. Learn more at https://github.com/vrajroham/laravel-bitpay#1-setup-your-webhook-route
$ada->setNotificationUrl('https://example.com/your-custom-webhook-url');
// Batch all individual recipients
$recipients = LaravelBitpay::PayoutRecipients([$jane, $ada]);
// Submit invites
$recipientsInvited = LaravelBitpay::invitePayoutRecipients($recipients);
// Do something with the returned invitees
foreach ($recipientsInvited as $recipient) {
$recipientId = $recipient->getId();
$recipientToken = $recipient->getToken();
// ... store Recipient ID and Token somewhere persistent
// Perform other desired actions
\App\Events\LookOutForAnInviteEmail::dispatch($recipient->getEmail());
}
ℹ️ It is highly recommended you store the Recipient ID and Token on your internal model(s). The token can come in handy when verifying webhooks.
$recipient = LaravelBitpay::getPayoutRecipient('recipientId_adaLovelace')
In this example, we retrieve 100 recipients (starting from the 50th) that have passed the good 'ole Onfido ID verification checks:
$verifiedRecipients = LaravelBitpay::getPayoutRecipients(RecipientStatus::VERIFIED, 100, 49);
$recipient = LaravelBitpay::getPayoutRecipient('recipientId_adaLovelace');
$recipient->setLabel('Cardano To The Moon');
$updatedRecipient = LaravelBitpay::updatePayoutRecipient($recipient->getId(), $recipient);
$recipientRemoved = LaravelBitpay::removePayoutRecipient('recipientId_janeDoe');
// True if the webhook has been resent for the current recipient status, false otherwise.
$webhookResent = LaravelBitpay::requestPayoutRecipientWebhook('recipientId_adaLovelace');
Payouts are individual (or batches of) bitcoin payments to employees, customers, partners, etc.
Let's assume Ada Lovelace accepted our invitation. In this example, we schedule her an individual payout for a 5-star rating she received from a referral:
// Initialize a Payout
// Pay Ada in USD and record it on the BTC ledger
$payoutData = LaravelBitpay::Payout(50.00, Currency::USD, Currency::BTC);
// Set Payout details
$payoutData->setRecipientId('recipientId_adaLovelace'); // From previously invited Recipient
$payoutData->setReference('1234'); // Uniquely identifies an equivalent payout entry in your system
$payoutData->setLabel('5-Star Bonus Affiliate Payment #1234 for Dec 2021');
$payoutData->setEffectiveDate('2021-12-31');
// Optional. Learn more at https://github.com/vrajroham/laravel-bitpay#1-setup-your-webhook-route
$payoutData->setNotificationURL('https://example.com/your-custom-webhook-url');
// Create Payout on BitPay's server
$payout = LaravelBitpay::createPayout($payoutData);
$payoutId = $payout->getId();
$payoutToken = $payout->getToken();
// ... store Payout ID and Token somewhere persistent
ℹ️ It is highly recommended you store the Payout ID and Token on your internal model(s). The token can come in handy when verifying webhooks.
Let's pay our two top-tier affiliates for all their hard work, batching both payments into a single API call, for the efficiency of it.
// Initialize a Payout Batch
$payoutBatchData = LaravelBitpay::PayoutBatch(Currency::USD); // Pay recipients in USD
$payoutBatchData->setLedgerCurrency(Currency::ETH); // Record the payout batch on the ETH ledger
$payoutBatchData->setAmount(500.00);
$payoutBatchData->setReference('Aff_Jan-Feb_2022'); // Uniquely identifies an equivalent payout batch in your system
$payoutBatchData->setLabel('Affiliate Payments for Jan-Feb 2022');
$payoutBatchData->setEffectiveDate('2022-02-28');
// Optional. Learn more at https://github.com/vrajroham/laravel-bitpay#1-setup-your-webhook-route
$payoutBatchData->setNotificationURL('https://example.com/your-custom-webhook-url');
// Define Instruction(s)
$payJane = LaravelBitpay::PayoutInstruction(
250.00,
RecipientReferenceMethod::RECIPIENT_ID,
'recipientId_janeDoe'
);
$payJane->setLabel('Affiliate Payment #1234 for Jan-Feb 2022');
$payAda = LaravelBitpay::PayoutInstruction(
250.00,
RecipientReferenceMethod::RECIPIENT_ID,
'recipientId_adaLovelace'
);
$payAda->setLabel('Affiliate Payment #5678 for Jan-Feb 2022');
// Attach Instruction(s) to Payout Batch
$payoutBatchData->setInstructions([$payJane, $payAda]);
// Create Payout Batch on BitPay's server
$payoutBatch = LaravelBitpay::createPayoutBatch($payoutBatchData);
$payoutBatchId = $payoutBatch->getId();
$payoutBatchToken = $payoutBatch->getToken();
// ... store Payout Batch ID and Token somewhere persistent
ℹ️ It is highly recommended you store the Payout Batch ID and Token on your internal model(s). The token can come in handy when verifying webhooks.
$payout = LaravelBitpay::getPayout('payoutId_jws43dbnfpg');
$payoutBatch = LaravelBitpay::getPayoutBatch('payoutBatchId_jws43dbnfpg');
In this example, we retrieve all completed, Year-To-Date (YTD) payouts.
$startDate = date('Y-m-d', strtotime('first day of this year'));
$endDate = date('Y-m-d');
$completedPayouts = LaravelBitpay::getPayouts($startDate, $endDate, PayoutStatus::Complete);
In this example, we retrieve all cancelled, Year-To-Date (YTD) payout batches.
$startDate = date('Y-m-d', strtotime('first day of this year'));
$endDate = date('Y-m-d');
$cancelledPayoutBatches = LaravelBitpay::getPayoutBatches($startDate, $endDate, PayoutStatus::Cancelled);
$payoutCancelled = LaravelBitpay::cancelPayout('payoutId_jws43dbnfpg');
$payoutBatchCancelled = LaravelBitpay::cancelPayoutBatch('payoutBatchId_jws43dbnfpg');
// True if the webhook has been resent for the current payout status, false otherwise.
$webhookResent = LaravelBitpay::requestPayoutWebhook('payoutId_jws43dbnfpg');
// True if the webhook has been resent for the current payout batch status, false otherwise.
$webhookResent = LaravelBitpay::requestPayoutBatchWebhook('payoutBatchId_jws43dbnfpg');
Currencies are fiat currencies supported by BitPay.
In this example, we retrieve the list of supported BitPay Currency objects.
$supportedCurrencies = LaravelBitpay::getCurrencies();
Rates are exchange rates, representing the number of fiat currency units equivalent to one BTC.
$rates = LaravelBitpay::getRates();
$btcToUsdRate = $rates->getRate(Currency::USD); // Always use the BitPay Currency model to prevent typos
$ethRates = LaravelBitpay::getCurrencyRates(Currency::ETH);
$ethToUsdRate = $ethRates->getRate(Currency::USD);
$dogeToUsdRate = LaravelBitpay::getCurrencyPairRate(Currrency::DOGE, Currency::USD);
composer test
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
If you discover any security related issues, please email vaibhavraj@vrajroham.me or iamalexstewart@gmail.com instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.