├── .styleci.yml ├── src ├── Openpay │ ├── Card.php │ ├── Plan.php │ ├── Webhook.php │ ├── Customer.php │ ├── BankAccount.php │ ├── Subscription.php │ ├── Charge.php │ └── Base.php ├── OpenpayInstance.php ├── Cashier.php ├── Http │ └── Controllers │ │ ├── BaseWebhookController.php │ │ └── WebhookController.php.stub ├── BankAccount.php ├── Card.php ├── CashierOpenpayServiceProvider.php ├── Subscription.php ├── OpenpayExceptionsHandler.php └── Billable.php ├── todo.md ├── license.md ├── changelog.md ├── .gitignore ├── resources ├── views │ ├── js_load.blade.php │ ├── js_jquery.blade.php │ └── js.blade.php └── lang │ ├── en.json │ └── es.json ├── routes └── webhook.php ├── tests ├── Fixtures │ ├── redirect_localhost.html │ └── User.php ├── database │ └── migrations │ │ └── create_users_table.php ├── Http │ └── Controllers │ │ └── WebhookControllerTest.php ├── SubscriptionTest.php ├── BillableTest.php ├── BaseTestCase.php └── Traits │ └── OpenpayExceptionHandlerTest.php ├── .travis.yml ├── database └── migrations │ ├── create_customer_columns.php.stub │ ├── create_bank_accounts_table.php.stub │ ├── create_subscriptions_table.php.stub │ └── create_cards_table.php.stub ├── contributing.md ├── phpunit.xml.dist ├── composer.json ├── config └── cashier_openpay.php └── readme.md /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | finder: 3 | exclude: 4 | - "tests/database/migrations" 5 | -------------------------------------------------------------------------------- /src/Openpay/Card.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | ...Add your license text here... -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `CashierOpenpay` will be documented in this file. 4 | 5 | ## Version 1.0 6 | 7 | ### Added 8 | - Everything 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | composer.lock 3 | docs 4 | vendor 5 | coverage 6 | phpunit.xml 7 | .phpunit.result.cache 8 | .idea 9 | .phpstorm.meta.php 10 | .php_cs.cache 11 | -------------------------------------------------------------------------------- /resources/views/js_load.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /routes/webhook.php: -------------------------------------------------------------------------------- 1 | post( 7 | Cashier::webhookUrl(), 8 | Cashier::webhookController().'@'.Cashier::webhookMethod() 9 | ); 10 | -------------------------------------------------------------------------------- /resources/views/js_jquery.blade.php: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /resources/views/js.blade.php: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /tests/Fixtures/redirect_localhost.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Redirecting to http://localhost 8 | 9 | 10 | Redirecting to http://localhost. 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/Fixtures/User.php: -------------------------------------------------------------------------------- 1 | refund($data); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | php: 6 | - 7.2 7 | - 7.3 8 | - 7.4 9 | 10 | env: 11 | matrix: 12 | - LARAVEL='6.0.*' TESTBENCH='4.0.*' 13 | - LARAVEL='7.0.*' TESTBENCH='5.0.*' 14 | 15 | cache: 16 | directories: 17 | - "$HOME/.composer/cache" 18 | 19 | matrix: 20 | fast_finish: true 21 | 22 | before_script: composer config discard-changes true 23 | 24 | before_install: 25 | - travis_retry composer self-update 26 | - travis_retry composer require "illuminate/support:${LARAVEL}" "orchestra/testbench:${TESTBENCH}" --no-interaction --no-update 27 | 28 | install: travis_retry composer install --prefer-dist --no-interaction --no-suggest 29 | 30 | script: vendor/bin/phpunit 31 | 32 | -------------------------------------------------------------------------------- /database/migrations/create_customer_columns.php.stub: -------------------------------------------------------------------------------- 1 | string('openpay_id')->nullable()->index(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::table('users', function (Blueprint $table) { 29 | $table->dropColumn([ 30 | 'openpay_id', 31 | ]); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/OpenpayInstance.php: -------------------------------------------------------------------------------- 1 | increments('id'); 20 | $table->string('name'); 21 | $table->string('email')->unique(); 22 | $table->timestamp('email_verified_at')->nullable(); 23 | $table->string('password'); 24 | $table->rememberToken(); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('users'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/create_bank_accounts_table.php.stub: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->unsignedBigInteger('user_id'); 19 | 20 | $table->string('openpay_id')->index(); 21 | $table->string('holder_name'); 22 | $table->string('clabe'); 23 | $table->string('bank_name'); 24 | $table->string('bank_code'); 25 | 26 | $table->string('alias')->nullable(); 27 | $table->timestamps(); 28 | }); 29 | } 30 | 31 | /** 32 | * Reverse the migrations. 33 | * 34 | * @return void 35 | */ 36 | public function down() 37 | { 38 | Schema::dropIfExists('bank_accounts'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Http/Controllers/BaseWebhookController.php: -------------------------------------------------------------------------------- 1 | getContent(), true); 20 | $method = 'handle'.Str::studly(str_replace('.', '_', $payload['type'])); 21 | 22 | if (method_exists($this, $method)) { 23 | return $this->{$method}($payload); 24 | } 25 | 26 | return $this->missingMethod(); 27 | } 28 | 29 | /** 30 | * Handle calls to missing methods on the controller. 31 | * 32 | * @param array $parameters 33 | * @return \Symfony\Component\HttpFoundation\Response 34 | */ 35 | public function missingMethod($parameters = []) 36 | { 37 | return new Response; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/create_subscriptions_table.php.stub: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->unsignedBigInteger('user_id'); 19 | 20 | $table->string('openpay_id')->index(); 21 | $table->string('name'); 22 | $table->string('openpay_status'); 23 | $table->string('openpay_plan'); 24 | $table->timestamp('trial_ends_at')->nullable(); 25 | $table->timestamp('ends_at')->nullable(); 26 | $table->timestamps(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('subscriptions'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/create_cards_table.php.stub: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->unsignedBigInteger('user_id'); 19 | 20 | $table->string('openpay_id')->index(); 21 | $table->string('type'); 22 | $table->string('brand')->nullable(); 23 | $table->string('holder_name'); 24 | $table->string('card_number'); 25 | $table->string('expiration_month', 2); 26 | $table->string('expiration_year', 2); 27 | $table->string('bank_name'); 28 | $table->string('bank_code'); 29 | 30 | $table->timestamps(); 31 | }); 32 | } 33 | 34 | /** 35 | * Reverse the migrations. 36 | * 37 | * @return void 38 | */ 39 | public function down() 40 | { 41 | Schema::dropIfExists('cards'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome and will be fully credited. 4 | 5 | Contributions are accepted via Pull Requests on [Github](https://github.com/perafan/cashieropenpay). 6 | 7 | # Things you could do 8 | If you want to contribute but do not know where to start, this list provides some starting points. 9 | - Add license text 10 | - Remove rewriteRules.php 11 | - Set up TravisCI, StyleCI, ScrutinizerCI 12 | - Write a comprehensive ReadMe 13 | 14 | ## Pull Requests 15 | 16 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 17 | 18 | - **Document any change in behaviour** - Make sure the `readme.md` and any other relevant documentation are kept up-to-date. 19 | 20 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 21 | 22 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 23 | 24 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 25 | 26 | 27 | **Happy coding**! 28 | -------------------------------------------------------------------------------- /src/BankAccount.php: -------------------------------------------------------------------------------- 1 | owner(); 22 | } 23 | 24 | /** 25 | * Get the model related to the subscription. 26 | * 27 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 28 | */ 29 | public function owner() 30 | { 31 | $model = config('cashier_openpay.model'); 32 | 33 | return $this->belongsTo($model, (new $model)->getForeignKey()); 34 | } 35 | 36 | /** 37 | * Get the subscription as a Openpay bank account object. 38 | * 39 | * @return \OpenpayBankAccount 40 | */ 41 | public function asOpenpayBankAccount() 42 | { 43 | $customer = $this->owner->asOpenpayCustomer(); 44 | 45 | return OpenpayBankAccount::find($this->openpay_id, $customer); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Card.php: -------------------------------------------------------------------------------- 1 | owner(); 23 | } 24 | 25 | /** 26 | * Get the model related to the subscription. 27 | * 28 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 29 | */ 30 | public function owner() 31 | { 32 | $model = config('cashier_openpay.model'); 33 | 34 | return $this->belongsTo($model, (new $model)->getForeignKey()); 35 | } 36 | 37 | /** 38 | * Get the subscription as a Openpay card object. 39 | * 40 | * @return \OpenpayCard 41 | */ 42 | public function asOpenpayCard() 43 | { 44 | $customer = $this->owner->asOpenpayCustomer(); 45 | 46 | return OpenpayCard::find($this->openpay_id, $customer); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | tests 16 | 17 | 18 | 19 | 20 | src/ 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "perafan/cashier-openpay", 3 | "description": "Laravel Cashier provides an expressive, fluent interface to Openpay's subscription billing services.", 4 | "keywords": ["laravel", "billing", "cashier", "openpay"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Pedro Perafán Carrasco", 9 | "email": "pedro.perafan.carrasco@gmail.com" 10 | } 11 | ], 12 | "homepage": "https://github.com/Perafan18/cashier-openpay", 13 | "require": { 14 | "illuminate/support": "~6|~7|~8", 15 | "openpay/sdk": "dev-master", 16 | "ext-json": "*" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "^8.0", 20 | "mockery/mockery": "^1.1", 21 | "orchestra/testbench": "~3|~4", 22 | "sempro/phpunit-pretty-print": "^1.0" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Perafan\\CashierOpenpay\\": "src/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Perafan\\CashierOpenpay\\Tests\\": "tests/" 32 | } 33 | }, 34 | "extra": { 35 | "laravel": { 36 | "providers": [ 37 | "Perafan\\CashierOpenpay\\CashierOpenpayServiceProvider" 38 | ], 39 | "aliases": { 40 | "CashierOpenpay": "Perafan\\CashierOpenpay\\Facades\\CashierOpenpay" 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Http/Controllers/WebhookControllerTest.php: -------------------------------------------------------------------------------- 1 | request('/', 'POST', ['type' => 'charge.succeeded']); 14 | 15 | $response = (new WebhookControllerTestStub)->handleWebhook($request); 16 | 17 | $this->assertEquals('Webhook Charge Succeeded', $response->getContent()); 18 | 19 | $this->assertEquals(200, $response->getStatusCode()); 20 | } 21 | 22 | public function testVerificationAndMethodExists() 23 | { 24 | $request = $this->request('/', 'POST', ['type' => 'verification']); 25 | 26 | $response = (new WebhookControllerTestStub)->handleWebhook($request); 27 | 28 | $this->assertEquals('Webhook Verification', $response->getContent()); 29 | 30 | $this->assertEquals(200, $response->getStatusCode()); 31 | } 32 | 33 | public function testRandomEventAndTheMethodDoesntExists() 34 | { 35 | $request = $this->request('/', 'POST', ['type' => 'random.event']); 36 | 37 | $response = (new WebhookControllerTestStub)->handleWebhook($request); 38 | 39 | $this->assertEquals('', $response->getContent()); 40 | } 41 | } 42 | 43 | class WebhookControllerTestStub extends BaseWebhookController 44 | { 45 | protected function handleChargeSucceeded() 46 | { 47 | return new Response('Webhook Charge Succeeded', 200); 48 | } 49 | 50 | protected function handleVerification() 51 | { 52 | return new Response('Webhook Verification', 200); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Openpay/Base.php: -------------------------------------------------------------------------------- 1 | {self::resource()}->get($id); 20 | } 21 | 22 | return $customer->{self::resource()}->get($id); 23 | } 24 | 25 | /** 26 | * @param array $options 27 | * @param OpenpayCustomer|null $customer 28 | * @return mixed 29 | */ 30 | public static function create(array $options = [], OpenpayCustomer $customer = null) 31 | { 32 | if (is_null($customer)) { 33 | return OpenpayInstance::getInstance()->{self::resource()}->create($options); 34 | } 35 | 36 | return $customer->{self::resource()}->create($options); 37 | } 38 | 39 | /** 40 | * @param array $options 41 | * @param OpenpayCustomer|null $customer 42 | * @return mixed 43 | */ 44 | public static function add(array $options = [], OpenpayCustomer $customer = null) 45 | { 46 | if (is_null($customer)) { 47 | return OpenpayInstance::getInstance()->{self::resource()}->add($options); 48 | } 49 | 50 | return $customer->{self::resource()}->add($options); 51 | } 52 | 53 | /** 54 | * @param $id 55 | * @param OpenpayCustomer|null $customer 56 | * @return mixed 57 | */ 58 | public static function delete($id, OpenpayCustomer $customer = null) 59 | { 60 | $resource_object = self::find($id, $customer); 61 | 62 | return $resource_object->delete(); 63 | } 64 | 65 | /** 66 | * @param array $options 67 | * @param OpenpayCustomer $customer 68 | * @return mixed 69 | */ 70 | public static function all(array $options = [], OpenpayCustomer $customer = null) 71 | { 72 | if (is_null($customer)) { 73 | return OpenpayInstance::getInstance()->{self::resource()}->getList($options); 74 | } 75 | 76 | return $customer->{self::resource()}->getList($options); 77 | } 78 | 79 | /** 80 | * @return string 81 | */ 82 | protected static function resource() 83 | { 84 | $klass = preg_replace('/Perafan|CashierOpenpay|Openpay|\\\\/', '', get_called_class()); 85 | $klass = Str::lower($klass); 86 | 87 | return Str::plural($klass); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /config/cashier_openpay.php: -------------------------------------------------------------------------------- 1 | env('OPENPAY_PRODUCTION_MODE', false), 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Openpay API Keys 19 | |-------------------------------------------------------------------------- 20 | | 21 | | You can retrieve your Openpay API keys from the Openpay control panel. 22 | | 23 | */ 24 | 25 | 'id' => env('OPENPAY_ID', ''), 26 | 27 | 'private_key' => env('OPENPAY_PRIVATE_KEY', ''), 28 | 29 | 'public_key' => env('OPENPAY_PUBLIC_KEY', ''), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Log Errors 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Set as true if you want to see the data from Openpay exceptions (HTTP 37 | | requests) in your laravel.log 38 | | Note: You need to use OpenpayExceptionsHandler 39 | */ 40 | 41 | 'log_errors' => env('OPENPAY_LOG_ERRORS', false), 42 | 43 | /* 44 | |-------------------------------------------------------------------------- 45 | | Cashier Model 46 | |-------------------------------------------------------------------------- 47 | | 48 | | This is the model in your application that implements the Billable trait 49 | | provided by Cashier. It will serve as the primary model you use while 50 | | interacting with Cashier related methods, subscriptions, and so on. 51 | | 52 | */ 53 | 54 | 'model' => env('OPENPAY_MODEL', App\Models\User::class), 55 | 56 | /* 57 | |-------------------------------------------------------------------------- 58 | | Webhook 59 | |-------------------------------------------------------------------------- 60 | | 61 | | Cashier Openpay provides a WebhookController with the methods to add your 62 | | own business logic. 63 | | Note: First you need to publish the tag "cashier-openpay-webhook-controller" 64 | | 65 | | You may change the url of the webhook, the route name, the controller path 66 | | or maybe the method. 67 | | 68 | */ 69 | 70 | 'webhook' => [ 71 | 72 | 'route_name' => env('OPENPAY_WEBHOOK_ROUTE', 'openpay.webhooks.handle'), 73 | 74 | 'url' => env('OPENPAY_WEBHOOK_URL', 'openpay/webhooks/handle'), 75 | 76 | 'controller' => env('OPENPAY_WEBHOOK_CONTROLLER', '\App\Http\Controllers\WebhookController'), 77 | 78 | 'method' => env('OPENPAY_WEBHOOK_METHOD', 'handleWebhook'), 79 | ], 80 | ]; 81 | -------------------------------------------------------------------------------- /tests/SubscriptionTest.php: -------------------------------------------------------------------------------- 1 | withPackageMigrations(); 21 | self::$user = $this->createUser(); 22 | 23 | self::$user->createAsOpenpayCustomer([ 24 | 'external_id' => $this->randomExternalId(), 25 | ]); 26 | } 27 | 28 | public function tearDown(): void 29 | { 30 | self::$user->asOpenpayCustomer()->delete(); 31 | 32 | parent::tearDown(); 33 | } 34 | 35 | /** @test */ 36 | public function testOnGracePeriod() 37 | { 38 | $plan = $this->createPlan(); 39 | 40 | $subscription_data = [ 41 | 'card' => $this->cardData(), 42 | ]; 43 | 44 | $subscription = self::$user->newSubscription($plan->id, $subscription_data); 45 | 46 | $this->assertTrue($subscription->hasPlan($plan->id)); 47 | 48 | $this->assertFalse($subscription->onGracePeriod()); 49 | 50 | $this->assertTrue($subscription->onTrial()); 51 | 52 | $this->assertFalse($subscription->pastDue()); 53 | 54 | $this->assertFalse($subscription->unpaid()); 55 | 56 | $this->assertFalse($subscription->ended()); 57 | 58 | $this->assertEquals(self::$user->id, $subscription->owner->id); 59 | 60 | $this->assertFalse($subscription->cancelled()); 61 | 62 | $subscription->cancel(); 63 | 64 | $this->assertTrue($subscription->cancelled()); 65 | 66 | $this->assertTrue($subscription->onGracePeriod()); 67 | 68 | $subscription->cancelNow(); 69 | 70 | Plan::delete($plan->id); 71 | } 72 | 73 | /** @test */ 74 | public function testOnGracePeriods() 75 | { 76 | $plan = $this->createPlan(); 77 | 78 | $subscription_data = [ 79 | 'card' => $this->cardData(), 80 | 'trial_end_date' => Carbon::now()->subDay(), 81 | ]; 82 | 83 | $subscription = self::$user->newSubscription($plan->id, $subscription_data); 84 | 85 | $this->assertTrue($subscription->hasPlan($plan->id)); 86 | 87 | $this->assertFalse($subscription->onGracePeriod()); 88 | 89 | $this->assertFalse($subscription->onTrial()); 90 | 91 | $this->assertFalse($subscription->pastDue()); 92 | 93 | $this->assertFalse($subscription->unpaid()); 94 | 95 | $this->assertFalse($subscription->ended()); 96 | 97 | $this->assertEquals(self::$user->id, $subscription->owner->id); 98 | 99 | $this->assertFalse($subscription->cancelled()); 100 | 101 | $subscription->cancelNow(); 102 | 103 | $this->assertFalse($subscription->onGracePeriod()); 104 | 105 | $this->assertTrue($subscription->cancelled()); 106 | 107 | Plan::delete($plan->id); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/CashierOpenpayServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom(__DIR__.'/../config/cashier_openpay.php', 'cashier_openpay'); 18 | } 19 | 20 | /** 21 | * Perform post-registration booting of services. 22 | * 23 | * @return void 24 | */ 25 | public function boot() 26 | { 27 | $this->bootRoutes(); 28 | $this->bootResources(); 29 | $this->bootDirectives(); 30 | $this->bootPublishing(); 31 | } 32 | 33 | /** 34 | * Boot the package resources. 35 | * 36 | * @return void 37 | */ 38 | protected function bootResources() 39 | { 40 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'cashier_openpay'); 41 | } 42 | 43 | /** 44 | * Boot the package routes. 45 | * 46 | * @return void 47 | */ 48 | protected function bootRoutes() 49 | { 50 | $this->loadRoutesFrom(__DIR__.'/../routes/webhook.php'); 51 | } 52 | 53 | /** 54 | * Boot the package directives. 55 | * 56 | * @return void 57 | */ 58 | protected function bootDirectives() 59 | { 60 | Blade::directive('openpayJSLoad', function () { 61 | return ""; 62 | }); 63 | 64 | Blade::directive('openpayJqueryJSInit', function () { 65 | return ""; 66 | }); 67 | 68 | Blade::directive('openpayJSInit', function () { 69 | return ""; 70 | }); 71 | } 72 | 73 | /** 74 | * Boot the package's migrations. 75 | * 76 | * @return void 77 | */ 78 | protected function bootPublishingMigrations() 79 | { 80 | $prefix = 'migrations/'.date('Y_m_d_His', time()); 81 | 82 | $this->publishes([ 83 | __DIR__.'/../database/migrations/create_customer_columns.php.stub' => database_path($prefix.'_create_customer_columns.php'), 84 | __DIR__.'/../database/migrations/create_subscriptions_table.php.stub' => database_path($prefix.'_create_subscriptions_table.php'), 85 | __DIR__.'/../database/migrations/create_cards_table.php.stub' => database_path($prefix.'_create_cards_table.php'), 86 | __DIR__.'/../database/migrations/create_bank_accounts_table.php.stub' => database_path($prefix.'_create_bank_accounts_table.php'), 87 | ], 'cashier-openpay-migrations'); 88 | } 89 | 90 | /** 91 | * Boot the package's controller. 92 | * 93 | * @return void 94 | */ 95 | protected function bootPublishingController() 96 | { 97 | $this->publishes([ 98 | __DIR__.'/Http/Controllers/WebhookController.php.stub' => app_path('Http/Controllers/WebhookController.php'), 99 | ], 'cashier-openpay-webhook-controller'); 100 | } 101 | 102 | /** 103 | * Boot the package's config file. 104 | * 105 | * @return void 106 | */ 107 | protected function bootPublishingConfig() 108 | { 109 | $this->publishes([ 110 | __DIR__.'/../config/cashier_openpay.php' => config_path('cashier_openpay.php'), 111 | ], 'cashier-openpay-configs'); 112 | } 113 | 114 | /** 115 | * Boot the package's publishable resources. 116 | * 117 | * @return void 118 | */ 119 | protected function bootPublishing() 120 | { 121 | if ($this->app->runningInConsole()) { 122 | $this->bootPublishingConfig(); 123 | 124 | $this->bootPublishingController(); 125 | 126 | $this->bootPublishingMigrations(); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /resources/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Internal server error, contact support": "Internal server error, contact support.", 3 | "Bad Request": "Bad Request.", 4 | "The api key or merchant id are invalid": "The api key or merchant id are invalid.", 5 | "Parameters look valid but request failed": "Parameters look valid but request failed.", 6 | "The resource is unavailable at this moment. Please try again later": "The resource is unavailable at this moment. Please try again later.", 7 | "The requested resource doesn't exist": "The requested resource doesn't exist.", 8 | "The order_id has already been processed": "The order_id has already been processed.", 9 | "Operation rejected by processor": "Operation rejected by processor.", 10 | "The account is inactive": "The account is inactive.", 11 | "The request is too large": "The request is too large.", 12 | "Method not allowed for public API key, use private key instead": "Method not allowed for public API key, use private key instead.", 13 | "The resource was previously deleted": "The resource was previously deleted.", 14 | "The transaction amount exceeds your allowed transaction limit": "The transaction amount exceeds your allowed transaction limit.", 15 | "The operation is not allowed on the resource": "The operation is not allowed on the resource.", 16 | "Your account is inactive, please contact to soporte@openpay.mx for more information": "Your account is inactive, please contact to soporte@openpay.mx for more information.", 17 | "Could not get any response from gateway. Please try again later": "Could not get any response from gateway. Please try again later.", 18 | "The merchant email has been already processed": "The merchant email has been already processed.", 19 | "The payment gateway is not available at the moment, please try again later": "The payment gateway is not available at the moment, please try again later.", 20 | "The number of retries of charge is greater than allowed": "The number of retries of charge is greater than allowed.", 21 | "The number of decimal digits is not valid for this currency": "The number of decimal digits is not valid for this currency.", 22 | "The bank account already exists": "The bank account already exists.", 23 | "The external_id already exists": "The external_id already exists.", 24 | "The card number verification digit is invalid": "The card number verification digit is invalid.", 25 | "The expiration date has expired": "The expiration date has expired.", 26 | "The CVV2 security code is required": "The CVV2 security code is required.", 27 | "The card number is only valid in sandbox": "The card number is only valid in sandbox.", 28 | "The card is not valid for points": "The card is not valid for points.", 29 | "The CVV2 security code is invalid": "The CVV2 security code is invalid.", 30 | "3D Secure authentication failed": "3D Secure authentication failed.", 31 | "Card product type not supported": "Card product type not supported.", 32 | "The card was declined by the bank": "The card was declined by the bank.", 33 | "The card has expired": "The card has expired.", 34 | "The card doesn't have sufficient funds": "The card doesn't have sufficient funds.", 35 | "The card was reported as stolen": "The card was reported as stolen.", 36 | "Fraud risk detected by anti-fraud system": "Fraud risk detected by anti-fraud system.", 37 | "Request not allowed": "Request not allowed.", 38 | "The card was reported as lost": "The card was reported as lost.", 39 | "The bank has restricted the card": "The bank has restricted the card.", 40 | "The bank has requested the card to be retained": "The bank has requested the card to be retained.", 41 | "Bank authorization is required for this charge": "Bank authorization is required for this charge.", 42 | "Merchant not authorized to use payment plan": "Merchant not authorized to use payment plan.", 43 | "Invalid promotion for such card type": "Invalid promotion for such card type.", 44 | "Transaction amount is less than minimum for promotion": "Transaction amount is less than minimum for promotion.", 45 | "Promotion not allowed": "Promotion not allowed.", 46 | "There are not enough funds in the openpay account": "There are not enough funds in the openpay account.", 47 | "The operation can't be completed until pending fees are paid": "The operation can't be completed until pending fees are paid.", 48 | "The webhook has already been processed": "The webhook has already been processed.", 49 | "Could not connect with webhook service, verify URL": "Could not connect with webhook service, verify URL.", 50 | "Service responded with an error on this moment. Please try again later": "Service responded with an error on this moment. Please try again later" 51 | } 52 | -------------------------------------------------------------------------------- /tests/BillableTest.php: -------------------------------------------------------------------------------- 1 | withPackageMigrations(); 20 | 21 | self::$user = $this->createUser(); 22 | 23 | self::$user->createAsOpenpayCustomer([ 24 | 'external_id' => $this->randomExternalId(), 25 | ]); 26 | 27 | self::$plan = $this->createPlan(); 28 | } 29 | 30 | public function tearDown(): void 31 | { 32 | self::$user->asOpenpayCustomer()->delete(); 33 | 34 | OpenpayPlan::delete(self::$plan->id); 35 | 36 | parent::tearDown(); 37 | } 38 | 39 | /** @test */ 40 | public function testOpenpayId() 41 | { 42 | $user = $this->createUser(['email' => 'random@email.com']); 43 | 44 | $this->assertFalse($user->hasOpenpayId()); 45 | } 46 | 47 | /** @test */ 48 | public function testCreateOpenpayCustomer() 49 | { 50 | $user = $this->createUser(['name' => 'Pedro Perafán', 'email' => 'random@email.com']); 51 | 52 | $external_id = $this->randomExternalId(); 53 | 54 | $this->assertFalse($user->hasOpenpayId()); 55 | 56 | $user->createAsOpenpayCustomer([ 57 | 'external_id' => $external_id, 58 | ]); 59 | 60 | $this->assertTrue($user->hasOpenpayId()); 61 | 62 | $customer = $user->asOpenpayCustomer(); 63 | 64 | $this->assertIsObject($customer); 65 | 66 | $this->assertEquals('Pedro Perafán', $customer->name); 67 | 68 | $this->assertEquals($external_id, $customer->external_id); 69 | 70 | $user->asOpenpayCustomer()->delete(); 71 | } 72 | 73 | /** @test */ 74 | public function testNewCard() 75 | { 76 | $card = self::$user->addCard($this->cardData(), $this->addressData()); 77 | 78 | $this->assertEquals('411111XXXXXX1111', $card->card_number); 79 | 80 | $openpayCard = $card->asOpenpayCard(); 81 | 82 | $this->assertTrue($openpayCard->allows_charges); 83 | 84 | $this->assertTrue($openpayCard->allows_payouts); 85 | } 86 | 87 | /** @test */ 88 | public function testCharge() 89 | { 90 | $options = [ 91 | 'method' => 'bank_account', 92 | 'description' => 'Cargo con banco', 93 | ]; 94 | 95 | $charge = self::$user->charge(100.00, $options); 96 | 97 | $this->assertEquals('in_progress', $charge->status); 98 | $this->assertEquals('charge', $charge->transaction_type); 99 | $this->assertEquals('bank_transfer', $charge->payment_method->type); 100 | } 101 | 102 | /** @test */ 103 | public function testNewSubscription() 104 | { 105 | $card_data = $this->cardData(); 106 | 107 | $address = $this->addressData(); 108 | 109 | $card = self::$user->addCard($card_data, $address); 110 | 111 | $openpayCard = $card->asOpenpayCard(); 112 | 113 | $subscription = self::$user->newSubscription(self::$plan->id, ['source_id' => $openpayCard->id]); 114 | 115 | $this->assertEquals(self::$plan->id, $subscription->openpay_plan); 116 | 117 | $this->assertEquals(Subscription::STATUS_TRIAL, $subscription->openpay_status); 118 | 119 | $this->assertTrue(self::$user->onTrial('default')); 120 | 121 | $this->assertFalse(self::$user->onTrial('test')); 122 | 123 | $subscription->cancelNow(); 124 | } 125 | 126 | /** @test */ 127 | public function HasSubscription() 128 | { 129 | $subscription_data = [ 130 | 'card' => $this->cardData(), 131 | 'trial_end_date' => Carbon::now()->subDay(), 132 | ]; 133 | 134 | $subscription = self::$user->newSubscription(self::$plan->id, $subscription_data, 'test'); 135 | 136 | $this->assertEquals(Subscription::STATUS_ACTIVE, $subscription->openpay_status); 137 | 138 | $this->assertEquals($subscription->id, self::$user->subscription('test')->id); 139 | 140 | $this->assertFalse(self::$user->onTrial('test')); 141 | 142 | $this->assertTrue(self::$user->subscribed('test')); 143 | 144 | $this->assertTrue(self::$user->subscribed('test', self::$plan->id)); 145 | 146 | $this->assertFalse(self::$user->subscribed('hello', self::$plan->id)); 147 | 148 | $this->assertFalse(self::$user->subscribed('hello')); 149 | 150 | $subscription->cancelNow(); 151 | } 152 | 153 | /** @test */ 154 | public function checkIfBillableModel() 155 | { 156 | $subscription_data = [ 157 | 'card' => $this->cardData(), 158 | 'trial_end_date' => Carbon::now()->subDay(), 159 | ]; 160 | 161 | $subscription = self::$user->newSubscription(self::$plan->id, $subscription_data); 162 | 163 | $this->assertTrue(self::$user->subscribedToPlan(self::$plan->id, 'default')); 164 | 165 | $this->assertTrue(self::$user->onPlan(self::$plan->id)); 166 | 167 | $subscription->cancelNow(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /resources/lang/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Internal server error, contact support": "Ocurrió un error interno en el servidor de Openpay.", 3 | "Bad Request": "El formato de la petición no es JSON, los campos no tienen el formato correcto, o la petición no tiene campos que son requeridos.", 4 | "The api key or merchant id are invalid": "La llamada no esta autenticada o la autenticación es incorrecta.", 5 | "Parameters look valid but request failed": "La operación no se pudo completar por que el valor de uno o más de los parámetros no es correcto.", 6 | "The resource is unavailable at this moment. Please try again later": "Un servicio necesario para el procesamiento de la transacción no se encuentra disponible.", 7 | "The requested resource doesn't exist": "Uno de los recursos requeridos no existe.", 8 | "The order_id has already been processed": "Ya existe una transacción con el mismo ID de orden.", 9 | "Operation rejected by processor": "La transferencia de fondos entre una cuenta de banco o tarjeta y la cuenta de Openpay no fue aceptada.", 10 | "The account is inactive": "Una de las cuentas requeridas en la petición se encuentra desactivada.", 11 | "The request is too large": "El cuerpo de la petición es demasiado grande.", 12 | "Method not allowed for public API key, use private key instead": "Se esta utilizando la llave pública para hacer una llamada que requiere la llave privada, o bien, se esta usando la llave privada desde JavaScript.", 13 | "The resource was previously deleted": "Se solicita un recurso que esta marcado como eliminado.", 14 | "The transaction amount exceeds your allowed transaction limit": "El monto transacción esta fuera de los limites permitidos.", 15 | "The operation is not allowed on the resource": "La operación no esta permitida para el recurso.", 16 | "Your account is inactive, please contact to soporte@openpay.mx for more information": "La cuenta esta inactiva.", 17 | "Could not get any response from gateway. Please try again later": "No se ha obtenido respuesta de la solicitud realizada al servicio.", 18 | "The merchant email has been already processed": "El mail del comercio ya ha sido procesada.", 19 | "The payment gateway is not available at the moment, please try again later": "El gateway no se encuentra disponible en ese momento.", 20 | "The number of retries of charge is greater than allowed": "El número de intentos de cargo es mayor al permitido.", 21 | "The number of decimal digits is not valid for this currency": "El número de dígitos decimales es inválido para esta moneda.", 22 | "The bank account already exists": "La cuenta de banco con esta CLABE ya se encuentra registrada en el cliente.", 23 | "The external_id already exists": "El cliente con este identificador externo (External ID) ya existe.", 24 | "The card number verification digit is invalid": "El número de tarjeta es invalido.", 25 | "The expiration date has expired": "La fecha de expiración de la tarjeta es anterior a la fecha actual.", 26 | "The CVV2 security code is required": "El código de seguridad de la tarjeta (CVV2) no fue proporcionado.", 27 | "The card number is only valid in sandbox": "El número de tarjeta es de prueba, solamente puede usarse en Sandbox.", 28 | "The card is not valid for points": "La tarjeta no es valida para pago con puntos.", 29 | "The CVV2 security code is invalid": "El código de seguridad de la tarjeta (CVV2) es inválido.", 30 | "3D Secure authentication failed": "Autenticación 3D Secure fallida.", 31 | "Card product type not supported": "Tipo de tarjeta no soportada.", 32 | "The card was declined by the bank": "La tarjeta fue declinada por el banco.", 33 | "The card has expired": "La tarjeta ha expirado.", 34 | "The card doesn't have sufficient funds": "La tarjeta no tiene fondos suficientes.", 35 | "The card was reported as stolen": "La tarjeta ha sido identificada como una tarjeta robada.", 36 | "Fraud risk detected by anti-fraud system": "La tarjeta ha sido rechazada por el sistema antifraude.", 37 | "Request not allowed": "La operación no esta permitida para este cliente o esta transacción.", 38 | "The card was reported as lost": "La tarjeta fue reportada como perdida.", 39 | "The bank has restricted the card": "El banco ha restringido la tarjeta.", 40 | "The bank has requested the card to be retained": "El banco ha solicitado que la tarjeta sea retenida. Contacte al banco.", 41 | "Bank authorization is required for this charge": "Se requiere solicitar al banco autorización para realizar este pago.", 42 | "Merchant not authorized to use payment plan": "Comercio no autorizado para procesar pago a meses sin intereses.", 43 | "Invalid promotion for such card type": "Promoción no valida para este tipo de tarjetas.", 44 | "Transaction amount is less than minimum for promotion": "El monto de la transacción es menor al mínimo permitido para la promoción.", 45 | "Promotion not allowed": "Promoción no permitida.", 46 | "There are not enough funds in the openpay account": "La cuenta de Openpay no tiene fondos suficientes.", 47 | "The operation can't be completed until pending fees are paid": "La operación no puede ser completada hasta que sean pagadas las comisiones pendientes.", 48 | "The webhook has already been processed": "El webhook ya ha sido procesado.", 49 | "Could not connect with webhook service, verify URL": "No se ha podido conectar con el servicio de webhook.", 50 | "Service responded with an error on this moment. Please try again later": "El servicio respondió con errores." 51 | } 52 | -------------------------------------------------------------------------------- /tests/BaseTestCase.php: -------------------------------------------------------------------------------- 1 | runMigrations( 35 | collect( 36 | [ 37 | [ 38 | 'class' => CreateUsersTable::class, 39 | 'file_path' => __DIR__.'/database/migrations/create_users_table.php', 40 | ], 41 | [ 42 | 'class' => '\CreateCustomerColumns', 43 | 'file_path' => $migrations_dir.'/create_customer_columns.php.stub', 44 | ], 45 | [ 46 | 'class' => '\CreateSubscriptionsTable', 47 | 'file_path' => $migrations_dir.'/create_subscriptions_table.php.stub', 48 | ], 49 | [ 50 | 'class' => '\CreateCardsTable', 51 | 'file_path' => $migrations_dir.'/create_cards_table.php.stub', 52 | ], 53 | [ 54 | 'class' => '\CreateBankAccountsTable', 55 | 'file_path' => $migrations_dir.'/create_bank_accounts_table.php.stub', 56 | ], 57 | ] 58 | ) 59 | ); 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Runs a collection of migrations. 66 | * 67 | * @param Collection $migrations 68 | */ 69 | protected function runMigrations(Collection $migrations) 70 | { 71 | $migrations->each(function ($migration) { 72 | $this->runMigration($migration['class'], $migration['file_path']); 73 | }); 74 | } 75 | 76 | /** 77 | * @param string $class 78 | * @param string $file_path 79 | */ 80 | protected function runMigration($class, $file_path) 81 | { 82 | include_once $file_path; 83 | (new $class)->up(); 84 | } 85 | 86 | protected function openpayExceptions() 87 | { 88 | include_once __DIR__.'/../vendor/openpay/sdk/data/OpenpayApiError.php'; 89 | } 90 | 91 | /** 92 | * @param string $url 93 | * @param string $method 94 | * @param array $response 95 | * @return Request 96 | */ 97 | protected function ajaxRequest($url = '/', $method = 'POST', $response = []) 98 | { 99 | $request = $this->request($url, $method, $response); 100 | $request->headers->set('X-Requested-With', 'XMLHttpRequest'); 101 | 102 | return $request; 103 | } 104 | 105 | /** 106 | * @param string $url 107 | * @param string $method 108 | * @param array $response 109 | * @return Request 110 | */ 111 | protected function request($url = '/', $method = 'POST', $response = []) 112 | { 113 | return Request::create( 114 | $url, 115 | $method, 116 | [], 117 | [], 118 | [], 119 | [], 120 | json_encode($response) 121 | ); 122 | } 123 | 124 | /** 125 | * @return int 126 | */ 127 | protected function randomExternalId() 128 | { 129 | return rand(1000, 10000); 130 | } 131 | 132 | /** 133 | * @param array $options 134 | * @return User 135 | */ 136 | protected function createUser(array $options = []) 137 | { 138 | return User::create(array_merge([ 139 | 'email' => 'email@cashier-test.com', 140 | 'name' => 'Taylor Otwell', 141 | 'password' => Hash::make('HelloCashier123'), 142 | ], $options)); 143 | } 144 | 145 | /** 146 | * @param array $options 147 | * @return OpenpayPlan 148 | */ 149 | protected function createPlan(array $options = []) 150 | { 151 | return OpenpayPlan::add(array_merge([ 152 | 'amount' => 150.00, 153 | 'status_after_retry' => 'cancelled', 154 | 'retry_times' => 2, 155 | 'name' => 'Plan 1', 156 | 'repeat_unit' => 'month', 157 | 'trial_days' => '30', 158 | 'repeat_every' => '1', 159 | 'currency' => 'MXN', 160 | ], $options)); 161 | } 162 | 163 | /** 164 | * @param array $options 165 | * @return array 166 | */ 167 | protected function cardData(array $options = []) 168 | { 169 | return array_merge([ 170 | 'holder_name' => 'Taylor Otwell', 171 | 'card_number' => '4111111111111111', 172 | 'cvv2' => '123', 173 | 'expiration_month' => '12', 174 | 'expiration_year' => '30', 175 | ], $options); 176 | } 177 | 178 | /** 179 | * @param array $options 180 | * @return array 181 | */ 182 | protected function addressData(array $options = []) 183 | { 184 | return array_merge([ 185 | 'line1' => 'Avenida Carranza 1115', 186 | 'postal_code' => '78230', 187 | 'state' => 'San Luis Potosí', 188 | 'city' => 'San Luis Potosí', 189 | 'country_code' => 'MX', 190 | ], $options); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/Http/Controllers/WebhookController.php.stub: -------------------------------------------------------------------------------- 1 | active() || $this->onTrial() || $this->onGracePeriod(); 39 | } 40 | 41 | /** 42 | * Determine if the subscription is active. 43 | * 44 | * @return bool 45 | */ 46 | public function active() 47 | { 48 | return is_null($this->ends_at) || $this->onGracePeriod(); 49 | } 50 | 51 | /** 52 | * Determine if the subscription is within its grace period after cancellation. 53 | * 54 | * @return bool 55 | */ 56 | public function onGracePeriod() 57 | { 58 | return $this->ends_at && $this->ends_at->isFuture(); 59 | } 60 | 61 | /** 62 | * Determine if the subscription is within its trial period. 63 | * 64 | * @return bool 65 | */ 66 | public function onTrial() 67 | { 68 | return $this->trial_ends_at && $this->trial_ends_at->isFuture(); 69 | } 70 | 71 | /** 72 | * Filter query by on trial. 73 | * 74 | * @param \Illuminate\Database\Eloquent\Builder $query 75 | * @return void 76 | */ 77 | public function scopeOnTrial($query) 78 | { 79 | $query->whereNotNull('trial_ends_at')->where('trial_ends_at', '>', Carbon::now()); 80 | } 81 | 82 | /** 83 | * Determine if the subscription is past due. 84 | * 85 | * @return bool 86 | */ 87 | public function pastDue() 88 | { 89 | return $this->openpay_status === self::STATUS_PAST_DUE; 90 | } 91 | 92 | /** 93 | * Filter query by past due. 94 | * 95 | * @param \Illuminate\Database\Eloquent\Builder $query 96 | * @return void 97 | */ 98 | public function scopePastDue($query) 99 | { 100 | $query->where('openpay_status', self::STATUS_PAST_DUE); 101 | } 102 | 103 | /** 104 | * Determine if the subscription is past due. 105 | * 106 | * @return bool 107 | */ 108 | public function unpaid() 109 | { 110 | return $this->openpay_status === self::STATUS_UNPAID; 111 | } 112 | 113 | /** 114 | * Filter query by past due. 115 | * 116 | * @param \Illuminate\Database\Eloquent\Builder $query 117 | * @return void 118 | */ 119 | public function scopeUnpaid($query) 120 | { 121 | $query->where('openpay_status', self::STATUS_UNPAID); 122 | } 123 | 124 | /** 125 | * Determine if the subscription is no longer active. 126 | * 127 | * @return bool 128 | */ 129 | public function cancelled() 130 | { 131 | return ! is_null($this->ends_at); 132 | } 133 | 134 | /** 135 | * Filter query by cancelled. 136 | * 137 | * @param \Illuminate\Database\Eloquent\Builder $query 138 | * @return void 139 | */ 140 | public function scopeCancelled($query) 141 | { 142 | $query->whereNotNull('ends_at'); 143 | } 144 | 145 | /** 146 | * Filter query by not cancelled. 147 | * 148 | * @param \Illuminate\Database\Eloquent\Builder $query 149 | * @return void 150 | */ 151 | public function scopeNotCancelled($query) 152 | { 153 | $query->whereNull('ends_at'); 154 | } 155 | 156 | /** 157 | * Determine if the subscription has ended and the grace period has expired. 158 | * 159 | * @return bool 160 | */ 161 | public function ended() 162 | { 163 | return $this->cancelled() && ! $this->onGracePeriod(); 164 | } 165 | 166 | /** 167 | * Filter query by ended. 168 | * 169 | * @param \Illuminate\Database\Eloquent\Builder $query 170 | * @return void 171 | */ 172 | public function scopeEnded($query) 173 | { 174 | $query->cancelled()->notOnGracePeriod(); 175 | } 176 | 177 | /** 178 | * Get the user that owns the subscription. 179 | * 180 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 181 | */ 182 | public function user() 183 | { 184 | return $this->owner(); 185 | } 186 | 187 | /** 188 | * Get the model related to the subscription. 189 | * 190 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 191 | */ 192 | public function owner() 193 | { 194 | $model = config('cashier_openpay.model'); 195 | 196 | return $this->belongsTo($model, (new $model)->getForeignKey()); 197 | } 198 | 199 | /** 200 | * Extend an existing subscription's trial period. 201 | * 202 | * @param CarbonInterface $date 203 | * @return Subscription 204 | * @throws \Exception 205 | */ 206 | public function extendTrial(CarbonInterface $date) 207 | { 208 | if (! $date->isFuture()) { 209 | throw new \Exception("Extending a subscription's trial requires a date in the future."); 210 | } 211 | 212 | $subscription = $this->asOpenpaySubscription(); 213 | 214 | $subscription->trial_end_date = $date->getTimestamp(); 215 | 216 | $subscription->save(); 217 | 218 | $this->trial_ends_at = $date; 219 | 220 | $this->save(); 221 | 222 | return $this; 223 | } 224 | 225 | /** 226 | * Cancel the subscription at the end of the billing period. 227 | * 228 | * @return $this 229 | */ 230 | public function cancel() 231 | { 232 | $subscription = $this->asOpenpaySubscription(); 233 | 234 | $subscription->cancel_at_period_end = true; 235 | 236 | $subscription = $subscription->save(); 237 | 238 | $this->openpay_status = self::STATUS_CANCELLED; 239 | 240 | if ($this->onTrial()) { 241 | $this->ends_at = $this->trial_ends_at; 242 | } else { 243 | $this->ends_at = $subscription->charge_date; 244 | } 245 | 246 | $this->save(); 247 | 248 | return $this; 249 | } 250 | 251 | /** 252 | * Cancel the subscription immediately. 253 | * 254 | * @return $this 255 | */ 256 | public function cancelNow() 257 | { 258 | $this->asOpenpaySubscription()->delete(); 259 | 260 | $this->fill([ 261 | 'openpay_status' => self::STATUS_CANCELLED, 262 | 'ends_at' => Carbon::now(), 263 | ])->save(); 264 | 265 | return $this; 266 | } 267 | 268 | /** 269 | * Determine if the subscription has a specific plan. 270 | * 271 | * @param int $plan 272 | * @return bool 273 | */ 274 | public function hasPlan($plan) 275 | { 276 | return $this->openpay_plan == $plan; 277 | } 278 | 279 | /** 280 | * Get the subscription as a Openpay subscription object. 281 | * 282 | * @return \OpenpaySubscription 283 | */ 284 | public function asOpenpaySubscription() 285 | { 286 | $customer = $this->owner->asOpenpayCustomer(); 287 | 288 | return OpenpaySubscription::find($this->openpay_id, $customer); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/OpenpayExceptionsHandler.php: -------------------------------------------------------------------------------- 1 | getMessage(); 34 | $http_error_code = $exception->getHttpCode(); 35 | $error_code = $exception->getErrorCode(); 36 | 37 | if ($error_code >= 1000 && $error_code <= 1020) { 38 | $message = $this->generalErrors($error_code); 39 | } elseif ($error_code >= 2001 && $error_code <= 2003) { 40 | $message = $this->storageErrors($error_code); 41 | } elseif ($error_code >= 2004 && $error_code <= 3205) { 42 | $message = $this->cardsErrors($error_code); 43 | } elseif ($error_code >= 4001 && $error_code <= 4002) { 44 | $message = $this->accountsErrors($error_code); 45 | } elseif ($error_code >= 6001 && $error_code <= 6003) { 46 | $message = $this->webhooksErrors($error_code); 47 | } 48 | 49 | $response_data = [ 50 | 'openpay_error_request_id' => $exception->getRequestId(), 51 | 'openpay_error_message' => $message, 52 | 'openpay_error_message_original' => $exception->getMessage(), 53 | 'openpay_error_http_code' => $http_error_code, 54 | 'openpay_error_category' => $exception->getCategory(), 55 | 'openpay_error_code' => $error_code, 56 | ]; 57 | 58 | if (config('cashier_openpay.log_errors')) { 59 | Log::error('OPENPAY ERROR REQUEST ID: '.$exception->getRequestId()); 60 | Log::error('OPENPAY ERROR MESSAGE: '.$exception->getMessage()); 61 | Log::error('OPENPAY ERROR HTTP CODE: '.$http_error_code); 62 | Log::error('OPENPAY ERROR CODE: '.$error_code); 63 | Log::error('OPENPAY ERROR CATEGORY: '.$exception->getCategory()); 64 | } 65 | 66 | if ($request->ajax() || $request->wantsJson()) { 67 | return response()->json($response_data, $http_error_code); 68 | } else { 69 | return back() 70 | ->withInput() 71 | ->withErrors($response_data, 'cashier'); 72 | } 73 | } 74 | 75 | protected function generalErrors($error_code) 76 | { 77 | switch ($error_code) { 78 | case 1000: 79 | return __('Internal server error, contact support'); 80 | case 1001: 81 | return __('Bad Request'); 82 | case 1002: 83 | return __('The api key or merchant id are invalid'); 84 | case 1003: 85 | return __('Parameters look valid but request failed'); 86 | case 1004: 87 | return __('The resource is unavailable at this moment. Please try again later'); 88 | case 1005: 89 | return __('The requested resource doesn\'t exist'); 90 | case 1006: 91 | return __('The order_id has already been processed'); 92 | case 1007: 93 | return __('Operation rejected by processor'); 94 | case 1008: 95 | return __('The account is inactive'); 96 | case 1009: 97 | return __('The request is too large'); 98 | case 1010: 99 | return __('Method not allowed for public API key, use private key instead'); 100 | case 1011: 101 | return __('The resource was previously deleted'); 102 | case 1012: 103 | return __('The transaction amount exceeds your allowed transaction limit'); 104 | case 1013: 105 | return __('The operation is not allowed on the resource'); 106 | case 1014: 107 | return __('Your account is inactive, please contact to soporte@openpay.mx for more information'); 108 | case 1015: 109 | return __('Could not get any response from gateway. Please try again later'); 110 | case 1016: 111 | return __('The merchant email has been already processed'); 112 | case 1017: 113 | return __('The payment gateway is not available at the moment, please try again later'); 114 | case 1018: 115 | return __('The number of retries of charge is greater than allowed'); 116 | case 1020: 117 | return __('The number of decimal digits is not valid for this currency'); 118 | } 119 | } 120 | 121 | protected function storageErrors($error_code) 122 | { 123 | switch ($error_code) { 124 | case 2001: 125 | return __('The bank account already exists'); 126 | case 2003: 127 | return __('The external_id already exists'); 128 | } 129 | } 130 | 131 | protected function cardsErrors($error_code) 132 | { 133 | switch ($error_code) { 134 | case 2004: 135 | return __('The card number verification digit is invalid'); 136 | case 2005: 137 | return __('The expiration date has expired'); 138 | case 2006: 139 | return __('The CVV2 security code is required'); 140 | case 2007: 141 | return __('The card number is only valid in sandbox'); 142 | case 2008: 143 | return __('The card is not valid for points'); 144 | case 2009: 145 | return __('The CVV2 security code is invalid'); 146 | case 2010: 147 | return __('3D Secure authentication failed'); 148 | case 2011: 149 | return __('Card product type not supported'); 150 | case 3001: 151 | return __('The card was declined by the bank'); 152 | case 3002: 153 | return __('The card has expired'); 154 | case 3003: 155 | return __('The card doesn\'t have sufficient funds'); 156 | case 3004: 157 | return __('The card was reported as stolen'); 158 | case 3005: 159 | return __('Fraud risk detected by anti-fraud system'); 160 | case 3006: 161 | return __('Request not allowed'); 162 | case 3009: 163 | return __('The card was reported as lost'); 164 | case 3010: 165 | return __('The bank has restricted the card'); 166 | case 3011: 167 | return __('The bank has requested the card to be retained'); 168 | case 3012: 169 | return __('Bank authorization is required for this charge'); 170 | case 3201: 171 | return __('Merchant not authorized to use payment plan'); 172 | case 3203: 173 | return __('Invalid promotion for such card type'); 174 | case 3204: 175 | return __('Transaction amount is less than minimum for promotion'); 176 | case 3205: 177 | return __('Promotion not allowed'); 178 | } 179 | } 180 | 181 | protected function accountsErrors($error_code) 182 | { 183 | switch ($error_code) { 184 | case 4001: 185 | return __('There are not enough funds in the openpay account'); 186 | case 4002: 187 | return __('The operation can\'t be completed until pending fees are paid'); 188 | } 189 | } 190 | 191 | protected function webhooksErrors($error_code) 192 | { 193 | switch ($error_code) { 194 | case 6001: 195 | return __('The webhook has already been processed'); 196 | case 6002: 197 | return __('Could not connect with webhook service, verify URL'); 198 | case 6003: 199 | return __('Service responded with an error on this moment. Please try again later'); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/Billable.php: -------------------------------------------------------------------------------- 1 | $amount, 24 | ], $options); 25 | 26 | $customer = $this->asOpenpayCustomer(); 27 | 28 | return OpenpayCharge::create($options, $customer); 29 | } 30 | 31 | /** 32 | * @param $charge_id 33 | * @param null $amount 34 | * @param string $description 35 | * @return mixed 36 | */ 37 | public function refund($charge_id, $description = '', $amount = null) 38 | { 39 | $refund_data = [ 40 | 'description' => $description, 41 | ]; 42 | 43 | if ($amount != null) { 44 | $refund_data = array_merge([ 45 | 'amount' => $amount, 46 | ], $refund_data); 47 | } 48 | 49 | $customer = $this->asOpenpayCustomer(); 50 | 51 | return OpenpayCharge::refund($charge_id, $refund_data, $customer); 52 | } 53 | 54 | /** 55 | * @param $plan_id 56 | * @param string $name 57 | * @param array $options 58 | * @return Subscription 59 | */ 60 | public function newSubscription($plan_id, array $options = [], $name = 'default') 61 | { 62 | $options = array_merge([ 63 | 'plan_id' => $plan_id, 64 | ], $options); 65 | 66 | $customer = $this->asOpenpayCustomer(); 67 | $openpay_subscription = OpenpaySubscription::add($options, $customer); 68 | 69 | /** @var Subscription $subscription */ 70 | $subscription = $this->subscriptions()->create([ 71 | 'name' => $name, 72 | 'user_id' => $this->id, 73 | 'openpay_id' => $openpay_subscription->id, 74 | 'openpay_status' => $openpay_subscription->status, 75 | 'openpay_plan' => $plan_id, 76 | 'trial_ends_at' => $openpay_subscription->trial_end_date, 77 | 'ends_at' => null, 78 | ]); 79 | 80 | $subscription->save(); 81 | 82 | return $subscription; 83 | } 84 | 85 | /** 86 | * @param string $name 87 | * @param null $plan 88 | * @return bool 89 | */ 90 | public function subscribed($name = 'default', $plan = null) 91 | { 92 | $subscription = $this->subscription($name); 93 | 94 | if (! $subscription || ! $subscription->valid()) { 95 | return false; 96 | } 97 | 98 | return $plan ? $subscription->hasPlan($plan) : true; 99 | } 100 | 101 | /** 102 | * @param $plans 103 | * @param string $name 104 | * @return bool 105 | */ 106 | public function subscribedToPlan($plans, $name = 'default') 107 | { 108 | $subscription = $this->subscription($name); 109 | 110 | if (! $subscription || ! $subscription->valid()) { 111 | return false; 112 | } 113 | 114 | foreach ((array) $plans as $plan) { 115 | if ($subscription->hasPlan($plan)) { 116 | return true; 117 | } 118 | } 119 | 120 | return false; 121 | } 122 | 123 | /** 124 | * @param $plan 125 | * @return bool 126 | */ 127 | public function onPlan($plan) 128 | { 129 | return ! is_null($this->subscriptions->first(function (Subscription $subscription) use ($plan) { 130 | return $subscription->valid() && $subscription->hasPlan($plan); 131 | })); 132 | } 133 | 134 | /** 135 | * Determine if the model is on trial. 136 | * 137 | * @param string $name 138 | * @param string|null $plan 139 | * @return bool 140 | */ 141 | public function onTrial($name = 'default', $plan = null) 142 | { 143 | $subscription = $this->subscription($name); 144 | 145 | if (! $subscription || ! $subscription->onTrial()) { 146 | return false; 147 | } 148 | 149 | return $plan ? $subscription->hasPlan($plan) : true; 150 | } 151 | 152 | /** 153 | * @param string $name 154 | * @return Subscription|null 155 | */ 156 | public function subscription($name = 'default') 157 | { 158 | return $this->subscriptions()->where('name', $name)->first(); 159 | } 160 | 161 | /** 162 | * Get all of the subscriptions for the Openpay model. 163 | * 164 | * @return HasMany 165 | */ 166 | public function subscriptions() 167 | { 168 | return $this->hasMany(Subscription::class, $this->getForeignKey()); 169 | } 170 | 171 | /** 172 | * @param $card_data 173 | * @param $address 174 | * @param array $options 175 | * @return Card 176 | */ 177 | public function addCard(array $card_data, array $address = [], array $options = []) 178 | { 179 | if ($address != []) { 180 | $card_data['address'] = $address; 181 | } 182 | 183 | $data = array_merge($card_data, $options); 184 | 185 | $customer = $this->asOpenpayCustomer(); 186 | $card_openpay = OpenpayCard::add($data, $customer); 187 | 188 | /** @var Card $card */ 189 | $card = $this->cards()->create([ 190 | 'user_id' => $this->id, 191 | 'openpay_id' => $card_openpay->id, 192 | 'type' => $card_openpay->type, 193 | 'brand' => $card_openpay->brand, 194 | 'holder_name' => $card_openpay->holder_name, 195 | 'card_number' => $card_openpay->card_number, 196 | 'expiration_month' => $card_openpay->expiration_month, 197 | 'expiration_year' => $card_openpay->expiration_year, 198 | 'bank_name' => $card_openpay->bank_name, 199 | 'bank_code' => $card_openpay->bank_code, 200 | ]); 201 | 202 | $card->save(); 203 | 204 | return $card; 205 | } 206 | 207 | /** 208 | * @return HasMany 209 | */ 210 | public function cards() 211 | { 212 | return $this->hasMany(Card::class, $this->getForeignKey()); 213 | } 214 | 215 | public function addBankAccount(array $bank_account_data) 216 | { 217 | $customer = $this->asOpenpayCustomer(); 218 | $bank_account = OpenpayBankAccount::add($bank_account_data, $customer); 219 | 220 | /** @var BankAccount $bank_account */ 221 | $bank_account = $this->bank_accounts()->create([ 222 | 'user_id' => $this->id, 223 | 'openpay_id' => $bank_account->id, 224 | 'holder_name' => $bank_account->holder_name, 225 | 'clabe' => $bank_account->clabe, 226 | 'bank_name' => $bank_account->bank_name, 227 | 'bank_code' => $bank_account->bank_code, 228 | 'alias' => $bank_account->alias, 229 | ]); 230 | 231 | $bank_account->save(); 232 | 233 | return $bank_account; 234 | } 235 | 236 | /** 237 | * @return HasMany 238 | */ 239 | public function bank_accounts() 240 | { 241 | return $this->hasMany(BankAccount::class, $this->getForeignKey()); 242 | } 243 | 244 | /** 245 | * Determine if the entity has a OpenPay customer ID. 246 | * 247 | * @return bool 248 | */ 249 | public function hasOpenpayId() 250 | { 251 | return ! is_null($this->openpay_id); 252 | } 253 | 254 | /** 255 | * Create a Openpay customer for the given Openpay model. 256 | * 257 | * @param array $options 258 | * @return OpenpayCustomer 259 | */ 260 | public function createAsOpenpayCustomer(array $options = []) 261 | { 262 | if ($this->hasOpenpayId()) { 263 | return $this->asOpenpayCustomer(); 264 | } 265 | 266 | $options = array_key_exists('name', $options) ? $options : array_merge($options, ['name' => $this->name]); 267 | $options = array_key_exists('email', $options) ? $options : array_merge($options, ['email' => $this->email]); 268 | $options = array_key_exists('external_id', $options) ? $options : array_merge($options, ['external_id' => $this->id]); 269 | 270 | $customer = Customer::add($options); 271 | 272 | $this->openpay_id = $customer->id; 273 | $this->save(); 274 | 275 | return $customer; 276 | } 277 | 278 | /** 279 | * Get the Openpay customer for the Openpay model. 280 | * 281 | * @return OpenpayCustomer 282 | */ 283 | public function asOpenpayCustomer() 284 | { 285 | if (is_null($this->openpay_id)) { 286 | return null; 287 | } 288 | 289 | return Customer::find($this->openpay_id); 290 | } 291 | 292 | /** 293 | * Get the Openpay customer for the Openpay model. 294 | * 295 | * @return OpenpayCustomer 296 | */ 297 | public function createOrGetOpenpayCustomer() 298 | { 299 | if ($this->openpay_id) { 300 | return Customer::find($this->openpay_id); 301 | } 302 | 303 | $this->createAsOpenpayCustomer(); 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /tests/Traits/OpenpayExceptionHandlerTest.php: -------------------------------------------------------------------------------- 1 | openpayExceptions(); 23 | } 24 | 25 | public function testIsOpenPayExceptionGenericException() 26 | { 27 | $result = (new Handler)->isOpenpayException(new Exception); 28 | 29 | $this->assertFalse($result); 30 | } 31 | 32 | public function testIsOpenPayExceptionApiError() 33 | { 34 | $result = (new Handler)->isOpenpayException(new OpenpayApiError); 35 | 36 | $this->assertTrue($result); 37 | } 38 | 39 | public function testIsOpenPayExceptionApiAuthError() 40 | { 41 | $result = (new Handler)->isOpenpayException(new OpenpayApiAuthError); 42 | 43 | $this->assertTrue($result); 44 | } 45 | 46 | public function testIsOpenPayExceptionApiTransactionError() 47 | { 48 | $result = (new Handler)->isOpenpayException(new OpenpayApiTransactionError); 49 | 50 | $this->assertTrue($result); 51 | } 52 | 53 | public function testIsOpenPayExceptionApiConnectionError() 54 | { 55 | $result = (new Handler)->isOpenpayException(new OpenpayApiConnectionError); 56 | 57 | $this->assertTrue($result); 58 | } 59 | 60 | public function testRenderWithGenericException() 61 | { 62 | $request = $this->request(); 63 | $message = 'Error'; 64 | 65 | try { 66 | throw new Exception($message); 67 | } catch (Exception $exception) { 68 | $response = (new Handler)->render($request, $exception); 69 | } 70 | 71 | $this->assertEquals(json_encode(['message' => $message]), $response->getContent()); 72 | } 73 | 74 | public function testRenderJsonAjaxRequestWithApiAuthError() 75 | { 76 | $request = $this->ajaxRequest(); 77 | 78 | $request_id = 1; 79 | $exception_message = 'Error'; 80 | $message = __('Internal server error, contact support'); 81 | $category = 'request'; 82 | $error_code = 1000; 83 | $http_code = 404; 84 | $fraud_rules = []; 85 | 86 | $expected_response_data = $this->expectedResponseJson($message, $exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules); 87 | 88 | try { 89 | throw new OpenpayApiAuthError($exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules); 90 | } catch (Exception $exception) { 91 | $response = (new Handler)->render($request, $exception); 92 | } 93 | 94 | $this->assertEquals($expected_response_data, $response->getContent()); 95 | $this->assertEquals($http_code, $response->getStatusCode()); 96 | } 97 | 98 | public function testRenderJsonAjaxRequestWithApiTransactionError() 99 | { 100 | $request = $this->ajaxRequest(); 101 | 102 | $request_id = 1; 103 | $exception_message = 'The card number verification digit is invalid'; 104 | $message = __('The card number verification digit is invalid'); 105 | $category = 'request'; 106 | $error_code = 2004; 107 | $http_code = 422; 108 | $fraud_rules = []; 109 | 110 | $expected_response_data = $this->expectedResponseJson($message, $exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules); 111 | 112 | try { 113 | throw new OpenpayApiTransactionError($exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules); 114 | } catch (Exception $exception) { 115 | $response = (new Handler)->render($request, $exception); 116 | } 117 | 118 | $this->assertEquals($expected_response_data, $response->getContent()); 119 | $this->assertEquals($http_code, $response->getStatusCode()); 120 | } 121 | 122 | public function testRenderJsonAjaxRequestWithApiRequestError() 123 | { 124 | $request = $this->ajaxRequest(); 125 | 126 | $request_id = 1; 127 | $exception_message = 'The card number verification digit is invalid'; 128 | $message = __('The card number verification digit is invalid'); 129 | $category = 'request'; 130 | $error_code = 2004; 131 | $http_code = 422; 132 | $fraud_rules = []; 133 | 134 | $expected_response_data = $this->expectedResponseJson($message, $exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules); 135 | 136 | try { 137 | throw new OpenpayApiRequestError($exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules); 138 | } catch (Exception $exception) { 139 | $response = (new Handler)->render($request, $exception); 140 | } 141 | 142 | $this->assertEquals($expected_response_data, $response->getContent()); 143 | 144 | $this->assertEquals($http_code, $response->getStatusCode()); 145 | } 146 | 147 | public function testRenderBackWithApiTransactionError() 148 | { 149 | $request = $this->request(); 150 | $request_id = 1; 151 | $exception_message = 'Error'; 152 | $message = __('The card number verification digit is invalid'); 153 | $category = 'request'; 154 | $error_code = 2004; 155 | $http_code = 422; 156 | $fraud_rules = []; 157 | 158 | $expected_response_data = $this->expectedResponseHtml(); 159 | 160 | try { 161 | throw new OpenpayApiTransactionError($exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules); 162 | } catch (Exception $exception) { 163 | $response = (new Handler)->render($request, $exception); 164 | } 165 | 166 | $this->assertEquals($expected_response_data, $response->getContent()); 167 | $this->assertEquals([$request_id], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_request_id')); 168 | $this->assertEquals([$message], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_message')); 169 | $this->assertEquals([$exception_message], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_message_original')); 170 | $this->assertEquals([$http_code], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_http_code')); 171 | $this->assertEquals([$category], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_category')); 172 | $this->assertEquals([$error_code], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_code')); 173 | $this->assertEquals(302, $response->getStatusCode()); 174 | } 175 | 176 | public function testRenderBackWithUnknownOpenpayError() 177 | { 178 | $request = $this->request(); 179 | $request_id = 1; 180 | $exception_message = 'Error'; 181 | $message = 'Error'; 182 | $category = 'request'; 183 | $error_code = 8000; 184 | $http_code = 500; 185 | $fraud_rules = []; 186 | 187 | $expected_response_data = $this->expectedResponseHtml(); 188 | 189 | try { 190 | throw new OpenpayApiTransactionError($exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules); 191 | } catch (Exception $exception) { 192 | $response = (new Handler)->render($request, $exception); 193 | } 194 | 195 | $this->assertEquals($expected_response_data, $response->getContent()); 196 | $this->assertEquals([$request_id], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_request_id')); 197 | $this->assertEquals([$message], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_message')); 198 | $this->assertEquals([$exception_message], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_message_original')); 199 | $this->assertEquals([$http_code], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_http_code')); 200 | $this->assertEquals([$category], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_category')); 201 | $this->assertEquals([$error_code], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_code')); 202 | $this->assertEquals(302, $response->getStatusCode()); 203 | } 204 | 205 | private function expectedResponseJson($message, $original_message, $code, $category, $request_id, $http_code, $fraud_rules) 206 | { 207 | return json_encode([ 208 | 'openpay_error_request_id' => $request_id, 209 | 'openpay_error_message' => $message, 210 | 'openpay_error_message_original' => $original_message, 211 | 'openpay_error_http_code' => $http_code, 212 | 'openpay_error_category' => $category, 213 | 'openpay_error_code' => $code, 214 | ]); 215 | } 216 | 217 | private function expectedResponseHtml() 218 | { 219 | $expected_response_data = file_get_contents(__DIR__.'/../Fixtures/redirect_localhost.html'); 220 | 221 | return substr($expected_response_data, 0, -1); 222 | } 223 | } 224 | 225 | class Handler 226 | { 227 | use OpenpayExceptionsHandler; 228 | 229 | /** 230 | * @param $request 231 | * @param Throwable $exception 232 | * @return JsonResponse|RedirectResponse 233 | */ 234 | public function render($request, Throwable $exception) 235 | { 236 | if ($this->isOpenpayException($exception)) { 237 | return $this->renderOpenpayException($request, $exception); 238 | } 239 | 240 | return response()->json(['message' => $exception->getMessage()]); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # CashierOpenpay 2 | 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Total Downloads][ico-downloads]][link-downloads] 5 | [![Build Status][ico-travis]][link-travis] 6 | ![StyleCI](https://github.styleci.io/repos/133201440/shield?branch=master) 7 | 8 | ![Banner](https://banners.beyondco.de/Cashier%20OpenPay.png?theme=light&packageManager=composer+require&packageName=perafan%2Fcashier-openpay&pattern=endlessClouds&style=style_1&description=The+same+cashier+you+already+know+but+with+Openpay+&md=1&showWatermark=0&fontSize=100px&images=cash) 9 | 10 | ## Installation 11 | 12 | Require the Cashier package for Openpay with Composer: 13 | 14 | ```bash 15 | composer require perafan/cashier-openpay 16 | ``` 17 | 18 | | CashierOpenpay | Laravel | 19 | | :------------: |:--------:| 20 | | 1.X | 7.X | 21 | | 2.X | 8.X | 22 | 23 | Run to publish migrations, WebHookController and config file. 24 | 25 | ```bash 26 | php artisan vendor:publish --tag="cashier-openpay-migrations" 27 | php artisan vendor:publish --tag="cashier-openpay-configs" 28 | php artisan vendor:publish --tag="cashier-openpay-webhook-controller" 29 | ``` 30 | 31 | The Cashier service provider registers its own database migration directory, so remember to migrate your database after installing the package. 32 | The Cashier migrations will add several columns to your users table as well as create a new subscriptions table to hold all of your customer's subscriptions: 33 | 34 | ``` bash 35 | php artisan migrate 36 | ``` 37 | 38 | ## Configuration 39 | 40 | ### Billable Model 41 | 42 | Add the `Billable` trait to your model definition. 43 | `Billable` trait provides methods to allow yo to perform common billing tasks (creating subscriptions, add payment method information, creating charges ,etc.) 44 | 45 | ```php 46 | use Perafan\CashierOpenpay\Billable; 47 | 48 | class User extends Authenticatable 49 | { 50 | use Billable; 51 | } 52 | ``` 53 | 54 | Cashier assumes your Billable model will be the `App\Models\User class that ships with Laravel. If you wish to change this you can specify a different model in your `.env` file: 55 | 56 | ```dotenv 57 | OPENPAY_MODEL=App\Models\User 58 | ``` 59 | 60 | ### API Keys 61 | Next, you should configure your Openpay keys in your .env file. You can retrieve your Openpay API keys from the Openpay control panel. 62 | 63 | ```dotenv 64 | OPENPAY_PUBLIC_KEY=-your-openpay-public-key- 65 | OPENPAY_PRIVATE_KEY=-your-openpay-private-key- 66 | OPENPAY_ID=-your-openpay-id- 67 | ``` 68 | 69 | ### Environment 70 | 71 | By convenience and security, the sandbox mode is activated by default in the client library. This allows you to test your own code when implementing Openpay, before charging any credit card in production environment. 72 | 73 | ```dotenv 74 | OPENPAY_PRODUCTION_MODE=false 75 | ``` 76 | 77 | ### Logging 78 | 79 | Cashier allows you to specify the log channel to be used when logging all Openpay related exceptions. 80 | 81 | ```dotenv 82 | OPENPAY_LOG_ERRORS=true 83 | ``` 84 | 85 | ### Show openpay errors (Optional)` 86 | 87 | If you want to catch all the openpay exceptions add in your `app/Exceptions/Handler.php` 88 | 89 | ```php 90 | isOpenpayException($exception)) { 106 | return $this->renderOpenpayException($request, $exception); 107 | } 108 | return parent::render($request, $exception); 109 | } 110 | } 111 | ``` 112 | 113 | To render the error response in blade you could use the follow snippets. 114 | 115 | #### Show errors with [bootstrap](https://getbootstrap.com/) 116 | 117 | ```blade 118 | @if($errors->cashier->isNotEmpty()) 119 | 124 | @endif 125 | ``` 126 | 127 | #### Show errors with [tailwindcss](https://tailwindcss.com/) 128 | 129 | ```blade 130 | @if($errors->cashier->isNotEmpty()) 131 | 136 | @endif 137 | ``` 138 | 139 | #### Your own Openpay Exceptions Handler (Optional) 140 | 141 | You can modify the response creating your own handler. 142 | 143 | ```php 144 | trait MyOpenpayExceptionsHandler 145 | { 146 | use OpenpayExceptionsHandler { 147 | OpenpayExceptionsHandler::renderOpenpayException as parentRenderOpenpayException; 148 | } 149 | 150 | public function renderOpenpayException(Request $request, OpenpayApiError $exception) 151 | { 152 | $this->parentRenderOpenpayException($request, $exception); 153 | 154 | //your code 155 | } 156 | } 157 | ``` 158 | 159 | ## Use 160 | 161 | ### Customers 162 | 163 | 164 | **On a User:** 165 | 166 | Add a new customer to a merchant: 167 | 168 | ```php 169 | $user->createAsOpenpayCustomer(); 170 | 171 | //Or you can send additional data 172 | 173 | $data = [ 174 | 'name' => 'Teofilo', 175 | 'last_name' => 'Velazco', 176 | 'phone_number' => '4421112233', 177 | 'address' => [ 178 | 'line1' => 'Privada Rio No. 12', 179 | 'line2' => 'Co. El Tintero', 180 | 'line3' => '', 181 | 'postal_code' => '76920', 182 | 'state' => 'Querétaro', 183 | 'city' => 'Querétaro.', 184 | 'country_code' => 'MX' 185 | ] 186 | ]; 187 | 188 | $openpay_customer = $user->createAsOpenpayCustomer($data); 189 | ```` 190 | 191 | Get a customer: 192 | ```php 193 | $openpay_customer = $user->asOpenpayCustomer(); 194 | ``` 195 | 196 | Update a customer: 197 | ```php 198 | $openpay_customer = $user->asOpenpayCustomer(); 199 | $openpay_customer->name = 'Juan'; 200 | $openpay_customer->last_name = 'Godinez'; 201 | $openpay_customer->save(); 202 | ``` 203 | 204 | Delete a customer: 205 | ```php 206 | $openpay_customer = $user->asOpenpayCustomer(); 207 | $openpay_customer->delete(); 208 | ``` 209 | 210 | **On a merchant:** 211 | 212 | Add a new customer to a merchant: 213 | 214 | ```php 215 | use Perafan\CashierOpenpay\Openpay\Customer as OpenpayCustomer; 216 | 217 | OpenpayCustomer::create([ 218 | 'name' => 'Teofilo', 219 | 'last_name' => 'Velazco', 220 | 'email' => 'teofilo@payments.com', 221 | 'phone_number' => '4421112233', 222 | 'address' => [ 223 | 'line1' => 'Privada Rio No. 12', 224 | 'line2' => 'Co. El Tintero', 225 | 'line3' => '', 226 | 'postal_code' => '76920', 227 | 'state' => 'Querétaro', 228 | 'city' => 'Querétaro.', 229 | 'country_code' => 'MX' 230 | ] 231 | ]); 232 | ```` 233 | 234 | Get a customer: 235 | ```php 236 | use Perafan\CashierOpenpay\Openpay\Customer as OpenpayCustomer; 237 | 238 | $customer = OpenpayCustomer::find('a9ualumwnrcxkl42l6mh'); 239 | ``` 240 | 241 | Get the list of customers: 242 | ```php 243 | use Perafan\CashierOpenpay\Openpay\Customer as OpenpayCustomer; 244 | 245 | $customer_list = OpenpayCustomer::all(); 246 | 247 | // with filters 248 | 249 | $filters = [ 250 | 'creation[gte]' => '2020-01-01', 251 | 'creation[lte]' => '2020-12-31', 252 | 'offset' => 0, 253 | 'limit' => 5 254 | ]; 255 | 256 | $customer_list = OpenpayCustomer::all($filters); 257 | ``` 258 | 259 | Update a customer: 260 | ```php 261 | use Perafan\CashierOpenpay\Openpay\Customer as OpenpayCustomer; 262 | 263 | $customer = OpenpayCustomer::find('a9ualumwnrcxkl42l6mh'); 264 | $customer->name = 'Juan'; 265 | $customer->last_name = 'Godinez'; 266 | $customer->save(); 267 | ``` 268 | 269 | Delete a customer: 270 | ```php 271 | use Perafan\CashierOpenpay\Openpay\Customer as OpenpayCustomer; 272 | 273 | $customer = OpenpayCustomer::find('a9ualumwnrcxkl42l6mh'); 274 | $customer->delete(); 275 | ``` 276 | 277 | #### Cards #### 278 | 279 | **On a user:** 280 | 281 | Add a card: 282 | ```php 283 | $card_data = [ 284 | 'holder_name' => 'Teofilo Velazco', 285 | 'card_number' => '4916394462033681', 286 | 'cvv2' => '123', 287 | 'expiration_month' => '12', 288 | 'expiration_year' => '15' 289 | ]; 290 | 291 | $card = $user->addCard($card_data); 292 | 293 | // with token 294 | 295 | $card_data = [ 296 | 'token_id' => 'tokgslwpdcrkhlgxqi9a', 297 | 'device_session_id' => '8VIoXj0hN5dswYHQ9X1mVCiB72M7FY9o' 298 | ]; 299 | 300 | $card = $user->addCard($card_data); 301 | 302 | // with address 303 | 304 | $address_data = [ 305 | 'line1' => 'Privada Rio No. 12', 306 | 'line2' => 'Co. El Tintero', 307 | 'line3' => '', 308 | 'postal_code' => '76920', 309 | 'state' => 'Querétaro', 310 | 'city' => 'Querétaro.', 311 | 'country_code' => 'MX' 312 | ]; 313 | 314 | $card = $user->addCard($card_data, $address_data); 315 | ``` 316 | 317 | Get a card: 318 | ```php 319 | use Perafan\CashierOpenpay\Card; 320 | 321 | $card = $user->cards->first; 322 | //or 323 | $card = Card::find(1); 324 | 325 | $openpay_card = $card->asOpenpayCard(); 326 | ``` 327 | 328 | Get user cards: 329 | ```php 330 | use Perafan\CashierOpenpay\Card; 331 | 332 | $cards = $user->cards; 333 | // or 334 | $cards = Card::where('user_id', $user->id)->get(); 335 | ``` 336 | 337 | Get user cards from Openpay 338 | ```php 339 | use Perafan\CashierOpenpay\Card; 340 | use Perafan\CashierOpenpay\Openpay\Card as OpenpayCard; 341 | 342 | $cards = $user->cards; 343 | 344 | $openpay_cards = $cards->map(function($card) { 345 | return $card->asOpenpayCard(); 346 | }); 347 | 348 | // or 349 | 350 | $cards = Card::where('user_id', $user->id)->get(); 351 | 352 | $openpay_cards = $cards->map(function($card) { 353 | return $card->asOpenpayCard(); 354 | }); 355 | 356 | // or 357 | 358 | $openpay_customer = $user->asOpenpayCustomer(); 359 | 360 | $openpay_cards = OpenpayCard::all([], $openpay_customer); 361 | 362 | // with filters 363 | 364 | $filters = [ 365 | 'creation[gte]' => '2020-01-01', 366 | 'creation[lte]' => '2020-12-31', 367 | 'offset' => 0, 368 | 'limit' => 5 369 | ]; 370 | 371 | $openpay_cards = OpenpayCard::all($filters, $openpay_customer); 372 | ``` 373 | 374 | Delete a card 375 | ```php 376 | use Perafan\CashierOpenpay\Card; 377 | 378 | $card = $user->cards->first; 379 | //or 380 | $card = Card::find(1); 381 | 382 | $openpay_card = $card->asOpenpayCard(); 383 | $deleted_card = $openpay_card->delete(); 384 | 385 | if (!is_array($deleted_card)) { 386 | //The card was deleted in Openpay 387 | $card->delete(); 388 | } 389 | ``` 390 | 391 | **On a merchant:** 392 | 393 | Add a card: 394 | ```php 395 | use Perafan\CashierOpenpay\Openpay\Card as OpenpayCard; 396 | 397 | $card_data = [ 398 | 'holder_name' => 'Teofilo Velazco', 399 | 'card_number' => '4916394462033681', 400 | 'cvv2' => '123', 401 | 'expiration_month' => '12', 402 | 'expiration_year' => '15' 403 | ]; 404 | 405 | $openpay_card = OpenpayCard::add($card_data); 406 | 407 | // with token 408 | 409 | $card_data = [ 410 | 'token_id' => 'tokgslwpdcrkhlgxqi9a', 411 | 'device_session_id' => '8VIoXj0hN5dswYHQ9X1mVCiB72M7FY9o' 412 | ]; 413 | 414 | $openpay_card = OpenpayCard::add($card_data); 415 | 416 | // with address 417 | 418 | $address_data = [ 419 | 'line1' => 'Privada Rio No. 12', 420 | 'line2' => 'Co. El Tintero', 421 | 'line3' => '', 422 | 'postal_code' => '76920', 423 | 'state' => 'Querétaro', 424 | 'city' => 'Querétaro.', 425 | 'country_code' => 'MX' 426 | ]; 427 | 428 | $card_data['address'] = $address_data; 429 | 430 | $openpay_card = OpenpayCard::add($card_data); 431 | ``` 432 | 433 | Get a card: 434 | ```php 435 | use Perafan\CashierOpenpay\Openpay\Card as OpenpayCard; 436 | 437 | $openpay_card = OpenpayCard::find('k9pn8qtsvr7k7gxoq1r5'); 438 | ``` 439 | 440 | Get the list of cards: 441 | ```php 442 | use Perafan\CashierOpenpay\Openpay\Card as OpenpayCard; 443 | 444 | $openpay_card = OpenpayCard::all(); 445 | 446 | // with filters 447 | 448 | $filters = [ 449 | 'creation[gte]' => '2020-01-01', 450 | 'creation[lte]' => '2020-12-31', 451 | 'offset' => 0, 452 | 'limit' => 5 453 | ]; 454 | 455 | $openpay_card = OpenpayCard::all($filters); 456 | ``` 457 | 458 | Delete a card: 459 | ```php 460 | use Perafan\CashierOpenpay\Openpay\Card as OpenpayCard; 461 | 462 | $openpay_card = OpenpayCard::find('k9pn8qtsvr7k7gxoq1r5'); 463 | $openpay_card->delete(); 464 | //Card was not delete on your database, only was deleted in openpay 465 | ``` 466 | 467 | #### Bank Accounts #### 468 | 469 | Add a bank account to a customer: 470 | ```php 471 | $bank_data = [ 472 | 'clabe' => '072910007380090615', 473 | 'alias' => 'Cuenta principal', 474 | 'holder_name' => 'Teofilo Velazco' 475 | ]; 476 | 477 | $bank_account = $user->addBankAccount($bank_data); 478 | ``` 479 | 480 | Get a bank account 481 | ```php 482 | use Perafan\CashierOpenpay\BankAccount; 483 | 484 | $bank_account = $user->bank_accounts->first; 485 | // or 486 | $bank_account = BankAccount::where('user_id', $user->id)->first(); 487 | 488 | $openpay_bank_account = $bank_account->asOpenpayBankAccount(); 489 | ``` 490 | 491 | Get user bank accounts: 492 | ```php 493 | use Perafan\CashierOpenpay\BankAccount; 494 | 495 | $bank_accounts = $user->bank_accounts; 496 | // or 497 | $bank_accounts = BankAccount::where('user_id', $user->id)->get(); 498 | ``` 499 | 500 | Get user bank accounts from Openpay: 501 | ```php 502 | use Perafan\CashierOpenpay\BankAccount; 503 | use Perafan\CashierOpenpay\Openpay\BankAccount as OpenpayBankAccount; 504 | 505 | $bank_accounts = $user->bank_accounts; 506 | 507 | $openpay_bank_accounts = $bank_accounts->map(function($bank_account) { 508 | return $bank_account->asOpenpayBankAccount(); 509 | }); 510 | 511 | // or 512 | 513 | $bank_accounts = BankAccount::where('user_id', $user->id)->get(); 514 | 515 | $openpay_bank_accounts = $bank_accounts->map(function($bank_account) { 516 | return $bank_account->asOpenpayBankAccount(); 517 | }); 518 | 519 | // or 520 | 521 | $openpay_customer = $user->asOpenpayCustomer(); 522 | 523 | $openpay_bank_accounts = OpenpayBankAccount::all([], $openpay_customer); 524 | 525 | // with filters 526 | 527 | $filters = [ 528 | 'creation[gte]' => '2020-01-01', 529 | 'creation[lte]' => '2020-12-31', 530 | 'offset' => 0, 531 | 'limit' => 5 532 | ]; 533 | 534 | $openpay_bank_accounts = OpenpayBankAccount::all($filters, $openpay_customer); 535 | ``` 536 | 537 | Delete a bank account: 538 | ```php 539 | use Perafan\CashierOpenpay\BankAccount; 540 | 541 | $bank_account = $user->bank_accounts->first; 542 | // or 543 | $bank_account = BankAccount::where('user_id', $user->id)->first(); 544 | 545 | $openpay_bank_account = $bank_account->asOpenpayBankAccount(); 546 | 547 | $deleted_bank_account = $openpay_bank_account->delete(); 548 | 549 | if (!is_array($deleted_bank_account)) { 550 | //The card was deleted in Openpay 551 | $bank_account->delete(); 552 | } 553 | ``` 554 | 555 | 556 | #### Charges #### 557 | 558 | **On a Customer:** 559 | 560 | Make a charge on a customer: 561 | ```php 562 | $charge_data = [ 563 | 'source_id' => 'tvyfwyfooqsmfnaprsuk', 564 | 'method' => 'card', 565 | 'currency' => 'MXN', 566 | 'description' => 'Cargo inicial a mi merchant', 567 | 'order_id' => 'oid-00051', 568 | 'device_session_id' => 'kR1MiQhz2otdIuUlQkbEyitIqVMiI16f', 569 | ]; 570 | 571 | $openpay_charge = $user->charge(100, $charge_data); 572 | ``` 573 | 574 | Get a charge: 575 | ```php 576 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge; 577 | 578 | $openpay_charge = OpenpayCharge::find('a9ualumwnrcxkl42l6mh'); 579 | ``` 580 | 581 | Get list of charges per user: 582 | ```php 583 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge; 584 | 585 | $filters = [ 586 | 'creation[gte]' => '2020-01-01', 587 | 'creation[lte]' => '2020-12-31', 588 | 'offset' => 0, 589 | 'limit' => 5 590 | ]; 591 | 592 | $openpay_customer = $user->asOpenpayCustomer(); 593 | 594 | $openpay_charge = OpenpayCharge::all($filters, $openpay_customer); 595 | ``` 596 | 597 | Make a capture: 598 | ```php 599 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge; 600 | 601 | $capture_data = ['amount' => 150.00]; 602 | 603 | $openpay_charge = OpenpayCharge::find('a9ualumwnrcxkl42l6mh'); 604 | 605 | $openpay_charge->capture($capture_data); 606 | ``` 607 | 608 | Make a refund: 609 | ```php 610 | // Send charge id as first param 611 | $charge_id = 'tvyfwyfooqsmfnaprsuk'; 612 | $user->refund($charge_id); 613 | //or 614 | 615 | $description = 'Reembolso'; 616 | $user->refund($charge_id, $description); 617 | 618 | //or 619 | $amount = 150.00; 620 | $user->refund($charge_id, $description, $amount); 621 | ``` 622 | 623 | **On a Merchant:** 624 | 625 | Make a charge on a merchant: 626 | ```php 627 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge; 628 | 629 | $charge_data = [ 630 | 'method' => 'card', 631 | 'source_id' => 'krfkkmbvdk3hewatruem', 632 | 'amount' => 100, 633 | 'description' => 'Cargo inicial a mi merchant', 634 | 'order_id' => 'ORDEN-00071' 635 | ]; 636 | 637 | $openpay_charge = OpenpayCharge::create($charge_data); 638 | ``` 639 | 640 | Get a charge: 641 | ```php 642 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge; 643 | 644 | $openpay_charge = OpenpayCharge::find('tvyfwyfooqsmfnaprsuk'); 645 | ``` 646 | 647 | Get list of charges: 648 | ```php 649 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge; 650 | 651 | $openpay_charges = OpenpayCharge::all(); 652 | 653 | // with filters 654 | $filters = [ 655 | 'creation[gte]' => '2020-01-01', 656 | 'creation[lte]' => '2020-12-31', 657 | 'offset' => 0, 658 | 'limit' => 5 659 | ]; 660 | 661 | $openpay_charges = OpenpayCharge::all($filters); 662 | ``` 663 | 664 | Make a capture: 665 | ```php 666 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge; 667 | 668 | $capture_data = ['amount' => 150.00]; 669 | 670 | $openpay_charge = OpenpayCharge::find('tvyfwyfooqsmfnaprsuk'); 671 | $capture_data->capture($capture_data); 672 | ``` 673 | 674 | Make a refund: 675 | ```php 676 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge; 677 | 678 | $refund_data = ['description' => 'Devolución']; 679 | 680 | $openpay_charge = OpenpayCharge::find('tvyfwyfooqsmfnaprsuk'); 681 | $openpay_charge->refund($refund_data); 682 | ``` 683 | 684 | #### Transfers #### 685 | 686 | Make a transfer: 687 | ```php 688 | $transfer_data = [ 689 | 'customer_id' => 'aqedin0owpu0kexr2eor', 690 | 'amount' => 12.50, 691 | 'description' => 'Cobro de Comisión', 692 | 'order_id' => 'ORDEN-00061' 693 | ]; 694 | 695 | $openpay_customer = $user->asOpenpayCustomer(); 696 | $transfer = $openpay_customer->transfers->create($transfer_data); 697 | ``` 698 | 699 | Get a transfer: 700 | ```php 701 | $openpay_customer = $user->asOpenpayCustomer(); 702 | $transfer = $openpay_customer->transfers->get('tyxesptjtx1bodfdjmlb'); 703 | ``` 704 | 705 | Get list of transfers: 706 | ```php 707 | $filters = [ 708 | 'creation[gte]' => '2020-01-01', 709 | 'creation[lte]' => '2020-12-31', 710 | 'offset' => 0, 711 | 'limit' => 5 712 | ]; 713 | 714 | $openpay_customer = $user->asOpenpayCustomer(); 715 | $transfer_list = $openpay_customer->transfers->getList($filters); 716 | ``` 717 | 718 | #### Payouts #### 719 | 720 | **On a Customer:** 721 | 722 | Make a payout on a customer: 723 | ```php 724 | $payout_data = [ 725 | 'method' => 'card', 726 | 'destination_id' => 'k9pn8qtsvr7k7gxoq1r5', 727 | 'amount' => 1000, 728 | 'description' => 'Retiro de saldo semanal', 729 | 'order_id' => 'ORDEN-00062' 730 | ]; 731 | 732 | $openpay_customer = $user->asOpenpayCustomer(); 733 | $payout = $openpay_customer->payouts->create($payout_data); 734 | ``` 735 | 736 | Get a payout: 737 | ```php 738 | $openpay_customer = $user->asOpenpayCustomer(); 739 | $payout = $openpay_customer->payouts->get('tysznlyigrkwnks6eq2c'); 740 | ``` 741 | 742 | Get list pf payouts: 743 | ```php 744 | $filters = [ 745 | 'creation[gte]' => '2020-01-01', 746 | 'creation[lte]' => '2020-12-31', 747 | 'offset' => 0, 748 | 'limit' => 5 749 | ]; 750 | 751 | $openpay_customer = $user->asOpenpayCustomer(); 752 | $payout_list = $customer->payouts->getList($filters); 753 | ``` 754 | 755 | #### Fees #### 756 | Pending ... 757 | 758 | #### Plans #### 759 | 760 | Add a plan: 761 | ```php 762 | use Perafan\CashierOpenpay\Openpay\Plan as OpenpayPlan; 763 | 764 | $plan_data = [ 765 | 'amount' => 150.00, 766 | 'status_after_retry' => 'cancelled', 767 | 'retry_times' => 2, 768 | 'name' => 'Plan Curso Verano', 769 | 'repeat_unit' => 'month', 770 | 'trial_days' => '30', 771 | 'repeat_every' => '1', 772 | 'currency' => 'MXN' 773 | ]; 774 | 775 | $openpay_plan = OpenpayPlan::add($plan_data); 776 | ``` 777 | 778 | Get a plan: 779 | ```php 780 | use Perafan\CashierOpenpay\Openpay\Plan as OpenpayPlan; 781 | 782 | $openpay_plan = OpenpayPlan::find('pduar9iitv4enjftuwyl'); 783 | ``` 784 | 785 | Get list of plans: 786 | ```php 787 | use Perafan\CashierOpenpay\Openpay\Plan as OpenpayPlan; 788 | 789 | $openpay_plans = OpenpayPlan::all(); 790 | // with filters 791 | $filters = [ 792 | 'creation[gte]' => '2020-01-01', 793 | 'creation[lte]' => '2020-12-31', 794 | 'offset' => 0, 795 | 'limit' => 5 796 | ]; 797 | 798 | $openpay_plans = OpenpayPlan::all($filters); 799 | ``` 800 | 801 | Update a plan: 802 | ```php 803 | use Perafan\CashierOpenpay\Openpay\Plan as OpenpayPlan; 804 | 805 | $openpay_plan = OpenpayPlan::find('pduar9iitv4enjftuwyl'); 806 | $openpay_plan->name = 'Plan Curso de Verano 2021'; 807 | $openpay_plan->save(); 808 | ``` 809 | 810 | Delete a plan: 811 | ```php 812 | use Perafan\CashierOpenpay\Openpay\Plan as OpenpayPlan; 813 | 814 | $openpay_plan = OpenpayPlan::find('pduar9iitv4enjftuwyl'); 815 | $openpay_plan->delete(); 816 | ``` 817 | 818 | Get list of subscriptors of a plan: 819 | ```php 820 | use Perafan\CashierOpenpay\Openpay\Plan as OpenpayPlan; 821 | 822 | $openpay_plan = OpenpayPlan::find('pduar9iitv4enjftuwyl'); 823 | 824 | $filters = [ 825 | 'creation[gte]' => '2020-01-01', 826 | 'creation[lte]' => '2020-12-31', 827 | 'offset' => 0, 828 | 'limit' => 5 829 | ]; 830 | 831 | $subscription_list = $openpay_plan->subscriptions->getList($filters); 832 | ``` 833 | 834 | #### Subscriptions #### 835 | 836 | Add a subscription: 837 | ```php 838 | $plan_id = 'pduar9iitv4enjftuwyl'; 839 | 840 | $subscription = $user->newSubscription($plan_id); 841 | 842 | // Add the name of subscription 843 | $options = [ 844 | 'trial_end_date' => '2021-01-01', 845 | 'card_id' => 'konvkvcd5ih8ta65umie' 846 | ]; 847 | 848 | $subscription = $user->newSubscription($plan_id, $options); 849 | 850 | // Add the name of subscription 851 | $name = 'plan_verano_2021'; 852 | 853 | $subscription = $user->newSubscription($plan_id, $options, $name); 854 | ``` 855 | 856 | Checking Subscription Status 857 | ```php 858 | $name = 'plan_verano_2021'; 859 | $user->subscribed($name); 860 | 861 | $name_2027 = 'plan_verano_2027'; 862 | $user->subscribed($name_2027); 863 | 864 | $plan_id = 'pduar9iitv4enjftuwyl'; 865 | $user->subscribed($name, $plan_id); 866 | 867 | $user->subscribed($name, 'ptyui9iit40nfwftuwyl'); 868 | ``` 869 | 870 | ```php 871 | $plans = [ 872 | 'pduar9iitv4enjftuwyl', 873 | 'ptyui9iit40nfwftuwyl' 874 | ]; 875 | 876 | $user->subscribedToPlan($plans); 877 | ``` 878 | 879 | Subscription Trial 880 | ```php 881 | $subscription = $user->subscription(); 882 | 883 | $subscription->onTrial(); 884 | ``` 885 | 886 | 887 | Checking User Trial 888 | ```php 889 | $subscription_name = 'plan_verano_2027'; 890 | 891 | $user->onTrial($subscription_name); 892 | ``` 893 | 894 | Get a subscription: 895 | ```php 896 | use Perafan\CashierOpenpay\Subscription; 897 | $subscription = $user->subscriptions->first; 898 | // or 899 | $subscription = Subscription::find('s7ri24srbldoqqlfo4vp'); 900 | 901 | $subscription->asOpenpaySubscription(); 902 | ``` 903 | 904 | Get list of subscriptions: 905 | ```php 906 | $openpay = Openpay::getInstance('moiep6umtcnanql3jrxp', 'sk_3433941e467c1055b178ce26348b0fac'); 907 | 908 | $filters = [ 909 | 'creation[gte]' => '2020-01-01', 910 | 'creation[lte]' => '2020-12-31', 911 | 'offset' => 0, 912 | 'limit' => 5 913 | ]; 914 | 915 | $customer = $openpay->customers->get('a9ualumwnrcxkl42l6mh'); 916 | $subscriptionList = $customer->subscriptions->getList($filters); 917 | ``` 918 | 919 | Update a subscription: 920 | ```php 921 | $openpay = Openpay::getInstance('moiep6umtcnanql3jrxp', 'sk_3433941e467c1055b178ce26348b0fac'); 922 | 923 | $customer = $openpay->customers->get('a9ualumwnrcxkl42l6mh'); 924 | $subscription = $customer->subscriptions->get('s7ri24srbldoqqlfo4vp'); 925 | $subscription->trial_end_date = '2021-12-31'; 926 | $subscription->save(); 927 | ``` 928 | 929 | Delete a subscription: 930 | ```php 931 | $openpay = Openpay::getInstance('moiep6umtcnanql3jrxp', 'sk_3433941e467c1055b178ce26348b0fac'); 932 | 933 | $customer = $openpay->customers->get('a9ualumwnrcxkl42l6mh'); 934 | $subscription = $customer->subscriptions->get('s7ri24srbldoqqlfo4vp'); 935 | $subscription->delete(); 936 | ``` 937 | 938 | ## Openpay SDK 939 | 940 | Many of Cashier's objects are wrappers around Openpay SDK objects. If you would like to interact with the Openpay objects directly, you may conveniently retrieve them using the `asOpenpay...` methods: 941 | 942 | ```php 943 | 944 | $openpayCustomer = $user->asOpenpayCustomer(); 945 | 946 | $openpayCustomer->name = 'Pedro'; 947 | 948 | $openpayCustomer->save(); 949 | 950 | $openpaySubscription = $subscription->asOpenpaySubscription(); 951 | 952 | $subscription->trial_end_date = '2021-12-31'; 953 | 954 | $openpaySubscription->save(); 955 | ``` 956 | 957 | ## Testing 958 | 959 | To get started, add the testing version of your Openpay keys to your phpunit.xml file: 960 | 961 | ```xml 962 | 963 | 964 | 965 | ``` 966 | 967 | Then you can run on your term 968 | 969 | ``` bash 970 | $ composer install 971 | $ vendor/bin/phpunit 972 | ``` 973 | 974 | ## Contributing 975 | 976 | Please see [contributing.md](contributing.md) for details and a todolist. 977 | 978 | ## Security 979 | 980 | If you discover any security related issues, please email pedro.perafan.carrasco@gmail.com instead of using the issue tracker. 981 | 982 | ## Credits 983 | 984 | - [Pedro Perafán Carrasco][link-author] 985 | 986 | ## License 987 | 988 | MIT. Please see the [license file](license.md) for more information. 989 | 990 | [ico-version]: https://img.shields.io/packagist/v/perafan/cashier-openpay.svg?style=flat-square 991 | [ico-downloads]: https://img.shields.io/packagist/dt/perafan/cashier-openpay.svg?style=flat-square 992 | [ico-travis]: https://img.shields.io/travis/Perafan18/cashier-openpay/master.svg?style=flat-square 993 | [ico-styleci]: https://styleci.io/repos/133202140/shield 994 | 995 | [link-packagist]: https://packagist.org/packages/perafan/cashier-openpay 996 | [link-downloads]: https://packagist.org/packages/perafan/cashier-openpay 997 | [link-travis]: https://travis-ci.org/github/Perafan18/cashier-openpay 998 | [link-styleci]: https://styleci.io/repos/133202140 999 | [link-author]: https://github.com/perafan18 1000 | 1001 | 1002 | [https://www.openpay.mx/docs/api/#devolver-un-cargo]: https://www.openpay.mx/docs/api/#devolver-un-cargo --------------------------------------------------------------------------------