diff --git a/CHANGELOG.md b/CHANGELOG.md index cd698aa..1a5bb20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to `laravel-paymongo` will be documented in this file +## 2.4.0 (2022-12-15) + +### Added +- Links API +- Customers API + +### Fixed +- Failing tests + ## 1.3.0 (2020-10-31) ### Added diff --git a/docs/docs/Usage/customers.md b/docs/docs/Usage/customers.md new file mode 100644 index 0000000..1035a9e --- /dev/null +++ b/docs/docs/Usage/customers.md @@ -0,0 +1,85 @@ +--- +sidebar_position: 7 +slug: /customers +id: customers +--- + +# Customers + +## Create Customer + +Creates a customer record that holds billing information and is useful for card vaulting purposes. + +### Payload + +Refer to [Paymongo documentation](https://developers.paymongo.com/reference/customer-resource) for payload guidelines. + +### Sample + +```php +use Luigel\Paymongo\Facades\Paymongo; + +$customer = Paymongo::customer()->create([ + 'first_name' => 'Juan', + 'last_name' => 'Doe', + 'phone' => '+639123456789', + 'email' => 'customer@email.com', + 'default_device' => 'phone' +]); +``` + +## Get Customer + +Retrieve a customer by passing the customer id to the `find($id)` method. + +### Sample + +```php +use Luigel\Paymongo\Facades\Paymongo; + +$customer = Paymongo::customer()->find('cus_b9ENKVqcHBfQQmv26uDYDCsD'); +``` + +## Edit Customer + +Edit a customer by using the `find($id)` method and chaining the `->update()` method. Or by chaning `->update()` to your existing instance. + +### Sample + +```php +use Luigel\Paymongo\Facades\Paymongo; + +$customer = Paymongo::customer() + ->find('cus_b9ENKVqcHBfQQmv26uDYDCsD') + ->update([ + 'first_name' => 'Jane' + ]); +``` + +## Delete Customer + +Delete a customer by using the `find($id)` method and chaining the `->delete()` method. Or by chaning `->delete()` to your existing instance. + +### Sample + +```php +use Luigel\Paymongo\Facades\Paymongo; + +$link = Paymongo::customer() + ->find('cus_b9ENKVqcHBfQQmv26uDYDCsD') + ->delete(); +``` + +## Retrieve Customer's Payment Methods + +Retrieve the customer's payment methods by using the `find($id)` method and chaining the `->paymentMethods()` method. Or by chaning `->paymentMethods()` to your existing instance. + +### Sample + +```php +use Luigel\Paymongo\Facades\Paymongo; + +$link = Paymongo::customer() + ->find('cus_b9ENKVqcHBfQQmv26uDYDCsD') + ->paymentMethods(); +``` \ No newline at end of file diff --git a/docs/docs/Usage/links.md b/docs/docs/Usage/links.md new file mode 100644 index 0000000..dbffc53 --- /dev/null +++ b/docs/docs/Usage/links.md @@ -0,0 +1,65 @@ +--- +sidebar_position: 7 +slug: /links +id: links +--- + +# Links + +## Create Link + +Creates a payment link. A payment link that can be used for one-time payments. + +### Payload + +Refer to [Paymongo documentation](https://developers.paymongo.com/reference/links-resource) for payload guidelines. + +### Sample + +```php +use Luigel\Paymongo\Facades\Paymongo; + +$link = Paymongo::link()->create([ + 'amount' => 100.00, + 'description' => 'Link Test', + 'remarks' => 'laravel-paymongo' +]); +``` + +## Get Link + +Retrieve a payment link by passing the id or the reference number to the `find($id)` method. + +### Sample + +```php +use Luigel\Paymongo\Facades\Paymongo; + +$link = Paymongo::link()->find('link_wWaibr22CzEnficNhQNPUdoo'); +$linkbyReference = Paymongo::link()->find('WTmSJbV'); +``` + +## Archive Link + +Archive a payment link by using the `find($id)` method and chaining the `->archive()` method. + +### Sample + +```php +use Luigel\Paymongo\Facades\Paymongo; + +$link = Paymongo::link()->find('link_wWaibr22CzEnficNhQNPUdoo')->archive(); +``` + +## Unarchive Link + +Unarchive a payment link by using the `find($id)` method and chaining the `->unarchive()` method. + +### Sample + +```php +use Luigel\Paymongo\Facades\Paymongo; + +$link = Paymongo::link()->find('link_wWaibr22CzEnficNhQNPUdoo')->unarchive(); +``` + diff --git a/docs/docs/Usage/refunds.md b/docs/docs/Usage/refunds.md index 3b31c77..8163524 100644 --- a/docs/docs/Usage/refunds.md +++ b/docs/docs/Usage/refunds.md @@ -1,5 +1,5 @@ --- -sidebar_position: 8 +sidebar_position: 7 slug: /refunds id: refunds --- diff --git a/src/Models/Customer.php b/src/Models/Customer.php new file mode 100644 index 0000000..26d6725 --- /dev/null +++ b/src/Models/Customer.php @@ -0,0 +1,24 @@ +customer()->updateCustomer($this, $payload); + } + + public function delete(): BaseModel + { + return (new Paymongo)->customer()->deleteCustomer($this); + } + + public function paymentMethods(): BaseModel|Collection + { + return (new Paymongo)->customer()->getPaymentMethods($this); + } +} diff --git a/src/Models/Link.php b/src/Models/Link.php new file mode 100644 index 0000000..0fecef8 --- /dev/null +++ b/src/Models/Link.php @@ -0,0 +1,18 @@ +link()->archive($this); + } + + public function unarchive(): BaseModel + { + return (new Paymongo)->link()->unarchive($this); + } +} diff --git a/src/Paymongo.php b/src/Paymongo.php index b42d58e..c853c9a 100644 --- a/src/Paymongo.php +++ b/src/Paymongo.php @@ -2,15 +2,17 @@ namespace Luigel\Paymongo; -use Luigel\Paymongo\Models\Payment; -use Luigel\Paymongo\Models\PaymentIntent; -use Luigel\Paymongo\Models\PaymentMethod; +use Luigel\Paymongo\Models\Link; +use Luigel\Paymongo\Models\Token; use Luigel\Paymongo\Models\Refund; use Luigel\Paymongo\Models\Source; -use Luigel\Paymongo\Models\Token; +use Luigel\Paymongo\Models\Payment; use Luigel\Paymongo\Models\Webhook; -use Luigel\Paymongo\Traits\HasToggleWebhook; use Luigel\Paymongo\Traits\Request; +use Luigel\Paymongo\Models\Customer; +use Luigel\Paymongo\Models\PaymentIntent; +use Luigel\Paymongo\Models\PaymentMethod; +use Luigel\Paymongo\Traits\HasToggleWebhook; class Paymongo { @@ -28,6 +30,8 @@ class Paymongo protected const ENDPOINT_PAYMENT = 'payments/'; protected const ENDPOINT_TOKEN = 'tokens/'; protected const ENDPOINT_REFUND = 'refunds/'; + protected const ENDPOINT_LINK = 'links/'; + protected const ENDPOINT_CUSTOMER = 'customers/'; public const SOURCE_GCASH = 'gcash'; public const SOURCE_GRAB_PAY = 'grab_pay'; public const AMOUNT_TYPE_FLOAT = 'float'; @@ -108,4 +112,20 @@ public function refund(): self return $this; } + + public function link(): self + { + $this->apiUrl = self::BASE_API.self::ENDPOINT_LINK; + $this->returnModel = Link::class; + + return $this; + } + + public function customer(): self + { + $this->apiUrl = self::BASE_API.self::ENDPOINT_CUSTOMER; + $this->returnModel = Customer::class; + + return $this; + } } diff --git a/src/Traits/Request.php b/src/Traits/Request.php index b9fb889..81bec2e 100644 --- a/src/Traits/Request.php +++ b/src/Traits/Request.php @@ -4,16 +4,18 @@ use Exception; use GuzzleHttp\Client; -use GuzzleHttp\Exception\ClientException; +use Luigel\Paymongo\Models\Link; use Illuminate\Support\Collection; -use Luigel\Paymongo\Exceptions\AmountTypeNotSupportedException; -use Luigel\Paymongo\Exceptions\BadRequestException; +use Luigel\Paymongo\Models\Webhook; +use Luigel\Paymongo\Models\Customer; +use Luigel\Paymongo\Models\BaseModel; +use GuzzleHttp\Exception\ClientException; +use Luigel\Paymongo\Models\PaymentIntent; use Luigel\Paymongo\Exceptions\NotFoundException; +use Luigel\Paymongo\Exceptions\BadRequestException; use Luigel\Paymongo\Exceptions\PaymentErrorException; use Luigel\Paymongo\Exceptions\UnauthorizedException; -use Luigel\Paymongo\Models\BaseModel; -use Luigel\Paymongo\Models\PaymentIntent; -use Luigel\Paymongo\Models\Webhook; +use Luigel\Paymongo\Exceptions\AmountTypeNotSupportedException; trait Request { @@ -143,6 +145,94 @@ public function attach(PaymentIntent $intent, string $paymentMethodId, string|nu return $this->request(); } + /** + * Archives the link + */ + public function archive(Link $link){ + $this->method = 'POST'; + $this->apiUrl = $this->apiUrl.$link->id.'/archive'; + + $this->setOptions([ + 'headers' => [ + 'Accept' => 'application/json', + ], + 'auth' => [config('paymongo.secret_key'), ''], + ]); + + return $this->request(); + } + + /** + * Unarchives the link + */ + public function unarchive(Link $link){ + $this->method = 'POST'; + $this->apiUrl = $this->apiUrl . $link->id . '/unarchive'; + + $this->setOptions([ + 'headers' => [ + 'Accept' => 'application/json', + ], + 'auth' => [config('paymongo.secret_key'), ''], + ]); + + return $this->request(); + } + + /** + * Update the customer information + */ + public function updateCustomer(Customer $customer, array $payload){ + $this->method = 'PATCH'; + $this->apiUrl = $this->apiUrl . $customer->id; + $this->payload = $payload; + + $this->formRequestData(); + $this->setOptions([ + 'headers' => [ + 'Accept' => 'application/json', + ], + 'json' => $this->data, + 'auth' => [config('paymongo.secret_key'), ''], + ]); + + return $this->request(); + } + + /** + * Delete the customer + */ + public function deleteCustomer(Customer $customer){ + $this->method = 'DELETE'; + $this->apiUrl = $this->apiUrl . $customer->id; + + $this->setOptions([ + 'headers' => [ + 'Accept' => 'application/json', + ], + 'auth' => [config('paymongo.secret_key'), ''], + ]); + + return $this->request(); + } + + /** + * Get Customer's Payment Methods + */ + public function getPaymentMethods(Customer $customer){ + $this->method = 'GET'; + $this->apiUrl = $this->apiUrl . $customer->id . '/payment_methods'; + + $this->setOptions([ + 'headers' => [ + 'Accept' => 'application/json', + ], + 'auth' => [config('paymongo.secret_key'), ''], + ]); + + return $this->request(); + } + /** * Send request to API. * diff --git a/tests/CustomerTest.php b/tests/CustomerTest.php new file mode 100644 index 0000000..0bbf338 --- /dev/null +++ b/tests/CustomerTest.php @@ -0,0 +1,52 @@ +toBeInstanceOf(Customer::class); +}); + +it('can not retrieve a customer with invalid id', function () { + $this->expectException(NotFoundException::class); + + Paymongo::customer() + ->find('test'); +}); + +it('can retrieve a customer', function () { + $customer = createCustomer(); + + $retrieve = Paymongo::customer() + ->find($customer->id); + + expect($customer->id)->toBe($retrieve->id); +}); + +it('can update a customer', function () { + $customer = createCustomer(); + + expect($customer->last_name)->toBe('Felix'); + + $updatedCustomer = $customer->update([ + 'last_name' => 'Mongo' + ]); + + expect($updatedCustomer->last_name)->toBe('Mongo'); +}); + +it('can delete a customer', function () { + $customer = createCustomer()->delete(); + + expect($customer->deleted)->toBe(true); +}); + +it('can retrieve a customer\'s payment methods', function () { + $customer = createCustomer()->paymentMethods(); + + expect($customer)->toBeInstanceOf(Collection::class); +}); \ No newline at end of file diff --git a/tests/LinkTest.php b/tests/LinkTest.php new file mode 100644 index 0000000..a754bcc --- /dev/null +++ b/tests/LinkTest.php @@ -0,0 +1,49 @@ +toBeInstanceOf(Link::class); +}); + +it('can not retrieve a link with invalid id', function () { + $this->expectException(NotFoundException::class); + + Paymongo::link() + ->find('test'); +}); + +it('can retrieve a link by id', function () { + $link = createLink(); + + $retrieve = Paymongo::link() + ->find($link->id); + + expect($link->id)->toBe($retrieve->id); +}); + +it('can retrieve a link by reference number', function () { + $link = createLink(); + $retrieve = Paymongo::link() + ->find($link->reference_number); + + expect($link->id)->toBe($retrieve->id); +}); + +it('can archive a link', function () { + $link = createLink()->archive(); + + expect($link->archived)->toBe(true); +}); + +it('can unarchive a link', function () { + $archivedLink = createLink()->archive(); + + $unarchivedLink = $archivedLink->unarchive(); + + expect($unarchivedLink->archived)->toBe(false); +}); \ No newline at end of file diff --git a/tests/PaymentMethodTest.php b/tests/PaymentMethodTest.php index 798a6f2..d75b2bd 100644 --- a/tests/PaymentMethodTest.php +++ b/tests/PaymentMethodTest.php @@ -56,30 +56,4 @@ ->toBeInstanceOf(PaymentMethod::class) ->type->toBe('payment_method') ->payment_method_type->toBe('paymaya'); -}); - -function createPaymentMethod() -{ - return Paymongo::paymentMethod() - ->create([ - 'type' => 'card', - 'details' => [ - 'card_number' => getTestCardWithout3dSecure(), - 'exp_month' => 12, - 'exp_year' => 25, - 'cvc' => '123', - ], - 'billing' => [ - 'address' => [ - 'line1' => 'Somewhere there', - 'city' => 'Cebu City', - 'state' => 'Cebu', - 'country' => 'PH', - 'postal_code' => '6000', - ], - 'name' => 'Rigel Kent Carbonel', - 'email' => 'rigel20.kent@gmail.com', - 'phone' => '0935454875545', - ], - ]); -} +}); \ No newline at end of file diff --git a/tests/PaymentTest.php b/tests/PaymentTest.php index 6a33dbd..564c24a 100644 --- a/tests/PaymentTest.php +++ b/tests/PaymentTest.php @@ -20,64 +20,21 @@ }); it('can retrieve a payment', function () { - $token = createToken(); - - $createdPayment = createPayment($token); + $cardPayment = createCardPayment(); $payment = Paymongo::payment() - ->find($createdPayment->id); - - expect($payment->id)->toBe($createdPayment->id); -}); - -it('cannot create payment when token is not valid', function () { - $this->expectException(BadRequestException::class); + ->find($cardPayment->id); - $token = Paymongo::token()->create([ - 'number' => '5100000000000198', - 'exp_month' => 12, - 'exp_year' => 25, - 'cvc' => '123', - 'billing' => [ - 'address' => [ - 'line1' => 'Test Address', - 'city' => 'Cebu City', - 'postal_code' => '6000', - 'country' => 'PH', - ], - 'name' => 'Rigel Kent Carbonel', - 'email' => 'rigel20.kent@gmail.com', - 'phone' => '928392893', - ], - ]); - - createPayment($token); -}); - -it('cannot create payment when token is used more than once', function () { - $this->expectException(BadRequestException::class); - - $token = createToken(); - $payment = createPayment($token); - - expect($payment) - ->amount->toBe(100.00) - ->currency->toBe('PHP') - ->statement_descriptor->toBe('Test Paymongo') - ->status->toBe('paid'); - - createPayment($token); + expect($cardPayment->id)->toBe($payment->id); }); it('can create payment', function () { - $token = createToken(); - - $payment = createPayment($token); + $cardPayment = createCardPayment(); - expect($payment) + expect($cardPayment) ->toBeInstanceOf(Payment::class) ->amount->toBe(100.00) ->currency->toBe('PHP') - ->statement_descriptor->toBe('Test Paymongo') + ->statement_descriptor->toBe('LUIGEL STORE') ->status->toBe('paid'); -}); +}); \ No newline at end of file diff --git a/tests/Pest.php b/tests/Pest.php index eefa1d5..96a850b 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,12 +1,16 @@ in(__DIR__); @@ -71,6 +75,32 @@ function createPaymentIntent(): PaymentIntent ]); } +function createPaymentMethod(): PaymentMethod +{ + return Paymongo::paymentMethod() + ->create([ + 'type' => 'card', + 'details' => [ + 'card_number' => getTestCardWithout3dSecure(), + 'exp_month' => 12, + 'exp_year' => 25, + 'cvc' => '123', + ], + 'billing' => [ + 'address' => [ + 'line1' => 'Somewhere there', + 'city' => 'Cebu City', + 'state' => 'Cebu', + 'country' => 'PH', + 'postal_code' => '6000', + ], + 'name' => 'Rigel Kent Carbonel', + 'email' => 'rigel20.kent@gmail.com', + 'phone' => '0935454875545', + ], + ]); +} + function createSource($type = 'gcash'): Source { return Paymongo::source()->create([ @@ -99,6 +129,37 @@ function createPayment(Source|Token $source): Payment ]); } +function createCardPayment(): Payment +{ + $paymentIntent = createPaymentIntent(); + $paymentMethod = createPaymentMethod(); + $attachedPaymentIntent = $paymentIntent->attach($paymentMethod->id, 'http://example.com/success'); + $cardPayment = new Payment(); + $cardPayment = $cardPayment->setData($attachedPaymentIntent->payments[0]); + + return $cardPayment; +} + +function createLink(): Link +{ + return Paymongo::link()->create([ + 'amount' => 100.00, + 'description' => 'Link Test', + 'remarks' => 'laravel-paymongo' + ]); +} + +function createCustomer(): Customer +{ + return Paymongo::customer()->create([ + 'first_name' => 'Gringiemar', + 'last_name' => 'Felix', + 'phone' => '+6391234' . rand(10000, 99999), + 'email' => 'customer' . Str::random(8) . rand(0, 100) . '@email.com', + 'default_device' => 'phone' + ]); +} + function createRequest( $method, $content, diff --git a/tests/RefundTest.php b/tests/RefundTest.php index 7d0ed5b..4437188 100644 --- a/tests/RefundTest.php +++ b/tests/RefundTest.php @@ -5,13 +5,12 @@ use Luigel\Paymongo\Models\Refund; it('can create refund', function (string $reason) { - $token = createToken(); - $payment = createPayment($token); + $cardPayment = createCardPayment(); $refund = Paymongo::refund()->create([ 'amount' => 10, 'notes' => 'test refund', - 'payment_id' => $payment->id, + 'payment_id' => $cardPayment->id, 'reason' => $reason, ]); @@ -19,7 +18,7 @@ ->type->toBe('refund') ->amount->toBe(10.0) ->notes->toBe('test refund') - ->payment_id->toBe($payment->id) + ->payment_id->toBe($cardPayment->id) ->reason->toBe($reason) ->status->toBe('pending'); })->with([ @@ -30,13 +29,12 @@ ]); it('can retrieve a refund', function () { - $token = createToken(); - $payment = createPayment($token); + $cardPayment = createCardPayment(); $refund = Paymongo::refund()->create([ 'amount' => 10, 'notes' => 'test refund', - 'payment_id' => $payment->id, + 'payment_id' => $cardPayment->id, 'reason' => Refund::REASON_DUPLICATE, ]); @@ -46,7 +44,7 @@ ->type->toBe('refund') ->amount->toBe(10.0) ->notes->toBe('test refund') - ->payment_id->toBe($payment->id) + ->payment_id->toBe($cardPayment->id) ->reason->toBe(Refund::REASON_DUPLICATE) ->status->toBe('succeeded'); }); diff --git a/tests/TokenTest.php b/tests/TokenTest.php deleted file mode 100644 index 95fcc8e..0000000 --- a/tests/TokenTest.php +++ /dev/null @@ -1,75 +0,0 @@ -create([ - 'number' => getTestCardWithout3dSecure(), - 'exp_month' => 12, - 'exp_year' => 25, - 'cvc' => '123', - 'billing' => [ - 'address' => [ - 'line1' => 'Test Address', - 'city' => 'Cebu City', - 'postal_code' => '6000', - 'country' => 'PH', - ], - 'name' => 'Rigel Kent Carbonel', - 'email' => 'rigel20.kent@gmail.com', - 'phone' => '928392893', - ], - ]); - - expect($token) - ->toBeInstanceOf(Token::class) - ->card->toBeArray()->toMatchArray([ - 'last4' => '4345', - 'exp_month' => 12, - ]) - ->billing->toBeArray()->toMatchArray([ - 'name' => 'Rigel Kent Carbonel', - ]); -}); - -it('can retrieve token', function () { - $createdToken = Paymongo::token() - ->create([ - 'number' => getTestCardWithout3dSecure(), - 'exp_month' => 12, - 'exp_year' => 25, - 'cvc' => '123', - 'billing' => [ - 'address' => [ - 'line1' => 'Test Address', - 'city' => 'Cebu City', - 'postal_code' => '6000', - 'country' => 'PH', - ], - 'name' => 'Rigel Kent Carbonel', - 'email' => 'rigel20.kent@gmail.com', - 'phone' => '928392893', - ], - ]); - - $token = Paymongo::token()->find($createdToken->id); - - $this->assertEquals($createdToken, $token); -}); - -it('cannot create token with invalid data', function () { - $this->expectException(BadRequestException::class); - - Paymongo::token() - ->create([ - 'number' => '424242424242424222', - 'exp_month' => 12, - 'exp_year' => 25, - 'cvc' => '123', - ]); -});