kevin_zhangl 3 лет назад
Родитель
Сommit
f11c55df6c
100 измененных файлов с 8050 добавлено и 171 удалено
  1. 1 0
      backendApi/config/urlManagerRules.php
  2. 13 0
      backendApi/modules/v1/controllers/ShopController.php
  3. 1 0
      backendApi/modules/v1/models/lists/shop/OrderList.php
  4. 24 1
      backendEle/src/views/shop/order-list.vue
  5. 22 0
      common/config/main.php
  6. 9 1
      common/config/params.php
  7. 66 0
      common/helpers/PayStack.php
  8. 10 0
      common/helpers/user/Info.php
  9. 3 1
      common/models/Order.php
  10. 3 1
      common/models/OrderGoods.php
  11. 13 3
      common/models/ShopGoods.php
  12. 7 3
      common/models/User.php
  13. 130 12
      common/models/forms/OrderForm.php
  14. 7 3
      common/models/forms/UserForm.php
  15. 3 1
      composer.json
  16. 1 0
      frontendApi/config/urlManagerRules.php
  17. 17 3
      frontendApi/modules/v1/controllers/ShopController.php
  18. 2 1
      frontendEle/package.json
  19. 1 0
      frontendEle/src/utils/network.js
  20. 12 1
      frontendEle/src/utils/userInfo.js
  21. 6 11
      frontendEle/src/views/shop/order-list.vue
  22. 242 128
      frontendEle/src/views/shop/order.vue
  23. 6 1
      frontendEle/src/views/user/index.vue
  24. 8 0
      vendor/matscode/paystack/.gitignore
  25. 674 0
      vendor/matscode/paystack/LICENSE
  26. 137 0
      vendor/matscode/paystack/README.md
  27. 19 0
      vendor/matscode/paystack/composer.json
  28. 152 0
      vendor/matscode/paystack/src/Base.php
  29. 247 0
      vendor/matscode/paystack/src/CURL.php
  30. 287 0
      vendor/matscode/paystack/src/Transaction.php
  31. 44 0
      vendor/matscode/paystack/src/Utility/Debug.php
  32. 24 0
      vendor/matscode/paystack/src/Utility/Http.php
  33. 55 0
      vendor/matscode/paystack/src/Utility/Text.php
  34. 48 0
      vendor/matscode/paystack/test/callback.php
  35. 45 0
      vendor/matscode/paystack/test/index.php
  36. 6 0
      vendor/nategood/httpful/.gitignore
  37. 12 0
      vendor/nategood/httpful/.travis.yml
  38. 7 0
      vendor/nategood/httpful/LICENSE.txt
  39. 226 0
      vendor/nategood/httpful/README.md
  40. 4 0
      vendor/nategood/httpful/bootstrap.php
  41. 51 0
      vendor/nategood/httpful/build
  42. 27 0
      vendor/nategood/httpful/composer.json
  43. 12 0
      vendor/nategood/httpful/examples/freebase.php
  44. 9 0
      vendor/nategood/httpful/examples/github.php
  45. 44 0
      vendor/nategood/httpful/examples/override.php
  46. 24 0
      vendor/nategood/httpful/examples/showclix.php
  47. 97 0
      vendor/nategood/httpful/src/Httpful/Bootstrap.php
  48. 54 0
      vendor/nategood/httpful/src/Httpful/Exception/ConnectionErrorException.php
  49. 7 0
      vendor/nategood/httpful/src/Httpful/Exception/JsonParseException.php
  50. 51 0
      vendor/nategood/httpful/src/Httpful/Handlers/CsvHandler.php
  51. 30 0
      vendor/nategood/httpful/src/Httpful/Handlers/FormHandler.php
  52. 44 0
      vendor/nategood/httpful/src/Httpful/Handlers/JsonHandler.php
  53. 54 0
      vendor/nategood/httpful/src/Httpful/Handlers/MimeHandlerAdapter.php
  54. 44 0
      vendor/nategood/httpful/src/Httpful/Handlers/README.md
  55. 15 0
      vendor/nategood/httpful/src/Httpful/Handlers/XHtmlHandler.php
  56. 152 0
      vendor/nategood/httpful/src/Httpful/Handlers/XmlHandler.php
  57. 86 0
      vendor/nategood/httpful/src/Httpful/Http.php
  58. 47 0
      vendor/nategood/httpful/src/Httpful/Httpful.php
  59. 60 0
      vendor/nategood/httpful/src/Httpful/Mime.php
  60. 16 0
      vendor/nategood/httpful/src/Httpful/Proxy.php
  61. 1224 0
      vendor/nategood/httpful/src/Httpful/Request.php
  62. 178 0
      vendor/nategood/httpful/src/Httpful/Response.php
  63. 106 0
      vendor/nategood/httpful/src/Httpful/Response/Headers.php
  64. 635 0
      vendor/nategood/httpful/tests/Httpful/HttpfulTest.php
  65. 28 0
      vendor/nategood/httpful/tests/Httpful/requestTest.php
  66. 42 0
      vendor/nategood/httpful/tests/bootstrap-server.php
  67. 17 0
      vendor/nategood/httpful/tests/phpunit.xml
  68. 1 0
      vendor/nategood/httpful/tests/static/test.json
  69. BIN
      vendor/nategood/httpful/tests/test_image.jpg
  70. 3 0
      vendor/perfectmak/paystack-php/.gitignore
  71. 16 0
      vendor/perfectmak/paystack-php/.travis.yml
  72. 53 0
      vendor/perfectmak/paystack-php/README.md
  73. 32 0
      vendor/perfectmak/paystack-php/composer.json
  74. 31 0
      vendor/perfectmak/paystack-php/example/index.php
  75. 35 0
      vendor/perfectmak/paystack-php/lib/Paystack/Customer.php
  76. 46 0
      vendor/perfectmak/paystack-php/lib/Paystack/Http/HttpClient.php
  77. 23 0
      vendor/perfectmak/paystack-php/lib/Paystack/Interfaces/Http/IClient.php
  78. 44 0
      vendor/perfectmak/paystack-php/lib/Paystack/Interfaces/IRequest.php
  79. 11 0
      vendor/perfectmak/paystack-php/lib/Paystack/Interfaces/IResource.php
  80. 21 0
      vendor/perfectmak/paystack-php/lib/Paystack/Interfaces/IResponse.php
  81. 47 0
      vendor/perfectmak/paystack-php/lib/Paystack/Paystack.php
  82. 35 0
      vendor/perfectmak/paystack-php/lib/Paystack/Plan.php
  83. 101 0
      vendor/perfectmak/paystack-php/lib/Paystack/Request.php
  84. 45 0
      vendor/perfectmak/paystack-php/lib/Paystack/Response.php
  85. 12 0
      vendor/perfectmak/paystack-php/lib/Paystack/Subscription.php
  86. 89 0
      vendor/perfectmak/paystack-php/lib/Paystack/Traits/ResourceTrait.php
  87. 51 0
      vendor/perfectmak/paystack-php/lib/Paystack/Transaction.php
  88. 12 0
      vendor/perfectmak/paystack-php/phpunit.xml
  89. 18 0
      vendor/perfectmak/paystack-php/tests/CustomerTest.php
  90. 26 0
      vendor/perfectmak/paystack-php/tests/PaystackTest.php
  91. 17 0
      vendor/perfectmak/paystack-php/tests/TestCase.php
  92. 21 0
      vendor/smladeoye/yii2-paystack/LICENSE
  93. 332 0
      vendor/smladeoye/yii2-paystack/README.md
  94. 21 0
      vendor/smladeoye/yii2-paystack/composer.json
  95. 150 0
      vendor/smladeoye/yii2-paystack/paystack/Customer.php
  96. 136 0
      vendor/smladeoye/yii2-paystack/paystack/Page.php
  97. 357 0
      vendor/smladeoye/yii2-paystack/paystack/Paystack.php
  98. 108 0
      vendor/smladeoye/yii2-paystack/paystack/Plan.php
  99. 348 0
      vendor/smladeoye/yii2-paystack/paystack/Resources.php
  100. 60 0
      vendor/smladeoye/yii2-paystack/paystack/Settlement.php

+ 1 - 0
backendApi/config/urlManagerRules.php

@@ -53,6 +53,7 @@ return [
             'GET order-list' => 'order-list',
             'GET order-list-export' => 'order-list-export',
             'POST order-delivery' => 'order-delivery',
+            'POST order-refund' => 'order-refund',
             'GET order-shop-list' => 'order-shop-list',
             'GET order-dec-list' => 'order-dec-list',
             'POST import-order-shop-to-excel-table' => 'import-order-shop-to-excel-table',

+ 13 - 0
backendApi/modules/v1/controllers/ShopController.php

@@ -704,4 +704,17 @@ class ShopController extends BaseController {
         }
         return static::notice('导出开始,请到文件管理-导出文件查看');
     }
+
+    /**
+     * 管理员订单退款
+     * @return mixed
+     * @throws \yii\web\HttpException
+     */
+    public function actionOrderRefund()
+    {
+        if(\Yii::$app->request->isPost) {
+            return parent::edit(OrderForm::class, '退款成功', 'adminRefund', ['adminRefund']);
+        }
+        return static::notice('非法请求', 400);
+    }
 }

+ 1 - 0
backendApi/modules/v1/models/lists/shop/OrderList.php

@@ -201,6 +201,7 @@ class OrderList extends \common\libs\dataList\DataList implements DataListInterf
                     },
                     'headerOther' => ['width' => '190'],
                 ],
+                'PAY_TYPE' => null,
                 'REAL_PRICE' => [
                     'header' => '商品单价',
                     'headerOther' => [

+ 24 - 1
backendEle/src/views/shop/order-list.vue

@@ -27,6 +27,7 @@
               <el-dropdown-menu slot="dropdown">
                 <el-dropdown-item command="edit" @click.native="handleEdit(scope.row)" v-if="permission.hasPermission(`shop/edit`)">编辑订单</el-dropdown-item>
                 <el-dropdown-item command="delivery" @click.native="handleShowDeliveryDialog(scope.row)" v-if="permission.hasPermission(`shop/order-delivery`) && scope.row['STATUS'] === '1' && scope.row['DELIVERY_STATUS'] === '0' ">发货</el-dropdown-item>
+                <el-dropdown-item command="refund" @click.native="handleRefund(scope.row.SN)" v-if="permission.hasPermission(`shop/order-delivery`) && scope.row['PAY_TYPE'] === 'pay_stack' && scope.row['STATUS'] === '1' && scope.row['DELIVERY_STATUS'] === '0' ">退款</el-dropdown-item>
               </el-dropdown-menu>
             </el-dropdown>
           </template>
@@ -192,7 +193,29 @@
         }).catch(response => {
           this.dialogDeliveryVisible = false
         })
-      }
+      },
+        // 退款
+        handleRefund(orderSn) {
+            this.$confirm(`确定要将此订单退款吗?`, '提示', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                type: 'warning'
+            }).then(() => {
+                return network.postData(`shop/order-refund`, { sn: orderSn.value })
+            }).then(response => {
+                this.$message({
+                    message: response,
+                    type: 'success'
+                })
+
+                this.getData(this.currentPage, this.pageSize)
+            }).catch(response => {
+                this.$message({
+                    message: response,
+                    type: 'error'
+                })
+            })
+        },
     }
   }
 

+ 22 - 0
common/config/main.php

@@ -49,6 +49,28 @@ return [
         'swooleAsyncTimer' => [
             'class' => 'common\components\SwooleAsyncTimer',
         ],
+        // payStack配置
+        'Paystack' => [
+            'class'         => 'smladeoye\paystack\Paystack',
+            'environment'   => 'test',
+            'testPublicKey' => 'pk_test_2eed10135c4a958c5073795b22854ded9d1a6c55',
+            'testSecretKey' => 'sk_test_5ece72377432376f5cf6bb5c468395a650220309',
+            'livePublicKey' => '',
+            'liveSecretKey' => '',
+        ],
+        // 配置SQL语句输出
+        'log' => [
+            'traceLevel' => YII_DEBUG ? 3 : 0,
+            'targets' => [
+                [
+                    'class' => 'yii\log\FileTarget',
+                    'levels' => ['error', 'warning', 'info'],
+                    'categories' => ['yii\db\*', 'app\models\*'],
+                    'logFile' => '@runtime/logs/sql.log',
+                ],
+                'db' => [ 'class' => 'yii\log\FileTarget'],
+            ],
+        ],
     ],
     'controllerMap' => [
         'swoole_server' => [

+ 9 - 1
common/config/params.php

@@ -9,7 +9,7 @@ return [
     'frontAccessTokenExpiresIn' => 3000 * 60,
     'frontRefreshTokenExpiresIn' => 3000 * 60 * 60,
     'user.passwordResetTokenExpire' => 3600,
-    'operationTimeOut' => 15 * 60,     // 这里设置的15分钟超时
+    'operationTimeOut' => 60 * 60 * 24,     // 这里设置的15分钟超时
     'pageSize' => 20,
     'http' => [
         'shopApi' => [
@@ -317,6 +317,14 @@ return [
             'value' => 6,
             'label' => '订单删除'
         ],
+        '7' => [
+            'value' => 7,
+            'label' => '订单退款'
+        ],
+        'refund' => [
+            'value' => 7,
+            'label' => '订单退款'
+        ],
     ],
     'exportModule' => [
         'shop' => ['label' => '商城管理', 'value'=>'shop'],

+ 66 - 0
common/helpers/PayStack.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace common\helpers;
+
+class PayStack
+{
+    /**
+     * 交易支付.
+     * @param $ref
+     * @return array
+     */
+    public static function transactionVerify($ref): array
+    {
+        $payStack = \Yii::$app->Paystack;
+        $transaction = $payStack->transaction()->setRequestOptions($ref);
+        $transaction->verify();
+
+        return [
+            'status'    => $transaction->status,
+            'message'   => $transaction->message,
+            'data'      => $transaction->data,
+        ];
+    }
+
+    /**
+     * 交易退款.
+     * @param string $transaction
+     * @param int $amount
+     */
+    public static function transactionRefund(string $reference, int $amount)
+    {
+        $secretKey = \Yii::$app->Paystack->testSecretKey;
+
+        $url = "https://api.paystack.co/refund";
+        $fields = [
+            'transaction' => $reference,
+            'amount'      => $amount,
+        ];
+        $fields_string = http_build_query($fields);
+        //open connection
+        $ch = curl_init();
+
+        //set the url, number of POST vars, POST data
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_POST, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
+            "Authorization: Bearer {$secretKey}",
+            "Cache-Control: no-cache",
+        ));
+
+        //So that curl_exec returns the contents of the cURL; rather than echoing it
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+
+        //execute post
+        $response = curl_exec($ch);
+        $response = json_decode($response, true);
+
+        return [
+            'status'    => $response['status'],
+            'message'   => $response['message'],
+            'data'      => $response['data'],
+        ];
+    }
+
+}

+ 10 - 0
common/helpers/user/Info.php

@@ -63,6 +63,16 @@ class Info {
         return $user ? $user['REAL_NAME'] : null;
     }
 
+    /**
+     * 获取会员邮箱通过ID
+     * @param $userId
+     * @return null
+     */
+    public static function getUserEmailByUserId($userId) {
+        $user = User::findOneAsArray('ID=:ID AND DELETED=0', [':ID' => $userId], 'EMAIL');
+        return $user ? $user['EMAIL'] : null;
+    }
+
     /**
      * 获取手机号通过ID
      * @param $userId

+ 3 - 1
common/models/Order.php

@@ -46,6 +46,7 @@ use Yii;
  * @property int $IS_DELETE 是否删除
  * @property int $DELETED_AT 删除时间
  * @property int $WAREHOUSE 发货仓
+ * @property string $EMAIL 邮箱
  */
 class Order extends \common\components\ActiveRecord
 {
@@ -63,7 +64,7 @@ class Order extends \common\components\ActiveRecord
     public function rules()
     {
         return [
-            [['USER_ID', 'USER_NAME', 'ORDER_TYPE', 'CREATE_USER'], 'required'],
+            [['USER_ID', 'USER_NAME', 'ORDER_TYPE', 'CREATE_USER', 'EMAIL'], 'required'],
             [['ORDER_AMOUNT', 'PV', 'PAY_AMOUNT', 'PAY_PV', 'FREIGHT', 'PAY_FREIGHT'], 'number'],
             [['PAY_AT', 'DELIVERY_STATUS', 'DELIVERY_PERIOD', 'DELIVERY_AT', 'EXPRESS_TYPE', 'PERIOD_NUM', 'STATUS', 'PROVINCE', 'CITY', 'COUNTY', 'CREATED_AT', 'UPDATED_AT', 'IS_DELETE', 'DELETED_AT'], 'integer'],
             [['ID','SN', 'DEC_SN', 'USER_ID', 'ORDER_TRACK_NO','PAY_TYPE'], 'string', 'max' => 32],
@@ -124,6 +125,7 @@ class Order extends \common\components\ActiveRecord
             'IS_DELETE' => '是否删除',
             'DELETED_AT' => '删除时间',
             'WAREHOUSE' => '发货仓',
+            'EMAIL' => 'Email'
         ];
     }
 }

+ 3 - 1
common/models/OrderGoods.php

@@ -21,6 +21,7 @@ use Yii;
  * @property string $P_CALC_MONTH 分区日期
  * @property int CATEGORY_TYPE 商品分类
  * @property int PAY_TYPE 支付方式
+ * @property string $EMAIL Email
  */
 class OrderGoods extends \common\components\ActiveRecord
 {
@@ -38,7 +39,7 @@ class OrderGoods extends \common\components\ActiveRecord
     public function rules()
     {
         return [
-            [['ORDER_SN', 'GOODS_ID', 'GOODS_TITLE', 'SKU_CODE', 'CATEGORY_TYPE', 'PAY_TYPE'], 'required'],
+            [['ORDER_SN', 'GOODS_ID', 'GOODS_TITLE', 'SKU_CODE', 'CATEGORY_TYPE', 'PAY_TYPE', 'EMAIL'], 'required'],
             [['PRICE', 'REAL_PRICE', 'PV', 'REAL_PV', 'POINT', 'CATEGORY_TYPE', 'PAY_TYPE'], 'number'],
             [['BUY_NUMS'], 'integer'],
             [['ID', 'ORDER_SN', 'GOODS_ID'], 'string', 'max' => 32],
@@ -68,6 +69,7 @@ class OrderGoods extends \common\components\ActiveRecord
             'P_CALC_MONTH' => '分区日期',
             'CATEGORY_TYPE' => '商品分类',
             'PAY_TYPE' => '支付方式',
+            'EMAIL' => 'Email'
         ];
     }
 }

+ 13 - 3
common/models/ShopGoods.php

@@ -61,6 +61,11 @@ class ShopGoods extends \common\components\ActiveRecord
             'name' => '豪宅积分',
             'label' => 'house_points',
         ],
+        7 => [
+            'id' => 7,
+            'name' => 'PayStack',
+            'label' => 'pay_stack',
+        ],
     ];
     const GOODS_TYPE = [
         1 =>[
@@ -96,8 +101,10 @@ class ShopGoods extends \common\components\ActiveRecord
             'name' => '普通商品',
             'sell_type' => [
                 self::SALE_TYPE[1],
-                self::SALE_TYPE[3]]
-            ],
+                self::SALE_TYPE[3],
+                self::SALE_TYPE[7],
+            ]
+        ],
         [
             'id' => 4,
             'name' => '旅游积分商品',
@@ -229,7 +236,10 @@ class ShopGoods extends \common\components\ActiveRecord
             ],
             'house_points' => [
                 'name' => '豪宅积分'
-            ]
+            ],
+            'pay_stack' => [
+                'name' => 'PayStack'
+            ],
         ];
     }
 

+ 7 - 3
common/models/User.php

@@ -92,6 +92,7 @@ use common\libs\logging\operate\valueType\Config as ValueTypeConfig;
  * @property int $IS_FIRST_OPEN 首次开通
  * @property int $IS_MODIFY_PASSWORD 是否修改密码
  * @property int $IS_STUDIO 是否是工作室
+ * @property string $EMAIL 邮箱
  */
 class User extends \common\components\ActiveRecord
 {
@@ -112,7 +113,7 @@ class User extends \common\components\ActiveRecord
             [['USER_NAME', 'PASSWORD_HASH', 'PAY_PASSWORD', 'REAL_NAME', 'ID_CARD', 'AVATAR'], 'required'],
             [['NATION', 'ID_TYPE', 'BANK_PROVINCE', 'BANK_CITY', 'BANK_COUNTY', 'CREATED_AT', 'UPDATED_AT', 'STATUS', 'DEC_CLOSED', 'DEC_CLOSED_AT', 'PROVINCE', 'CITY', 'COUNTY', 'DELETED', 'DELETED_AT', 'IS_DEC', 'IS_ATLAS', 'IS_RECHARGE','IS_STUDIO',  'PERIOD_AT', 'DEC_PROVINCE', 'DEC_CITY', 'DEC_COUNTY', 'IS_UNION', 'STATUS_AT', 'VERIFIED', 'VERIFIED_AT', 'ALLOW_LOGIN', 'NOT_OPERATING', 'REG_FROM', 'BANK_UPDATED_AT', 'IS_DIRECT_SELLER', 'DEC_LV_UPDATED_AT', 'DEC_LV_UPDATED_PERIOD', 'PART_FUNC_CLOSED', 'LAST_DEC_LV_UPDATED_PERIOD', 'LAST_DEC_LV_UPDATED_AT', 'DEC_CREATED_AT', 'DEC_CREATED_PERIOD', 'PASSWORD_CHANGED', 'SUB_COM_LEADER', 'READ_AGREEMENT', 'IS_FIRST_OPEN', 'IS_MODIFY_PASSWORD'], 'integer'],
             [['ZG_UPGRADE_PV'], 'number'],
-            [['ID', 'OPEN_BANK', 'BANK_NO', 'DEC_LV', 'EMP_LV', 'SUB_COM_ID', 'DEC_ID', 'DEC_ROLE_ID', 'ID_CARD_PREFIX', 'SEX', 'DEC_PHONE', 'GUARANTOR', 'USER_CREATOR', 'USER_UPDATER', 'LAST_DEC_LV'], 'string', 'max' => 32],
+            [['ID', 'OPEN_BANK', 'BANK_NO', 'DEC_LV', 'EMP_LV', 'SUB_COM_ID', 'DEC_ID', 'DEC_ROLE_ID', 'ID_CARD_PREFIX', 'SEX', 'DEC_PHONE', 'GUARANTOR', 'USER_CREATOR', 'USER_UPDATER', 'LAST_DEC_LV', 'EMAIL'], 'string', 'max' => 32],
             [['USER_NAME', 'SPOUSE_NAME', 'TEL'], 'string', 'max' => 16],
             [['PASSWORD_HASH', 'PAY_PASSWORD', 'ID_IMAGE', 'BANK_ADDRESS', 'AVATAR'], 'string', 'max' => 255],
             [['REAL_NAME', 'APP_CLIENT_ID', 'BONUS_APP_CLIENT_ID'], 'string', 'max' => 128],
@@ -212,6 +213,7 @@ class User extends \common\components\ActiveRecord
             'IS_FIRST_OPEN' => '首次开通',
             'IS_MODIFY_PASSWORD' => '是否修改密码',
             'IS_STUDIO' => '是否是工作室',
+            'EMAIL' => 'Email',
         ];
     }
 
@@ -237,7 +239,7 @@ class User extends \common\components\ActiveRecord
     public static function getBaseInfoFromRedis($userId){
         $userInfo = Yii::$app->redis->hget(Cache::USER_INFO_KEY, $userId);
         if(!$userInfo){
-            $userInfo = static::find()->select('ID,USER_NAME,NATION,REAL_NAME,ID_CARD,ID_TYPE,MOBILE,ADDRESS,STATUS,DEC_LV,LAST_DEC_LV,EMP_LV,DEC_CLOSED,AVATAR,OPEN_BANK,BANK_ADDRESS,BANK_NO,VERIFIED,IS_UNION,IS_DEC,IS_ATLAS,IS_RECHARGE,IS_STUDIO,DEC_ROLE_ID,PROVINCE,CITY,COUNTY')->where('ID=:ID', [':ID'=>$userId])->asArray()->one();
+            $userInfo = static::find()->select('ID,USER_NAME,NATION,REAL_NAME,ID_CARD,ID_TYPE,MOBILE,ADDRESS,STATUS,DEC_LV,LAST_DEC_LV,EMP_LV,DEC_CLOSED,AVATAR,OPEN_BANK,BANK_ADDRESS,BANK_NO,VERIFIED,IS_UNION,IS_DEC,IS_ATLAS,IS_RECHARGE,IS_STUDIO,DEC_ROLE_ID,PROVINCE,CITY,COUNTY,EMAIL')->where('ID=:ID', [':ID'=>$userId])->asArray()->one();
             Yii::$app->redis->hset(Cache::USER_INFO_KEY, $userId, Json::encode($userInfo));
         } else {
             $userInfo = Json::decode($userInfo);
@@ -251,7 +253,7 @@ class User extends \common\components\ActiveRecord
      * @return mixed
      */
     public static function updateBaseInfoToRedis($userId){
-        $userInfo = static::find()->select('ID,USER_NAME,NATION,REAL_NAME,ID_CARD,ID_TYPE,MOBILE,ADDRESS,STATUS,DEC_LV,LAST_DEC_LV,EMP_LV,DEC_CLOSED,AVATAR,OPEN_BANK,BANK_ADDRESS,BANK_NO,VERIFIED,IS_UNION,IS_DEC,IS_ATLAS,IS_RECHARGE,IS_STUDIO,DEC_ROLE_ID,PROVINCE,CITY,COUNTY')->where('ID=:ID', [':ID'=>$userId])->asArray()->one();
+        $userInfo = static::find()->select('ID,USER_NAME,NATION,REAL_NAME,ID_CARD,ID_TYPE,MOBILE,ADDRESS,STATUS,DEC_LV,LAST_DEC_LV,EMP_LV,DEC_CLOSED,AVATAR,OPEN_BANK,BANK_ADDRESS,BANK_NO,VERIFIED,IS_UNION,IS_DEC,IS_ATLAS,IS_RECHARGE,IS_STUDIO,DEC_ROLE_ID,PROVINCE,CITY,COUNTY,EMAIL')->where('ID=:ID', [':ID'=>$userId])->asArray()->one();
         return Yii::$app->redis->hset(Cache::USER_INFO_KEY, $userId, Json::encode($userInfo));
     }
 
@@ -297,6 +299,7 @@ class User extends \common\components\ActiveRecord
             'COUNTY' => $userInfo['COUNTY'],
             'BANK_NO' => $userInfo['BANK_NO'],
 //            'BANK_NO' => Tool::hideBankNo($userInfo['BANK_NO']),
+            'EMAIL' => $userInfo['EMAIL'],
         ];
     }
 
@@ -442,6 +445,7 @@ class User extends \common\components\ActiveRecord
             ],
             'BIRTHDAY' => '生日',
             'SEX' => '性别',
+            'EMAIL' => 'Email'
         ];
     }
 

+ 130 - 12
common/models/forms/OrderForm.php

@@ -5,6 +5,7 @@ use common\helpers\Cache;
 use common\helpers\Date;
 use common\components\Model;
 use common\helpers\Form;
+use common\helpers\PayStack;
 use common\helpers\user\Balance;
 use common\helpers\user\Cash;
 use common\helpers\user\Info;
@@ -37,6 +38,7 @@ class OrderForm extends Model
     public $goodsId;
     public $goodsNum;
     public $payPassword;
+    public $email;
 
     public $userName;
     public $consignee;
@@ -71,8 +73,8 @@ class OrderForm extends Model
     public function rules()
     {
         return [
-            [['sn', 'expressCompany', 'orderTrackNo', 'status', 'remark','type','addressId','payType','goodsId','goodsNum', 'payPassword','userName','consignee','acceptMobile','province','city','county','detailaddress'], 'trim'],
-            [['sn', 'expressCompany', 'orderTrackNo', 'status', 'remark','type','addressId','payType','goodsId','goodsNum', 'payPassword','userName','consignee','acceptMobile','province','city','county','detailaddress'], 'required'],
+            [['sn', 'expressCompany', 'orderTrackNo', 'status', 'remark','type','addressId','payType','goodsId','goodsNum', 'payPassword','userName','consignee','acceptMobile','province','city','county','detailaddress','email'], 'trim'],
+            [['sn', 'expressCompany', 'orderTrackNo', 'status', 'remark','type','addressId','payType','goodsId','goodsNum', 'payPassword','userName','consignee','acceptMobile','province','city','county','detailaddress','email'], 'required'],
             [['status'], 'isStatus'],
             [['addressId'], 'isAddress'],
             [['payType'], 'isPayType'],
@@ -100,6 +102,7 @@ class OrderForm extends Model
             'city' => '市',
             'county' => '区',
             'detailaddress' => '收货详细地址',
+            'email' => 'Email',
         ];
     }
 
@@ -120,9 +123,13 @@ class OrderForm extends Model
             // 管理员修改备注
             'adminRemark' => ['sn', 'remark'],
             // 会员下单
-            'userOrder' => ['type','addressId', 'payType','goodsId','goodsNum', 'remark', 'payPassword'],
+            'userOrder' => ['type','addressId', 'payType','goodsId','goodsNum', 'remark', 'payPassword', 'email'],
             // 帮会员复消下单
             'reconsumeOrder' => ['type','userName', 'payType','goodsId','goodsNum', 'remark', 'payPassword','consignee','acceptMobile','province','city','county','detailaddress'],
+            // 管理员修改订单状态
+            'verifyPayStack' => ['sn', 'remark'],
+            // 管理员退款
+            'adminRefund' => ['sn'],
         ];
         return array_merge($parentScenarios, $customScenarios);
     }
@@ -152,6 +159,32 @@ class OrderForm extends Model
             }
         }
 
+        if ($this->scenario == 'verifyPayStack'){
+            if ($this->_model['STATUS'] != \Yii::$app->params['orderStatus']['notPaid']['value']) {
+                $this->addError('sn', '订单状态非未支付');
+                return false;
+            }
+        }
+
+        if ($this->scenario == 'adminRefund'){
+            if ($this->_model['STATUS'] != \Yii::$app->params['orderStatus']['paid']['value']) {
+                $this->addError('sn', '订单状态支付状态不支持退款');
+                return false;
+            }
+            if ($this->_model['DELIVERY_STATUS'] != \Yii::$app->params['deliveryStatus']['notDelivery']['value']) {
+                $this->addError('sn', '订单物流状态不支持退款');
+                return false;
+            }
+            if ($this->_model['PAY_TYPE'] != 'pay_stack') {
+                $this->addError('sn', '订单支付方式不支持退款');
+                return false;
+            }
+            if (!$this->_model['REMARK']) {
+                $this->addError('sn', '订单支付信息不存在');
+                return false;
+            }
+        }
+
         return $parentValidate;
     }
 
@@ -300,9 +333,90 @@ class OrderForm extends Model
         return $this->_model;
     }
 
+    /**
+     * 校验PayStack支付,更新订单状态.
+     * @return Order|null
+     * @throws Exception
+     */
+    public function verifyPayStack(): ?Order
+    {
+        if(!$this->validate()){
+            return null;
+        }
+
+        // 调用PayStack支付校验
+        $payload = PayStack::transactionVerify($this->remark['reference']);
+        if ($payload['status'] !== true) {
+            throw new Exception(Form::formatErrorsForApi($payload['message']));
+        }
+        if ($payload['data']['amount'] != $this->_model->PAY_AMOUNT * 100) {
+            throw new Exception(Form::formatErrorsForApi('支付金额与订单金额不符'));
+        }
+
+        $db = \Yii::$app->db;
+        $transaction = $db->beginTransaction();
+        try {
+            $this->_model->STATUS = \Yii::$app->params['orderStatus']['paid']['value'];
+            $this->_model->REMARK = json_encode($this->remark);
+            $this->_model->PAY_AT = Date::nowTime();
+            if (!$this->_model->save()) {
+                throw new Exception(Form::formatErrorsForApi($this->_model->getErrors()));
+            }
+            $transaction->commit();
+        } catch (Exception $e) {
+            $transaction->rollBack();
+            $this->addError('edit', $e->getMessage());
+            return null;
+        }
+
+        return $this->_model;
+    }
+
+
+    public function adminRefund()
+    {
+        if(!$this->validate()){
+            return null;
+        }
+
+        // 支付信息存在于remark中
+        $remark = $this->_model->REMARK ? json_decode($this->_model->REMARK, true) : [];
+        $reference = $remark['reference'] ?? '';
+        if (!$reference) {
+            throw new Exception(Form::formatErrorsForApi('支付信息不存在'));
+        }
+        // 退款金额. 订单支付金额 * 100
+        $amount = $this->_model->PAY_AMOUNT * 100;
+        // 调用PayStack支付校验
+        $payload = PayStack::transactionRefund($reference, $amount);
+        if ($payload['status'] !== true) {
+            throw new Exception(Form::formatErrorsForApi($payload['message']));
+        }
+
+        $db = \Yii::$app->db;
+        $transaction = $db->beginTransaction();
+        try {
+            $this->_model->STATUS = \Yii::$app->params['orderStatus']['refund']['value'];
+            $this->_model->REMARK = json_encode([
+                    'payment' => $remark,
+                    'refund'  => $payload['data']
+                ]
+            );
+            if (!$this->_model->save()) {
+                throw new Exception(Form::formatErrorsForApi($this->_model->getErrors()));
+            }
+            $transaction->commit();
+        } catch (Exception $e) {
+            $transaction->rollBack();
+            $this->addError('edit', $e->getMessage());
+            return null;
+        }
+
+        return $this->_model;
+    }
+
     /**
      * 复销
-     * @return bool|null
      * @throws Exception
      * @throws \yii\db\Exception
      */
@@ -353,6 +467,7 @@ class OrderForm extends Model
                         'GOODS_TITLE' => $goods['GOODS_NAME'],
                         'CATEGORY_TYPE' => $goods['CATEGORY_TYPE'],
                         'PAY_TYPE' => $this->payType,
+                        'EMAIL' => $this->email,
                     ];
                 }
             }
@@ -380,13 +495,13 @@ class OrderForm extends Model
             }
 
             $transaction->commit();
+
+            return $orderResult;
         }catch (\Exception $e){
             $transaction->rollBack();
             $this->addError('add', $e->getMessage());
             return null;
         }
-
-        return true;
     }
 
     /**
@@ -412,9 +527,9 @@ class OrderForm extends Model
                 return ['code' => 500, 'message' => (Balance::BALANCE_TYPE[$payType]['title'] ?? '积分') . '不足,无法购买商品'];
             }
         } else{
-            if ($payAmount > Balance::getBalanceReconsumePoints($loginUserId)) {
-                return ['code' => 500, 'message' => '复消积分不足,无法购买商品'];
-            }
+//            if ($payAmount > Balance::getBalanceReconsumePoints($loginUserId)) {
+//                return ['code' => 500, 'message' => '复消积分不足,无法购买商品'];
+//            }
         }
 
         return ['code' => 200];
@@ -434,6 +549,7 @@ class OrderForm extends Model
         $userName = Info::getUserNameByUserId($userId);
         $userRealName = Info::getUserRealNameByUserId($userId);
         $userMobile = Info::getUserMobileByUserId($userId);
+        $userEmail = Info::getUserEmailByUserId($userId);
         // 加入订单信息
         if ($this->_address['PROVINCE'] != 1) {
             $warehouse = Region::getWarehouseByCode($this->_address['PROVINCE']);//仓库
@@ -456,7 +572,7 @@ class OrderForm extends Model
         $orderModel->PV = $_hasPV;
         $orderModel->PAY_AMOUNT = $this->_payAmount;
         $orderModel->PAY_PV = $_hasPV; // 兑换积分不能算业绩
-        $orderModel->PAY_AT = Date::nowTime();
+        $orderModel->PAY_AT = ($this->payType == 'pay_stack' ? 0 :Date::nowTime());
         $orderModel->PAY_TYPE = $this->payType;
         $orderModel->PERIOD_NUM = $nowPeriodNum;
         $orderModel->P_CALC_MONTH = Date::ociToDate($nowCalcMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH);
@@ -470,9 +586,10 @@ class OrderForm extends Model
         $orderModel->ADDRESS = $this->_address['ADDRESS'];
         $orderModel->FRONT_REMARK = $this->remark;
         $orderModel->WAREHOUSE = $warehouse;
-        $orderModel->STATUS = 1;
+        $orderModel->STATUS = ($this->payType == 'pay_stack' ? \Yii::$app->params['orderStatus']['notPaid']['value'] : \Yii::$app->params['orderStatus']['paid']['value']);
         $orderModel->CREATED_AT = Date::nowTime();
         $orderModel->CREATE_USER = $userName;
+        $orderModel->EMAIL = $userEmail;
         if($this->_address['PROVINCE']==1){
             $orderModel->EXPRESS_TYPE = 1;
             $orderModel->CONSIGNEE = $userRealName;
@@ -508,9 +625,10 @@ class OrderForm extends Model
             Balance::changeUserBonus(\Yii::$app->user->id, 'car_points', -abs($this->_payAmount), ['DEAL_TYPE_ID' => DealType::CAR_POINTS_EXCHANGE,'REMARK' => '会员名车积分兑换']);
         } else if ($this->payType == 'house_points') {
             Balance::changeUserBonus(\Yii::$app->user->id, 'house_points', -abs($this->_payAmount), ['DEAL_TYPE_ID' => DealType::HOUSE_POINTS_EXCHANGE,'REMARK' => '会员豪宅积分兑换']);
-        } else{
+        } else if ($this->payType == 'reconsume_points') {
             Balance::changeUserBonus(\Yii::$app->user->id,'reconsume_points', -abs($this->_payAmount),['DEAL_TYPE_ID' => DealType::RECONSUME_POINTS_EXCHANGE,'REMARK' => '会员复销积分兑换']);
         }
+
         return $orderModel;
     }
 

+ 7 - 3
common/models/forms/UserForm.php

@@ -41,6 +41,8 @@ class UserForm extends Model
     public $openBank;
     public $bankAddress;
     public $bankNo;
+    public $email;
+
     //修改密码
     public $password;
     public $oldPassword;
@@ -69,7 +71,7 @@ class UserForm extends Model
     public function rules()
     {
         return [
-            [['userId', 'userName', 'zcPv', 'zcAmount', 'conUserName', 'recUserName','conUid', 'recUid', 'location','nation','realName', 'mobile','openBank','bankAddress','bankNo'], 'trim'],
+            [['userId', 'userName', 'zcPv', 'zcAmount', 'conUserName', 'recUserName','conUid', 'recUid', 'location','nation','realName', 'mobile','openBank','bankAddress','bankNo', 'email'], 'trim'],
             [['userId', 'userName', 'zcPv', 'zcAmount', 'conUid', 'recUid', 'location'], 'required', 'on'=>'addWithUid'],
             [['userId', 'userName', 'zcPv', 'zcAmount', 'conUserName', 'recUserName', 'location'], 'required', 'on'=>['addWithUserName', 'addByAdmin']],
             [['idCard', 'allData'], 'required', 'on'=>['addWithUserName']],
@@ -81,7 +83,7 @@ class UserForm extends Model
             [['conUid', 'location'], 'required', 'on'=>['validateLocation', 'validateAddWithUid']],
             [['conUserName', 'location'], 'required', 'on'=>['validateLocationConName', 'validateAddWithName']],
 
-            [['nation','realName', 'mobile', 'idCard', 'openBank', 'bankAddress', 'bankNo'], 'required', 'on'=>'modifyProfile'],
+            [['nation','realName', 'mobile', 'idCard', 'openBank', 'bankAddress', 'bankNo', 'email'], 'required', 'on'=>'modifyProfile'],
             [['oldPassword','verifyPassword'], 'required','on' => ['modifyPassword', 'modifyPasswordPay', 'noLoginModifyPassword']],
             [['password'], 'required','on' => ['modifyPassword', 'noLoginModifyPassword']],
             [['payPassword'], 'required','on' => ['modifyPasswordPay']],
@@ -122,6 +124,7 @@ class UserForm extends Model
             'oldPassword' => '原密码',
             'password' => '登录密码',
             'payPassword' => '支付密码',
+            'email' => 'Email',
         ];
     }
 
@@ -537,7 +540,7 @@ class UserForm extends Model
         if(!$this->validate()){
             return null;
         }
-        $this->userOperateLogger->beforeUpdate(\Yii::$app->user->id,'ID',['select'=>'NATION,OPEN_BANK,BANK_NO,BANK_ADDRESS']);
+        $this->userOperateLogger->beforeUpdate(\Yii::$app->user->id,'ID',['select'=>'NATION,OPEN_BANK,BANK_NO,BANK_ADDRESS,EMAIL']);
 
         $uid = \Yii::$app->user->id;
         $model = User::findOne(['ID'=>$uid]);
@@ -548,6 +551,7 @@ class UserForm extends Model
         $model->OPEN_BANK = $this->openBank;
         $model->BANK_NO = $this->bankNo;
         $model->BANK_ADDRESS = $this->bankAddress;
+        $model->EMAIL = $this->email;
         if(!$model->save()){
             $this->addErrors($model->getErrors());
             return null;

+ 3 - 1
composer.json

@@ -27,7 +27,9 @@
         "ext-pdo": "*",
         "ext-json": "*",
         "monolog/monolog": "^2.4",
-        "tecnickcom/tcpdf": "^6.4"
+        "tecnickcom/tcpdf": "^6.4",
+        "ext-curl": "*",
+        "smladeoye/yii2-paystack": "^1.0"
     },
     "require-dev": {
         "yiisoft/yii2-debug": "~2.0.0",

+ 1 - 0
frontendApi/config/urlManagerRules.php

@@ -68,6 +68,7 @@ return [
             'GET show-cart' => 'show-cart',
             'GET reconsume-cart' => 'reconsume-cart',
             'POST sure-order' => 'sure-order',
+            'POST verify-order' => 'verify-order',
             'POST reconsume-sure-order' => 'reconsume-sure-order',
             'GET pay-success' => 'pay-success',
             'GET order-list' => 'order-list',

+ 17 - 3
frontendApi/modules/v1/controllers/ShopController.php

@@ -131,8 +131,8 @@ class ShopController extends BaseController {
             $formModel->remark = '复销备注';
             $post = \Yii::$app->request->post();
             $post['type'] = DeclarationForm::TYPE_FX;
-            if ($formModel->load($post, '') && $formModel->add()) {
-                return static::notice('购物成功');
+            if ($formModel->load($post, '') && $order = $formModel->add()) {
+                return static::notice($order);
             } else {
                 return static::notice(Form::formatErrorsForApi($formModel->getErrors()),400);
             }
@@ -152,6 +152,18 @@ class ShopController extends BaseController {
         return static::notice($data);
     }
 
+    /**
+     * 订单支付成功
+     * @throws \yii\web\HttpException
+     */
+    public function actionVerifyOrder(){
+        if (\Yii::$app->request->isPost) {
+            return parent::edit(OrderForm::class, 'PayStack pay Success', 'verifyPayStack', ['verifyPayStack']);
+        }
+
+        return static::notice('非法请求', 400);
+    }
+
     /**
      * 我的报单
      * @return mixed
@@ -194,8 +206,10 @@ class ShopController extends BaseController {
             ],
         ]);
         foreach ($data['list'] as $key => $value) {
-            $data['list'][$key]['PAY_AT'] = Date::convert($value['PAY_AT'],'Y-m-d H:i:s');
+            $data['list'][$key]['PAY_AT'] = $value['PAY_AT'] ? Date::convert($value['PAY_AT'],'Y-m-d H:i:s') : '';
             $data['list'][$key]['CATEGORY_TYPE'] = intval($value['CATEGORY_TYPE']);
+            $data['list'][$key]['PAY_TYPE'] = array_column(ShopGoods::SALE_TYPE, NULL, 'label')[$value['PAY_TYPE']]['name'] ?? '';
+            $data['list'][$key]['STATUS'] = \Yii::$app->params['orderStatus'][$value['STATUS']]['label'] ?? '';
             $data['list'][$key]['CATEGORY'] = array_column(ShopGoods::CATEGORY_TYPE, NULL, 'id')[$value['CATEGORY_TYPE']]['name'] ?? '普通商品';
             $data['list'][$key]['FULL_ADDRESS'] = '';
             if($value['PROVINCE']){

+ 2 - 1
frontendEle/package.json

@@ -22,8 +22,9 @@
     "element-ui": "^2.13.0",
     "nix-tinymce": "^1.0.7",
     "v-charts": "^1.16.20",
-    "vue": "^2.6.11",
+    "vue": "^2.6.14",
     "vue-countup-v2": "^1.0.3",
+    "vue-paystack": "^2.0.4",
     "vue-router": "^3.0.2",
     "vuex": "^3.0.1"
   },

+ 1 - 0
frontendEle/src/utils/network.js

@@ -100,6 +100,7 @@ const network = {
           // 更新本地userInfo
           userInfo.userId(response.ID)
           userInfo.userName(response.USER_NAME)
+          userInfo.userEmail(response.EMAIL)
           userInfo.baseData({AVATAR: response.AVATAR,VERIFIED: response.VERIFIED,DEC_LV: response.DEC_LV,EMP_LV: response.EMP_LV,PROVINCE: response.PROVINCE,CITY: response.CITY,COUNTY: response.COUNTY})
           // 成功
           resolve(response)

+ 12 - 1
frontendEle/src/utils/userInfo.js

@@ -7,6 +7,7 @@ const userInfo = {
   clear () {
     localStorage.removeItem('userId')
     localStorage.removeItem('userName')
+    localStorage.removeItem('userEmail')
     localStorage.removeItem('accessToken')
     localStorage.removeItem('accessTokenExpiresIn')
     localStorage.removeItem('accessTokenUpdateAt')
@@ -57,6 +58,16 @@ const userInfo = {
     }
     localStorage.setItem('userName', arg[0])
   },
+  userEmail (...arg) {
+    if (arg.length === 0) {
+      return localStorage.getItem('userEmail')
+    }
+    if (arg[0] === '') {
+      localStorage.removeItem('userEmail')
+      return
+    }
+    localStorage.setItem('userEmail', arg[0])
+  },
   accessToken (...arg) {
     if (arg.length === 0) {
       return localStorage.getItem('accessToken')
@@ -156,4 +167,4 @@ const userInfo = {
   },
 }
 
-export default userInfo
+export default userInfo

+ 6 - 11
frontendEle/src/views/shop/order-list.vue

@@ -23,7 +23,9 @@
                 <el-table-column label="收货人" prop="CONSIGNEE"></el-table-column>
                 <el-table-column label="收货电话" prop="MOBILE"></el-table-column>
                 <el-table-column label="地址" prop="FULL_ADDRESS"></el-table-column>
+                <el-table-column label="支付方式" prop="PAY_TYPE"></el-table-column>
                 <el-table-column label="支付时间" prop="PAY_AT"></el-table-column>
+                <el-table-column label="订单状态" prop="STATUS"></el-table-column>
                 <el-table-column label="操作">
                   <template slot-scope="scope">
                       <el-button type="primary" size="small" @click.native="handleOrderExportPDF(scope.row.SN)">导出PDF</el-button>
@@ -75,16 +77,7 @@
   //
       objectSpanMethod(obj) {
 
-        if (
-          obj.columnIndex === 5 ||
-          obj.columnIndex === 6 ||
-          obj.columnIndex === 7 ||
-          obj.columnIndex === 8 ||
-          obj.columnIndex === 9 ||
-          obj.columnIndex === 10||
-          obj.columnIndex === 11||
-          obj.columnIndex === 12
-        ) {
+        if (obj.columnIndex > 4) {
           // ⼆维数组存储的数据取出
           var _row = this.spanArr[obj.rowIndex];
           var _col = _row > 0 ? 1 : 0;
@@ -113,7 +106,9 @@
               orderList[index].REAL_NAME === orderList[index - 1].REAL_NAME &&
               orderList[index].CONSIGNEE === orderList[index - 1].CONSIGNEE &&
               orderList[index].MOBILE === orderList[index - 1].MOBILE &&
-              orderList[index].PAY_AT === orderList[index - 1].PAY_AT
+              orderList[index].PAY_AT === orderList[index - 1].PAY_AT &&
+              orderList[index].STATUS === orderList[index - 1].STATUS &&
+              orderList[index].PAY_TYPE === orderList[index - 1].PAY_TYPE
             ) {
               // 查找到符合条件的数据时每次要把之前存储的数据+1
               this.spanArr[this.pos] += 1;

+ 242 - 128
frontendEle/src/views/shop/order.vue

@@ -1,138 +1,177 @@
 <template>
-    <div v-loading="loading">
-        <div class="white-box">
-            <div class="table">
-                <el-table
-                    :data="goods"
-                    style="width: 100%"
-                    show-summary
-                    :summary-method="getSummaries">
-                     <el-table-column label="商品名称" prop="GOODS_NAME">
-                    </el-table-column>
+    <div>
+        <div v-loading="loading">
+            <div class="white-box">
+                <div class="table">
+                    <el-table
+                        :data="goods"
+                        style="width: 100%"
+                        show-summary
+                        :summary-method="getSummaries">
+                         <el-table-column label="商品名称" prop="GOODS_NAME">
+                        </el-table-column>
 
-                    <el-table-column label="图片" >
-                        <template slot-scope="scope">
-                            <img :src="scope.row.COVER" alt="" style="width:100%">
-                        </template>
-                    </el-table-column>
+                        <el-table-column label="图片" >
+                            <template slot-scope="scope">
+                                <img :src="scope.row.COVER" alt="" style="width:100%">
+                            </template>
+                        </el-table-column>
 
-                    <el-table-column label="会员价格" prop="member_price">
-                        <template slot-scope="scope">
-                            <span>{{ Math.round(scope.row.member_price * 100) / 100 }}</span>
-                        </template>
-                    </el-table-column>
-                    <el-table-column label="会员PV" prop="member_pv" v-if="category_type === 1">
-                        <template slot-scope="scope">
-                            <span>{{ Math.round(scope.row.member_pv * 100) / 100 }}</span>
-                        </template>
-                    </el-table-column>
-<!--                    <el-table-column label="库存" prop="STORE_NUMS">-->
-<!--                    </el-table-column>-->
+                        <el-table-column label="会员价格" prop="member_price">
+                            <template slot-scope="scope">
+                                <span>{{ Math.round(scope.row.member_price * 100) / 100 }}</span>
+                            </template>
+                        </el-table-column>
+                        <el-table-column label="会员PV" prop="member_pv" v-if="category_type === 1">
+                            <template slot-scope="scope">
+                                <span>{{ Math.round(scope.row.member_pv * 100) / 100 }}</span>
+                            </template>
+                        </el-table-column>
+    <!--                    <el-table-column label="库存" prop="STORE_NUMS">-->
+    <!--                    </el-table-column>-->
 
-                    <el-table-column label="数量" prop="chose_num">
+                        <el-table-column label="数量" prop="chose_num">
 
-                    </el-table-column>
-                    <el-table-column label="合计金额" prop="member_price_plus">
-                        <template slot-scope="scope">
-                            <span>{{ Math.round(scope.row.member_price_plus * 100) / 100 }}</span>
-                        </template>
-                    </el-table-column>
-                    <el-table-column label="合计PV" prop="member_pv_plus" v-if="category_type === 1">
-                        <template slot-scope="scope">
-                            <span>{{ Math.round(scope.row.member_pv_plus * 100) / 100 }}</span>
-                        </template>
-                    </el-table-column>
-                </el-table>
-            </div>
-            <div class="address_box">
-                请选择收货地址:
-                <el-radio-group v-model="addressId" @change='choseAddress'>
-                    <div v-for="(item , index) in all_address" :key='index' class="address">
-                        <el-radio :label="item.ID" >
-                            {{item.PROVINCE_NAME}}省 {{item.CITY_NAME}}市 {{item.COUNTY_NAME}}县&nbsp;&nbsp;&nbsp;&nbsp;
-                            详细地址:{{item.ADDRESS}}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-                            收件人姓名:{{item.CONSIGNEE}}&nbsp;&nbsp;&nbsp;&nbsp;
-                            手机号码:{{item.MOBILE}}
-                        </el-radio>
-                    </div>
-                    <div class="address">
-                      <el-radio label="100000000000000000">自提</el-radio>
-                    </div>
-                </el-radio-group>
-            </div>
-            <div class="address_box">
-                请选择支付方式:
-                <el-radio-group v-model="payType" @change='chosePayType'>
-                    <div v-for="(item, index) in payList" :key='index' class="address">
-                        <el-radio :label="item.label">{{ item.name }}</el-radio>
-                    </div>
-                </el-radio-group>
-            </div>
-            <div class="box address_box">
-                订单合计:
-                <div class="sum">
-                    <div class="sum_box" v-if="category_type === 1">
-                        <div>运费</div>
-                        <div><span v-if="category_type === 1">{{ prefixSign }}</span> {{ payType === "cash" ? freight : pointFreight }} <span v-if="category_type === 1">{{ unit }}</span></div>
-                    </div>
-                    <div class="sum_box">
-                        <div>实付金额</div>
-                        <div><span v-if="category_type === 1">{{ prefixSign }}</span> {{ payType === "cash" ? cashSum : pointsSum }} <span v-if="category_type === 1">{{ unit }}</span></div>
-                    </div>
+                        </el-table-column>
+                        <el-table-column label="合计金额" prop="member_price_plus">
+                            <template slot-scope="scope">
+                                <span>{{ Math.round(scope.row.member_price_plus * 100) / 100 }}</span>
+                            </template>
+                        </el-table-column>
+                        <el-table-column label="合计PV" prop="member_pv_plus" v-if="category_type === 1">
+                            <template slot-scope="scope">
+                                <span>{{ Math.round(scope.row.member_pv_plus * 100) / 100 }}</span>
+                            </template>
+                        </el-table-column>
+                    </el-table>
                 </div>
-            </div>
-
-            <div class="box address_box">
-                账户余额:
-                <div class="sum">
-                    <div v-if="category_type === 1">
-<!--                        <div class="sum_box">-->
-<!--                            <div>账户积分</div>-->
-<!--                            <div>{{ balance.points }}</div>-->
-<!--                        </div>-->
-                        <div class="sum_box">
-                            <div>账户余额</div>
-                            <div>¥{{ balance.cash }}元</div>
+                <div class="address_box">
+                    请选择收货地址:
+                    <el-radio-group v-model="addressId" @change='choseAddress'>
+                        <div v-for="(item , index) in all_address" :key='index' class="address">
+                            <el-radio :label="item.ID" >
+                                {{item.PROVINCE_NAME}}省 {{item.CITY_NAME}}市 {{item.COUNTY_NAME}}县&nbsp;&nbsp;&nbsp;&nbsp;
+                                详细地址:{{item.ADDRESS}}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                                收件人姓名:{{item.CONSIGNEE}}&nbsp;&nbsp;&nbsp;&nbsp;
+                                手机号码:{{item.MOBILE}}
+                            </el-radio>
+                        </div>
+                        <div class="address">
+                          <el-radio label="100000000000000000">自提</el-radio>
+                        </div>
+                    </el-radio-group>
+                </div>
+                <div class="address_box">
+                    请选择支付方式:
+                    <el-radio-group v-model="payType" @change='chosePayType'>
+                        <div v-for="(item, index) in payList" :key='index' class="address">
+                            <el-radio :label="item.label">{{ item.name }}</el-radio>
+                        </div>
+                    </el-radio-group>
+                </div>
+                <div class="box address_box">
+                    订单合计:
+                    <div class="sum">
+                        <div class="sum_box" v-if="category_type === 1">
+                            <div>运费</div>
+                            <div><span v-if="category_type === 1">{{ prefixSign }}</span> {{ payType === "cash" ? freight : pointFreight }} <span v-if="category_type === 1">{{ unit }}</span></div>
                         </div>
                         <div class="sum_box">
-                            <div>兑换点数</div>
-                            <div>{{ balance.exchange }}</div>
+                            <div>实付金额</div>
+                            <div><span v-if="category_type === 1">{{ prefixSign }}</span> {{ payType === "cash" ? cashSum : pointsSum }} <span v-if="category_type === 1">{{ unit }}</span></div>
                         </div>
                     </div>
+                </div>
 
-                    <div v-if="category_type === 4">
-                        <div class="sum_box">
-                            <div>旅游积分</div>
-                            <div>{{ balance.travel_points }}</div>
+                <div class="box address_box">
+                    账户余额:
+                    <div class="sum">
+                        <div v-if="category_type === 1">
+    <!--                        <div class="sum_box">-->
+    <!--                            <div>账户积分</div>-->
+    <!--                            <div>{{ balance.points }}</div>-->
+    <!--                        </div>-->
+                            <div class="sum_box">
+                                <div>账户余额</div>
+                                <div>¥{{ balance.cash }}元</div>
+                            </div>
+                            <div class="sum_box">
+                                <div>兑换点数</div>
+                                <div>{{ balance.exchange }}</div>
+                            </div>
                         </div>
-                    </div>
 
-                    <div v-if="category_type === 5">
-                        <div class="sum_box">
-                            <div>名车积分</div>
-                            <div>{{ balance.car_points }}</div>
+                        <div v-if="category_type === 4">
+                            <div class="sum_box">
+                                <div>旅游积分</div>
+                                <div>{{ balance.travel_points }}</div>
+                            </div>
                         </div>
-                    </div>
 
-                    <div v-if="category_type === 6">
-                        <div class="sum_box">
-                            <div>豪宅积分</div>
-                            <div>{{ balance.house_points }}</div>
+                        <div v-if="category_type === 5">
+                            <div class="sum_box">
+                                <div>名车积分</div>
+                                <div>{{ balance.car_points }}</div>
+                            </div>
+                        </div>
+
+                        <div v-if="category_type === 6">
+                            <div class="sum_box">
+                                <div>豪宅积分</div>
+                                <div>{{ balance.house_points }}</div>
+                            </div>
                         </div>
                     </div>
                 </div>
-            </div>
 
-            <div>
-                <el-button type="primary" @click="goToAccounts()" :loading="submitButtonStat">去结算</el-button>
+                <div>
+                    <el-button type="primary" @click="goToAccounts()" :loading="submitButtonStat">去结算</el-button>
+                </div>
             </div>
         </div>
+
+        <el-dialog title="订单支付" v-if="visible" :visible.sync="visible" width="30%" v-loading="payStackLoading" :before-close="handleClose">
+            <section>
+                <h1>Lorem Ipsum Dolor Sit Amet</h1>
+                <div class="formcontainer">
+                    <el-divider></el-divider>
+                    <div class="container">
+                        <el-form :model="form">
+                            <el-form-item label="Email" label-width="120px">
+                                <el-input v-model="form.email" autocomplete="off" readonly></el-input>
+                            </el-form-item>
+                            <el-form-item label="Amount" label-width="120px">
+                                <el-input v-model="form.amount" autocomplete="off" readonly></el-input>
+                            </el-form-item>
+                        </el-form>
+                    </div>
+                </div>
+            </section>
+
+            <paystack
+                :firstname="form.firstname"
+                :lastname="form.lastname"
+                :amount="form.amount * 100"
+                :email="form.email"
+                :currency="form.currency"
+                :paystackkey="form.publicKey"
+                :reference="reference"
+                :callback="processPayment"
+                :close="processClose"
+            >
+                <el-button type="primary" size="small">支 付</el-button>
+            </paystack>
+        </el-dialog>
     </div>
 </template>
 
 <script>
  import network from '@/utils/network'
  import tool from '@/utils/tool'
+ import userInfo from '@/utils/userInfo'
+ import { PAY_STACK_PUBLIC_KEY, PAY_STACK_CURRENCY } from '@/utils/config'
+ import paystack from 'vue-paystack'
+
     export default{
         name:'order',
         data(){
@@ -156,8 +195,23 @@
                 category_type: '',
                 prefixSign: '¥',
                 unit: '元',
+
+                visible: false,
+                payStackLoading: false,
+                form: {
+                    publicKey: PAY_STACK_PUBLIC_KEY,
+                    currency: PAY_STACK_CURRENCY,
+                    firstname: userInfo.userName(),
+                    lastname: '',
+                    email: userInfo.userEmail(),
+                    amount: 0,  // kobo
+                    orderSn: '',
+                },
             }
         },
+        components: {
+            paystack
+        },
         created(){
             let option= sessionStorage.getItem('order_goods');
             this.category_type = parseInt(sessionStorage.getItem('category_type'))
@@ -187,6 +241,16 @@
                 this.getSumMoney();
             }
         },
+        computed: {
+            reference() {
+                let text = "";
+                let possible =
+                    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+                for (let i = 0; i < 10; i++)
+                    text += possible.charAt(Math.floor(Math.random() * possible.length));
+                return text;
+            }
+        },
         methods:{
             setFreight(){
               if(this.addressId=='100000000000000000'){//如果地址为自提,则运费为0
@@ -220,7 +284,12 @@
                 let amountBalance = 0
                 // 前置判断余额是否够支付
                 if (this.category_type === 1) {
-                    amountBalance = (this.payType === 'cash') ? this.balance.cash : this.balance.exchange
+                    if (this.payType === 'cash') {
+                        amountBalance = this.balance.cash
+                    } else if (this.payType === 'exchange') {
+                        amountBalance = this.balance.exchange
+                    }
+                    // amountBalance = (this.payType === 'cash') ? this.balance.cash : this.balance.exchange
                 } else if (this.category_type === 4) {
                     amountBalance = this.balance.travel_points
                 } else if (this.category_type === 5) {
@@ -231,14 +300,15 @@
 
                 // 提示信息
                 let tips = '余额'
-                if (this.payType !== 'cash') {
+                if (this.payType !== 'cash' && this.payType !== 'pay_stack') {
                     let payObj = this.payList.find((item) => {
                         return item.label === this.payType
                     })
                     tips = (payObj.length <= 0) ? '' : payObj.name
                 }
 
-                if ((amountBalance - this.cashSum) < 0) {
+                // 余额是否充足
+                if ((amountBalance > 0) && (this.payType !== 'pay_stack') && ((amountBalance - this.cashSum) < 0)) {
                     let tips = this.payList[this.payType] ? this.payList[this.payType].label : '余额'
                     this.$message({
                         message: tips + '不足,无法购买商品',
@@ -249,7 +319,6 @@
                 }
 
                 this.submitButtonStat = true
-                // let path = 'sure-order'
                 this.$prompt('请输入支付密码', '提示', {
                     confirmButtonText: '确定',
                     cancelButtonText: '取消',
@@ -258,26 +327,29 @@
                     inputErrorMessage: '请输入支付密码'
                 }).then(({value}) => {
                     this.payPassword = value
-                    let data = {
+                    let params = {
                         addressId: this.addressId,
                         payType: this.payType,
                         goodsId: this.goodsId,
                         goodsNum: this.goodsNum,
-                        payPassword: this.payPassword
+                        payPassword: this.payPassword,
+                        email: this.form.email,
                     }
-                    return network.postData('shop/sure-order',data).then(response => {
-                        this.$message({
-                            message: response,
-                            type: 'success'
-                        })
-
-                        this.submitButtonStat = false
-                        this.$router.go(-1)
-                        this.$router.push({path: `/shop/order-list`})
-                    }).catch(() => {
+                    return network.postData('shop/sure-order', params).then((response) => {
                         this.submitButtonStat = false
+
+                        // PayStack支付
+                        if (this.payType === 'pay_stack') {
+                            this.form.orderSn = response.SN
+                            this.form.amount = this.cashSum
+                            this.visible = true
+                        } else {
+                            // 非PayStack支付
+                            this.$router.go(-1)
+                            this.$router.push({path: `/shop/order-list`})
+                        }
                     }).catch((response) => {
-                        this.submitButtonStat = false
+                        this.submitButtonStat = true
                     })
                 }).catch(() => {
                     this.submitButtonStat = false
@@ -321,8 +393,7 @@
                 //console.log('points_plus_sum',points_plus_sum);
                 //console.log('cash_plus_sum',cash_plus_sum);
                 // this.pointsSum=tool.formatPrice(tool.sum(points_plus_sum) + this.pointFreight);
-              console.log(this.freight)
-              this.pointsSum = this.cashSum = tool.formatPrice(tool.sum(cash_plus_sum) + this.freight) ;
+              this.pointsSum = this.cashSum = this.form.amount = tool.formatPrice(tool.sum(cash_plus_sum) + this.freight) ;
             },
             getShowCart(){
                 network.getData('shop/show-cart', { categoryType: this.categoryType })
@@ -356,7 +427,46 @@
             },
             choseAddress(addressId){
               this.getSumMoney()
-                // console.log(addressId);
+            },
+            // 关闭支付回调
+            handleClose(done) {
+                this.$confirm('确认关闭?').then(_ => done()).catch(_ => {});
+            },
+            // 支付成功回调
+            processPayment(response) {
+                // 支付失败
+                if (response.status !== 'success') {
+                    this.$message({
+                        message: 'Success!',
+                        type: 'error'
+                    })
+                    return false
+                }
+
+                this.payStackLoading = true
+                // 支付成功,更新订单
+                return network.postData('shop/verify-order', { sn: this.form.orderSn, remark: response }).then(_ => {
+                    this.$message({
+                        message: 'Success!',
+                        type: 'success'
+                    })
+
+                    this.visible = false
+                    this.payStackLoading = false
+                    this.$router.go(-1)
+                    this.$router.push({path: `/shop/order-list`})
+                }).catch((err) => {
+                    this.$message({
+                        message: err,
+                        type: 'error'
+                    })
+                    this.payStackLoading = false
+                    return false
+                })
+            },
+            // 关闭支付回调
+            processClose() {
+                this.visible = false
             }
         }
     }
@@ -391,4 +501,8 @@
 .sum_box > div:nth-child(1){
     margin-right: 1rem;
 }
+.payButton {
+    border: none;
+    padding: 0;
+}
 </style>

+ 6 - 1
frontendEle/src/views/user/index.vue

@@ -22,6 +22,9 @@
             <el-form-item label="手机号">
               <el-input v-model="infoData.MOBILE" :disabled="true"></el-input>
             </el-form-item>
+            <el-form-item label="Email">
+              <el-input v-model="infoData.EMAIL"></el-input>
+            </el-form-item>
             <el-form-item label="银行名称">
               <el-select v-model="infoData.OPEN_BANK" placeholder="请选择银行名称">
                 <el-option v-for="(item,index) in allOpenBank" :key="index" :label="item.BANK_NAME"
@@ -67,6 +70,7 @@ export default {
         OPEN_BANK: '',
         BANK_ADDRESS: '',
         BANK_NO: '',
+        EMAIL: '',
       },
       allOpenBank:null,
       allNation:null,
@@ -95,6 +99,7 @@ export default {
             openBank: this.infoData.OPEN_BANK,
             bankAddress: this.infoData.BANK_ADDRESS,
             bankNo: this.infoData.BANK_NO,
+            email: this.infoData.EMAIL,
         }
         return network.postData(path, postData).then(response => {
 
@@ -117,4 +122,4 @@ export default {
 
 <style scoped>
 
-</style>
+</style>

+ 8 - 0
vendor/matscode/paystack/.gitignore

@@ -0,0 +1,8 @@
+composer.phar
+/vendor/
+
+# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
+# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
+# composer.lock
+
+/.idea

+ 674 - 0
vendor/matscode/paystack/LICENSE

@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    {one line to give the program's name and a brief idea of what it does.}
+    Copyright (C) {year}  {name of author}
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    {project}  Copyright (C) {year}  {fullname}
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.

+ 137 - 0
vendor/matscode/paystack/README.md

@@ -0,0 +1,137 @@
+# matscode/paystack
+###### This package is for communicating with PAYSTACK RESTful API. [Paystack](https://paystack.com/)
+Having other resource point available on PAYSTACK API, Resources like; 
+- Transaction
+- Customers
+- Plans
+- Subscription
+- Transfers
+- Charges
+- and many more
+
+Just to name a few, it is only the Transaction Resource that is made available currently in this package. Development is ongoing while releases are Stable. Incase you find a BUG/Security Issue, Please, do be kind to open an issue or email [matscode(at)Gmail(dot)Com](mailto://matscode@gmail.com).
+<br>
+**PS**: This library also works for you if you use paystack inline. You just have to use the `verify()` method, passing in your transaction `$reference|$token|$id` is compulsory.
+
+## Requirements
+- Curl 
+
+## Install
+
+### Via Composer
+
+``` bash
+$ composer require matscode/paystack
+```
+If you use a Framework, check your documentation for how vendor packages are autoloaded else Add this to the top of your source file;
+
+``` php
+require_once __DIR__ . "/vendor/autoload.php";
+```
+
+## Making Transactions/Recieving Payment
+
+### Starting Up Paystack Transaction
+
+``` php
+use Matscode\Paystack\Transaction;
+use Matscode\Paystack\Utility\Debug; // for Debugging purpose
+use Matscode\Paystack\Utility\Http;
+
+$secretKey = 'sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
+
+// creating the transaction object
+$Transaction = new Transaction( $secretKey );
+```
+
+### Initializing Transaction
+
+Set data/payload/requestBody to post with initialize request. Minimum required data are email and amount.
+
+``` php
+// Set data to post using array
+$data = 
+[
+    'email'  => 'customer@email.com',
+    'amount' => 500000 // amount is treated in kobo using this method
+];
+$response = $Transaction->initialize($data);
+```
+OR 
+``` php
+// Set data to post using this method
+$response =
+        $Transaction
+            ->setCallbackUrl('http://michaelakanji.com') // to override/set callback_url, it can also be set on your dashboard 
+            ->setEmail( 'matscode@gmail.com' )
+            ->setAmount( 75000 ) // amount is treated in Naira while using this method
+            ->initialize();
+```
+If you want to get the 200OK raw Object as it is sent by Paystack, Set the 2nd argument of the `initialize()` to `true`, example below
+``` php
+// Set data to post using this method
+$response =
+        $Transaction 
+            ->setEmail( 'matscode@gmail.com' )
+            ->setAmount( 75000 ) // amount is treated in Naira while using this method
+            ->initialize([], true);
+```
+Now do a redirect to payment page (using authorization_url)
+<br>
+NOTE: Recommended to Debug `$response` or check if authorizationUrl is set, and save your Transaction reference code. useful to verify Transaction status
+
+``` php
+// recommend to save Transaction reference in database and do a redirect
+$reference = $response->reference;
+// redirect
+Http::redirect($response->authorizationUrl); 
+```
+Using a Framework? It is recommended you use the reverse routing/redirection functions provided by your Framework
+
+
+### Verifying Transaction
+This part would live in your callback file i.e `callback.php` or `whatsoever_you_name.php`
+<br>
+It is also imperative that you create Transaction Obj once more.
+<br>
+This method would return the Transaction Obj but `false` if saved `$reference` is not passed in as argument and also cant be guessed. Using `verify()` would require you do a manual check on the response Obj
+``` php
+$response = $Transaction->verify();
+// Debuging the $response
+Debug::print_r( $response);
+```
+OR
+``` php
+// This method does the check for you and return `(bool) true|false` 
+$response = $Transaction->isSuccessful();
+```
+The two methods above try to guess your Transaction `$reference` but it is highly recommended you pass the Transaction `$reference` as an argument on the method as follows
+``` php
+// This method does the check for you and return `(bool) true|false`
+$response = $Transaction->isSuccessful($reference);
+```
+Moreso, you can also compare if amount paid by customer is the amount expected. This method only works after calling `verify()` or `isSuccessful()` in the same script. It is recommended to do this if you use paystack inline to initialize the transaction.
+``` php
+$amountExpected = 5000; // amount must be in kobo
+// returns `(bool) true|false`
+$Transaction->amountEquals($amountExpected);
+```
+Now you can process Customer Valuable.
+<br>
+<br>
+You might wanna save Transaction `$authorizationCode` for the current customer subsequent Transaction but not a nessecity. It would only counts to future updates of this package or if you choose to extend the package.
+``` php
+// returns Auth_xxxxxxx 
+$response = $Transaction->authorizationCode($reference); // can also guess Transaction $reference
+```
+
+## Hire Me
+I am available on contract if you need help integrating paystack into your Business Website or something.
+<br>
+Contact me via my email above - [michaelakanji.com](http://michaelakanji.com)
+
+## Contributions
+If you seem to understand the architecture, you are welcome to fork and pull else you can wait a bit more till when i provide convention documentation.
+
+## Licence
+GNU GPLV3

+ 19 - 0
vendor/matscode/paystack/composer.json

@@ -0,0 +1,19 @@
+{
+    "name": "matscode/paystack",
+    "description": "This package is for communicating with PAYSTACK API",
+    "type": "library",
+    "keywords": ["payment","paystack", "php"],
+    "license": "GNU GPLv3",
+    "minimum-stability": "stable",
+    "autoload": {
+        "psr-4": {"Matscode\\Paystack\\": "src/"}
+    },
+    "authors": [
+        {
+            "name": "Michael Akanji",
+            "email": "matscode@gmail.com",
+            "homepage": "http://michaelakanji.com"
+        }
+    ],
+    "require": {}
+}

+ 152 - 0
vendor/matscode/paystack/src/Base.php

@@ -0,0 +1,152 @@
+<?php
+	/**
+	 *
+	 * Description
+	 *
+	 * @package        Paystack
+	 * @category       Source
+	 * @author         Michael Akanji <matscode@gmail.com>
+	 * @date           2017-06-26
+	 * @copyright (c)  2016 - 2017, TECRUM (http://www.tecrum.com)
+	 *
+	 */
+
+	namespace Matscode\Paystack;
+
+	use Matscode\Paystack\CURL;
+	use Matscode\Paystack\Utility\Text;
+
+	class Base
+	{
+
+		private
+			$_apiBaseUrl = 'https://api.paystack.co/', // with trailing slash
+			$_curl,
+			$_secretKey,
+			$_endPoint,
+
+			/*Getting Error Infomation*/
+			$_errorMessages = [];
+
+		public
+			$resource,
+			$action,
+			$args,
+			$data,
+			// response from the endpoint
+			$response;
+
+		public function __construct( $secretKey )
+		{
+			// save key in memory
+			$this->_secretKey = $secretKey;
+
+			return $this;
+		}
+
+		public function setResource( $resource )
+		{
+			$this->resource = $resource;
+
+			return $this;
+		}
+
+		public function setAction( $action, array $args = [] )
+		{
+			if ( ! is_array( $args ) ) {
+				throw new \Exception( 'Action arguments can only be of datatype Array' );
+			}
+
+			$this->action = $action;
+			$this->args   = $args;
+
+			return $this;
+		}
+
+		/**
+		 * Initiate Request to the paystack RESTful API and return response Obj
+		 *
+		 * @param array  $withData
+		 * @param string $requestMethod
+		 * @param bool   $returnArray set to true to return response as associate array
+		 *
+		 * @todo Utilize the third argument..
+		 *
+		 * @return mixed
+		 * @throws \Exception
+		 */
+		public function sendRequest( array $withData = [], $requestMethod = 'POST', $returnArray = false )
+		{
+			if ( ! is_array( $withData ) ) {
+				throw new \Exception( 'sendRequest arguments can only be of datatype Array' );
+			}
+
+			$this->data = $withData;
+
+			$this->_endPoint = $this->_apiBaseUrl .
+			                   Text::removeSlashes( $this->resource ) . '/' .
+			                   Text::removeSlashes( $this->action );
+			// append parameters to endPoint
+			if ( count( $this->args ) > 0 ) {
+				$this->_endPoint .= '/' . implode( '/', $this->args );
+			}
+
+			// send the request and return result as json object
+			$this->_curl =
+				( new CURL(
+					$this->_endPoint,
+					$requestMethod ) )
+					->setRequestHeader( 'Authorization', 'Bearer ' . $this->_secretKey );
+
+			$this->response =
+				json_decode(
+					$this->_curl
+						->run( $this->data, 'json' ) );
+
+			return $this->response;
+		}
+
+		/**
+		 * @return mixed
+		 */
+		public function getEndPoint()
+		{
+			// this works only after executing sendRequest
+			return $this->_endPoint;
+		}
+
+		/**
+		 * @param mixed $errorMessages
+		 */
+		public function setErrorMessages( $errorMessages )
+		{
+			//if errorMessages is string
+			if ( is_string( $errorMessages ) ) {
+				$this->_errorMessages[] = $errorMessages;
+			}
+			//if errorMessages is array
+			if ( is_array( $errorMessages ) ) {
+				$this->_errorMessages = array_merge( $this->_errorMessages, $errorMessages );
+			}
+		}
+
+		/**
+		 * @param bool   $toString
+		 * @param string $delimiter
+		 *
+		 * @return array|string
+		 */
+		public function getErrorMessages( $toString = false, $delimiter = '<br>' )
+		{
+			$errorMessages = $this->_errorMessages;
+			if ( $toString ) {
+				// return errorMessage as String
+				unset( $errorMessages ); //to avoid datatype conflict
+				$errorMessages = join( $delimiter, $this->_errorMessages );
+			}
+
+			return $errorMessages;
+		}
+
+
+	}

+ 247 - 0
vendor/matscode/paystack/src/CURL.php

@@ -0,0 +1,247 @@
+<?php
+	/**
+	 *
+	 * Description
+	 *
+	 * @package        Paystack
+	 * @category       Source
+	 * @author         Michael Akanji <matscode@gmail.com>
+	 * @date           2017-06-25
+	 * @copyright (c)  2016 - 2017, TECRUM (http://www.tecrum.com)
+	 *
+	 */
+
+	namespace Matscode\Paystack;
+
+	class CURL
+	{
+		private
+			$_url,
+			$_curl,
+			$_requestWithPost = false,
+			$_allowedReqMethodForPost =
+			[
+				'POST',
+				'UPDATE',
+				'PUT',
+				'PATCH',
+			],
+			$_errorCode,
+			$_errorMessage;
+
+		public
+			$requestMethod,
+			$requestHeader = [];
+
+		const
+			USER_AGENT = 'PHP CURL/1.0 (@matscode)';
+
+
+		public function __construct( $url, $requestMethod = null )
+		{
+			if ( ! extension_loaded( 'curl' ) ) {
+				throw new \ErrorException( 'CURL Extension not loaded, Install libcurl php extension' );
+			}
+
+			//initialize CUrl
+			$this->_url = $url;
+			$this->_curlInit( $this->_url );
+
+			//set requestMethod property
+			$this->requestMethod = $requestMethod;
+
+			//set the default request method to 'POST'
+			if ( ! is_null( $this->requestMethod ) ) {
+				$this->setRequestMethod( $this->requestMethod );
+			}
+
+			return $this;
+		}
+
+		private function _curlInit( $url )
+		{
+			$this->_curl = curl_init();
+
+			$this->setOption( CURLOPT_USERAGENT, self::USER_AGENT )
+			     ->setOption( CURLOPT_URL, $url )
+				// return response
+				 ->setOption( CURLOPT_RETURNTRANSFER, true );
+
+			// turn off caching
+			$this->setRequestHeader( 'Cache-Control', 'no-cache' );
+
+			return $this;
+		}
+
+		public function setOption( $option, $value )
+		{
+			// Set curl option
+			curl_setopt( $this->_curl, $option, $value );
+
+			return $this;
+		}
+
+		public function setRequestMethod( $requestMethod )
+		{
+			switch ( $requestMethod ) {
+				case 'GET' :
+					$this->setOption( CURLOPT_HTTPGET, true );
+					break;
+				case 'UPDATE' :
+					$this->setOption( CURLOPT_CUSTOMREQUEST, 'UPDATE' );
+					break;
+				case 'PUT' :
+					$this->setOption( CURLOPT_CUSTOMREQUEST, 'PUT' );
+					break;
+				case 'PATCH' :
+					$this->setOption( CURLOPT_CUSTOMREQUEST, 'PATCH' );
+					break;
+				case 'DELETE' :
+					$this->setOption( CURLOPT_CUSTOMREQUEST, 'DELETE' );
+					break;
+				case 'POST':
+					$this->doPostRequest();
+
+				default:
+					$this->setOption( CURLOPT_POST, true );
+					break;
+			}
+
+			return $this;
+		}
+
+
+		/**
+		 * @param null $qStringArray
+		 *
+		 * @return string
+		 */
+		public function getUrl( $qStringArray = null )
+		{
+			if ( ! is_null( $qStringArray ) &&
+			     ( is_array( $qStringArray ) || is_object( $qStringArray ) )
+			) {
+				$this->_url .= '?' . http_build_query( $qStringArray );
+			}
+
+			return $this->_url;
+		}
+
+		/**
+		 * @param string $url
+		 *
+		 * @return $this
+		 */
+		public function setUrl(
+			$url
+		) {
+			$this->setOption( CURLOPT_URL, $url );
+			$this->_url = $url;
+
+			return $this;
+		}
+
+		public function doPostRequest()
+		{
+			if ( in_array( $this->requestMethod, $this->_allowedReqMethodForPost ) ) {
+				$this->_requestWithPost = true;
+			}
+
+			return $this;
+		}
+
+		public function run( $data, $as = 'urlencoded', $closeCurl = false ) // urlencoded | json | form-data
+		{
+			if ( $this->_requestWithPost ) {
+				//make a post request
+				switch ( $as ) {
+					case 'json':
+						if ( is_array( $data ) ) {
+							$data = json_encode( $data );
+							$this->setRequestHeader( 'Content-Type', 'application/json' );
+						} else {
+							throw new \ErrorException( 'Data argument passed to the run method must be of datatype Array when posting as JSON' );
+						}
+						break;
+					case 'form-data':
+						if ( is_array( $data ) || is_object( $data ) ) {
+							$this->setRequestHeader( 'Content-Type', 'multipart/form-data' );
+						} else {
+							throw new \ErrorException( 'Data argument passed to the run method must be of datatype Array or Object when postiing as FORM-DATA' );
+						}
+						break;
+					case 'urlenconded':
+						// convert data to queryString - Native
+						$data = http_build_query( $data );
+					// continue to default
+					default:
+						$this->setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
+						break;
+				}
+
+				$this->setOption( CURLOPT_POSTFIELDS, $data );  //Post Fields
+			} else {
+				//make a built query string and reset CURLOPT_URL
+				$this->setOption( CURLOPT_URL, $this->getUrl( $data ) );
+			}
+
+			// execute curl
+			$response = curl_exec( $this->_curl );
+			// save error details in memory
+			$this->_errorCode    = curl_errno( $this->_curl );
+			$this->_errorMessage = curl_error( $this->_curl );
+
+			if ( $closeCurl ) {
+				// close curl connection by default
+				if ( is_resource( $this->_curl ) ) {
+					curl_close( $this->_curl );
+				}
+			}
+
+			// return response from endpoint
+			return $response;
+		}
+
+		/**
+		 * @param $key
+		 * @param $value
+		 *
+		 * @return $this
+		 */
+		public function setRequestHeader( $key, $value = null )
+		{
+			if ( ! is_array( $key ) ) {
+				// assist with capitalizing http header keys
+				$headers[] = ucwords( $key ) . ': ' . $value;
+			} else {
+				$headers = $key;
+			}
+
+			// merge requestHeader to base header
+			$this->requestHeader = array_merge( $headers, $this->requestHeader );
+
+			$this->setOption( CURLOPT_HTTPHEADER, $this->requestHeader );
+
+			return $this;
+		}
+
+
+		public function getErrorCode()
+		{
+			return $this->_errorCode;
+		}
+
+		public function getErrorMessage()
+		{
+			return $this->_errorMessage;
+		}
+
+		/**
+		 * @return array
+		 */
+		public function getRequestHeader()
+		{
+			return $this->requestHeader;
+		}
+
+	}

+ 287 - 0
vendor/matscode/paystack/src/Transaction.php

@@ -0,0 +1,287 @@
+<?php
+	/**
+	 *
+	 * Class for Transaction Logic
+	 *
+	 * @package        Paystack
+	 * @category       Source
+	 * @author         Michael Akanji <matscode@gmail.com>
+	 * @date           2017-06-25
+	 * @copyright (c)  2016 - 2017, TECRUM (http://www.tecrum.com)
+	 *
+	 */
+
+	namespace Matscode\Paystack;
+
+
+	use Matscode\Paystack\Utility\Text;
+
+	class Transaction extends Base
+	{
+		public
+			$amount = 0,
+			$email = null,
+			$reference = null,
+			$transactionResponse =
+			[
+				'verify'     => null,
+				'initialize' => null
+			];
+
+		private
+			$_callbackUrl = null;
+
+
+		public function __construct( $secretKey = null )
+		{
+			if ( ! is_null( $secretKey ) ) {
+				parent::__construct( $secretKey );
+			}
+			// set the default resource for current class
+			$this
+				->setResource( 'transaction' );
+
+			return $this;
+		}
+
+		/**
+		 * This method must be called to request for payment. which return an initial transaction obj
+		 *
+		 * @param array $data
+		 * @param bool  $rawResponse
+		 *
+		 * @return mixed|\stdClass
+		 */
+		public function initialize( array $data = [], $rawResponse = false )
+		{
+			// values set via mutator
+			$data['callback_url'] = $this->_callbackUrl;
+
+			// override refernce $data['reference'] value
+			if ( $this->reference ) {
+				$data['reference'] = $this->reference;
+			} elseif ( ! isset( $data['reference'] ) ) {
+				$this->reference = $data['reference'] = Text::uniqueRef();
+			} else {
+				$this->reference = $data['reference'];
+			}
+
+			// override amount $data['amount'] value
+			if ( $this->amount ) {
+				$data['amount'] = $this->amount;
+			} else {
+				// save amount in memory
+				$this->amount = $data['amount'];
+			}
+
+			// override email $data['email'] value
+			if ( ! is_null( $this->email ) ) {
+				$data['email'] = $this->email;
+			} else {
+				// save amount in memory
+				$this->email = $data['email'];
+			}
+
+			$this->transactionResponse['initialize'] =
+				$this
+					->setAction( 'initialize' )
+					->sendRequest( $data );
+
+
+			if ( $rawResponse ) {
+				$response =
+					$this->transactionResponse['initialize'];
+			} else {
+				// Initialize a new Obj to save Striped response
+				$response = new \stdClass();
+				if ( isset( $this->transactionResponse['initialize']->data ) &&
+				     is_object( $this->transactionResponse['initialize']->data )
+				) {
+					$response->authorizationUrl = $this->transactionResponse['initialize']->data->authorization_url;
+					$response->reference        = $this->transactionResponse['initialize']->data->reference;
+				} else {
+					// return the raw response
+					$response =
+						$this->transactionResponse['initialize'];
+				}
+			}
+
+			return $response;
+		}
+
+		/**
+		 * Is used to Check if a transaction is successful and return the transaction object datd
+		 *
+		 * @param null $reference
+		 *
+		 * @todo Use session to keep reference temporary per transaction To enhance Transaction reference guessing.
+		 *
+		 * @return mixed
+		 * @throws \Exception
+		 */
+		public function verify( $reference = null )
+		{
+			// try to guess reference if not set
+			if ( is_null( $reference ) ) {
+				// guess reference
+				if ( isset( $_GET['reference'] ) ) {
+					$reference = $_GET['reference'];
+				} else {
+					// return false
+					return false;
+				}
+			}
+
+			$this->transactionResponse['verify'] =
+				$this
+					->setAction( 'verify', [ $reference ] )
+					->sendRequest( [], 'GET' );
+
+			return $this->transactionResponse['verify'];
+		}
+
+		/**
+		 * Like verify(), but it only checks to see if a transactions is successful returning boolean
+		 *
+		 * @param null $reference
+		 *
+		 * @return bool
+		 */
+		public function isSuccessful( $reference = null )
+		{
+			// get verify response
+			$response = $this->verify( $reference );
+
+			// Initialize as !isSuccessful
+			$isSuccessful = false;
+
+			// check if transaction is successful
+			if ( isset($response->data) && is_object( $response->data ) &&
+			     $response->status == true &&
+			     $response->data->status == 'success'
+			) {
+				$isSuccessful = true;
+			}
+
+			return $isSuccessful;
+		}
+
+		/**
+		 * Compares the amount paid by customer to the amount passed into it
+		 *
+		 * @param $amountExpected
+		 *
+		 * @return bool
+		 */
+		public function amountEquals( $amountExpected )
+		{
+			// $this->verify(); // call verify() or isSuccessful() before calling this method
+			$transactionResponse = $this->transactionResponse['verify'];
+			if ( is_object( $transactionResponse ) ) {
+				return
+					( (int) $transactionResponse->data->amount === $amountExpected );
+			}
+
+			return false;
+		}
+
+		/**
+		 * @param null $reference
+		 *
+		 * @return string|null
+		 */
+		public function getAuthorizationCode( $reference = null )
+		{
+			$authorizationCode = null;
+			// get verify response
+			if ( $this->isSuccessful( $reference ) ) {
+				$response          = $this->verify( $reference );
+				$authorizationCode = $response->data->authorization->authorization_code;
+			}
+
+			return $authorizationCode;
+		}
+
+		/**
+		 * @param $email
+		 *
+		 * @return $this
+		 */
+		public function setEmail( $email )
+		{
+			// setting the email
+			$this->email = $email;
+
+			return $this;
+		}
+
+		public function getEmail( $email )
+		{
+			// setting the email
+			$this->email = $email;
+		}
+
+		/**
+		 * @param int $amount
+		 *
+		 * @todo Allow to set kobo using '.' syntax
+		 * @return $this
+		 */
+		public function setAmount( $amount )
+		{
+			// setting amount in naira //TODO: Allow to set kobo using '.' syntax
+			$this->amount = ( $amount * 100 );
+
+			return $this;
+		}
+
+		/**
+		 * @return int
+		 */
+		public function getAmount()
+		{
+			return $this->amount;
+		}
+
+		/**
+		 * Sets the transaction reference code/id
+		 *
+		 * @param null $reference
+		 */
+		public function setReference( $reference )
+		{
+			$this->reference = $reference;
+		}
+
+		/**
+		 * @param bool $afterInitialize
+		 *
+		 * @return null
+		 *
+		 */
+		public function getReference( $afterInitialize = false )
+		{
+			if ( $afterInitialize ) {
+				$reference = $this->response->data->reference;
+			} else {
+				$reference = $this->reference;
+			}
+
+			return $reference;
+		}
+
+		/**
+		 * To set callback URL, can be used to override callback URL set on paystack dashboard
+		 *
+		 * @param string $callbackUrl
+		 *
+		 * @return $this
+		 */
+		public function setCallbackUrl( $callbackUrl )
+		{
+			$this->_callbackUrl = $callbackUrl;
+
+			return $this;
+		}
+
+	}

+ 44 - 0
vendor/matscode/paystack/src/Utility/Debug.php

@@ -0,0 +1,44 @@
+<?php
+	/**
+	 *
+	 * Description
+	 *
+	 * @package        Paystack
+	 * @category       Source
+	 * @author         Michael Akanji <matscode@gmail.com>
+	 * @date           2017-06-27
+	 * @copyright (c)  2016 - 2017, TECRUM (http://www.tecrum.com)
+	 *
+	 */
+
+	namespace Matscode\Paystack\Utility;
+
+
+	class Debug
+	{
+		private
+		static
+			$openTag = '<pre style="overflow: auto; max-height: 70%; max-width: 95%; position: fixed; z-index: 9999; left: 15px; top: 15px; padding: 15px; background-color: #fcfcfc; border: solid 1px #aaa; line-height: 1.1rem;">',
+			$closeTag = '</pre>';
+
+		public static function printStr( $value )
+		{
+			echo self::$openTag .
+			     $value .
+			     self::$closeTag;
+		}
+
+		public static function print_r( $value )
+		{
+			echo self::$openTag .
+			     print_r( $value, true ) .
+			     self::$closeTag;
+		}
+
+		public static function var_dump( $value )
+		{
+			echo self::$openTag;
+			var_dump( $value );
+			echo self::$closeTag;
+		}
+	}

+ 24 - 0
vendor/matscode/paystack/src/Utility/Http.php

@@ -0,0 +1,24 @@
+<?php
+	/**
+	 *
+	 * Description
+	 *
+	 * @package        Paystack
+	 * @category       Source
+	 * @author         Michael Akanji <matscode@gmail.com>
+	 * @date           2017-06-27
+	 * @copyright (c)  2016 - 2017, TECRUM (http://www.tecrum.com)
+	 *
+	 */
+
+	namespace Matscode\Paystack\Utility;
+
+	class Http
+	{
+		public static function redirect( $location, $replace = true, $httpResponseCode = null )
+		{
+			// do a redirect
+			header( 'Location: ' . $location, $replace, $httpResponseCode );
+		}
+
+	}

+ 55 - 0
vendor/matscode/paystack/src/Utility/Text.php

@@ -0,0 +1,55 @@
+<?php
+	/**
+	 *
+	 * Description
+	 *
+	 * @package        Paystack
+	 * @category       Source
+	 * @author         Michael Akanji <matscode@gmail.com>
+	 * @date           2017-06-26
+	 * @copyright (c)  2016 - 2017, TECRUM (http://www.tecrum.com)
+	 *
+	 */
+
+	namespace Matscode\Paystack\Utility;
+
+
+	class Text
+	{
+		/**
+		 *
+		 * @author Hackan <hackan@gmail.com>
+		 * @link   https://php.net/manual/en/function.uniqid.php#120123
+		 *
+		 * @param int $length
+		 *
+		 * @param int $capsMix
+		 *
+		 * @return bool|string
+		 * @throws \Exception
+		 */
+		public static function uniqueRef( $length = 15, $capsMix = 5 )
+		{
+			// uniqid gives 15 chars, but you could adjust it to your needs.
+			if ( function_exists( "random_bytes" ) ) {
+				$bytes = random_bytes( ceil( $length / 2 ) );
+			} elseif ( function_exists( "openssl_random_pseudo_bytes" ) ) {
+				$bytes = openssl_random_pseudo_bytes( ceil( $length / 2 ) );
+			} else {
+				throw new \Exception( "No cryptographically secure random function available" );
+			}
+
+			if ( $capsMix > 10 ) {
+				throw new \Exception( 'capsMix can not be greater than 10' );
+			}
+			$caps = substr( str_shuffle( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ), 1, $capsMix );
+
+			return str_shuffle( substr( bin2hex( $bytes ), 0, $length ) . $caps );
+		}
+
+
+		public static function removeSlashes( $string )
+		{
+			return trim( $string, '/' );
+		}
+	}

+ 48 - 0
vendor/matscode/paystack/test/callback.php

@@ -0,0 +1,48 @@
+<?php
+	/**
+	 *
+	 * Description
+	 *
+	 * @package        Paystack
+	 * @category       Source
+	 * @author         Michael Akanji <matscode@gmail.com>
+	 * @date           2017-06-27
+	 * @copyright (c)  2016 - 2017, TECRUM (http://www.tecrum.com)
+	 *
+	 */
+	require_once "../vendor/autoload.php";
+
+	use Matscode\Paystack\Transaction;
+	use Matscode\Paystack\Utility\Debug;
+
+	$secretKey = 'sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
+
+	// creating the transaction object
+	$Transaction = new Transaction( $secretKey );
+
+	// transaction can be verified by doing manual check on the response Obj
+	/*
+	$response = $Transaction->verify();
+
+	Debug::print_r( $response);*/
+
+	// OR
+	$result = $Transaction->isSuccessful();
+
+	/*
+	Debug::print_r( $result);
+	*/
+
+	// To check if verified amount is the expected amount before giving value to customer
+
+	$result = $Transaction->amountEquals(5000); // amount in kobo
+
+	Debug::printStr( $result );
+
+
+	// Getting AuthorizationCode
+	/*
+	$authorizationCode = $Transaction->getAuthorizationCode();
+
+	Debug::print_r( $authorizationCode);
+	*/

+ 45 - 0
vendor/matscode/paystack/test/index.php

@@ -0,0 +1,45 @@
+<?php
+	/**
+	 *
+	 * Description
+	 *
+	 * @package        Paystack
+	 * @category       Source
+	 * @author         Michael Akanji <matscode@gmail.com>
+	 * @date           2017-06-26
+	 * @copyright (c)  2016 - 2017, TECRUM (http://www.tecrum.com)
+	 *
+	 */
+	require_once "../vendor/autoload.php";
+
+	use Matscode\Paystack\Transaction;
+	use Matscode\Paystack\Utility\Debug;
+	use Matscode\Paystack\Utility\Http;
+
+	$secretKey = 'sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
+
+	// creating the transaction object
+	$Transaction = new Transaction( $secretKey );
+
+	// Set data to post using this method
+	/*
+	$response = $Transaction->initialize( [
+		'email'  => 'customer@email.com',
+		'amount' => 500000
+	] );
+	*/
+
+	// OR
+
+	$response =
+		$Transaction
+			->setEmail( 'matscode@gmail.com' )
+			->setAmount( 50 )
+			->initialize();
+
+	// print response
+	 Debug::print_r( $response );
+
+	// save reference somewhere and do a redirect
+	 Http::redirect($response->authorizationUrl);
+

+ 6 - 0
vendor/nategood/httpful/.gitignore

@@ -0,0 +1,6 @@
+.DS_Store
+composer.lock
+vendor
+downloads
+.idea/*
+tests/.phpunit.result.cache

+ 12 - 0
vendor/nategood/httpful/.travis.yml

@@ -0,0 +1,12 @@
+language: php
+
+php:
+  - 7.2
+  - 7.3
+  - 7.4
+
+matrix:
+  fast_finish: true
+
+script:
+  - phpunit -c ./tests/phpunit.xml

+ 7 - 0
vendor/nategood/httpful/LICENSE.txt

@@ -0,0 +1,7 @@
+Copyright (c) 2012 Nate Good <me@nategood.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 226 - 0
vendor/nategood/httpful/README.md

@@ -0,0 +1,226 @@
+# Httpful
+
+[![Build Status](https://secure.travis-ci.org/nategood/httpful.png?branch=master)](http://travis-ci.org/nategood/httpful) [![Total Downloads](https://poser.pugx.org/nategood/httpful/downloads.png)](https://packagist.org/packages/nategood/httpful)
+
+Httpful is a simple Http Client library for PHP 7.2+.  There is an emphasis of readability, simplicity, and flexibility – basically provide the features and flexibility to get the job done and make those features really easy to use.
+
+Features
+
+ - Readable HTTP Method Support (GET, PUT, POST, DELETE, HEAD, PATCH and OPTIONS)
+ - Custom Headers
+ - Automatic "Smart" Parsing
+ - Automatic Payload Serialization
+ - Basic Auth
+ - Client Side Certificate Auth
+ - Request "Templates"
+
+# Sneak Peak
+
+Here's something to whet your appetite.  Search the twitter API for tweets containing "#PHP".  Include a trivial header for the heck of it.  Notice that the library automatically interprets the response as JSON (can override this if desired) and parses it as an array of objects.
+
+```php
+
+// Make a request to the GitHub API with a custom
+// header of "X-Trvial-Header: Just as a demo".
+$url = "https://api.github.com/users/nategood";
+$response = \Httpful\Request::get($url)
+    ->expectsJson()
+    ->withXTrivialHeader('Just as a demo')
+    ->send();
+
+echo "{$response->body->name} joined GitHub on " .
+                        date('M jS', strtotime($response->body->created_at)) ."\n";
+```
+
+# Installation
+
+## Composer
+
+Httpful is PSR-0 compliant and can be installed using [composer](http://getcomposer.org/).  Simply add `nategood/httpful` to your composer.json file.  _Composer is the sane alternative to PEAR.  It is excellent for managing dependencies in larger projects_.
+
+    {
+        "require": {
+            "nategood/httpful": "*"
+        }
+    }
+
+## Install from Source
+
+Because Httpful is PSR-0 compliant, you can also just clone the Httpful repository and use a PSR-0 compatible autoloader to load the library, like [Symfony's](http://symfony.com/doc/current/components/class_loader.html). Alternatively you can use the PSR-0 compliant autoloader included with the Httpful (simply `require("bootstrap.php")`).
+
+## Build your Phar
+
+If you want the build your own [Phar Archive](http://php.net/manual/en/book.phar.php) you can use the `build` script included.
+Make sure that your `php.ini` has the *Off* or 0 value for the `phar.readonly` setting.
+Also you need to create an empty `downloads` directory in the project root.
+
+# Contributing
+
+Httpful highly encourages sending in pull requests.  When submitting a pull request please:
+
+ - All pull requests should target the `dev` branch (not `master`)
+ - Make sure your code follows the [coding conventions](http://pear.php.net/manual/en/standards.php)
+ - Please use soft tabs (four spaces) instead of hard tabs
+ - Make sure you add appropriate test coverage for your changes
+ - Run all unit tests in the test directory via `phpunit ./tests`
+ - Include commenting where appropriate and add a descriptive pull request message
+
+# Changelog
+
+## 0.3.2
+
+ - REFACTOR [PR #276](https://github.com/nategood/httpful/pull/276) Add properly subclassed, more descriptive Exceptions for JSON parse errors
+
+## 0.3.1
+
+ - FIX [PR #286](https://github.com/nategood/httpful/pull/286) Fixed header case sensitivity
+
+## 0.3.0
+
+ - REFACTOR Dropped support for dead versions of PHP. Updated the PHPUnit tests.
+
+## 0.2.20
+
+ - MINOR Move Response building logic into separate function [PR #193](https://github.com/nategood/httpful/pull/193)
+
+## 0.2.19
+
+ - FEATURE Before send hook [PR #164](https://github.com/nategood/httpful/pull/164)
+ - MINOR More descriptive connection exceptions [PR #166](https://github.com/nategood/httpful/pull/166)
+
+## 0.2.18
+
+ - FIX [PR #149](https://github.com/nategood/httpful/pull/149)
+ - FIX [PR #150](https://github.com/nategood/httpful/pull/150)
+ - FIX [PR #156](https://github.com/nategood/httpful/pull/156)
+
+## 0.2.17
+
+ - FEATURE [PR #144](https://github.com/nategood/httpful/pull/144) Adds additional parameter to the Response class to specify additional meta data about the request/response (e.g. number of redirect).
+
+## 0.2.16
+
+ - FEATURE Added support for whenError to define a custom callback to be fired upon error. Useful for logging or overriding the default error_log behavior.
+
+## 0.2.15
+
+ - FEATURE [I #131](https://github.com/nategood/httpful/pull/131) Support for SOCKS proxy
+
+## 0.2.14
+
+ - FEATURE [I #138](https://github.com/nategood/httpful/pull/138) Added alternative option for XML request construction. In the next major release this will likely supplant the older version.
+
+## 0.2.13
+
+ - REFACTOR [I #121](https://github.com/nategood/httpful/pull/121) Throw more descriptive exception on curl errors
+ - REFACTOR [I #122](https://github.com/nategood/httpful/issues/122) Better proxy scrubbing in Request
+ - REFACTOR [I #119](https://github.com/nategood/httpful/issues/119) Better document the mimeType param on Request::body
+ - Misc code and test cleanup
+
+## 0.2.12
+
+ - REFACTOR [I #123](https://github.com/nategood/httpful/pull/123) Support new curl file upload method
+ - FEATURE [I #118](https://github.com/nategood/httpful/pull/118) 5.4 HTTP Test Server
+ - FIX [I #109](https://github.com/nategood/httpful/pull/109) Typo
+ - FIX [I #103](https://github.com/nategood/httpful/pull/103)  Handle also CURLOPT_SSL_VERIFYHOST for strictSsl mode
+
+## 0.2.11
+
+ - FIX [I #99](https://github.com/nategood/httpful/pull/99) Prevent hanging on HEAD requests
+
+## 0.2.10
+
+ - FIX [I #93](https://github.com/nategood/httpful/pull/86) Fixes edge case where content-length would be set incorrectly
+
+## 0.2.9
+
+ - FEATURE [I #89](https://github.com/nategood/httpful/pull/89) multipart/form-data support (a.k.a. file uploads)! Thanks @dtelaroli!
+
+## 0.2.8
+
+ - FIX Notice fix for Pull Request 86
+
+## 0.2.7
+
+ - FIX [I #86](https://github.com/nategood/httpful/pull/86) Remove Connection Established header when using a proxy
+
+## 0.2.6
+
+ - FIX [I #85](https://github.com/nategood/httpful/issues/85) Empty Content Length issue resolved
+
+## 0.2.5
+
+ - FEATURE [I #80](https://github.com/nategood/httpful/issues/80) [I #81](https://github.com/nategood/httpful/issues/81) Proxy support added with `useProxy` method.
+
+## 0.2.4
+
+ - FEATURE [I #77](https://github.com/nategood/httpful/issues/77) Convenience method for setting a timeout (seconds) `$req->timeoutIn(10);`
+ - FIX [I #75](https://github.com/nategood/httpful/issues/75) [I #78](https://github.com/nategood/httpful/issues/78) Bug with checking if digest auth is being used.
+
+## 0.2.3
+
+ - FIX Overriding default Mime Handlers
+ - FIX [PR #73](https://github.com/nategood/httpful/pull/73) Parsing http status codes
+
+## 0.2.2
+
+ - FEATURE Add support for parsing JSON responses as associative arrays instead of objects
+ - FEATURE Better support for setting constructor arguments on Mime Handlers
+
+## 0.2.1
+
+ - FEATURE [PR #72](https://github.com/nategood/httpful/pull/72) Allow support for custom Accept header
+
+## 0.2.0
+
+ - REFACTOR [PR #49](https://github.com/nategood/httpful/pull/49) Broke headers out into their own class
+ - REFACTOR [PR #54](https://github.com/nategood/httpful/pull/54) Added more specific Exceptions
+ - FIX [PR #58](https://github.com/nategood/httpful/pull/58) Fixes throwing an error on an empty xml response
+ - FEATURE [PR #57](https://github.com/nategood/httpful/pull/57) Adds support for digest authentication
+
+## 0.1.6
+
+ - Ability to set the number of max redirects via overloading `followRedirects(int max_redirects)`
+ - Standards Compliant fix to `Accepts` header
+ - Bug fix for bootstrap process when installed via Composer
+
+## 0.1.5
+
+ - Use `DIRECTORY_SEPARATOR` constant [PR #33](https://github.com/nategood/httpful/pull/32)
+ - [PR #35](https://github.com/nategood/httpful/pull/35)
+ - Added the raw\_headers property reference to response.
+ - Compose request header and added raw\_header to Request object.
+ - Fixed response has errors and added more comments for clarity.
+ - Fixed header parsing to allow the minimum (status line only) and also cater for the actual CRLF ended headers as per RFC2616.
+ - Added the perfect test Accept: header for all Acceptable scenarios see  @b78e9e82cd9614fbe137c01bde9439c4e16ca323 for details.
+ - Added default User-Agent header
+  - `User-Agent: Httpful/0.1.5` + curl version + server software + PHP version
+ - To bypass this "default" operation simply add a User-Agent to the request headers even a blank User-Agent is sufficient and more than simple enough to produce me thinks.
+ - Completed test units for additions.
+ - Added phpunit coverage reporting and helped phpunit auto locate the tests a bit easier.
+
+## 0.1.4
+
+ - Add support for CSV Handling [PR #32](https://github.com/nategood/httpful/pull/32)
+
+## 0.1.3
+
+ - Handle empty responses in JsonParser and XmlParser
+
+## 0.1.2
+
+ - Added support for setting XMLHandler configuration options
+ - Added examples for overriding XmlHandler and registering a custom parser
+ - Removed the httpful.php download (deprecated in favor of httpful.phar)
+
+## 0.1.1
+
+ - Bug fix serialization default case and phpunit tests
+
+## 0.1.0
+
+ - Added Support for Registering Mime Handlers
+  - Created AbstractMimeHandler type that all Mime Handlers must extend
+  - Pulled out the parsing/serializing logic from the Request/Response classes into their own MimeHandler classes
+  - Added ability to register new mime handlers for mime types
+

+ 4 - 0
vendor/nategood/httpful/bootstrap.php

@@ -0,0 +1,4 @@
+<?php
+
+require(__DIR__ . '/src/Httpful/Bootstrap.php');
+\Httpful\Bootstrap::init();

+ 51 - 0
vendor/nategood/httpful/build

@@ -0,0 +1,51 @@
+#!/usr/bin/env php
+<?php
+
+/**
+ * Build the whole library into a single file
+ * as an easy drop in solution as opposed to
+ * relying on autoloader.  Sometimes we just
+ * want to hack with an API as a one off thing.
+ * Httpful should make this easy.
+ */
+
+function exit_unless($condition, $msg = null) {
+    if ($condition)
+        return;
+    echo "[FAIL]\n$msg\n";
+    exit(1);
+}
+
+// Create the Httpful Phar
+echo "Building Phar... ";
+$base_dir = dirname(__FILE__);
+$source_dir = $base_dir . '/src/Httpful/';
+$phar_path = $base_dir . '/downloads/httpful.phar';
+$phar = new Phar($phar_path, 0, 'httpful.phar');
+$stub = <<<HEREDOC
+<?php
+    // Phar Stub File
+    Phar::mapPhar('httpful.phar');
+    include('phar://httpful.phar/Httpful/Bootstrap.php');
+    \Httpful\Bootstrap::pharInit();
+
+    __HALT_COMPILER();
+HEREDOC;
+try {
+    $phar->setStub($stub);
+} catch(Exception $e) {
+    $phar = false;
+}
+exit_unless($phar, "Unable to create a phar.  Make certain you have phar.readonly=0 set in your ini file.");
+$phar->buildFromDirectory(dirname($source_dir));
+echo "[ OK ]\n";
+
+
+
+// Add it to git!
+//echo "Adding httpful.phar to the repo... ";
+//$return_code = 0;
+//passthru("git add $phar_path", $return_code);
+//exit_unless($return_code === 0, "Unable to add download files to git.");
+//echo "[ OK ]\n";
+echo "\nBuild completed successfully.\n\n";

+ 27 - 0
vendor/nategood/httpful/composer.json

@@ -0,0 +1,27 @@
+{
+    "name": "nategood/httpful",
+    "description": "A Readable, Chainable, REST friendly, PHP HTTP Client",
+    "homepage": "http://github.com/nategood/httpful",
+    "license": "MIT",
+    "keywords": ["http", "curl", "rest", "restful", "api", "requests"],
+    "version": "0.3.2",
+    "authors": [
+        {
+            "name": "Nate Good",
+            "email": "me@nategood.com",
+            "homepage": "http://nategood.com"
+        }
+    ],
+    "require": {
+        "php": ">=7.2",
+        "ext-curl": "*"
+    },
+    "autoload": {
+        "psr-0": {
+            "Httpful": "src/"
+        }
+    },
+    "require-dev": {
+        "phpunit/phpunit": "*"
+    }
+}

+ 12 - 0
vendor/nategood/httpful/examples/freebase.php

@@ -0,0 +1,12 @@
+<?php
+/**
+ * Grab some The Dead Weather albums from Freebase
+ */
+require(__DIR__ . '/../bootstrap.php');
+
+$uri = "https://www.googleapis.com/freebase/v1/mqlread?query=%7B%22type%22:%22/music/artist%22%2C%22name%22:%22The%20Dead%20Weather%22%2C%22album%22:%5B%5D%7D";
+$response = \Httpful\Request::get($uri)
+    ->expectsJson()
+    ->sendIt();
+
+echo 'The Dead Weather has ' . count($response->body->result->album) . " albums.\n";

+ 9 - 0
vendor/nategood/httpful/examples/github.php

@@ -0,0 +1,9 @@
+<?php
+// XML Example from GitHub
+require(__DIR__ . '/../bootstrap.php');
+use \Httpful\Request;
+
+$uri = 'https://github.com/api/v2/xml/user/show/nategood';
+$request = Request::get($uri)->send();
+
+echo "{$request->body->name} joined GitHub on " . date('M jS', strtotime($request->body->{'created-at'})) ."\n";

+ 44 - 0
vendor/nategood/httpful/examples/override.php

@@ -0,0 +1,44 @@
+<?php
+require(__DIR__ . '/../bootstrap.php');
+
+// We can override the default parser configuration options be registering
+// a parser with different configuration options for a particular mime type
+
+// Example setting a namespace for the XMLHandler parser
+$conf = array('namespace' => 'http://example.com');
+\Httpful\Httpful::register(\Httpful\Mime::XML, new \Httpful\Handlers\XmlHandler($conf));
+
+// We can also add the parsers with our own...
+class SimpleCsvHandler extends \Httpful\Handlers\MimeHandlerAdapter
+{
+    /**
+     * Takes a response body, and turns it into
+     * a two dimensional array.
+     *
+     * @param string $body
+     * @return mixed
+     */
+    public function parse($body)
+    {
+        return str_getcsv($body);
+    }
+
+    /**
+     * Takes a two dimensional array and turns it
+     * into a serialized string to include as the
+     * body of a request
+     *
+     * @param mixed $payload
+     * @return string
+     */
+    public function serialize($payload)
+    {
+        $serialized = '';
+        foreach ($payload as $line) {
+            $serialized .= '"' . implode('","', $line) . '"' . "\n";
+        }
+        return $serialized;
+    }
+}
+
+\Httpful\Httpful::register('text/csv', new SimpleCsvHandler());

+ 24 - 0
vendor/nategood/httpful/examples/showclix.php

@@ -0,0 +1,24 @@
+<?php
+
+require(__DIR__ . '/../bootstrap.php');
+
+use \Httpful\Request;
+
+// Get event details for a public event
+$uri = "http://api.showclix.com/Event/8175";
+$response = Request::get($uri)
+    ->expectsType('json')
+    ->send();
+
+// Print out the event details
+echo "The event {$response->body->event} will take place on {$response->body->event_start}\n";
+
+// Example overriding the default JSON handler with one that encodes the response as an array
+\Httpful\Httpful::register(\Httpful\Mime::JSON, new \Httpful\Handlers\JsonHandler(array('decode_as_array' => true)));
+
+$response = Request::get($uri)
+    ->expectsType('json')
+    ->send();
+
+// Print out the event details
+echo "The event {$response->body['event']} will take place on {$response->body['event_start']}\n";

+ 97 - 0
vendor/nategood/httpful/src/Httpful/Bootstrap.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace Httpful;
+
+/**
+ * Bootstrap class that facilitates autoloading.  A naive
+ * PSR-0 autoloader.
+ *
+ * @author Nate Good <me@nategood.com>
+ */
+class Bootstrap
+{
+
+    const DIR_GLUE = DIRECTORY_SEPARATOR;
+    const NS_GLUE = '\\';
+
+    public static $registered = false;
+
+    /**
+     * Register the autoloader and any other setup needed
+     */
+    public static function init()
+    {
+        spl_autoload_register(array('\Httpful\Bootstrap', 'autoload'));
+        self::registerHandlers();
+    }
+
+    /**
+     * The autoload magic (PSR-0 style)
+     *
+     * @param string $classname
+     */
+    public static function autoload($classname)
+    {
+        self::_autoload(dirname(dirname(__FILE__)), $classname);
+    }
+
+    /**
+     * Register the autoloader and any other setup needed
+     */
+    public static function pharInit()
+    {
+        spl_autoload_register(array('\Httpful\Bootstrap', 'pharAutoload'));
+        self::registerHandlers();
+    }
+
+    /**
+     * Phar specific autoloader
+     *
+     * @param string $classname
+     */
+    public static function pharAutoload($classname)
+    {
+        self::_autoload('phar://httpful.phar', $classname);
+    }
+
+    /**
+     * @param string $base
+     * @param string $classname
+     */
+    private static function _autoload($base, $classname)
+    {
+        $parts      = explode(self::NS_GLUE, $classname);
+        $path       = $base . self::DIR_GLUE . implode(self::DIR_GLUE, $parts) . '.php';
+
+        if (file_exists($path)) {
+            require_once($path);
+        }
+    }
+    /**
+     * Register default mime handlers.  Is idempotent.
+     */
+    public static function registerHandlers()
+    {
+        if (self::$registered === true) {
+            return;
+        }
+
+        // @todo check a conf file to load from that instead of
+        // hardcoding into the library?
+        $handlers = array(
+            \Httpful\Mime::JSON => new \Httpful\Handlers\JsonHandler(),
+            \Httpful\Mime::XML  => new \Httpful\Handlers\XmlHandler(),
+            \Httpful\Mime::FORM => new \Httpful\Handlers\FormHandler(),
+            \Httpful\Mime::CSV  => new \Httpful\Handlers\CsvHandler(),
+        );
+
+        foreach ($handlers as $mime => $handler) {
+            // Don't overwrite if the handler has already been registered
+            if (Httpful::hasParserRegistered($mime))
+                continue;
+            Httpful::register($mime, $handler);
+        }
+
+        self::$registered = true;
+    }
+}

+ 54 - 0
vendor/nategood/httpful/src/Httpful/Exception/ConnectionErrorException.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace Httpful\Exception;
+
+
+class ConnectionErrorException extends \Exception {
+
+
+	/**
+	 * @var string
+	 */
+	private $curlErrorNumber;
+
+	/**
+	 * @var string
+	 */
+	private $curlErrorString;
+
+	/**
+	 * @return string
+	 */
+	public function getCurlErrorNumber() {
+		return $this->curlErrorNumber;
+	}
+
+	/**
+	 * @param string $curlErrorNumber
+	 * @return $this
+	 */
+	public function setCurlErrorNumber($curlErrorNumber) {
+		$this->curlErrorNumber = $curlErrorNumber;
+
+		return $this;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getCurlErrorString() {
+		return $this->curlErrorString;
+	}
+
+	/**
+	 * @param string $curlErrorString
+	 * @return $this
+	 */
+	public function setCurlErrorString($curlErrorString) {
+		$this->curlErrorString = $curlErrorString;
+
+		return $this;
+	}
+
+
+}

+ 7 - 0
vendor/nategood/httpful/src/Httpful/Exception/JsonParseException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Httpful\Exception;
+
+class JsonParseException extends \Exception
+{
+}

+ 51 - 0
vendor/nategood/httpful/src/Httpful/Handlers/CsvHandler.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Mime Type: text/csv
+ * @author Raja Kapur <rajak@twistedthrottle.com>
+ */
+
+namespace Httpful\Handlers;
+
+class CsvHandler extends MimeHandlerAdapter
+{
+    /**
+     * @param string $body
+     * @return mixed
+     * @throws \Exception
+     */
+    public function parse($body)
+    {
+        if (empty($body))
+            return null;
+
+        $parsed = array();
+        $fp = fopen('data://text/plain;base64,' . base64_encode($body), 'r');
+        while (($r = fgetcsv($fp)) !== FALSE) {
+            $parsed[] = $r;
+        }
+
+        if (empty($parsed))
+            throw new \Exception("Unable to parse response as CSV");
+        return $parsed;
+    }
+
+    /**
+     * @param mixed $payload
+     * @return string
+     */
+    public function serialize($payload)
+    {
+        $fp = fopen('php://temp/maxmemory:'. (6*1024*1024), 'r+');
+        $i = 0;
+        foreach ($payload as $fields) {
+            if($i++ == 0) {
+                fputcsv($fp, array_keys($fields));
+            }
+            fputcsv($fp, $fields);
+        }
+        rewind($fp);
+        $data = stream_get_contents($fp);
+        fclose($fp);
+        return $data;
+    }
+}

+ 30 - 0
vendor/nategood/httpful/src/Httpful/Handlers/FormHandler.php

@@ -0,0 +1,30 @@
+<?php
+/**
+ * Mime Type: application/x-www-urlencoded
+ * @author Nathan Good <me@nategood.com>
+ */
+
+namespace Httpful\Handlers;
+
+class FormHandler extends MimeHandlerAdapter 
+{
+    /**
+     * @param string $body
+     * @return mixed
+     */
+    public function parse($body)
+    {
+        $parsed = array();
+        parse_str($body, $parsed);
+        return $parsed;
+    }
+    
+    /**
+     * @param mixed $payload
+     * @return string
+     */
+    public function serialize($payload)
+    {
+        return http_build_query($payload, null, '&');
+    }
+}

+ 44 - 0
vendor/nategood/httpful/src/Httpful/Handlers/JsonHandler.php

@@ -0,0 +1,44 @@
+<?php
+/**
+ * Mime Type: application/json
+ * @author Nathan Good <me@nategood.com>
+ */
+
+namespace Httpful\Handlers;
+
+use Httpful\Exception\JsonParseException;
+
+class JsonHandler extends MimeHandlerAdapter
+{
+    private $decode_as_array = false;
+
+    public function init(array $args)
+    {
+        $this->decode_as_array = !!(array_key_exists('decode_as_array', $args) ? $args['decode_as_array'] : false);
+    }
+
+    /**
+     * @param string $body
+     * @return mixed
+     * @throws \Exception
+     */
+    public function parse($body)
+    {
+        $body = $this->stripBom($body);
+        if (empty($body))
+            return null;
+        $parsed = json_decode($body, $this->decode_as_array);
+        if (is_null($parsed) && 'null' !== strtolower($body))
+            throw new JsonParseException('Unable to parse response as JSON: ' . json_last_error_msg());
+        return $parsed;
+    }
+
+    /**
+     * @param mixed $payload
+     * @return string
+     */
+    public function serialize($payload)
+    {
+        return json_encode($payload);
+    }
+}

+ 54 - 0
vendor/nategood/httpful/src/Httpful/Handlers/MimeHandlerAdapter.php

@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * Handlers are used to parse and serialize payloads for specific
+ * mime types.  You can register a custom handler via the register
+ * method.  You can also override a default parser in this way.
+ */
+
+namespace Httpful\Handlers;
+
+class MimeHandlerAdapter
+{
+    public function __construct(array $args = array())
+    {
+        $this->init($args);
+    }
+
+    /**
+     * Initial setup of
+     * @param array $args
+     */
+    public function init(array $args)
+    {
+    }
+
+    /**
+     * @param string $body
+     * @return mixed
+     */
+    public function parse($body)
+    {
+        return $body;
+    }
+
+    /**
+     * @param mixed $payload
+     * @return string
+     */
+    function serialize($payload)
+    {
+        return (string) $payload;
+    }
+
+    protected function stripBom($body)
+    {
+        if ( substr($body,0,3) === "\xef\xbb\xbf" )  // UTF-8
+            $body = substr($body,3);
+        else if ( substr($body,0,4) === "\xff\xfe\x00\x00" || substr($body,0,4) === "\x00\x00\xfe\xff" )  // UTF-32
+            $body = substr($body,4);
+        else if ( substr($body,0,2) === "\xff\xfe" || substr($body,0,2) === "\xfe\xff" )  // UTF-16
+            $body = substr($body,2);
+        return $body;
+    }
+}

+ 44 - 0
vendor/nategood/httpful/src/Httpful/Handlers/README.md

@@ -0,0 +1,44 @@
+# Handlers
+
+Handlers are simple classes that are used to parse response bodies and serialize request payloads.  All Handlers must extend the `MimeHandlerAdapter` class and implement two methods: `serialize($payload)` and `parse($response)`.  Let's build a very basic Handler to register for the `text/csv` mime type.
+
+    <?php
+
+    class SimpleCsvHandler extends \Httpful\Handlers\MimeHandlerAdapter
+    {
+        /**
+         * Takes a response body, and turns it into 
+         * a two dimensional array.
+         *
+         * @param string $body
+         * @return mixed
+         */
+        public function parse($body)
+        {
+            return str_getcsv($body);
+        }
+    
+        /**
+         * Takes a two dimensional array and turns it
+         * into a serialized string to include as the 
+         * body of a request
+         *
+         * @param mixed $payload
+         * @return string
+         */
+        public function serialize($payload)
+        {
+            $serialized = '';
+            foreach ($payload as $line) {
+                $serialized .= '"' . implode('","', $line) . '"' . "\n";
+            }
+            return $serialized;
+        }
+    }
+
+
+Finally, you must register this handler for a particular mime type.
+
+    Httpful::register('text/csv', new SimpleCsvHandler());
+
+After this registering the handler in your source code, by default, any responses with a mime type of text/csv should be parsed by this handler.

+ 15 - 0
vendor/nategood/httpful/src/Httpful/Handlers/XHtmlHandler.php

@@ -0,0 +1,15 @@
+<?php
+/**
+ * Mime Type: text/html
+ * Mime Type: application/html+xml
+ *
+ * @author Nathan Good <me@nategood.com>
+ */
+
+namespace Httpful\Handlers;
+
+class XHtmlHandler extends MimeHandlerAdapter
+{
+    // @todo add html specific parsing
+    // see DomDocument::load http://docs.php.net/manual/en/domdocument.loadhtml.php
+}

+ 152 - 0
vendor/nategood/httpful/src/Httpful/Handlers/XmlHandler.php

@@ -0,0 +1,152 @@
+<?php
+/**
+ * Mime Type: application/xml
+ *
+ * @author Zack Douglas <zack@zackerydouglas.info>
+ * @author Nathan Good <me@nategood.com>
+ */
+
+namespace Httpful\Handlers;
+
+class XmlHandler extends MimeHandlerAdapter
+{
+    /**
+     *  @var string $namespace xml namespace to use with simple_load_string
+     */
+    private $namespace;
+
+    /**
+     * @var int $libxml_opts see http://www.php.net/manual/en/libxml.constants.php
+     */
+    private $libxml_opts;
+
+    /**
+     * @param array $conf sets configuration options
+     */
+    public function __construct(array $conf = array())
+    {
+        $this->namespace =      isset($conf['namespace']) ? $conf['namespace'] : '';
+        $this->libxml_opts =    isset($conf['libxml_opts']) ? $conf['libxml_opts'] : 0;
+    }
+
+    /**
+     * @param string $body
+     * @return mixed
+     * @throws \Exception if unable to parse
+     */
+    public function parse($body)
+    {
+        $body = $this->stripBom($body);
+        if (empty($body))
+            return null;
+        $parsed = simplexml_load_string($body, null, $this->libxml_opts, $this->namespace);
+        if ($parsed === false)
+            throw new \Exception("Unable to parse response as XML");
+        return $parsed;
+    }
+
+    /**
+     * @param mixed $payload
+     * @return string
+     * @throws \Exception if unable to serialize
+     */
+    public function serialize($payload)
+    {
+        list($_, $dom) = $this->_future_serializeAsXml($payload);
+        return $dom->saveXml();
+    }
+
+    /**
+     * @param mixed $payload
+     * @return string
+     * @author Ted Zellers
+     */
+    public function serialize_clean($payload)
+    {
+        $xml = new \XMLWriter;
+        $xml->openMemory();
+        $xml->startDocument('1.0','ISO-8859-1');
+        $this->serialize_node($xml, $payload);
+        return $xml->outputMemory(true);
+    }
+
+    /**
+     * @param \XMLWriter $xmlw
+     * @param mixed $node to serialize
+     * @author Ted Zellers
+     */
+    public function serialize_node(&$xmlw, $node){
+        if (!is_array($node)){
+            $xmlw->text($node);
+        } else {
+            foreach ($node as $k => $v){
+                $xmlw->startElement($k);
+                    $this->serialize_node($xmlw, $v);
+                $xmlw->endElement();
+            }
+        }
+    }
+
+    /**
+     * @author Zack Douglas <zack@zackerydouglas.info>
+     */
+    private function _future_serializeAsXml($value, $node = null, $dom = null)
+    {
+        if (!$dom) {
+            $dom = new \DOMDocument;
+        }
+        if (!$node) {
+            if (!is_object($value)) {
+                $node = $dom->createElement('response');
+                $dom->appendChild($node);
+            } else {
+                $node = $dom;
+            }
+        }
+        if (is_object($value)) {
+            $objNode = $dom->createElement(get_class($value));
+            $node->appendChild($objNode);
+            $this->_future_serializeObjectAsXml($value, $objNode, $dom);
+        } else if (is_array($value)) {
+            $arrNode = $dom->createElement('array');
+            $node->appendChild($arrNode);
+            $this->_future_serializeArrayAsXml($value, $arrNode, $dom);
+        } else if (is_bool($value)) {
+            $node->appendChild($dom->createTextNode($value?'TRUE':'FALSE'));
+        } else {
+            $node->appendChild($dom->createTextNode($value));
+        }
+        return array($node, $dom);
+    }
+    /**
+     * @author Zack Douglas <zack@zackerydouglas.info>
+     */
+    private function _future_serializeArrayAsXml($value, &$parent, &$dom)
+    {
+        foreach ($value as $k => &$v) {
+            $n = $k;
+            if (is_numeric($k)) {
+                $n = "child-{$n}";
+            }
+            $el = $dom->createElement($n);
+            $parent->appendChild($el);
+            $this->_future_serializeAsXml($v, $el, $dom);
+        }
+        return array($parent, $dom);
+    }
+    /**
+     * @author Zack Douglas <zack@zackerydouglas.info>
+     */
+    private function _future_serializeObjectAsXml($value, &$parent, &$dom)
+    {
+        $refl = new \ReflectionObject($value);
+        foreach ($refl->getProperties() as $pr) {
+            if (!$pr->isPrivate()) {
+                $el = $dom->createElement($pr->getName());
+                $parent->appendChild($el);
+                $this->_future_serializeAsXml($pr->getValue($value), $el, $dom);
+            }
+        }
+        return array($parent, $dom);
+    }
+}

+ 86 - 0
vendor/nategood/httpful/src/Httpful/Http.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace Httpful;
+
+/**
+ * @author Nate Good <me@nategood.com>
+ */
+class Http
+{
+    const HEAD      = 'HEAD';
+    const GET       = 'GET';
+    const POST      = 'POST';
+    const PUT       = 'PUT';
+    const DELETE    = 'DELETE';
+    const PATCH     = 'PATCH';
+    const OPTIONS   = 'OPTIONS';
+    const TRACE     = 'TRACE';
+
+    /**
+     * @return array of HTTP method strings
+     */
+    public static function safeMethods()
+    {
+        return array(self::HEAD, self::GET, self::OPTIONS, self::TRACE);
+    }
+
+    /**
+     * @param string HTTP method
+     * @return bool
+     */
+    public static function isSafeMethod($method)
+    {
+        return in_array($method, self::safeMethods());
+    }
+
+    /**
+     * @param string HTTP method
+     * @return bool
+     */
+    public static function isUnsafeMethod($method)
+    {
+        return !in_array($method, self::safeMethods());
+    }
+
+    /**
+     * @return array list of (always) idempotent HTTP methods
+     */
+    public static function idempotentMethods()
+    {
+        // Though it is possible to be idempotent, POST
+        // is not guarunteed to be, and more often than
+        // not, it is not.
+        return array(self::HEAD, self::GET, self::PUT, self::DELETE, self::OPTIONS, self::TRACE, self::PATCH);
+    }
+
+    /**
+     * @param string HTTP method
+     * @return bool
+     */
+    public static function isIdempotent($method)
+    {
+        return in_array($method, self::safeidempotentMethodsMethods());
+    }
+
+    /**
+     * @param string HTTP method
+     * @return bool
+     */
+    public static function isNotIdempotent($method)
+    {
+        return !in_array($method, self::idempotentMethods());
+    }
+
+    /**
+     * @deprecated Technically anything *can* have a body,
+     * they just don't have semantic meaning.  So say's Roy
+     * http://tech.groups.yahoo.com/group/rest-discuss/message/9962
+     *
+     * @return array of HTTP method strings
+     */
+    public static function canHaveBody()
+    {
+        return array(self::POST, self::PUT, self::PATCH, self::OPTIONS);
+    }
+
+}

+ 47 - 0
vendor/nategood/httpful/src/Httpful/Httpful.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace Httpful;
+
+class Httpful {
+    const VERSION = '0.3.0';
+
+    private static $mimeRegistrar = array();
+    private static $default = null;
+
+    /**
+     * @param string $mimeType
+     * @param \Httpful\Handlers\MimeHandlerAdapter $handler
+     */
+    public static function register($mimeType, \Httpful\Handlers\MimeHandlerAdapter $handler)
+    {
+        self::$mimeRegistrar[$mimeType] = $handler;
+    }
+
+    /**
+     * @param string $mimeType defaults to MimeHandlerAdapter
+     * @return \Httpful\Handlers\MimeHandlerAdapter
+     */
+    public static function get($mimeType = null)
+    {
+        if (isset(self::$mimeRegistrar[$mimeType])) {
+            return self::$mimeRegistrar[$mimeType];
+        }
+
+        if (empty(self::$default)) {
+            self::$default = new \Httpful\Handlers\MimeHandlerAdapter();
+        }
+
+        return self::$default;
+    }
+
+    /**
+     * Does this particular Mime Type have a parser registered
+     * for it?
+     * @param string $mimeType
+     * @return bool
+     */
+    public static function hasParserRegistered($mimeType)
+    {
+        return isset(self::$mimeRegistrar[$mimeType]);
+    }
+}

+ 60 - 0
vendor/nategood/httpful/src/Httpful/Mime.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace Httpful;
+
+/**
+ * Class to organize the Mime stuff a bit more
+ * @author Nate Good <me@nategood.com>
+ */
+class Mime
+{
+    const JSON    = 'application/json';
+    const XML     = 'application/xml';
+    const XHTML   = 'application/html+xml';
+    const FORM    = 'application/x-www-form-urlencoded';
+    const UPLOAD  = 'multipart/form-data';
+    const PLAIN   = 'text/plain';
+    const JS      = 'text/javascript';
+    const HTML    = 'text/html';
+    const YAML    = 'application/x-yaml';
+    const CSV     = 'text/csv';
+
+    /**
+     * Map short name for a mime type
+     * to a full proper mime type
+     */
+    public static $mimes = array(
+        'json'      => self::JSON,
+        'xml'       => self::XML,
+        'form'      => self::FORM,
+        'plain'     => self::PLAIN,
+        'text'      => self::PLAIN,
+        'upload'      => self::UPLOAD,
+        'html'      => self::HTML,
+        'xhtml'     => self::XHTML,
+        'js'        => self::JS,
+        'javascript'=> self::JS,
+        'yaml'      => self::YAML,
+        'csv'       => self::CSV,
+    );
+
+    /**
+     * Get the full Mime Type name from a "short name".
+     * Returns the short if no mapping was found.
+     * @param string $short_name common name for mime type (e.g. json)
+     * @return string full mime type (e.g. application/json)
+     */
+    public static function getFullMime($short_name)
+    {
+        return array_key_exists($short_name, self::$mimes) ? self::$mimes[$short_name] : $short_name;
+    }
+
+    /**
+     * @param string $short_name
+     * @return bool
+     */
+    public static function supportsMimeType($short_name)
+    {
+        return array_key_exists($short_name, self::$mimes);
+    }
+}

+ 16 - 0
vendor/nategood/httpful/src/Httpful/Proxy.php

@@ -0,0 +1,16 @@
+<?php
+namespace Httpful;
+
+if (!defined('CURLPROXY_SOCKS4')) {
+    define('CURLPROXY_SOCKS4', 4);
+}
+
+/**
+ * Class to organize the Proxy stuff a bit more
+ */
+class Proxy
+{
+    const HTTP = CURLPROXY_HTTP;
+    const SOCKS4 = CURLPROXY_SOCKS4;
+    const SOCKS5 = CURLPROXY_SOCKS5;
+}

+ 1224 - 0
vendor/nategood/httpful/src/Httpful/Request.php

@@ -0,0 +1,1224 @@
+<?php
+
+namespace Httpful;
+
+use Httpful\Exception\ConnectionErrorException;
+
+/**
+ * Clean, simple class for sending HTTP requests
+ * in PHP.
+ *
+ * There is an emphasis of readability without loosing concise
+ * syntax.  As such, you will notice that the library lends
+ * itself very nicely to "chaining".  You will see several "alias"
+ * methods: more readable method definitions that wrap
+ * their more concise counterparts.  You will also notice
+ * no public constructor.  This two adds to the readability
+ * and "chainabilty" of the library.
+ *
+ * @author Nate Good <me@nategood.com>
+ * 
+ * @method self sendsJson()
+ * @method self sendsXml()
+ * @method self sendsForm()
+ * @method self sendsPlain()
+ * @method self sendsText()
+ * @method self sendsUpload()
+ * @method self sendsHtml()
+ * @method self sendsXhtml()
+ * @method self sendsJs()
+ * @method self sendsJavascript()
+ * @method self sendsYaml()
+ * @method self sendsCsv()
+ * @method self expectsJson()
+ * @method self expectsXml()
+ * @method self expectsForm()
+ * @method self expectsPlain()
+ * @method self expectsText()
+ * @method self expectsUpload()
+ * @method self expectsHtml()
+ * @method self expectsXhtml()
+ * @method self expectsJs()
+ * @method self expectsJavascript()
+ * @method self expectsYaml()
+ * @method self expectsCsv()
+ */
+class Request
+{
+
+    // Option constants
+    const SERIALIZE_PAYLOAD_NEVER   = 0;
+    const SERIALIZE_PAYLOAD_ALWAYS  = 1;
+    const SERIALIZE_PAYLOAD_SMART   = 2;
+
+    const MAX_REDIRECTS_DEFAULT     = 25;
+
+    public $uri,
+           $method                  = Http::GET,
+           $headers                 = array(),
+           $raw_headers             = '',
+           $strict_ssl              = false,
+           $content_type,
+           $expected_type,
+           $additional_curl_opts    = array(),
+           $auto_parse              = true,
+           $serialize_payload_method = self::SERIALIZE_PAYLOAD_SMART,
+           $username,
+           $password,
+           $serialized_payload,
+           $payload,
+           $parse_callback,
+           $error_callback,
+           $send_callback,
+           $follow_redirects        = false,
+           $max_redirects           = self::MAX_REDIRECTS_DEFAULT,
+           $payload_serializers     = array();
+
+    // Options
+    // private $_options = array(
+    //     'serialize_payload_method' => self::SERIALIZE_PAYLOAD_SMART
+    //     'auto_parse' => true
+    // );
+
+    // Curl Handle
+    public $_ch,
+           $_debug;
+
+    // Template Request object
+    private static $_template;
+
+    /**
+     * We made the constructor protected to force the factory style.  This was
+     * done to keep the syntax cleaner and better the support the idea of
+     * "default templates".  Very basic and flexible as it is only intended
+     * for internal use.
+     * @param array $attrs hash of initial attribute values
+     */
+    protected function __construct($attrs = null)
+    {
+        if (!is_array($attrs)) return;
+        foreach ($attrs as $attr => $value) {
+            $this->$attr = $value;
+        }
+    }
+
+    // Defaults Management
+
+    /**
+     * Let's you configure default settings for this
+     * class from a template Request object.  Simply construct a
+     * Request object as much as you want to and then pass it to
+     * this method.  It will then lock in those settings from
+     * that template object.
+     * The most common of which may be default mime
+     * settings or strict ssl settings.
+     * Again some slight memory overhead incurred here but in the grand
+     * scheme of things as it typically only occurs once
+     * @param Request $template
+     */
+    public static function ini(Request $template)
+    {
+        self::$_template = clone $template;
+    }
+
+    /**
+     * Reset the default template back to the
+     * library defaults.
+     */
+    public static function resetIni()
+    {
+        self::_initializeDefaults();
+    }
+
+    /**
+     * Get default for a value based on the template object
+     * @param string|null $attr Name of attribute (e.g. mime, headers)
+     *    if null just return the whole template object;
+     * @return mixed default value
+     */
+    public static function d($attr)
+    {
+        return isset($attr) ? self::$_template->$attr : self::$_template;
+    }
+
+    // Accessors
+
+    /**
+     * @return bool does the request have a timeout?
+     */
+    public function hasTimeout()
+    {
+        return isset($this->timeout);
+    }
+
+    /**
+     * @return bool has the internal curl request been initialized?
+     */
+    public function hasBeenInitialized()
+    {
+        return isset($this->_ch);
+    }
+
+    /**
+     * @return bool Is this request setup for basic auth?
+     */
+    public function hasBasicAuth()
+    {
+        return isset($this->password) && isset($this->username);
+    }
+
+    /**
+     * @return bool Is this request setup for digest auth?
+     */
+    public function hasDigestAuth()
+    {
+        return isset($this->password) && isset($this->username) && $this->additional_curl_opts[CURLOPT_HTTPAUTH] == CURLAUTH_DIGEST;
+    }
+
+    /**
+     * Specify a HTTP timeout
+     * @param float|int $timeout seconds to timeout the HTTP call
+     * @return Request
+     */
+    public function timeout($timeout)
+    {
+        $this->timeout = $timeout;
+        return $this;
+    }
+
+    // alias timeout
+    public function timeoutIn($seconds)
+    {
+        return $this->timeout($seconds);
+    }
+
+    /**
+     * If the response is a 301 or 302 redirect, automatically
+     * send off another request to that location
+     * @param bool|int $follow follow or not to follow or maximal number of redirects
+     * @return Request
+     */
+    public function followRedirects($follow = true)
+    {
+        $this->max_redirects = $follow === true ? self::MAX_REDIRECTS_DEFAULT : max(0, $follow);
+        $this->follow_redirects = (bool) $follow;
+        return $this;
+    }
+
+    /**
+     * @see Request::followRedirects()
+     * @return Request
+     */
+    public function doNotFollowRedirects()
+    {
+        return $this->followRedirects(false);
+    }
+
+    /**
+     * Actually send off the request, and parse the response
+     * @return Response with parsed results
+     * @throws ConnectionErrorException when unable to parse or communicate w server
+     */
+    public function send()
+    {
+        if (!$this->hasBeenInitialized())
+            $this->_curlPrep();
+
+        $result = curl_exec($this->_ch);
+
+        $response = $this->buildResponse($result);
+
+        curl_close($this->_ch);
+        unset($this->_ch);
+
+        return $response;
+    }
+    public function sendIt()
+    {
+        return $this->send();
+    }
+
+    // Setters
+
+    /**
+     * @param string $uri
+     * @return Request
+     */
+    public function uri($uri)
+    {
+        $this->uri = $uri;
+        return $this;
+    }
+
+    /**
+     * User Basic Auth.
+     * Only use when over SSL/TSL/HTTPS.
+     * @param string $username
+     * @param string $password
+     * @return Request
+     */
+    public function basicAuth($username, $password)
+    {
+        $this->username = $username;
+        $this->password = $password;
+        return $this;
+    }
+    // @alias of basicAuth
+    public function authenticateWith($username, $password)
+    {
+        return $this->basicAuth($username, $password);
+    }
+    // @alias of basicAuth
+    public function authenticateWithBasic($username, $password)
+    {
+        return $this->basicAuth($username, $password);
+    }
+
+    // @alias of ntlmAuth
+    public function authenticateWithNTLM($username, $password)
+    {
+        return $this->ntlmAuth($username, $password);
+    }
+
+    public function ntlmAuth($username, $password)
+    {
+        $this->addOnCurlOption(CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
+        return $this->basicAuth($username, $password);
+    }
+
+    /**
+     * User Digest Auth.
+     * @param string $username
+     * @param string $password
+     * @return Request
+     */
+    public function digestAuth($username, $password)
+    {
+        $this->addOnCurlOption(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
+        return $this->basicAuth($username, $password);
+    }
+
+    // @alias of digestAuth
+    public function authenticateWithDigest($username, $password)
+    {
+        return $this->digestAuth($username, $password);
+    }
+
+    /**
+     * @return bool is this request setup for client side cert?
+     */
+    public function hasClientSideCert()
+    {
+        return isset($this->client_cert) && isset($this->client_key);
+    }
+
+    /**
+     * Use Client Side Cert Authentication
+     * @param string $key file path to client key
+     * @param string $cert file path to client cert
+     * @param string $passphrase for client key
+     * @param string $encoding default PEM
+     * @return Request
+     */
+    public function clientSideCert($cert, $key, $passphrase = null, $encoding = 'PEM')
+    {
+        $this->client_cert          = $cert;
+        $this->client_key           = $key;
+        $this->client_passphrase    = $passphrase;
+        $this->client_encoding      = $encoding;
+
+        return $this;
+    }
+    // @alias of basicAuth
+    public function authenticateWithCert($cert, $key, $passphrase = null, $encoding = 'PEM')
+    {
+        return $this->clientSideCert($cert, $key, $passphrase, $encoding);
+    }
+
+    /**
+     * Set the body of the request
+     * @param mixed $payload
+     * @param string $mimeType currently, sets the sends AND expects mime type although this
+     *    behavior may change in the next minor release (as it is a potential breaking change).
+     * @return Request
+     */
+    public function body($payload, $mimeType = null)
+    {
+        $this->mime($mimeType);
+        $this->payload = $payload;
+        // Iserntentially don't call _serializePayload yet.  Wait until
+        // we actually send off the request to convert payload to string.
+        // At that time, the `serialized_payload` is set accordingly.
+        return $this;
+    }
+
+    /**
+     * Helper function to set the Content type and Expected as same in
+     * one swoop
+     * @param string $mime mime type to use for content type and expected return type
+     * @return Request
+     */
+    public function mime($mime)
+    {
+        if (empty($mime)) return $this;
+        $this->content_type = $this->expected_type = Mime::getFullMime($mime);
+        if ($this->isUpload()) {
+            $this->neverSerializePayload();
+        }
+        return $this;
+    }
+    // @alias of mime
+    public function sendsAndExpectsType($mime)
+    {
+        return $this->mime($mime);
+    }
+    // @alias of mime
+    public function sendsAndExpects($mime)
+    {
+        return $this->mime($mime);
+    }
+
+    /**
+     * Set the method.  Shouldn't be called often as the preferred syntax
+     * for instantiation is the method specific factory methods.
+     * @param string $method
+     * @return Request
+     */
+    public function method($method)
+    {
+        if (empty($method)) return $this;
+        $this->method = $method;
+        return $this;
+    }
+
+    /**
+     * @param string $mime
+     * @return Request
+     */
+    public function expects($mime)
+    {
+        if (empty($mime)) return $this;
+        $this->expected_type = Mime::getFullMime($mime);
+        return $this;
+    }
+    // @alias of expects
+    public function expectsType($mime)
+    {
+        return $this->expects($mime);
+    }
+
+    public function attach($files)
+    {
+        $finfo = finfo_open(FILEINFO_MIME_TYPE);
+        foreach ($files as $key => $file) {
+            $mimeType = finfo_file($finfo, $file);
+            if (function_exists('curl_file_create')) {
+                $this->payload[$key] = curl_file_create($file, $mimeType);
+            } else {
+                $this->payload[$key] = '@' . $file;
+	            if ($mimeType) {
+		            $this->payload[$key] .= ';type=' . $mimeType;
+	            }
+            }
+        }
+        $this->sendsType(Mime::UPLOAD);
+        return $this;
+    }
+
+    /**
+     * @param string $mime
+     * @return Request
+     */
+    public function contentType($mime)
+    {
+        if (empty($mime)) return $this;
+        $this->content_type  = Mime::getFullMime($mime);
+        if ($this->isUpload()) {
+            $this->neverSerializePayload();
+        }
+        return $this;
+    }
+    // @alias of contentType
+    public function sends($mime)
+    {
+        return $this->contentType($mime);
+    }
+    // @alias of contentType
+    public function sendsType($mime)
+    {
+        return $this->contentType($mime);
+    }
+
+    /**
+     * Do we strictly enforce SSL verification?
+     * @param bool $strict
+     * @return Request
+     */
+    public function strictSSL($strict)
+    {
+        $this->strict_ssl = $strict;
+        return $this;
+    }
+    public function withoutStrictSSL()
+    {
+        return $this->strictSSL(false);
+    }
+    public function withStrictSSL()
+    {
+        return $this->strictSSL(true);
+    }
+
+    /**
+     * Use proxy configuration
+     * @param string $proxy_host Hostname or address of the proxy
+     * @param int $proxy_port Port of the proxy. Default 80
+     * @param string $auth_type Authentication type or null. Accepted values are CURLAUTH_BASIC, CURLAUTH_NTLM. Default null, no authentication
+     * @param string $auth_username Authentication username. Default null
+     * @param string $auth_password Authentication password. Default null
+     * @return Request
+     */
+    public function useProxy($proxy_host, $proxy_port = 80, $auth_type = null, $auth_username = null, $auth_password = null, $proxy_type = Proxy::HTTP)
+    {
+        $this->addOnCurlOption(CURLOPT_PROXY, "{$proxy_host}:{$proxy_port}");
+        $this->addOnCurlOption(CURLOPT_PROXYTYPE, $proxy_type);
+        if (in_array($auth_type, array(CURLAUTH_BASIC,CURLAUTH_NTLM))) {
+            $this->addOnCurlOption(CURLOPT_PROXYAUTH, $auth_type)
+                ->addOnCurlOption(CURLOPT_PROXYUSERPWD, "{$auth_username}:{$auth_password}");
+        }
+        return $this;
+    }
+
+    /**
+     * Shortcut for useProxy to configure SOCKS 4 proxy
+     * @see Request::useProxy
+     * @return Request
+     */
+    public function useSocks4Proxy($proxy_host, $proxy_port = 80, $auth_type = null, $auth_username = null, $auth_password = null)
+    {
+        return $this->useProxy($proxy_host, $proxy_port, $auth_type, $auth_username, $auth_password, Proxy::SOCKS4);
+    }
+
+    /**
+     * Shortcut for useProxy to configure SOCKS 5 proxy
+     * @see Request::useProxy
+     * @return Request
+     */
+    public function useSocks5Proxy($proxy_host, $proxy_port = 80, $auth_type = null, $auth_username = null, $auth_password = null)
+    {
+        return $this->useProxy($proxy_host, $proxy_port, $auth_type, $auth_username, $auth_password, Proxy::SOCKS5);
+    }
+
+    /**
+     * @return bool is this request setup for using proxy?
+     */
+    public function hasProxy()
+    {
+        /* We must be aware that proxy variables could come from environment also.
+           In curl extension, http proxy can be specified not only via CURLOPT_PROXY option, 
+           but also by environment variable called http_proxy.
+        */
+        return isset($this->additional_curl_opts[CURLOPT_PROXY]) && is_string($this->additional_curl_opts[CURLOPT_PROXY]) ||
+            getenv("http_proxy");
+    }
+
+    /**
+     * Determine how/if we use the built in serialization by
+     * setting the serialize_payload_method
+     * The default (SERIALIZE_PAYLOAD_SMART) is...
+     *  - if payload is not a scalar (object/array)
+     *    use the appropriate serialize method according to
+     *    the Content-Type of this request.
+     *  - if the payload IS a scalar (int, float, string, bool)
+     *    than just return it as is.
+     * When this option is set SERIALIZE_PAYLOAD_ALWAYS,
+     * it will always use the appropriate
+     * serialize option regardless of whether payload is scalar or not
+     * When this option is set SERIALIZE_PAYLOAD_NEVER,
+     * it will never use any of the serialization methods.
+     * Really the only use for this is if you want the serialize methods
+     * to handle strings or not (e.g. Blah is not valid JSON, but "Blah"
+     * is).  Forcing the serialization helps prevent that kind of error from
+     * happening.
+     * @param int $mode
+     * @return Request
+     */
+    public function serializePayload($mode)
+    {
+        $this->serialize_payload_method = $mode;
+        return $this;
+    }
+
+    /**
+     * @see Request::serializePayload()
+     * @return Request
+     */
+    public function neverSerializePayload()
+    {
+        return $this->serializePayload(self::SERIALIZE_PAYLOAD_NEVER);
+    }
+
+    /**
+     * This method is the default behavior
+     * @see Request::serializePayload()
+     * @return Request
+     */
+    public function smartSerializePayload()
+    {
+        return $this->serializePayload(self::SERIALIZE_PAYLOAD_SMART);
+    }
+
+    /**
+     * @see Request::serializePayload()
+     * @return Request
+     */
+    public function alwaysSerializePayload()
+    {
+        return $this->serializePayload(self::SERIALIZE_PAYLOAD_ALWAYS);
+    }
+
+    /**
+     * Add an additional header to the request
+     * Can also use the cleaner syntax of
+     * $Request->withMyHeaderName($my_value);
+     * @see Request::__call()
+     *
+     * @param string $header_name
+     * @param string $value
+     * @return Request
+     */
+    public function addHeader($header_name, $value)
+    {
+        $this->headers[$header_name] = $value;
+        return $this;
+    }
+
+    /**
+     * Add group of headers all at once.  Note: This is
+     * here just as a convenience in very specific cases.
+     * The preferred "readable" way would be to leverage
+     * the support for custom header methods.
+     * @param array $headers
+     * @return Request
+     */
+    public function addHeaders(array $headers)
+    {
+        foreach ($headers as $header => $value) {
+            $this->addHeader($header, $value);
+        }
+        return $this;
+    }
+
+    /**
+     * @param bool $auto_parse perform automatic "smart"
+     *    parsing based on Content-Type or "expectedType"
+     *    If not auto parsing, Response->body returns the body
+     *    as a string.
+     * @return Request
+     */
+    public function autoParse($auto_parse = true)
+    {
+        $this->auto_parse = $auto_parse;
+        return $this;
+    }
+
+    /**
+     * @see Request::autoParse()
+     * @return Request
+     */
+    public function withoutAutoParsing()
+    {
+        return $this->autoParse(false);
+    }
+
+    /**
+     * @see Request::autoParse()
+     * @return Request
+     */
+    public function withAutoParsing()
+    {
+        return $this->autoParse(true);
+    }
+
+    /**
+     * Use a custom function to parse the response.
+     * @param \Closure $callback Takes the raw body of
+     *    the http response and returns a mixed
+     * @return Request
+     */
+    public function parseWith(\Closure $callback)
+    {
+        $this->parse_callback = $callback;
+        return $this;
+    }
+
+    /**
+     * @see Request::parseResponsesWith()
+     * @param \Closure $callback
+     * @return Request
+     */
+    public function parseResponsesWith(\Closure $callback)
+    {
+        return $this->parseWith($callback);
+    }
+
+    /**
+     * Callback called to handle HTTP errors. When nothing is set, defaults
+     * to logging via `error_log`
+     * @param \Closure $callback (string $error)
+     * @return Request
+     */
+    public function whenError(\Closure $callback)
+    {
+        $this->error_callback = $callback;
+        return $this;
+    }
+
+    /**
+     * Callback invoked after payload has been serialized but before
+     * the request has been built.
+     * @param \Closure $callback (Request $request)
+     * @return Request
+     */
+    public function beforeSend(\Closure $callback)
+    {
+        $this->send_callback = $callback;
+        return $this;
+    }
+
+    /**
+     * Register a callback that will be used to serialize the payload
+     * for a particular mime type.  When using "*" for the mime
+     * type, it will use that parser for all responses regardless of the mime
+     * type.  If a custom '*' and 'application/json' exist, the custom
+     * 'application/json' would take precedence over the '*' callback.
+     *
+     * @param string $mime mime type we're registering
+     * @param \Closure $callback takes one argument, $payload,
+     *    which is the payload that we'll be
+     * @return Request
+     */
+    public function registerPayloadSerializer($mime, \Closure $callback)
+    {
+        $this->payload_serializers[Mime::getFullMime($mime)] = $callback;
+        return $this;
+    }
+
+    /**
+     * @see Request::registerPayloadSerializer()
+     * @param \Closure $callback
+     * @return Request
+     */
+    public function serializePayloadWith(\Closure $callback)
+    {
+        return $this->registerPayloadSerializer('*', $callback);
+    }
+
+    /**
+     * Magic method allows for neatly setting other headers in a
+     * similar syntax as the other setters.  This method also allows
+     * for the sends* syntax.
+     * @param string $method "missing" method name called
+     *    the method name called should be the name of the header that you
+     *    are trying to set in camel case without dashes e.g. to set a
+     *    header for Content-Type you would use contentType() or more commonly
+     *    to add a custom header like X-My-Header, you would use xMyHeader().
+     *    To promote readability, you can optionally prefix these methods with
+     *    "with"  (e.g. withXMyHeader("blah") instead of xMyHeader("blah")).
+     * @param array $args in this case, there should only ever be 1 argument provided
+     *    and that argument should be a string value of the header we're setting
+     * @return Request
+     */
+    public function __call($method, $args)
+    {
+        // This method supports the sends* methods
+        // like sendsJSON, sendsForm
+        //!method_exists($this, $method) &&
+        if (substr($method, 0, 5) === 'sends') {
+            $mime = strtolower(substr($method, 5));
+            if (Mime::supportsMimeType($mime)) {
+                $this->sends(Mime::getFullMime($mime));
+                return $this;
+            }
+            // else {
+            //     throw new \Exception("Unsupported Content-Type $mime");
+            // }
+        }
+        if (substr($method, 0, 7) === 'expects') {
+            $mime = strtolower(substr($method, 7));
+            if (Mime::supportsMimeType($mime)) {
+                $this->expects(Mime::getFullMime($mime));
+                return $this;
+            }
+            // else {
+            //     throw new \Exception("Unsupported Content-Type $mime");
+            // }
+        }
+
+        // This method also adds the custom header support as described in the
+        // method comments
+        if (count($args) === 0)
+            return;
+
+        // Strip the sugar.  If it leads with "with", strip.
+        // This is okay because: No defined HTTP headers begin with with,
+        // and if you are defining a custom header, the standard is to prefix it
+        // with an "X-", so that should take care of any collisions.
+        if (substr($method, 0, 4) === 'with')
+            $method = substr($method, 4);
+
+        // Precede upper case letters with dashes, uppercase the first letter of method
+        $header = ucwords(implode('-', preg_split('/([A-Z][^A-Z]*)/', $method, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY)));
+        $this->addHeader($header, $args[0]);
+        return $this;
+    }
+
+    // Internal Functions
+
+    /**
+     * This is the default template to use if no
+     * template has been provided.  The template
+     * tells the class which default values to use.
+     * While there is a slight overhead for object
+     * creation once per execution (not once per
+     * Request instantiation), it promotes readability
+     * and flexibility within the class.
+     */
+    private static function _initializeDefaults()
+    {
+        // This is the only place you will
+        // see this constructor syntax.  It
+        // is only done here to prevent infinite
+        // recusion.  Do not use this syntax elsewhere.
+        // It goes against the whole readability
+        // and transparency idea.
+        self::$_template = new Request(array('method' => Http::GET));
+
+        // This is more like it...
+        self::$_template
+            ->withoutStrictSSL();
+    }
+
+    /**
+     * Set the defaults on a newly instantiated object
+     * Doesn't copy variables prefixed with _
+     * @return Request
+     */
+    private function _setDefaults()
+    {
+        if (!isset(self::$_template))
+            self::_initializeDefaults();
+        foreach (self::$_template as $k=>$v) {
+            if ($k[0] != '_')
+                $this->$k = $v;
+        }
+        return $this;
+    }
+
+    private function _error($error)
+    {
+        // TODO add in support for various Loggers that follow
+        // PSR 3 https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
+        if (isset($this->error_callback)) {
+            $this->error_callback->__invoke($error);
+        } else {
+            error_log($error);
+        }
+    }
+
+    /**
+     * Factory style constructor works nicer for chaining.  This
+     * should also really only be used internally.  The Request::get,
+     * Request::post syntax is preferred as it is more readable.
+     * @param string $method Http Method
+     * @param string $mime Mime Type to Use
+     * @return Request
+     */
+    public static function init($method = null, $mime = null)
+    {
+        // Setup our handlers, can call it here as it's idempotent
+        Bootstrap::init();
+
+        // Setup the default template if need be
+        if (!isset(self::$_template))
+            self::_initializeDefaults();
+
+        $request = new Request();
+        return $request
+               ->_setDefaults()
+               ->method($method)
+               ->sendsType($mime)
+               ->expectsType($mime);
+    }
+
+    /**
+     * Does the heavy lifting.  Uses de facto HTTP
+     * library cURL to set up the HTTP request.
+     * Note: It does NOT actually send the request
+     * @return Request
+     * @throws \Exception
+     */
+    public function _curlPrep()
+    {
+        // Check for required stuff
+        if (!isset($this->uri))
+            throw new \Exception('Attempting to send a request before defining a URI endpoint.');
+
+        if (isset($this->payload)) {
+            $this->serialized_payload = $this->_serializePayload($this->payload);
+        }
+
+        if (isset($this->send_callback)) {
+            call_user_func($this->send_callback, $this);
+        }
+
+        $ch = curl_init($this->uri);
+
+        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method);
+        if ($this->method === Http::HEAD) {
+            curl_setopt($ch, CURLOPT_NOBODY, true);
+        }
+
+        if ($this->hasBasicAuth()) {
+            curl_setopt($ch, CURLOPT_USERPWD, $this->username . ':' . $this->password);
+        }
+
+        if ($this->hasClientSideCert()) {
+
+            if (!file_exists($this->client_key))
+                throw new \Exception('Could not read Client Key');
+
+            if (!file_exists($this->client_cert))
+                throw new \Exception('Could not read Client Certificate');
+
+            curl_setopt($ch, CURLOPT_SSLCERTTYPE,   $this->client_encoding);
+            curl_setopt($ch, CURLOPT_SSLKEYTYPE,    $this->client_encoding);
+            curl_setopt($ch, CURLOPT_SSLCERT,       $this->client_cert);
+            curl_setopt($ch, CURLOPT_SSLKEY,        $this->client_key);
+            curl_setopt($ch, CURLOPT_SSLKEYPASSWD,  $this->client_passphrase);
+            // curl_setopt($ch, CURLOPT_SSLCERTPASSWD,  $this->client_cert_passphrase);
+        }
+
+        if ($this->hasTimeout()) {
+            if (defined('CURLOPT_TIMEOUT_MS')) {
+                curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->timeout * 1000);
+            } else {
+                curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
+            }
+        }
+
+        if ($this->follow_redirects) {
+            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+            curl_setopt($ch, CURLOPT_MAXREDIRS, $this->max_redirects);
+        }
+
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->strict_ssl);
+        // zero is safe for all curl versions
+        $verifyValue = $this->strict_ssl + 0;
+        //Support for value 1 removed in cURL 7.28.1 value 2 valid in all versions
+        if ($verifyValue > 0) $verifyValue++;
+        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyValue);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+
+        // https://github.com/nategood/httpful/issues/84
+        // set Content-Length to the size of the payload if present
+        if (isset($this->payload)) {
+            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->serialized_payload);
+            if (!$this->isUpload()) {
+                $this->headers['Content-Length'] =
+                    $this->_determineLength($this->serialized_payload);
+            }
+        }
+
+        $headers = array();
+        // https://github.com/nategood/httpful/issues/37
+        // Except header removes any HTTP 1.1 Continue from response headers
+        $headers[] = 'Expect:';
+
+        if (!isset($this->headers['User-Agent'])) {
+            $headers[] = $this->buildUserAgent();
+        }
+
+        $headers[] = "Content-Type: {$this->content_type}";
+
+        // allow custom Accept header if set
+        if (!isset($this->headers['Accept'])) {
+            // http://pretty-rfc.herokuapp.com/RFC2616#header.accept
+            $accept = 'Accept: */*; q=0.5, text/plain; q=0.8, text/html;level=3;';
+
+            if (!empty($this->expected_type)) {
+                $accept .= "q=0.9, {$this->expected_type}";
+            }
+
+            $headers[] = $accept;
+        }
+
+        // Solve a bug on squid proxy, NONE/411 when miss content length
+        if (!isset($this->headers['Content-Length']) && !$this->isUpload()) {
+            $this->headers['Content-Length'] = 0;
+        }
+
+        foreach ($this->headers as $header => $value) {
+            $headers[] = "$header: $value";
+        }
+
+        $url = \parse_url($this->uri);
+        $path = (isset($url['path']) ? $url['path'] : '/').(isset($url['query']) ? '?'.$url['query'] : '');
+        $this->raw_headers = "{$this->method} $path HTTP/1.1\r\n";
+        $host = (isset($url['host']) ? $url['host'] : 'localhost').(isset($url['port']) ? ':'.$url['port'] : '');
+        $this->raw_headers .= "Host: $host\r\n";
+        $this->raw_headers .= \implode("\r\n", $headers);
+        $this->raw_headers .= "\r\n";
+
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+
+        if ($this->_debug) {
+            curl_setopt($ch, CURLOPT_VERBOSE, true);
+        }
+
+        curl_setopt($ch, CURLOPT_HEADER, 1);
+
+        // If there are some additional curl opts that the user wants
+        // to set, we can tack them in here
+        foreach ($this->additional_curl_opts as $curlopt => $curlval) {
+            curl_setopt($ch, $curlopt, $curlval);
+        }
+
+        $this->_ch = $ch;
+
+        return $this;
+    }
+
+    /**
+     * @param string $str payload
+     * @return int length of payload in bytes
+     */
+    public function _determineLength($str)
+    {
+        if (function_exists('mb_strlen')) {
+            return mb_strlen($str, '8bit');
+        } else {
+            return strlen($str);
+        }
+    }
+
+    /**
+     * @return bool
+     */
+    public function isUpload()
+    {
+        return Mime::UPLOAD == $this->content_type;
+    }
+
+    /**
+     * @return string
+     */
+    public function buildUserAgent()
+    {
+        $user_agent = 'User-Agent: Httpful/' . Httpful::VERSION . ' (cURL/';
+        $curl = \curl_version();
+
+        if (isset($curl['version'])) {
+            $user_agent .= $curl['version'];
+        } else {
+            $user_agent .= '?.?.?';
+        }
+
+        $user_agent .= ' PHP/'. PHP_VERSION . ' (' . PHP_OS . ')';
+
+        if (isset($_SERVER['SERVER_SOFTWARE'])) {
+            $user_agent .= ' ' . \preg_replace('~PHP/[\d\.]+~U', '',
+                $_SERVER['SERVER_SOFTWARE']);
+        } else {
+            if (isset($_SERVER['TERM_PROGRAM'])) {
+                $user_agent .= " {$_SERVER['TERM_PROGRAM']}";
+            }
+
+            if (isset($_SERVER['TERM_PROGRAM_VERSION'])) {
+                $user_agent .= "/{$_SERVER['TERM_PROGRAM_VERSION']}";
+            }
+        }
+
+        if (isset($_SERVER['HTTP_USER_AGENT'])) {
+            $user_agent .= " {$_SERVER['HTTP_USER_AGENT']}";
+        }
+
+        $user_agent .= ')';
+
+        return $user_agent;
+    }
+
+    /**
+     * Takes a curl result and generates a Response from it
+     * @return Response
+     */
+    public function buildResponse($result) {
+        if ($result === false) {
+            if ($curlErrorNumber = curl_errno($this->_ch)) {
+                $curlErrorString = curl_error($this->_ch);
+                $this->_error($curlErrorString);
+
+                $exception = new ConnectionErrorException('Unable to connect to "'.$this->uri.'": '
+                        . $curlErrorNumber . ' ' . $curlErrorString);
+
+                $exception->setCurlErrorNumber($curlErrorNumber)
+                    ->setCurlErrorString($curlErrorString);
+
+                throw $exception;
+            }
+
+            $this->_error('Unable to connect to "'.$this->uri.'".');
+            throw new ConnectionErrorException('Unable to connect to "'.$this->uri.'".');
+        }
+
+        $info = curl_getinfo($this->_ch);
+
+        // Remove the "HTTP/1.x 200 Connection established" string and any other headers added by proxy
+        $proxy_regex = "/HTTP\/1\.[01] 200 Connection established.*?\r\n\r\n/si";
+        if ($this->hasProxy() && preg_match($proxy_regex, $result)) {
+            $result = preg_replace($proxy_regex, '', $result);
+        }
+
+        $response = explode("\r\n\r\n", $result, 2 + $info['redirect_count']);
+
+        $body = array_pop($response);
+        $headers = array_pop($response);
+
+        return new Response($body, $headers, $this, $info);
+    }
+
+    /**
+     * Semi-reluctantly added this as a way to add in curl opts
+     * that are not otherwise accessible from the rest of the API.
+     * @param string $curlopt
+     * @param mixed $curloptval
+     * @return Request
+     */
+    public function addOnCurlOption($curlopt, $curloptval)
+    {
+        $this->additional_curl_opts[$curlopt] = $curloptval;
+        return $this;
+    }
+
+    /**
+     * Turn payload from structured data into
+     * a string based on the current Mime type.
+     * This uses the auto_serialize option to determine
+     * it's course of action.  See serialize method for more.
+     * Renamed from _detectPayload to _serializePayload as of
+     * 2012-02-15.
+     *
+     * Added in support for custom payload serializers.
+     * The serialize_payload_method stuff still holds true though.
+     * @see Request::registerPayloadSerializer()
+     *
+     * @param mixed $payload
+     * @return string
+     */
+    private function _serializePayload($payload)
+    {
+        if (empty($payload) || $this->serialize_payload_method === self::SERIALIZE_PAYLOAD_NEVER)
+            return $payload;
+
+        // When we are in "smart" mode, don't serialize strings/scalars, assume they are already serialized
+        if ($this->serialize_payload_method === self::SERIALIZE_PAYLOAD_SMART && is_scalar($payload))
+            return $payload;
+
+        // Use a custom serializer if one is registered for this mime type
+        if (isset($this->payload_serializers['*']) || isset($this->payload_serializers[$this->content_type])) {
+            $key = isset($this->payload_serializers[$this->content_type]) ? $this->content_type : '*';
+            return call_user_func($this->payload_serializers[$key], $payload);
+        }
+
+        return Httpful::get($this->content_type)->serialize($payload);
+    }
+
+    /**
+     * HTTP Method Get
+     * @param string $uri optional uri to use
+     * @param string $mime expected
+     * @return Request
+     */
+    public static function get($uri, $mime = null)
+    {
+        return self::init(Http::GET)->uri($uri)->mime($mime);
+    }
+
+
+    /**
+     * Like Request:::get, except that it sends off the request as well
+     * returning a response
+     * @param string $uri optional uri to use
+     * @param string $mime expected
+     * @return Response
+     */
+    public static function getQuick($uri, $mime = null)
+    {
+        return self::get($uri, $mime)->send();
+    }
+
+    /**
+     * HTTP Method Post
+     * @param string $uri optional uri to use
+     * @param string $payload data to send in body of request
+     * @param string $mime MIME to use for Content-Type
+     * @return Request
+     */
+    public static function post($uri, $payload = null, $mime = null)
+    {
+        return self::init(Http::POST)->uri($uri)->body($payload, $mime);
+    }
+
+    /**
+     * HTTP Method Put
+     * @param string $uri optional uri to use
+     * @param string $payload data to send in body of request
+     * @param string $mime MIME to use for Content-Type
+     * @return Request
+     */
+    public static function put($uri, $payload = null, $mime = null)
+    {
+        return self::init(Http::PUT)->uri($uri)->body($payload, $mime);
+    }
+
+    /**
+     * HTTP Method Patch
+     * @param string $uri optional uri to use
+     * @param string $payload data to send in body of request
+     * @param string $mime MIME to use for Content-Type
+     * @return Request
+     */
+    public static function patch($uri, $payload = null, $mime = null)
+    {
+        return self::init(Http::PATCH)->uri($uri)->body($payload, $mime);
+    }
+
+    /**
+     * HTTP Method Delete
+     * @param string $uri optional uri to use
+     * @return Request
+     */
+    public static function delete($uri, $mime = null)
+    {
+        return self::init(Http::DELETE)->uri($uri)->mime($mime);
+    }
+
+    /**
+     * HTTP Method Head
+     * @param string $uri optional uri to use
+     * @return Request
+     */
+    public static function head($uri)
+    {
+        return self::init(Http::HEAD)->uri($uri);
+    }
+
+    /**
+     * HTTP Method Options
+     * @param string $uri optional uri to use
+     * @return Request
+     */
+    public static function options($uri)
+    {
+        return self::init(Http::OPTIONS)->uri($uri);
+    }
+}

+ 178 - 0
vendor/nategood/httpful/src/Httpful/Response.php

@@ -0,0 +1,178 @@
+<?php
+
+namespace Httpful;
+
+/**
+ * Models an HTTP response
+ *
+ * @author Nate Good <me@nategood.com>
+ */
+class Response
+{
+
+    public $body,
+           $raw_body,
+           $headers,
+           $raw_headers,
+           $request,
+           $code = 0,
+           $content_type,
+           $parent_type,
+           $charset,
+           $meta_data,
+           $is_mime_vendor_specific = false,
+           $is_mime_personal = false;
+
+    private $parsers;
+
+    /**
+     * @param string $body
+     * @param string $headers
+     * @param Request $request
+     * @param array $meta_data
+     */
+    public function __construct($body, $headers, Request $request, array $meta_data = array())
+    {
+        $this->request      = $request;
+        $this->raw_headers  = $headers;
+        $this->raw_body     = $body;
+        $this->meta_data    = $meta_data;
+
+        $this->code         = $this->_parseCode($headers);
+        $this->headers      = Response\Headers::fromString($headers);
+
+        $this->_interpretHeaders();
+
+        $this->body         = $this->_parse($body);
+    }
+
+    /**
+     * Status Code Definitions
+     *
+     * Informational 1xx
+     * Successful    2xx
+     * Redirection   3xx
+     * Client Error  4xx
+     * Server Error  5xx
+     *
+     * http://pretty-rfc.herokuapp.com/RFC2616#status.codes
+     *
+     * @return bool Did we receive a 4xx or 5xx?
+     */
+    public function hasErrors()
+    {
+        return $this->code >= 400;
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasBody()
+    {
+        return !empty($this->body);
+    }
+
+    /**
+     * Parse the response into a clean data structure
+     * (most often an associative array) based on the expected
+     * Mime type.
+     * @param string Http response body
+     * @return array|string|object the response parse accordingly
+     */
+    public function _parse($body)
+    {
+        // If the user decided to forgo the automatic
+        // smart parsing, short circuit.
+        if (!$this->request->auto_parse) {
+            return $body;
+        }
+
+        // If provided, use custom parsing callback
+        if (isset($this->request->parse_callback)) {
+            return call_user_func($this->request->parse_callback, $body);
+        }
+
+        // Decide how to parse the body of the response in the following order
+        //  1. If provided, use the mime type specifically set as part of the `Request`
+        //  2. If a MimeHandler is registered for the content type, use it
+        //  3. If provided, use the "parent type" of the mime type from the response
+        //  4. Default to the content-type provided in the response
+        $parse_with = $this->request->expected_type;
+        if (empty($this->request->expected_type)) {
+            $parse_with = Httpful::hasParserRegistered($this->content_type)
+                ? $this->content_type
+                : $this->parent_type;
+        }
+
+       return Httpful::get($parse_with)->parse($body);
+    }
+
+    /**
+     * Parse text headers from response into
+     * array of key value pairs
+     * @param string $headers raw headers
+     * @return array parse headers
+     */
+    public function _parseHeaders($headers)
+    {
+        return Response\Headers::fromString($headers)->toArray();
+    }
+
+    public function _parseCode($headers)
+    {
+        $end = strpos($headers, "\r\n");
+        if ($end === false) $end = strlen($headers);
+        $parts = explode(' ', substr($headers, 0, $end));
+        if (count($parts) < 2 || !is_numeric($parts[1])) {
+            throw new \Exception("Unable to parse response code from HTTP response due to malformed response");
+        }
+        return intval($parts[1]);
+    }
+
+    /**
+     * After we've parse the headers, let's clean things
+     * up a bit and treat some headers specially
+     */
+    public function _interpretHeaders()
+    {
+        // Parse the Content-Type and charset
+        $content_type = isset($this->headers['Content-Type']) ? $this->headers['Content-Type'] : '';
+        $content_type = explode(';', $content_type);
+
+        $this->content_type = $content_type[0];
+        if (count($content_type) == 2 && strpos($content_type[1], '=') !== false) {
+            list($nill, $this->charset) = explode('=', $content_type[1]);
+        }
+
+        // RFC 2616 states "text/*" Content-Types should have a default
+        // charset of ISO-8859-1. "application/*" and other Content-Types
+        // are assumed to have UTF-8 unless otherwise specified.
+        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
+        // http://www.w3.org/International/O-HTTP-charset.en.php
+        if (!isset($this->charset)) {
+            $this->charset = substr($this->content_type, 5) === 'text/' ? 'iso-8859-1' : 'utf-8';
+        }
+
+        // Is vendor type? Is personal type?
+        if (strpos($this->content_type, '/') !== false) {
+            list($type, $sub_type) = explode('/', $this->content_type);
+            $this->is_mime_vendor_specific = substr($sub_type, 0, 4) === 'vnd.';
+            $this->is_mime_personal = substr($sub_type, 0, 4) === 'prs.';
+        }
+
+        // Parent type (e.g. xml for application/vnd.github.message+xml)
+        $this->parent_type = $this->content_type;
+        if (strpos($this->content_type, '+') !== false) {
+            list($vendor, $this->parent_type) = explode('+', $this->content_type, 2);
+            $this->parent_type = Mime::getFullMime($this->parent_type);
+        }
+    }
+
+    /**
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->raw_body;
+    }
+}

+ 106 - 0
vendor/nategood/httpful/src/Httpful/Response/Headers.php

@@ -0,0 +1,106 @@
+<?php
+
+namespace Httpful\Response;
+
+final class Headers implements \ArrayAccess, \Countable {
+
+    private $headers;
+
+    /**
+     * @param array $headers
+     */
+    private function __construct($headers)
+    {
+        $this->headers = $headers;
+    }
+
+    /**
+     * @param string $string
+     * @return Headers
+     */
+    public static function fromString($string)
+    {
+        $headers = preg_split("/(\r|\n)+/", $string, -1, \PREG_SPLIT_NO_EMPTY);
+        $parse_headers = array();
+        for ($i = 1; $i < count($headers); $i++) {
+            list($key, $raw_value) = explode(':', $headers[$i], 2);
+            $key = trim($key);
+            $value = trim($raw_value);
+            if (array_key_exists($key, $parse_headers)) {
+                // See HTTP RFC Sec 4.2 Paragraph 5
+                // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
+                // If a header appears more than once, it must also be able to
+                // be represented as a single header with a comma-separated
+                // list of values.  We transform accordingly.
+                $parse_headers[$key] .= ',' . $value;
+            } else {
+                $parse_headers[$key] = $value;
+            }
+        }
+        return new self($parse_headers);
+    }
+
+    /**
+     * @param string $offset
+     * @return bool
+     */
+    public function offsetExists($offset)
+    {
+        return $this->getCaseInsensitive($offset) !== null;
+    }
+
+    /**
+     * @param string $offset
+     * @return mixed
+     */
+    public function offsetGet($offset)
+    {
+        return $this->getCaseInsensitive($offset);
+    }
+
+    /**
+     * @param string $offset
+     * @param string $value
+     * @throws \Exception
+     */
+    public function offsetSet($offset, $value)
+    {
+        throw new \Exception("Headers are read-only.");
+    }
+
+    /**
+     * @param string $offset
+     * @throws \Exception
+     */
+    public function offsetUnset($offset)
+    {
+        throw new \Exception("Headers are read-only.");
+    }
+
+    /**
+     * @return int
+     */
+    public function count()
+    {
+        return count($this->headers);
+    }
+
+    /**
+     * @return array
+     */
+    public function toArray()
+    {
+        return $this->headers;
+    }
+
+    private function getCaseInsensitive(string $key)
+    {
+        foreach ($this->headers as $header => $value) {
+            if (strtolower($key) === strtolower($header)) {
+                return $value;
+            }
+        }
+
+        return null;
+    }
+}

+ 635 - 0
vendor/nategood/httpful/tests/Httpful/HttpfulTest.php

@@ -0,0 +1,635 @@
+<?php
+/**
+ * Port over the original tests into a more traditional PHPUnit
+ * format.  Still need to hook into a lightweight HTTP server to
+ * better test some things (e.g. obscure cURL settings).  I've moved
+ * the old tests and node.js server to the tests/.legacy directory.
+ *
+ * @author Nate Good <me@nategood.com>
+ */
+namespace Httpful\Test;
+
+require(dirname(dirname(dirname(__FILE__))) . '/bootstrap.php');
+\Httpful\Bootstrap::init();
+
+use Httpful\Httpful;
+use Httpful\Request;
+use Httpful\Mime;
+use Httpful\Http;
+use Httpful\Response;
+use Httpful\Handlers\JsonHandler;
+
+define('TEST_SERVER', WEB_SERVER_HOST . ':' . WEB_SERVER_PORT);
+
+class HttpfulTest extends \PHPUnit\Framework\TestCase
+{
+    const TEST_SERVER = TEST_SERVER;
+    const TEST_URL = 'http://127.0.0.1:8008';
+    const TEST_URL_400 = 'http://127.0.0.1:8008/400';
+
+    const SAMPLE_JSON_HEADER =
+"HTTP/1.1 200 OK
+Content-Type: application/json
+Connection: keep-alive
+Transfer-Encoding: chunked\r\n";
+    const SAMPLE_JSON_HEADER_LOWERCASE =
+        "HTTP/2 200 
+date: Tue, 07 Jan 2020 09:11:21 GMT
+content-type: application/json
+content-length: 513
+access-control-allow-origin: *
+access-control-allow-methods: GET, POST, PUT, PATCH, DELETE
+access-control-allow-headers: Authorization, Content-Type, Accept-Encoding, Cache-Control, DNT
+cache-control: private, must-revalidate\r\n";
+    const SAMPLE_JSON_RESPONSE = '{"key":"value","object":{"key":"value"},"array":[1,2,3,4]}';
+    const SAMPLE_CSV_HEADER =
+"HTTP/1.1 200 OK
+Content-Type: text/csv
+Connection: keep-alive
+Transfer-Encoding: chunked\r\n";
+    const SAMPLE_CSV_RESPONSE =
+"Key1,Key2
+Value1,Value2
+\"40.0\",\"Forty\"";
+    const SAMPLE_XML_RESPONSE = '<stdClass><arrayProp><array><k1><myClass><intProp>2</intProp></myClass></k1></array></arrayProp><stringProp>a string</stringProp><boolProp>TRUE</boolProp></stdClass>';
+    const SAMPLE_XML_HEADER =
+"HTTP/1.1 200 OK
+Content-Type: application/xml
+Connection: keep-alive
+Transfer-Encoding: chunked\r\n";
+    const SAMPLE_VENDOR_HEADER =
+"HTTP/1.1 200 OK
+Content-Type: application/vnd.nategood.message+xml
+Connection: keep-alive
+Transfer-Encoding: chunked\r\n";
+    const SAMPLE_VENDOR_TYPE = "application/vnd.nategood.message+xml";
+    const SAMPLE_MULTI_HEADER =
+"HTTP/1.1 200 OK
+Content-Type: application/json
+Connection: keep-alive
+Transfer-Encoding: chunked
+X-My-Header:Value1
+X-My-Header:Value2\r\n";
+
+    function testInit()
+    {
+      $r = Request::init();
+      // Did we get a 'Request' object?
+      $this->assertEquals('Httpful\Request', get_class($r));
+    }
+
+    function testDetermineLength()
+    {
+      $r = Request::init();
+      $this->assertEquals(1, $r->_determineLength('A'));
+      $this->assertEquals(2, $r->_determineLength('À'));
+      $this->assertEquals(2, $r->_determineLength('Ab'));
+      $this->assertEquals(3, $r->_determineLength('Àb'));
+      $this->assertEquals(6, $r->_determineLength('世界'));
+    }
+
+    function testMethods()
+    {
+      $valid_methods = array('get', 'post', 'delete', 'put', 'options', 'head');
+      $url = 'http://example.com/';
+      foreach ($valid_methods as $method) {
+        $r = call_user_func(array('Httpful\Request', $method), $url);
+        $this->assertEquals('Httpful\Request', get_class($r));
+        $this->assertEquals(strtoupper($method), $r->method);
+      }
+    }
+
+    function testDefaults()
+    {
+        // Our current defaults are as follows
+        $r = Request::init();
+        $this->assertEquals(Http::GET, $r->method);
+        $this->assertFalse($r->strict_ssl);
+    }
+
+    function testShortMime()
+    {
+        // Valid short ones
+        $this->assertEquals(Mime::JSON,  Mime::getFullMime('json'));
+        $this->assertEquals(Mime::XML,   Mime::getFullMime('xml'));
+        $this->assertEquals(Mime::HTML,  Mime::getFullMime('html'));
+        $this->assertEquals(Mime::CSV,  Mime::getFullMime('csv'));
+        $this->assertEquals(Mime::UPLOAD,  Mime::getFullMime('upload'));
+
+        // Valid long ones
+        $this->assertEquals(Mime::JSON, Mime::getFullMime(Mime::JSON));
+        $this->assertEquals(Mime::XML,  Mime::getFullMime(Mime::XML));
+        $this->assertEquals(Mime::HTML, Mime::getFullMime(Mime::HTML));
+        $this->assertEquals(Mime::CSV, Mime::getFullMime(Mime::CSV));
+        $this->assertEquals(Mime::UPLOAD, Mime::getFullMime(Mime::UPLOAD));
+
+        // No false positives
+        $this->assertNotEquals(Mime::XML,  Mime::getFullMime(Mime::HTML));
+        $this->assertNotEquals(Mime::JSON, Mime::getFullMime(Mime::XML));
+        $this->assertNotEquals(Mime::HTML, Mime::getFullMime(Mime::JSON));
+        $this->assertNotEquals(Mime::XML, Mime::getFullMime(Mime::CSV));
+    }
+
+    function testSettingStrictSsl()
+    {
+        $r = Request::init()
+             ->withStrictSsl();
+
+        $this->assertTrue($r->strict_ssl);
+
+        $r = Request::init()
+             ->withoutStrictSsl();
+
+        $this->assertFalse($r->strict_ssl);
+    }
+
+    function testSendsAndExpectsType()
+    {
+        $r = Request::init()
+            ->sendsAndExpectsType(Mime::JSON);
+        $this->assertEquals(Mime::JSON, $r->expected_type);
+        $this->assertEquals(Mime::JSON, $r->content_type);
+
+        $r = Request::init()
+            ->sendsAndExpectsType('html');
+        $this->assertEquals(Mime::HTML, $r->expected_type);
+        $this->assertEquals(Mime::HTML, $r->content_type);
+
+        $r = Request::init()
+            ->sendsAndExpectsType('form');
+        $this->assertEquals(Mime::FORM, $r->expected_type);
+        $this->assertEquals(Mime::FORM, $r->content_type);
+
+        $r = Request::init()
+            ->sendsAndExpectsType('application/x-www-form-urlencoded');
+        $this->assertEquals(Mime::FORM, $r->expected_type);
+        $this->assertEquals(Mime::FORM, $r->content_type);
+
+        $r = Request::init()
+            ->sendsAndExpectsType(Mime::CSV);
+        $this->assertEquals(Mime::CSV, $r->expected_type);
+        $this->assertEquals(Mime::CSV, $r->content_type);
+    }
+
+    function testIni()
+    {
+        // Test setting defaults/templates
+
+        // Create the template
+        $template = Request::init()
+            ->method(Http::POST)
+            ->withStrictSsl()
+            ->expectsType(Mime::HTML)
+            ->sendsType(Mime::FORM);
+
+        Request::ini($template);
+
+        $r = Request::init();
+
+        $this->assertTrue($r->strict_ssl);
+        $this->assertEquals(Http::POST, $r->method);
+        $this->assertEquals(Mime::HTML, $r->expected_type);
+        $this->assertEquals(Mime::FORM, $r->content_type);
+
+        // Test the default accessor as well
+        $this->assertTrue(Request::d('strict_ssl'));
+        $this->assertEquals(Http::POST, Request::d('method'));
+        $this->assertEquals(Mime::HTML, Request::d('expected_type'));
+        $this->assertEquals(Mime::FORM, Request::d('content_type'));
+
+        Request::resetIni();
+    }
+
+    function testAccept()
+    {
+        $r = Request::get('http://example.com/')
+            ->expectsType(Mime::JSON);
+
+        $this->assertEquals(Mime::JSON, $r->expected_type);
+        $r->_curlPrep();
+        $this->assertStringContainsString('application/json', $r->raw_headers);
+    }
+
+    function testCustomAccept()
+    {
+        $accept = 'application/api-1.0+json';
+        $r = Request::get('http://example.com/')
+            ->addHeader('Accept', $accept);
+
+        $r->_curlPrep();
+        $this->assertStringContainsString($accept, $r->raw_headers);
+        $this->assertEquals($accept, $r->headers['Accept']);
+    }
+
+    function testUserAgent()
+    {
+        $r = Request::get('http://example.com/')
+            ->withUserAgent('ACME/1.2.3');
+
+        $this->assertArrayHasKey('User-Agent', $r->headers);
+        $r->_curlPrep();
+        $this->assertStringContainsString('User-Agent: ACME/1.2.3', $r->raw_headers);
+        $this->assertStringNotContainsString('User-Agent: HttpFul/1.0', $r->raw_headers);
+
+        $r = Request::get('http://example.com/')
+            ->withUserAgent('');
+
+        $this->assertArrayHasKey('User-Agent', $r->headers);
+        $r->_curlPrep();
+        $this->assertStringContainsString('User-Agent:', $r->raw_headers);
+        $this->assertStringNotContainsString('User-Agent: HttpFul/1.0', $r->raw_headers);
+    }
+
+    function testAuthSetup()
+    {
+        $username = 'nathan';
+        $password = 'opensesame';
+
+        $r = Request::get('http://example.com/')
+            ->authenticateWith($username, $password);
+
+        $this->assertEquals($username, $r->username);
+        $this->assertEquals($password, $r->password);
+        $this->assertTrue($r->hasBasicAuth());
+    }
+
+    function testDigestAuthSetup()
+    {
+        $username = 'nathan';
+        $password = 'opensesame';
+
+        $r = Request::get('http://example.com/')
+            ->authenticateWithDigest($username, $password);
+
+        $this->assertEquals($username, $r->username);
+        $this->assertEquals($password, $r->password);
+        $this->assertTrue($r->hasDigestAuth());
+    }
+
+    function testJsonResponseParse()
+    {
+        $req = Request::init()->sendsAndExpects(Mime::JSON);
+        $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req);
+
+        $this->assertEquals("value", $response->body->key);
+        $this->assertEquals("value", $response->body->object->key);
+        $this->assertIsArray( $response->body->array);
+        $this->assertEquals(1, $response->body->array[0]);
+    }
+
+    function testJsonResponseParseLowercaseHeaders()
+    {
+        $req = Request::init();
+        $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER_LOWERCASE, $req);
+
+        $this->assertEquals("value", $response->body->key);
+        $this->assertEquals("value", $response->body->object->key);
+        $this->assertIsArray( $response->body->array);
+        $this->assertEquals(1, $response->body->array[0]);
+    }
+
+    function testXMLResponseParse()
+    {
+        $req = Request::init()->sendsAndExpects(Mime::XML);
+        $response = new Response(self::SAMPLE_XML_RESPONSE, self::SAMPLE_XML_HEADER, $req);
+        $sxe = $response->body;
+        $this->assertEquals("object", gettype($sxe));
+        $this->assertEquals("SimpleXMLElement", get_class($sxe));
+        $bools = $sxe->xpath('/stdClass/boolProp');
+        // list( , $bool ) = each($bools);
+        $bool = array_shift($bools);
+        $this->assertEquals("TRUE", (string) $bool);
+        $ints = $sxe->xpath('/stdClass/arrayProp/array/k1/myClass/intProp');
+        // list( , $int ) = each($ints);
+        $int = array_shift($ints);
+        $this->assertEquals("2", (string) $int);
+        $strings = $sxe->xpath('/stdClass/stringProp');
+        // list( , $string ) = each($strings);
+        $string = array_shift($strings);
+        $this->assertEquals("a string", (string) $string);
+    }
+
+    function testCsvResponseParse()
+    {
+        $req = Request::init()->sendsAndExpects(Mime::CSV);
+        $response = new Response(self::SAMPLE_CSV_RESPONSE, self::SAMPLE_CSV_HEADER, $req);
+
+        $this->assertEquals("Key1", $response->body[0][0]);
+        $this->assertEquals("Value1", $response->body[1][0]);
+        $this->assertIsString( $response->body[2][0]);
+        $this->assertEquals("40.0", $response->body[2][0]);
+    }
+
+    function testParsingContentTypeCharset()
+    {
+        $req = Request::init()->sendsAndExpects(Mime::JSON);
+        // $response = new Response(SAMPLE_JSON_RESPONSE, "", $req);
+        // // Check default content type of iso-8859-1
+        $response = new Response(self::SAMPLE_JSON_RESPONSE, "HTTP/1.1 200 OK
+Content-Type: text/plain; charset=utf-8\r\n", $req);
+        $this->assertInstanceOf('Httpful\Response\Headers', $response->headers);
+        $this->assertEquals($response->headers['Content-Type'], 'text/plain; charset=utf-8');
+        $this->assertEquals($response->content_type, 'text/plain');
+        $this->assertEquals($response->charset, 'utf-8');
+    }
+
+    function testParsingContentTypeUpload()
+    {
+        $req = Request::init();
+
+        $req->sendsType(Mime::UPLOAD);
+        // $response = new Response(SAMPLE_JSON_RESPONSE, "", $req);
+        // // Check default content type of iso-8859-1
+        $this->assertEquals($req->content_type, 'multipart/form-data');
+    }
+
+    function testAttach() {
+        $req = Request::init();
+        $testsPath = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..');
+        $filename = $testsPath . DIRECTORY_SEPARATOR . 'test_image.jpg';
+        $req->attach(array('index' => $filename));
+        $payload = $req->payload['index'];
+        // PHP 5.5  + will take advantage of CURLFile while previous
+        // versions just use the string syntax
+        if (is_string($payload)) {
+            $this->assertEquals($payload, '@' . $filename . ';type=image/jpeg');
+        } else {
+            $this->assertInstanceOf('CURLFile', $payload);
+        }
+
+        $this->assertEquals($req->content_type, Mime::UPLOAD);
+        $this->assertEquals($req->serialize_payload_method, Request::SERIALIZE_PAYLOAD_NEVER);
+    }
+
+    function testIsUpload() {
+        $req = Request::init();
+
+        $req->sendsType(Mime::UPLOAD);
+
+        $this->assertTrue($req->isUpload());
+    }
+
+    function testEmptyResponseParse()
+    {
+        $req = Request::init()->sendsAndExpects(Mime::JSON);
+        $response = new Response("", self::SAMPLE_JSON_HEADER, $req);
+        $this->assertEquals(null, $response->body);
+
+        $reqXml = Request::init()->sendsAndExpects(Mime::XML);
+        $responseXml = new Response("", self::SAMPLE_XML_HEADER, $reqXml);
+        $this->assertEquals(null, $responseXml->body);
+    }
+
+    function testNoAutoParse()
+    {
+        $req = Request::init()->sendsAndExpects(Mime::JSON)->withoutAutoParsing();
+        $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req);
+        $this->assertIsString( $response->body);
+        $req = Request::init()->sendsAndExpects(Mime::JSON)->withAutoParsing();
+        $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req);
+        $this->assertIsObject($response->body);
+    }
+
+    function testParseHeaders()
+    {
+        $req = Request::init()->sendsAndExpects(Mime::JSON);
+        $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req);
+        $this->assertEquals('application/json', $response->headers['Content-Type']);
+    }
+
+    function testRawHeaders()
+    {
+        $req = Request::init()->sendsAndExpects(Mime::JSON);
+        $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req);
+        $this->assertStringContainsString('Content-Type: application/json', $response->raw_headers);
+    }
+
+    function testHasErrors()
+    {
+        $req = Request::init()->sendsAndExpects(Mime::JSON);
+        $response = new Response('', "HTTP/1.1 100 Continue\r\n", $req);
+        $this->assertFalse($response->hasErrors());
+        $response = new Response('', "HTTP/1.1 200 OK\r\n", $req);
+        $this->assertFalse($response->hasErrors());
+        $response = new Response('', "HTTP/1.1 300 Multiple Choices\r\n", $req);
+        $this->assertFalse($response->hasErrors());
+        $response = new Response('', "HTTP/1.1 400 Bad Request\r\n", $req);
+        $this->assertTrue($response->hasErrors());
+        $response = new Response('', "HTTP/1.1 500 Internal Server Error\r\n", $req);
+        $this->assertTrue($response->hasErrors());
+    }
+
+    function testWhenError() {
+        $caught = false;
+
+        try {
+            Request::get('malformed:url')
+                ->whenError(function($error) use(&$caught) {
+                    $caught = true;
+                })
+                ->timeoutIn(0.1)
+                ->send();
+        } catch (\Httpful\Exception\ConnectionErrorException $e) {}
+
+        $this->assertTrue($caught);
+    }
+
+    function testBeforeSend() {
+        $invoked = false;
+        $changed = false;
+        $self = $this;
+
+        try {
+            Request::get('malformed://url')
+                ->beforeSend(function($request) use(&$invoked,$self) {
+                    $self->assertEquals('malformed://url', $request->uri);
+                    $self->assertEquals('A payload', $request->serialized_payload);
+                    $request->uri('malformed2://url');
+                    $invoked = true;
+                })
+                ->whenError(function($error) { /* Be silent */ })
+                ->body('A payload')
+                ->send();
+        } catch (\Httpful\Exception\ConnectionErrorException $e) {
+            $this->assertTrue(strpos($e->getMessage(), 'malformed2') !== false);
+            $changed = true;
+        }
+
+        $this->assertTrue($invoked);
+        $this->assertTrue($changed);
+    }
+
+    function test_parseCode()
+    {
+        $req = Request::init()->sendsAndExpects(Mime::JSON);
+        $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req);
+        $code = $response->_parseCode("HTTP/1.1 406 Not Acceptable\r\n");
+        $this->assertEquals(406, $code);
+    }
+
+    function testToString()
+    {
+        $req = Request::init()->sendsAndExpects(Mime::JSON);
+        $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req);
+        $this->assertEquals(self::SAMPLE_JSON_RESPONSE, (string)$response);
+    }
+
+    function test_parseHeaders()
+    {
+        $parse_headers = Response\Headers::fromString(self::SAMPLE_JSON_HEADER);
+        $this->assertCount(3, $parse_headers);
+        $this->assertEquals('application/json', $parse_headers['Content-Type']);
+        $this->assertTrue(isset($parse_headers['Connection']));
+    }
+
+    function testMultiHeaders()
+    {
+        $req = Request::init();
+        $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_MULTI_HEADER, $req);
+        $parse_headers = $response->_parseHeaders(self::SAMPLE_MULTI_HEADER);
+        $this->assertEquals('Value1,Value2', $parse_headers['X-My-Header']);
+    }
+
+    function testDetectContentType()
+    {
+        $req = Request::init();
+        $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req);
+        $this->assertEquals('application/json', $response->headers['Content-Type']);
+    }
+
+    function testMissingBodyContentType()
+    {
+        $body = 'A string';
+        $request = Request::post(HttpfulTest::TEST_URL, $body)->_curlPrep();
+        $this->assertEquals($body, $request->serialized_payload);
+    }
+
+    function testParentType()
+    {
+        // Parent type
+        $request = Request::init()->sendsAndExpects(Mime::XML);
+        $response = new Response('<xml><name>Nathan</name></xml>', self::SAMPLE_VENDOR_HEADER, $request);
+
+        $this->assertEquals("application/xml", $response->parent_type);
+        $this->assertEquals(self::SAMPLE_VENDOR_TYPE, $response->content_type);
+        $this->assertTrue($response->is_mime_vendor_specific);
+
+        // Make sure we still parsed as if it were plain old XML
+        $this->assertEquals("Nathan", $response->body->name->__toString());
+    }
+
+    function testMissingContentType()
+    {
+        // Parent type
+        $request = Request::init()->sendsAndExpects(Mime::XML);
+        $response = new Response('<xml><name>Nathan</name></xml>',
+"HTTP/1.1 200 OK
+Connection: keep-alive
+Transfer-Encoding: chunked\r\n", $request);
+
+        $this->assertEquals("", $response->content_type);
+    }
+
+    function testCustomMimeRegistering()
+    {
+        // Register new mime type handler for "application/vnd.nategood.message+xml"
+        Httpful::register(self::SAMPLE_VENDOR_TYPE, new DemoMimeHandler());
+
+        $this->assertTrue(Httpful::hasParserRegistered(self::SAMPLE_VENDOR_TYPE));
+
+        $request = Request::init();
+        $response = new Response('<xml><name>Nathan</name></xml>', self::SAMPLE_VENDOR_HEADER, $request);
+
+        $this->assertEquals(self::SAMPLE_VENDOR_TYPE, $response->content_type);
+        $this->assertEquals('custom parse', $response->body);
+    }
+
+    public function testShorthandMimeDefinition()
+    {
+        $r = Request::init()->expects('json');
+        $this->assertEquals(Mime::JSON, $r->expected_type);
+
+        $r = Request::init()->expectsJson();
+        $this->assertEquals(Mime::JSON, $r->expected_type);
+    }
+
+    public function testOverrideXmlHandler()
+    {
+        // Lazy test...
+        $prev = \Httpful\Httpful::get(\Httpful\Mime::XML);
+        $this->assertEquals($prev, new \Httpful\Handlers\XmlHandler());
+        $conf = array('namespace' => 'http://example.com');
+        \Httpful\Httpful::register(\Httpful\Mime::XML, new \Httpful\Handlers\XmlHandler($conf));
+        $new = \Httpful\Httpful::get(\Httpful\Mime::XML);
+        $this->assertNotEquals($prev, $new);
+    }
+
+    public function testHasProxyWithoutProxy()
+    {
+        $r = Request::get('someUrl');
+        $this->assertFalse($r->hasProxy());
+    }
+
+    public function testHasProxyWithProxy()
+    {
+        $r = Request::get('some_other_url');
+        $r->useProxy('proxy.com');
+        $this->assertTrue($r->hasProxy());
+    }
+
+    public function testHasProxyWithEnvironmentProxy()
+    {
+        putenv('http_proxy=http://127.0.0.1:300/');
+        $r = Request::get('some_other_url');
+        $this->assertTrue($r->hasProxy());
+    }
+
+
+    public function testParseJSON()
+    {
+        $handler = new JsonHandler();
+
+        $bodies = array(
+            'foo',
+            array(),
+            array('foo', 'bar'),
+            null
+        );
+        foreach ($bodies as $body) {
+            $this->assertEquals($body, $handler->parse(json_encode($body)));
+        }
+
+        try {
+            $result = $handler->parse('invalid{json');
+        } catch (\Httpful\Exception\JsonParseException $e) {
+            $this->assertEquals('Unable to parse response as JSON: ' . json_last_error_msg(), $e->getMessage());
+            return;
+        }
+        $this->fail('Expected an exception to be thrown due to invalid json');
+    }
+
+    // /**
+    //  * Skeleton for testing against the 5.4 baked in server
+    //  */
+    // public function testLocalServer()
+    // {
+    //     if (!defined('WITHOUT_SERVER') || (defined('WITHOUT_SERVER') && !WITHOUT_SERVER)) {
+    //         // PHP test server seems to always set content type to application/octet-stream
+    //         // so force parsing as JSON here
+    //         Httpful::register('application/octet-stream', new \Httpful\Handlers\JsonHandler());
+    //         $response = Request::get(TEST_SERVER . '/test.json')
+    //             ->sendsAndExpects(MIME::JSON);
+    //         $response->send();
+    //         $this->assertTrue(...);
+    //     }
+    // }
+}
+
+class DemoMimeHandler extends \Httpful\Handlers\MimeHandlerAdapter
+{
+    public function parse($body)
+    {
+        return 'custom parse';
+    }
+}
+

+ 28 - 0
vendor/nategood/httpful/tests/Httpful/requestTest.php

@@ -0,0 +1,28 @@
+<?php
+/**
+ * @author nick fox <quixand gmail com>
+ */
+namespace Httpful\Test;
+
+class requestTest extends \PHPUnit\Framework\TestCase
+{
+
+    /**
+     * @author Nick Fox
+     */
+    public function testGet_InvalidURL()
+    {
+        // Silence the default logger via whenError override
+        $caught = false;
+        try
+        {
+            \Httpful\Request::get('unavailable.url')->whenError(function($error) {})->send();
+        }
+        catch (\Httpful\Exception\ConnectionErrorException $e)
+        {
+            $caught = true;
+        }
+        $this->assertTrue($caught);
+    }
+
+}

+ 42 - 0
vendor/nategood/httpful/tests/bootstrap-server.php

@@ -0,0 +1,42 @@
+<?php
+
+$php_version = phpversion();
+$php_major = floatval(substr($php_version, 0, 3));
+
+// Define SIGKILL if pcntl is not found
+if (!function_exists('pcntl_signal')) {
+    define('SIGKILL', 9);
+}
+
+if ($php_major < 5.4) {
+    define('WITHOUT_SERVER', true);
+} else {
+    // Command that starts the built-in web server
+    $command = sprintf('php -S %s:%d -t %s >./server.log 2>&1 & echo $!', WEB_SERVER_HOST, WEB_SERVER_PORT, WEB_SERVER_DOCROOT);
+
+    // Execute the command and store the process ID
+    $output = array();
+    exec($command, $output, $exit_code);
+
+    // sleep for a second to let server come up
+    sleep(1);
+    $pid = (int) $output[0];
+
+    // check server.log to see if it failed to start
+    $server_logs = file_get_contents("./server.log");
+    if (strpos($server_logs, "Fail") !== false) {
+        // server failed to start for some reason
+        print "Failed to start server! Logs:" . PHP_EOL . PHP_EOL;
+        print_r($server_logs);
+        exit(1);
+    }
+
+    echo sprintf('%s - Web server started on %s:%d with PID %d', date('r'), WEB_SERVER_HOST, WEB_SERVER_PORT, $pid) . PHP_EOL;
+
+    register_shutdown_function(function() {
+        // cleanup after ourselves -- remove log file, shut down server
+        global $pid;
+        unlink("./server.log");
+        posix_kill($pid, SIGKILL);
+    });
+}

+ 17 - 0
vendor/nategood/httpful/tests/phpunit.xml

@@ -0,0 +1,17 @@
+<phpunit bootstrap="./bootstrap-server.php">
+    <testsuites>
+        <testsuite name="Httpful">
+            <directory>.</directory>
+        </testsuite>
+    </testsuites>
+    <php>
+        <const name="WEB_SERVER_HOST" value="localhost" />
+        <const name="WEB_SERVER_PORT" value="1349" />
+        <const name="WEB_SERVER_DOCROOT" value="./static" />
+	<env name="http_proxy" value="" />
+    </php>
+    <logging>
+        <log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/>
+    </logging>
+</phpunit>
+

+ 1 - 0
vendor/nategood/httpful/tests/static/test.json

@@ -0,0 +1 @@
+{"foo": "bar", "baz": false}

BIN
vendor/nategood/httpful/tests/test_image.jpg


+ 3 - 0
vendor/perfectmak/paystack-php/.gitignore

@@ -0,0 +1,3 @@
+/vendor/
+.idea
+composer.lock

+ 16 - 0
vendor/perfectmak/paystack-php/.travis.yml

@@ -0,0 +1,16 @@
+language: php
+
+php:
+  - '5.5'
+  - '5.6'
+  - '7.0'
+  - hhvm
+
+matrix:
+    allow_failures:
+        - php: hhvm
+
+install: composer install --no-interaction --prefer-source
+  
+script:
+  - php vendor/bin/phpunit -c phpunit.xml

+ 53 - 0
vendor/perfectmak/paystack-php/README.md

@@ -0,0 +1,53 @@
+# Paystack PHP
+
+[![Build Status](https://secure.travis-ci.org/perfectmak/paystack-php.png?branch=master)](http://travis-ci.org/perfectmak/paystack-php)
+
+Paystack PHP is a library for using the [Paystack](http://paystack.com) API from PHP.
+
+While there are other PHP libraries for Paystack, this library is designed to make it less cumbersome to implement a 
+payment flow on the Paystack payment platform.
+
+## Installation
+
+To install using composer
+```
+composer install perfectmak/paystack-php
+```
+
+## Usage
+First you initialize the library with your secret key
+
+```php
+\Paystack\Paystack::init('__secret_key_here__');
+```
+
+### Transaction
+
+#### Initialize a transaction
+```php
+    $payment = \Paystack\Transaction::initialize([
+        'email' => 'jame@gosling.com',
+        'amount' => '3000'
+    ]);
+```
+
+### Customer
+
+#### Create Customer
+```php
+    $customer = \Paystack\Customer::create([
+        'email' => 'google@gosling.com',
+        'first_name' => 'Perfect',
+        'last_name' => 'Makanju',
+        'phone' => 'xxxxxxx'
+    ]);
+
+    echo 'Customer\'s first name is: '.$customer->first_name;
+```
+
+## Todo
+This library is far from complete and not yet stable. So I don't advice using it yet.
+
+- Finish up all the resources
+- Tests for each Resource
+- Fix travis build script

+ 32 - 0
vendor/perfectmak/paystack-php/composer.json

@@ -0,0 +1,32 @@
+{
+	"name": "perfectmak/paystack-php",
+    "type": "library",
+    "description": "Paystack Library for PHP",
+    "keywords": ["paystack","payment"],
+    "homepage": "https://github.com/Seldaek/monolog",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Perfect Makanju",
+            "email": "damiperfect@gmail.com",
+            "homepage": "http://about.me/perfectmak",
+            "role": "Developer"
+        }
+    ],
+    "require": {
+        "php": ">=5.5",
+        "nategood/httpful": "*"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "~4.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "": "lib",
+            "PaystackTest\\": "test"
+        },
+        "classmap": [
+            "tests/TestCase.php"
+        ]
+    }
+}

+ 31 - 0
vendor/perfectmak/paystack-php/example/index.php

@@ -0,0 +1,31 @@
+<?php
+
+require('../vendor/autoload.php');
+
+
+\Paystack\Paystack::init('sk_test_19f3325a5a0abe546b508cd5926f3d6223d4e4d8');
+
+try {
+//    $customer = \Paystack\Customer::create([
+//        'email' => 'damiperfect@yahoo.com',
+//        'first_name' => 'Perfecto',
+//        'last_name' => 'Makanjuo',
+//        'phone' => '08064474572'
+//    ]);
+//
+//    echo 'Customer\'s first name is: '.$customer->first_name;
+
+//    $payment = \Paystack\Transaction::initialize([
+//        'email' => 'damiperfect@gmail.com',
+//        'amount' => '3000'
+//    ]);
+
+    $transaction = \Paystack\Transaction::verify('3beczdakli');
+    echo $transaction->amount;
+}
+catch(Exception $e)
+{
+    echo $e->getMessage();
+}
+
+?>

+ 35 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Customer.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Paystack;
+
+use Paystack\Interfaces\IResource;
+use Paystack\Traits\ResourceTrait;
+
+class Customer implements IResource
+{
+    use ResourceTrait;
+
+    protected static $resourceUrl = '/customer';
+
+    public static function create(array $param)
+    {
+        return self::_create(self::url(), $param);
+    }
+
+    public static function get($id)
+    {
+        return self::_get(self::url('/'.$id));
+    }
+
+    public static function all()
+    {
+        throw new \Exception();
+    }
+
+    public function save()
+    {
+        throw new \Exception();
+    }
+}
+
+?>

+ 46 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Http/HttpClient.php

@@ -0,0 +1,46 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: perfectmak
+ * Date: 2/12/16
+ * Time: 3:46 PM
+ */
+
+namespace Paystack\Http;
+
+
+use Httpful\Http;
+use Paystack\Interfaces\Http\IClient;
+use Paystack\Interfaces\IRequest;
+use Paystack\Interfaces\IResponse;
+use Paystack\Paystack;
+use Paystack\Response;
+
+
+/**
+ * This HttpClient uses
+ * Class HttpClient
+ * @package Paystack\Http
+ */
+class HttpClient implements IClient
+{
+
+    /**
+     * @param IRequest $request
+     * @return IResponse
+     */
+    public function sendRequest(IRequest $request)
+    {
+        $response = \Httpful\Request::init()
+            ->addHeader('Authorization', 'Bearer '.Paystack::getApiKey())
+            ->uri($request->getUrl())
+            ->method($request->getType())
+            ->addHeaders($request->getHeaders())
+            ->body($request->getBody())
+            ->sendsJson()
+            ->send();
+
+        $iResponse = new Response($response->code, $response->body);
+        return $iResponse;
+    }
+}

+ 23 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Interfaces/Http/IClient.php

@@ -0,0 +1,23 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: perfectmak
+ * Date: 2/12/16
+ * Time: 3:46 PM
+ */
+
+namespace Paystack\Interfaces\Http;
+
+
+use Paystack\Interfaces\IRequest;
+use Paystack\Interfaces\IResponse;
+use Paystack\Response;
+
+interface IClient
+{
+    /**
+     * @param IRequest $request
+     * @return IResponse
+     */
+    public function sendRequest(IRequest $request);
+}

+ 44 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Interfaces/IRequest.php

@@ -0,0 +1,44 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: perfectmak
+ * Date: 2/12/16
+ * Time: 5:54 PM
+ */
+
+namespace Paystack\Interfaces;
+
+
+use Paystack\Interfaces\Http\IClient;
+
+interface IRequest
+{
+    const TYPE_POST = 'POST';
+    const TYPE_GET = 'GET';
+    const TYPE_DELETE = 'DELETE';
+
+
+    public static function setClient(IClient $client);
+
+    public static function getClient();
+
+    public function setUrl($url);
+
+    public function getUrl();
+
+    public function setType($type);
+
+    public function getType();
+
+    public function setHeader($key, $value);
+
+    public function getHeader($key);
+
+    public function getHeaders();
+
+    public function setBody($body);
+
+    public function getBody();
+
+    public function send();
+}

+ 11 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Interfaces/IResource.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace Paystack\Interfaces;
+
+
+interface IResource
+{
+
+}
+
+?>

+ 21 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Interfaces/IResponse.php

@@ -0,0 +1,21 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: perfectmak
+ * Date: 2/12/16
+ * Time: 5:54 PM
+ */
+
+namespace Paystack\Interfaces;
+
+
+interface IResponse
+{
+    const CODE_OK = 200;
+    const CODE_VALIDATION_ERROR = 400;
+    const CODE_NOT_FOUND = 404;
+
+    public function getCode();
+
+    public function getBody();
+}

+ 47 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Paystack.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace Paystack;
+
+use Paystack\Http\HttpClient;
+
+class Paystack
+{
+    private static $apiKey;
+
+    const API_URL = 'https://api.paystack.co';
+
+
+    public static function init($apiKey)
+    {
+        //setup API Key
+        self::setApiKey($apiKey);
+
+        //set default request httpclient
+        Request::setClient(new HttpClient());
+    }
+
+    /**
+     * @return mixed
+     */
+    public static function getApiKey()
+    {
+        return self::$apiKey;
+    }
+
+    /**
+     * @param mixed $apiKey
+     */
+    public static function setApiKey($apiKey)
+    {
+        self::$apiKey = $apiKey;
+    }
+
+    public static function api($resourceUrl)
+    {
+        return self::API_URL.$resourceUrl;
+    }
+
+}
+
+
+?>

+ 35 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Plan.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Paystack;
+
+
+use Paystack\Interfaces\IResource;
+use Paystack\Traits\ResourceTrait;
+
+class Customer implements IResource
+{
+    use ResourceTrait;
+
+    protected static $resourceUrl = '/plan';
+
+    public static function create(array $params)
+    {
+        return self::_create(self::url('/create'), $params);
+    }
+
+    public static function get($id)
+    {
+        return self::_get(self::url('/'.$id));
+    }
+
+    public static function all()
+    {
+        throw new \Exception();
+    }
+
+    public function save()
+    {
+        throw new \Exception();
+    }
+}
+?>

+ 101 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Request.php

@@ -0,0 +1,101 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: perfectmak
+ * Date: 2/12/16
+ * Time: 5:53 PM
+ */
+
+namespace Paystack;
+
+
+use Paystack\Interfaces\Http\IClient;
+use Paystack\Interfaces\IRequest;
+
+class Request implements  IRequest
+{
+    private $_type = false;
+
+    private $_headers = [];
+
+    private $_body = '';
+
+    private $_url = '';
+
+    /**
+     * @var IClient
+     */
+    private static $_httpClient = null;
+
+    public function setHeader($key, $value)
+    {
+        $this->_headers[$key] = $value;
+
+        return $this;
+    }
+
+    public function getHeader($key)
+    {
+        return $this->_headers[$key];
+    }
+
+    public function getHeaders()
+    {
+        return $this->_headers;
+    }
+
+    public function setBody($body)
+    {
+        $this->_body = $body;
+
+        return $this;
+    }
+
+    public function getBody()
+    {
+        return $this->_body;
+    }
+
+    public function setUrl($url)
+    {
+        $this->_url = $url;
+
+        return $this;
+    }
+
+    public function getUrl()
+    {
+        return $this->_url;
+    }
+
+    public function setType($type)
+    {
+        $this->_type = $type;
+
+        return $this;
+    }
+
+    public function getType()
+    {
+        return $this->_type;
+    }
+
+
+    public static function setClient(IClient $client)
+    {
+        self::$_httpClient = $client;
+    }
+
+    public static function getClient()
+    {
+        return self::$_httpClient;
+    }
+
+    public function send()
+    {
+        if(!is_null(self::$_httpClient))
+            return self::$_httpClient->sendRequest($this);
+
+        throw new \Exception('No HttpClient specified for requests');
+    }
+}

+ 45 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Response.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: perfectmak
+ * Date: 2/12/16
+ * Time: 5:54 PM
+ */
+
+namespace Paystack;
+
+
+use Paystack\Interfaces\IResponse;
+
+class Response implements IResponse
+{
+    private $_code = null;
+
+    private $_body = null;
+
+    public function __construct($code, $body)
+    {
+        $this->setCode($code);
+        $this->setBody($body);
+    }
+
+    public function getCode()
+    {
+        return $this->_code;
+    }
+
+    public function getBody()
+    {
+        return $this->_body;
+    }
+
+    private function setCode($code)
+    {
+        $this->_code = $code;
+    }
+
+    private function setBody($body)
+    {
+        $this->_body = $body;
+    }
+}

+ 12 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Subscription.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace Paystack;
+
+use Paystack\Interfaces\IResource;
+
+class Subscription implements IResource
+{
+
+}
+
+?>

+ 89 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Traits/ResourceTrait.php

@@ -0,0 +1,89 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: perfectmak
+ * Date: 2/12/16
+ * Time: 4:01 PM
+ */
+
+namespace Paystack\Traits;
+
+
+use Paystack\Interfaces\IRequest;
+use Paystack\Interfaces\IResponse;
+use Paystack\Paystack;
+use Paystack\Request;
+
+trait ResourceTrait
+{
+    protected $_attributes = [];
+    protected function __construct($params)
+    {
+        if(is_array($params))
+            $this->_attributes = $params;
+        else{
+            $this->_attributes = json_decode(json_encode($params), true);
+        }
+    }
+
+
+    /**
+     * @param $url
+     * @param array $params
+     * @return static
+     * @throws \Exception
+     */
+    protected static function _create($url, array $params)
+    {
+        $response = (new Request())
+            ->setUrl(Paystack::api($url))
+            ->setType(IRequest::TYPE_POST)
+            ->setBody(json_encode($params))
+            ->send();
+
+
+        if($response->getCode() === IResponse::CODE_VALIDATION_ERROR)
+            throw new \Exception($response->getBody()->message);
+
+        return new static($response->getBody()->data);
+    }
+
+    protected static function _get($url)
+    {
+        $response = (new Request())
+            ->setUrl(Paystack::api($url))
+            ->setType(IRequest::TYPE_GET)
+            ->send();
+
+
+        if($response->getCode() === IResponse::CODE_VALIDATION_ERROR)
+            throw new \Exception($response->getBody()->message);
+
+        return new static($response->getBody()->data);
+    }
+
+    public function __get($key)
+    {
+        if(array_key_exists($key, $this->_attributes))
+        {
+            return $this->_attributes[$key];
+        }
+        else
+            return null;
+    }
+
+    /**
+     * @param $key
+     * @param $value
+     * @todo Should check in a list of properties defined in Resource class
+     */
+    public function __set($key, $value)
+    {
+        $this->_attributes[$key] = $value;
+    }
+
+    protected static function url($path = ''){
+        return rtrim(self::$resourceUrl, '/').'/'
+            .ltrim($path, '/');
+    }
+}

+ 51 - 0
vendor/perfectmak/paystack-php/lib/Paystack/Transaction.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace Paystack;
+
+use Paystack\Interfaces\IResource;
+use Paystack\Traits\ResourceTrait;
+
+class Transaction implements IResource
+{
+    use ResourceTrait;
+
+    protected static $resourceUrl = '/transaction';
+
+    public static function initialize(array $params)
+    {
+        return self::_create(self::url('/initialize'), $params);
+    }
+
+    public static function chargeCode(array $params)
+    {
+        return self::_create(self::url('/charge_authorization'), $params);
+    }
+
+    public static function chargeToken(array $params)
+    {
+        return self::_create(self::url('/charge_token'), $params);
+    }
+
+    public static function verify($reference)
+    {
+        $url = self::url('/verify/'.$reference);
+        return self::_get($url, []);
+    }
+
+    public static function totals()
+    {
+        $transaction = self::_get(self::url('/totals'));
+        return $transaction->_attributes;
+    }
+
+    public static function get($id)
+    {
+        return self::_get(self::url('/'.$id));
+    }
+
+    public static function all()
+    {
+        throw new \Exception();
+    }
+}
+?>

+ 12 - 0
vendor/perfectmak/paystack-php/phpunit.xml

@@ -0,0 +1,12 @@
+<phpunit bootstrap="vendor/autoload.php" colors="true">
+    <testsuites>
+        <testsuite name="paystack-php Test Suite">
+            <directory suffix="Test.php">tests</directory>
+        </testsuite>
+    </testsuites>
+    <filter>
+        <whitelist>
+            <directory suffix=".php">src/</directory>
+        </whitelist>
+    </filter>
+</phpunit>

+ 18 - 0
vendor/perfectmak/paystack-php/tests/CustomerTest.php

@@ -0,0 +1,18 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: perfectmak
+ * Date: 2/12/16
+ * Time: 4:12 PM
+ */
+
+namespace PaystackTest;
+
+
+class CustomerTest extends TestCase
+{
+    public function testCustomerCreation()
+    {
+        $this->assertEquals(true,true);
+    }
+}

+ 26 - 0
vendor/perfectmak/paystack-php/tests/PaystackTest.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: perfectmak
+ * Date: 2/12/16
+ * Time: 5:58 PM
+ */
+
+namespace PaystackTest;
+
+
+use \Paystack\Paystack;
+
+class PaystackTest extends TestCase
+{
+    public function testApiKey()
+    {
+        $expectedApiKey = "sk_tst_111";
+
+        Paystack::setApiKey($expectedApiKey);
+
+        $actualApiKey = Paystack::getApiKey();
+
+        $this->assertEquals($actualApiKey, $expectedApiKey);
+    }
+}

+ 17 - 0
vendor/perfectmak/paystack-php/tests/TestCase.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: perfectmak
+ * Date: 2/12/16
+ * Time: 4:14 PM
+ */
+
+namespace PaystackTest;
+
+
+use PHPUnit_Framework_TestCase;
+
+class TestCase extends PHPUnit_Framework_TestCase
+{
+
+}

+ 21 - 0
vendor/smladeoye/yii2-paystack/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 332 - 0
vendor/smladeoye/yii2-paystack/README.md

@@ -0,0 +1,332 @@
+# yii2-paystack
+YII 2 component for paystack payment integration
+
+[![Latest Stable Version](https://poser.pugx.org/smladeoye/yii2-paystack/v/stable.png)](https://packagist.org/packages/smladeoye/yii2-paystack)
+[![Total Downloads](https://poser.pugx.org/smladeoye/yii2-paystack/downloads.png)](https://packagist.org/packages/smladeoye/yii2-paystack)
+
+- [Installation] (https://github.com/smladeoye/yii2-paystack#installation)
+- [Configuration] (https://github.com/smladeoye/yii2-paystack#configuration)
+- [Usage] (https://github.com/smladeoye/yii2-paystack#usage-example)
+- [Event Handling for Paystack Operations] (https://github.com/smladeoye/yii2-paystack#handling-events)
+- [Paystack Inline Widget] (https://github.com/smladeoye/yii2-paystack#using-the-paystack-inline-payment-widget)
+
+
+## Installation
+
+The preferred way to install this extension is through composer.
+
+Either run
+
+```bash
+composer require smladeoye/yii2-paystack
+```
+
+**or**
+
+add '"smladeoye/yii2-paystack": "1.0.0"' to the require section of your composer.json file, then run:
+
+```bash
+composer install
+```
+
+## Configuration
+
+In your configuration file (web.php) register the component with the necessary configurations, for example:
+
+```php
+'components'=>[
+    //  ...
+    'Paystack' => [
+        'class' => 'smladeoye\paystack\Paystack',
+    	'environment' => 'test',
+    	'testPublicKey'=>'pk_test_365589cc6b0e95f1fb53fa0eeaef6b1819f1b0f2',
+    	'testSecretKey'=>'sk_test_1a4a88eaec6f4f3b23771edb2c60fe8d8b95cbe',
+    	'livePublicKey'=>'',
+    	'liveSecretKey'=>'',
+    ],
+    //  ...
+]
+```
+
+## Usage Example
+```php
+
+// Initializing a payment transaction
+
+$paystack = Yii::$app->Paystack;
+
+$transaction = $paystack->transaction();
+$transaction->initialize(['email'=>'smladeoye@gmail.com','amount'=>'100000','currency'=>'NGN']);
+
+// check if an error occured during the operation
+if (!$transaction->hasError)
+{
+    //response property for response gotten for any operation
+    $response = $transaction->getResponse()
+
+    // redirect the user to the payment page gotten from the initialization
+    $transaction->redirect();
+}
+else
+{
+    // display message
+    echo $transaction->message;
+
+    // get all the errors information regarding the operation from paystack
+    $error = $transaction->getError();
+}
+
+```
+
+There are seven operations available that can be performed which have been grouped based on Paystack's own grouping.
+Each of the operations also have their individual methods that can be called for performing different actions
+(create, list -- fetchAll, fetch, update,...) which can accept all the necessary parameters as an array.
+
+The following are the available operations and methods (all sample codes are based on the demo configuration above):
+
+
+1. **customer**:   To initiatiate any customer operation:
+
+  ```php
+  $paystack = Yii::$app->Paystack;
+  $customer = $paystack->customer();
+  ```
+  Distinct methods available to customer:
+
+  - **whitelist** --> whitelist a particular customer.Example:
+
+  ```php
+        $customer->whitelist($customer_id);
+  ```
+  + **blacklist** --> blacklist a particular customer.Example:
+
+  ```php
+        $customer->blacklist($customer_id);
+  ```
+
+2. **transaction**:    To initiate a transaction operation:
+
+  ```php
+  $paystack = Yii::$app->Paystack;
+  $transaction = $paystack->transaction();
+  ```
+  Distinct methods available to transaction:
+
+  - **initialize** --> initialize a transaction; an authorization url is generated from this method after which the redirect method can then be called to redirect to the payment page. Example:
+
+  ```php
+  $transaction->initialize(['email'=>'smladeoye@gmail.com','amount'=>'10000']);
+  if (!$transaction->hasError)
+        $transaction->redirect();
+  ```
+  + **verify** --> verify a transaction.Example:
+
+  ```php
+        $transaction->verify($trans_reference);
+  ```
+
+  + **charge** --> charge authorization for recurring transactions.Example:
+
+  ```php
+  $transaction->charge($options = []);
+  ```
+
+  + **timeline** --> timeline for a particular transactions.Example:
+
+  ```php
+  $transaction->timeline($trx_id);
+  ```
+
+  + **total** --> get total for transactions within a specified range.Example:
+
+  ```php
+   $transaction->total($from,$to);
+   //An array could be provided instead with the available parameters in key => value format.
+  ```
+
+  + **export** --> export a range of transaction details;a url is generated from this method from which the
+    file can be downloaded. To get the path simpley call the path method or call the download method to download the file. Example:
+
+  ```php
+     $transaction->export($options = []);
+
+    //get download link url
+    $transaction->getExportUrl();
+  ```
+
+  OR to download the file, call:
+
+  ```php
+        $transaction->download();
+  ```
+
+3. **subscription**:    To initiate a subscription operation:
+
+  ```php
+  $paystack = Yii::$app->Paystack;
+  $subscription = $paystack->subscription();
+  ```
+  Distinct methods available to subscription:
+
+  + **enable** --> enable a customer subscription.Example:
+
+  ```php
+      $subscription->enable($code, $token);
+      //an array can be provided instead, containing the necessary parameters as key => value
+  ```
+
+
+  + **disable** --> disable a customer subscription.Example:
+
+  ```php
+      $subscription->disable($code, $token);
+      //an array can be provided instead, containing the necessary parameters as key => value
+  ```
+
+4. **subaccount**:    To initiate a subaccount operation:
+
+  ```php
+  $paystack = Yii::$app->Paystack;
+  $subaccount = $paystack->subaccount();
+  ```
+  Distinct methods available to subaccount:
+
+  - **listBank** --> list the available bank for creating subaccounts on the system.Example:
+
+  ```php
+          $subaccount->enable($code, $token);
+          //an array can be provided instead, containing the necessary parameters as key => value
+  ```
+
+5. **plan**:    To initiate a plan operation:
+
+  ```php
+  $paystack = Yii::$app->Paystack;
+  $plan = $paystack->plan();
+  ```
+  Plan operation contains all the basic methods (create,fetch,fetchAll,update);
+
+6. **page**:    To initiate a page operation:
+  ```php
+  $paystack = Yii::$app->Paystack;
+  $page = $paystack->page();
+  ```
+  Distinct methods available to page:
+
+  - **checkAvailability** --> check the availability of a particular slug.Example:
+
+  ```php
+          $page->checkAvailability($slud_id);
+  ```
+
+7.  **settlement**:    To initiate a settlement operation:
+  ```php
+  $paystack = Yii::$app->Paystack;
+  $settlement = $paystack->settlement();
+  ```
+  Distinct methods available to settlement:
+
+  - **fetchAll** --> fetch all settlements.Example:
+
+  ```php
+  $settlement->fetchAll($from_date,$to_date);
+  //an array can be provided instead, containing the necessary parameters as key => value
+  ```
+
+#### The follwing methods are also available:
+
++ **fetchAll**: The fetchall (list) method is available for all operations.Example:
+
+```php
+        $customer->fetchAll(['page'=>'','perPage'=>'']);
+```
+
++ **create**: The create method is available for customer, subscription, subaccount, page and plan operations.Example:
+
+```php
+        $customer->create(['email'=>'smladeoye@gmail.com']);
+```
+
++ **fetch**   --> The fetch method is available to all operations except settlement.Example:
+
+```php
+        $customer->fetch($customer_id);
+```
+
++ **update**  --> The update method is available for customer, subaccount, page and plan operations.Example:
+
+```php
+        $customer->update($id,$info = array();
+```
+### Handling Events
+The component also supports event handling before and after every request. You can read more on
+YII2 EVENTS [HERE] (http://www.yiiframework.com/doc-2.0/guide-concept-events.html).
+You can set the event handler by configuring the property beforeSend - (event before sending
+the request) and the afterSend - (event after sending the request).
+The event handlers can be set for all operations or for specific operations; event handlers set
+for specific operations overwrites the one set for all operations.
+Example:
+
+```php
+'Paystack' => [
+            'class' => 'smladeoye\paystack\Paystack',
+            ...//other configurations just like example above
+
+            //setting the event handler for all operations before any request is made
+            'beforeSend'=>'myFunction',
+
+            //will set the event handler for all operations after any request is made
+            'afterSend'=>'myFunction',
+
+            //setting the event handler for the transaction operation; this will overwrite the event handlers above
+            'transaction'=>array(
+                //handler for the event before any request is made for a transaction operation
+                'beforeSend'=>'myFunction',
+
+                //handler for the event after any request is made for a transaction operation
+                'afterSend'=>'myFunction',
+            )
+        ]
+```
+### Using the Paystack Inline Payment Widget
+
+To use the widget, call the widget from your view and set the widget parameters, example:
+
+```php
+use smladeoye\paystack\widget\PaystackWidget;
+
+    echo PaystackWidget::widget(
+        [
+        //set the text to be displayed on the button
+            'buttonText'=>'PAY',
+        //array to set other button attributes like id, class,style etc
+            'buttonOptions'=>array(
+                'class'=>'btn btn-danger',
+                'style'=>'width: 80px;',
+            ),
+        //array to set all necessary paystack inline payment options
+        //some values can be set dynamically by passing the element id as value (email,amount,currency,quantity)
+            'options'=>[
+                //your paystack public key
+                'key'=>Yii::$app->Paystack->testPublicKey,
+                'email'=>'smladeoye@ymail.com',
+                'ref'=>'123456789',
+                //'amount' => '#amount' --> the value is gotten from the html element with id = amount
+                // OR
+                'amount'=>'2000',
+                //'currency' => '#currency' --> the value is gotten from the html element with id = currency
+                // OR
+                'currency' =>'NGN',
+                'plan' =>'my-plan',
+                //'quantity' => '#quantity' --> the value is gotten from the html element with id = quantity
+                // OR
+                'quantity' =>'2',
+                //callbackUrl can be set, where the tansaction reference would be passed as GET parameter
+                'callbackUrl' =>'www.google.com',
+                //also u can override the default with the callback option, simply provide javascript anonymous function as a string
+                //'callback'=>'function(response){alert(response.trxref);};',
+            ],
+        ]
+    );
+
+```

+ 21 - 0
vendor/smladeoye/yii2-paystack/composer.json

@@ -0,0 +1,21 @@
+{
+    "name": "smladeoye/yii2-paystack",
+    "description": "Yii 2 Paystack Payment extension (Component and widget)",
+    "keywords": ["yii2", "paystack", "payment", "component","yii2 extension", "smladeoye", "widget", "paystack widget"],
+    "type": "yii2-extension",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "smladeoye",
+            "email": "smladeoye@gmail.com"
+        }
+    ],
+    "autoload": {
+        "psr-4": {
+            "smladeoye\\": ""
+        }
+    },
+    "require": {
+        "yiisoft/yii2": ">2.0.1"
+    }
+}

+ 150 - 0
vendor/smladeoye/yii2-paystack/paystack/Customer.php

@@ -0,0 +1,150 @@
+<?php
+namespace smladeoye\paystack;
+
+use yii\base\Component;
+
+class Customer extends Component
+{
+    /** @var array holds the default customer operation configuration */
+    private $customer = array(
+        'baseUrl'=>'/customer',
+        'riskActionUrl'=>'/set_risk_action',
+        'beforeSend'=>array(),
+        'afterSend'=>array()
+    );
+
+    /*Constructor method to setup paystack component consumer operation configurations
+    * @param $paystack, Paystack instance
+     *@param config, Yii2 default object configuration array
+    */
+    public function __construct(Paystack $paystack, $config = [])
+    {
+        $this->attachBehavior('Resources',array('class'=> Resources::className()));
+
+        $this->setPaystack($paystack);
+
+        $this->customer = array_replace($this->customer,$paystack->customer);
+        $this->setConfig($this->customer);
+
+        parent::__construct($config);
+    }
+
+    /** create a customer
+     * @param $options string|array
+     * @return $this
+     */
+    public function create($options = null)
+    {
+        if (!empty($options))
+        {
+            if (is_array($options))
+                $this->setRequestOptions($options);
+            else
+                $this->setRequestOptions(['email'=>$options]);
+        }
+
+        $this->sendRequest(Paystack::OP_CUST_CREATE,Paystack::METHOD_POST);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+    /** fetch all customers
+     * @param $page string|integer
+     * @param $per_page string|integer
+     * @return $this
+     */
+    public function fetchAll($page = null,$per_page = null)
+    {
+        $options = array();
+        if (is_array($page))
+        {
+            $this->setRequestOptions($page);
+        }
+        else
+        {
+            if ($page)
+                $options['page'] = $page;
+            if ($per_page)
+                $options['perPage'] = $per_page;
+
+            $this->setRequestOptions($options);
+        }
+
+        $this->sendRequest(Paystack::OP_CUST_LIST);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+    /** fetch a particular customer
+     * @param $id string|integer customer id
+     * @return $this
+     */
+    public function fetch($id = null)
+    {
+        $this->acceptArray(false);
+
+        $this->setRequestOptions($id);
+
+        $this->sendRequest(Paystack::OP_CUST_FETCH);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+    /** update a particular customer record
+     * @param $id string|integer customer id or reference
+     * @param $options array, other parameters
+     * @return $this
+     */
+    public function update($customer_id,$options = null)
+    {
+        if (is_array($customer_id) || empty($customer_id))
+            throw new InvalidArgumentException('Invalid argument supplied for customer id, id must be string');
+
+        $options['id'] = $customer_id;
+        $this->setRequestOptions($options);
+
+        $this->sendRequest(Paystack::OP_CUST_UPDATE,Paystack::METHOD_PUT);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+    /** whitelist a particular customer
+     * @param $customer_id string, customer id
+     * @return $this
+     */
+    public function whitelist($customer_id)
+    {
+        $options['risk_action'] = Paystack::WHITELIST;
+
+        $options['customer'] = $customer_id;
+
+        $this->setRequestOptions($options);
+
+        $this->sendRequest(Paystack::OP_CUST_WHITELIST,Paystack::METHOD_POST);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+    /** blacklist a particular customer
+     * @param $customer_id string, customer id
+     * @return $this
+     */
+    public function blacklist($customer_id)
+    {
+        $options['risk_action'] = Paystack::BLACKLIST;
+
+        $options['customer'] = $customer_id;
+
+        $this->setRequestOptions($options);
+
+        $this->sendRequest(Paystack::OP_CUST_BLACKLIST,Paystack::METHOD_POST);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+}

+ 136 - 0
vendor/smladeoye/yii2-paystack/paystack/Page.php

@@ -0,0 +1,136 @@
+<?php
+namespace smladeoye\paystack;
+
+use yii\base\Component;
+
+class Page extends Component
+{
+    /** @var array holds the default page operation configuration */
+    private $page = array(
+        'baseUrl'=>'/page',
+        'slugAvailabilityUrl'=>'/check_slug_availability',
+        'beforeSend'=>array(),
+        'afterSend'=>array()
+    );
+
+    /*Constructor method to setup paystack component, page operation configurations
+    * @param $paystack, Paystack instance
+     *@param config, Yii2 default object configuration array
+    */
+    public function __construct(Paystack $paystack, $config = [])
+    {
+        $this->attachBehavior('Resources',array('class'=> Resources::className()));
+
+        $this->setPaystack($paystack);
+
+        $this->page = array_replace($this->page,$paystack->page);
+        $this->setConfig($this->page);
+
+        parent::__construct($config);
+    }
+
+    /** create a page
+     * @param $name string|array array
+     * @return $this
+     */
+    public function create($name = null)
+    {
+        $options = array();
+        if (is_array($name))
+        {
+            $this->setRequestOptions($name);
+        }
+        else
+        {
+            if ($name)
+                $options['name'] = $name;
+            $this->setRequestOptions($options);
+        }
+
+        $this->sendRequest(Paystack::OP_PAGE_CREATE,Paystack::METHOD_POST);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+    /** fetch all pages
+     * @param $page string|integer
+     * @param $per_page string|integer
+     * @return $this
+     */
+    public function fetchAll($page = null,$per_page = null)
+    {
+        $options = array();
+        if (is_array($page))
+        {
+            $this->setRequestOptions($page);
+        }
+        else
+        {
+            if ($page)
+                $options['page'] = $page;
+            if ($per_page)
+                $options['perPage'] = $per_page;
+
+            $this->setRequestOptions($options);
+        }
+
+        $this->sendRequest(Paystack::OP_PLAN_LIST);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+    /** fetch a particular page
+     * @param $id string|integer page id or slug
+     * @return $this
+     */
+    public function fetch($id = null)
+    {
+        $this->acceptArray(false);
+
+        $this->setRequestOptions($id);
+
+        $this->sendRequest(Paystack::OP_PAGE_FETCH);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+    /** update a particular page info
+     * @param $id string|integer page id or slug
+     * @param $options array, other parameters
+     * @throws InvalidArgumentException when page_id is not provided
+     * @return $this
+     */
+    public function update($page_id,$options = null)
+    {
+        if (is_array($page_id) || empty($page_id))
+            throw new InvalidArgumentException('Invalid argument supplied for page id/slug, id must be integer or string');
+
+        $options['id'] = $page_id;
+        $this->setRequestOptions($options);
+
+        $this->sendRequest(Paystack::OP_PLAN_UPDATE,Paystack::METHOD_PUT);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+    /** check the availability of a particular page before deciding to create
+     * @param $id string|integer transaction id or reference
+     * @return $this
+     */
+    public function checkAvailability($id = null)
+    {
+        $this->acceptArray(false);
+
+        $this->setRequestOptions($id);
+
+        $this->sendRequest(Paystack::OP_PAGE_AVAILABILITY);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+}

+ 357 - 0
vendor/smladeoye/yii2-paystack/paystack/Paystack.php

@@ -0,0 +1,357 @@
+<?php
+namespace smladeoye\paystack;
+
+use yii\base\Component;
+
+class Paystack extends Component
+{
+    /** Operation type to initialize transaction    */
+    CONST OP_TRANS_INITIALIZE = 0;
+
+    /** Operation type to verify transaction    */
+    CONST OP_TRANS_VERIFY = 1;
+
+    /* Operation type to list all transaction  */
+    CONST OP_TRANS_LIST = 2;
+
+    /** Operation type to fetch transaction */
+    CONST OP_TRANS_FETCH = 3;
+
+    /** Operation type to charge authorization */
+    CONST OP_TRANS_CHARGE = 4;
+
+    /** Operation type to get transaction timeline */
+    CONST OP_TRANS_TIMELINE = 5;
+
+    /** Operation type to get transaction total */
+    CONST OP_TRANS_TOTAL = 6;
+
+    /** Operation type to expot transaction data
+     *  returns a link for the export
+     */
+    CONST OP_TRANS_EXPORT = 7;
+
+    /**
+     * Operation type to create customers
+     */
+    CONST OP_CUST_CREATE = 8;
+
+    /** Operation type to list all customers  */
+    CONST OP_CUST_LIST = 9;
+
+    /**
+     * Operation type to fetch a particular customer
+     * The cutomer code or email or id is required.
+     */
+    CONST OP_CUST_FETCH = 10;
+
+    /**
+     * Operation type to update a particular customer information
+     * The customer code or email or id will be required.
+     */
+    CONST OP_CUST_UPDATE = 11;
+
+    /** Operation type to whitelist a particular customer
+     * The customer code or email or id will be required*/
+    CONST OP_CUST_WHITELIST = 12;
+
+    /** Operation type to blacklist a particular customer
+     * The customer code or email or id will be required*/
+    CONST OP_CUST_BLACKLIST = 13;
+
+    /** Operation code to create a subaccount
+     * The business_name, settlement_name, account_number and percentage_charge are required*/
+    CONST OP_SUBACCOUNT_CREATE = 14;
+
+    /**
+     * Operation code to list all subaccounts
+     */
+    CONST OP_SUBACCOUNT_LIST = 15;
+
+    /**
+     * Operation code to fetch a particular subaccount
+     * the account id is required
+     */
+    CONST OP_SUBACCOUNT_FETCH = 16;
+
+    /**
+     * Operation code to update a particular subaccount
+     * the account id is required along with the updated info
+     */
+    CONST OP_SUBACCOUNT_UPDATE = 17;
+
+    /**
+     * Operation code to list banks associated with a subaccount
+     * the account id is required along with the updated info
+     */
+    CONST OP_SUBACCOUNT_BANKS = 18;
+
+    /** Operation code to create a plan  */
+    CONST OP_PLAN_CREATE = 19;
+
+    /** Operation code to list all plans  */
+    CONST OP_PLAN_LIST = 20;
+
+    /** Operation code to detch a plan  */
+    CONST OP_PLAN_FETCH = 21;
+
+    /** Operation code to update a plan  */
+    CONST OP_PLAN_UPDATE = 22;
+
+    CONST OP_SUBSCRIPTION_CREATE = 23;
+
+    CONST OP_SUBSCRIPTION_LIST = 24;
+
+    CONST OP_SUBSCRIPTION_FETCH = 25;
+
+    CONST OP_SUBSCRIPTION_ENABLE = 26;
+
+    CONST OP_SUBSCRIPTION_DISABLE = 27;
+
+    CONST OP_PAGE_CREATE = 28;
+
+    CONST OP_PAGE_LIST = 29;
+
+    CONST OP_PAGE_FETCH = 30;
+
+    CONST OP_PAGE_UPDATE = 31;
+
+    CONST OP_PAGE_AVAILABILITY = 32;
+
+    /** operation code to list settlements */
+    CONST OP_SETTLEMENT_LIST = 33;
+
+    /** Paystack whitelist code */
+    CONST WHITELIST = 'allow';
+
+    /** Paystack blacklist code */
+    CONST BLACKLIST = 'deny';
+
+    /** GET method for request */
+    CONST METHOD_GET = 'GET';
+
+    /** POST method for request */
+    CONST METHOD_POST = 'POST';
+
+    /** PUT method for request */
+    CONST METHOD_PUT = 'PUT';
+
+    /** DELETE method for request */
+    CONST METHOD_DELETE = 'DELETE';
+
+    //plan interval code for hourly
+    CONST PLAN_INTERVAL_HOURLY = 'hourly';
+
+    //plan interval code for daily
+    CONST PLAN_INTERVAL_DAILY = 'daily';
+
+    //plan interval code for weekly
+    CONST PLAN_INTERVAL_WEEKLY = 'weekly';
+
+    //plan interval code for monthly
+    CONST PLAN_INTERVAL_MONTHLY = 'monthly';
+
+    //plan interval code for yearly
+    CONST PLAN_INTERVAL_YEARLY = 'yearly';
+
+    /**
+     * Test environmet
+     */
+    CONST ENV_TEST = 'TEST';
+
+    /**
+     * Live environment
+     */
+    CONST ENV_LIVE = 'LIVE';
+
+    /** Callback url after a transaction has been performed */
+    public $callbackUrl;
+
+
+    /** @var   string test-key for the test environment */
+    /** @var boolean sets the environment for the request to LIVE when test is false */
+    public $environment = self::ENV_TEST;
+
+    /** @var bool set to false to allow unsecured connection to the paystack api */
+    public $verifyPeer;
+
+    public $testSecretKey;
+
+    public $testPublicKey;
+
+    /** @var   string live-key for the test environment */
+    public $livePublicKey;
+
+    /** @var  string live secret key used in live environment */
+    public $liveSecretKey;
+
+    /** @var  string Paystack API base url, replaces the defaultApiUrl when set */
+    public $apiUrl;
+
+    /** @var  string Paystack API default base url*/
+    private $defaultApiUrl = "https://api.paystack.co";
+
+    private static $reference;
+
+    public $header;
+    public $authHeader;
+
+    public $beforeSend;
+    public $afterSend;
+
+    public $transaction = array(
+        'baseUrl'=>'/transaction',
+        'initializeUrl'=>'/initialize',
+        'verifyUrl'=>'/verify',
+        'chargeUrl'=>'/charge_authorization',
+        'beforeSend'=>'',
+        'afterSend'=>''
+    );
+
+    public $customer = array(
+        'baseUrl'=>'/customer',
+        'beforeSend'=>'',
+        'afterSend'=>''
+    );
+
+    public $subaccount = array(
+        'baseUrl'=>'/subaccount',
+        'beforeSend'=>'',
+        'afterSend'=>''
+    );
+
+    public $plan = array(
+        'baseUrl'=>'/plan',
+        'beforeSend'=>'',
+        'afterSend'=>''
+    );
+
+    public $page = array(
+        'baseUrl'=>'/page',
+        'slugAvailabilityUrl'=>'/check_slug_availability',
+        'beforeSend'=>'',
+        'afterSend'=>''
+    );
+
+    public $subscription = array(
+        'baseUrl'=>'/subscription',
+        'beforeSend'=>'',
+        'afterSend'=>''
+    );
+
+    public $settlement = array(
+        'baseUrl'=>'/settlement',
+        'beforeSend'=>'',
+        'afterSend'=>''
+    );
+
+    public function transaction()
+    {
+        return new Transaction($this);
+    }
+
+    public function customer()
+    {
+        return new Customer($this);
+    }
+
+    public function subaccount()
+    {
+        return new SubAccount($this);
+    }
+
+    public function plan()
+    {
+        return new Plan($this);
+    }
+
+    public function subscription()
+    {
+        return new Subscription($this);
+    }
+
+    public function page()
+    {
+        return new Page($this);
+    }
+
+    public function settlement()
+    {
+        return new Settlement($this);
+    }
+
+    public function init()
+    {
+        parent::init();
+
+        $this->setApiUrl();
+        $this->setAuthorization();
+    }
+
+    protected function getSecretKey()
+    {
+        return (strtolower($this->environment) == strtolower(self::ENV_LIVE))?$this->liveSecretKey:$this->testSecretKey;
+    }
+
+    public function getAuthKeys()
+    {
+        if (strtolower($this->environment) == strtolower(self::ENV_LIVE))
+        {
+            return array('secret_key'=>$this->liveSecretKey,'public_key'=>$this->livePublicKey);
+        }
+        else
+        {
+            return array('secret_key'=>$this->testSecretKey,'public_key'=>$this->testPublicKey);
+        }
+    }
+
+    protected function setAuthorization()
+    {
+        $this->authHeader = array('Authorization: Bearer '.$this->getSecretKey());
+    }
+
+    public function setHeader($headers = array())
+    {
+        $this->header = array_merge($this->authHeader,$headers);
+    }
+
+    public function getHeader()
+    {
+        return $this->header?$this->header:$this->authHeader;
+    }
+
+    protected function setApiUrl($url = null)
+    {
+        if ($url == null)
+        {
+            if (!isset($this->apiUrl) || empty($this->apiUrl))
+                $this->apiUrl = $this->defaultApiUrl;
+        }
+        else
+        {
+            $this->apiUrl = $url;
+        }
+        return $this;
+    }
+
+    public function getApiUrl()
+    {
+        if (!isset($this->apiUrl) || empty($this->apiUrl))
+            $this->apiUrl = $this->defaultApiUrl;
+        return $this->apiUrl;
+    }
+
+    public static function generateRef($length = 10)
+    {
+        $characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+        $charactersLength = strlen($characters);
+        $randomString = '';
+
+        for ($i = 0; $i < $length; $i++)
+        {
+            $randomString .= $characters[rand(0, $charactersLength - 1)];
+        }
+
+        return $randomString;
+    }
+}

+ 108 - 0
vendor/smladeoye/yii2-paystack/paystack/Plan.php

@@ -0,0 +1,108 @@
+<?php
+namespace smladeoye\paystack;
+
+use yii\base\Component;
+
+class Plan extends Component
+{
+    /** @var array holds the default plan operation configuration */
+    private $plan = array(
+        'baseUrl'=>'/plan',
+        'beforeSend'=>array(),
+        'afterSend'=>array()
+    );
+
+    /*Constructor method to setup paystack component, plan operation configurations
+    * @param $paystack, Paystack instance
+     *@param config, Yii2 default object configuration array
+    */
+    public function __construct(Paystack $paystack, $config = [])
+    {
+        $this->attachBehavior('Resources',array('class'=> Resources::className()));
+
+        $this->setPaystack($paystack);
+
+        $this->plan = array_replace($this->plan,$paystack->plan);
+        $this->setConfig($this->plan);
+
+        parent::__construct($config);
+    }
+
+    /** create a plan
+     * @param $options array
+     * @return $this
+     */
+    public function create($options = null)
+    {
+        $this->setRequestOptions($options);
+
+        $this->sendRequest(Paystack::OP_PLAN_CREATE,Paystack::METHOD_POST);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+    /** fetch all plans
+     * @param $page integer|array
+     * @param $per_page string|integer
+     * @return $this
+     */
+    public function fetchAll($page = null,$per_page = null)
+    {
+        $options = array();
+        if (is_array($page))
+        {
+            $this->setRequestOptions($page);
+        }
+        else
+        {
+            if ($page)
+                $options['page'] = $page;
+            if ($per_page)
+                $options['perPage'] = $per_page;
+
+            $this->setRequestOptions($options);
+        }
+
+        $this->sendRequest(Paystack::OP_PLAN_LIST);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+    /** fetch a particular plan
+     * @param $id string|integer plan id or slug
+     * @return $this
+     */
+    public function fetch($id = null)
+    {
+        $this->acceptArray(false);
+
+        $this->setRequestOptions($id);
+
+        $this->sendRequest(Paystack::OP_PLAN_FETCH);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+
+    /** update a particular plan info
+     * @param $id string|integer page id or slug
+     * @param $options array, other parameters
+     * @throws InvalidArgumentException when account_id is not provided
+     * @return $this
+     */
+    public function update($account_id,$options = null)
+    {
+        if (is_array($account_id) || empty($account_id))
+            throw new InvalidArgumentException('Invalid argument supplied for plan id, id must be an integer or sting');
+
+        $options['id'] = $account_id;
+        $this->setRequestOptions($options);
+
+        $this->sendRequest(Paystack::OP_PLAN_UPDATE,Paystack::METHOD_PUT);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+}

+ 348 - 0
vendor/smladeoye/yii2-paystack/paystack/Resources.php

@@ -0,0 +1,348 @@
+<?php
+namespace smladeoye\paystack;
+
+use yii\base\Behavior;
+
+class Resources extends Behavior
+{
+    CONST EVENT_AFTER_SEND = 'afterSend';
+    CONST EVENT_BEFORE_SEND = 'beforeSend';
+
+    public $hasError = false;
+
+    private $error;
+    private $errors;
+    private $operationUrl;
+
+    private $accept_array = true;
+
+    private $response;
+
+    private $status = true;
+    private $message;
+    private $data;
+    private $meta;
+
+    protected $requestOptions = array();
+
+    private $_paystack;
+    private $_config;
+    private $_beforeSend;
+    private $_afterSend;
+
+    public function setPaystack(Paystack $paystack)
+    {
+        $this->_paystack = $paystack;
+    }
+
+    public function paystack()
+    {
+        return $this->_paystack;
+    }
+
+    public function getConfig()
+    {
+        return $this->_config;
+    }
+
+    private function verifyPeer()
+    {
+        return !($this->paystack()->verifyPeer === false);
+    }
+
+    public function onBeforeSend()
+    {
+        if (!empty($this->_beforeSend))
+        {
+            $this->owner->on('beforeSend',$this->_beforeSend);
+        }
+    }
+
+    public function afterSend()
+    {
+        $this->owner->trigger(self::EVENT_AFTER_SEND);
+    }
+
+    public function beforeSend()
+    {
+        $this->owner->trigger(self::EVENT_BEFORE_SEND);
+    }
+
+    public function onAfterSend()
+    {
+        if (!empty($this->_afterSend))
+        {
+            $this->owner->on('afterSend', $this->_afterSend);
+        }
+    }
+
+    public function setConfig($config)
+    {
+        $this->_config = $config;
+
+        $this->_beforeSend = $config['beforeSend']?:$this->paystack()->beforeSend;
+        $this->_afterSend = $config['afterSend']?:$this->paystack()->afterSend;
+
+        $this->onBeforeSend();
+        $this->onAfterSend();
+    }
+
+    public function setResponse($response)
+    {
+        $this->response = $response;
+
+        foreach ($response as $key => $value)
+        {
+            if (property_exists($this,$key))
+            {
+                $this->$key = $value;
+            }
+        }
+
+        if (!$this->status || isset($this->error) || isset($this->errors))
+        {
+            $this->hasError = true;
+        }
+    }
+
+    public function acceptArray($value)
+    {
+        if (!is_bool($value))
+            throw new \InvalidArgumentException('Value must be boolean');
+        $this->accept_array = $value;
+    }
+
+    public function canAcceptArray()
+    {
+        return $this->accept_array;
+    }
+
+    public function getError()
+    {
+        return isset($this->error)?$this->error:$this->errors;
+    }
+
+    public function getOperationUrl()
+    {
+        return $this->operationUrl;
+    }
+
+    public function setRequestOptions($options = null)
+    {
+        if (!empty($options))
+        {
+            if (!$this->accept_array && is_array($options))
+            {
+                throw new InvalidArgumentException('Array provided, expecting string or integer');
+            }
+
+            if (is_array($options))
+                $this->requestOptions = $options + $this->requestOptions;
+            else
+                $this->requestOptions = $options;
+        }
+
+        return $this->owner;
+    }
+
+    public function getRequestOptions()
+    {
+        if (!$this->accept_array && is_array($this->requestOptions))
+        {
+            throw new InvalidArgumentException('Array provided, expecting string or integer');
+        }
+        return $this->requestOptions;
+    }
+
+    public function getResponse()
+    {
+        return $this->response;
+    }
+
+    public function getStatus()
+    {
+        return $this->status;
+    }
+
+    public function getMessage()
+    {
+        return $this->message;
+    }
+
+    public function getMeta()
+    {
+        return $this->meta;
+    }
+
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    public function sendRequest($operation,$method = Paystack::METHOD_GET)
+    {
+        $ch = curl_init();
+
+        curl_setopt_array($ch, array(
+                CURLOPT_RETURNTRANSFER => 1,
+                CURLOPT_SSL_VERIFYPEER => $this->verifyPeer(),
+                CURLOPT_URL => $this->setOperationUrl($operation),
+                CURLOPT_HTTPHEADER => $this->paystack()->getHeader(),
+            )
+        );
+
+        $this->beforeSend();
+
+        if ($method== Paystack::METHOD_GET)
+        {
+            curl_setopt($ch,CURLOPT_POST, false );
+        }
+        elseif ($method == Paystack::METHOD_POST)
+        {
+            curl_setopt($ch, CURLOPT_POST, 1);
+            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getRequestOptions() );
+        }
+        elseif ($method == Paystack::METHOD_PUT)
+        {
+            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, Paystack::METHOD_PUT);
+            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getRequestOptions());
+        }
+        elseif ($method == Paystack::METHOD_DELETE)
+        {
+            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, Paystack::METHOD_DELETE);
+            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getRequestOptions());
+        }
+
+        $response = json_decode(curl_exec($ch),true);
+
+        if (curl_error($ch))
+        {
+            $this->hasError = true;
+            $this->error = curl_error($ch);
+        }
+        else
+        {
+            $this->setResponse($response);
+        }
+        $this->afterSend();
+
+        curl_close($ch);
+
+        return $this->response;
+    }
+
+    private function setOperationUrl($operation)
+    {
+        switch ($operation)
+        {
+            case Paystack::OP_TRANS_INITIALIZE:
+                $opUrl = $this->getConfig()['baseUrl'].$this->getConfig()['initializeUrl'];
+                break;
+
+            case Paystack::OP_TRANS_VERIFY:
+                $opUrl = $this->getConfig()['baseUrl'].$this->getConfig()['verifyUrl'].'/'.$this->getRequestOptions();
+                break;
+
+            case Paystack::OP_TRANS_FETCH:
+                $opUrl = $this->getConfig()['baseUrl'].'/'.$this->getRequestOptions();
+                break;
+
+            case Paystack::OP_TRANS_CHARGE:
+                $opUrl = $this->getConfig()['baseUrl'].$this->getConfig()['chargeUrl'];
+                break;
+
+            case Paystack::OP_TRANS_TIMELINE:
+                $opUrl = $this->getConfig()['baseUrl'].$this->getConfig()['timelineUrl'].'/'.$this->getRequestOptions();
+                break;
+
+            case Paystack::OP_TRANS_TOTAL:
+                $opUrl = $this->getConfig()['baseUrl'].$this->getConfig()['totalUrl'];
+                break;
+
+            case Paystack::OP_TRANS_EXPORT:
+                $opUrl = $this->getConfig()['baseUrl'].$this->getConfig()['exportUrl'];
+                break;
+
+            case Paystack::OP_CUST_FETCH:
+                $opUrl = $this->getConfig()['baseUrl'].'/'.$this->getRequestOptions();
+                break;
+
+            case Paystack::OP_CUST_UPDATE:
+                $opUrl = $this->getConfig()['baseUrl'].'/'.$this->getRequestOptions()['id'];
+                break;
+
+            case Paystack::OP_CUST_WHITELIST:
+                $opUrl = $this->getConfig()['baseUrl'].$this->getConfig()['riskActionUrl'];
+                break;
+
+            case Paystack::OP_CUST_BLACKLIST:
+                $opUrl = $this->getConfig()['baseUrl'].$this->getConfig()['riskActionUrl'];
+                break;
+
+            case Paystack::OP_SUBACCOUNT_FETCH:
+                $opUrl = $this->getConfig()['baseUrl'].'/'.$this->getRequestOptions();
+                break;
+
+            case Paystack::OP_SUBACCOUNT_UPDATE:
+                $opUrl = $this->getConfig()['baseUrl'].'/'.$this->getRequestOptions()['id'];
+                break;
+
+            case Paystack::OP_SUBACCOUNT_BANKS:
+                $opUrl = $this->getConfig()['listBank'];
+                break;
+
+            case Paystack::OP_PLAN_FETCH:
+                $opUrl = $this->getConfig()['baseUrl'].'/'.$this->getRequestOptions();
+                break;
+
+            case Paystack::OP_PLAN_UPDATE:
+                $opUrl = $this->getConfig()['baseUrl'].'/'.$this->getRequestOptions()['id'];
+                break;
+
+            case Paystack::OP_SUBSCRIPTION_FETCH:
+                $opUrl = $this->getConfig()['baseUrl'].'/'.$this->getRequestOptions();
+                break;
+
+            case Paystack::OP_SUBSCRIPTION_ENABLE:
+                $opUrl = $this->getConfig()['baseUrl'].$this->getConfig()['enableUrl'];
+                break;
+
+            case Paystack::OP_SUBSCRIPTION_DISABLE:
+                $opUrl = $this->getConfig()['baseUrl'].$this->getConfig()['disableUrl'];
+                break;
+
+            case Paystack::OP_PAGE_FETCH:
+                $opUrl = $this->getConfig()['baseUrl'].'/'.$this->getRequestOptions();
+                break;
+
+            case Paystack::OP_PAGE_UPDATE:
+                $opUrl = $this->getConfig()['baseUrl'].'/'.$this->getRequestOptions()['id'];
+                break;
+
+            case Paystack::OP_PAGE_AVAILABILITY:
+                $opUrl = $this->getConfig()['baseUrl'].$this->getConfig()['slugAvailabilityUrl'].'/'.$this->getRequestOptions();
+                break;
+
+            default:
+                $opUrl = $this->getConfig()['baseUrl'];
+                break;
+        }
+
+        $this->operationUrl = $this->paystack()->apiUrl.$opUrl;
+        return $this->operationUrl;
+    }
+
+    public function setResponseOptions()
+    {
+        if (!empty($this->data))
+        {
+            foreach ($this->data as $key => $data)
+            {
+                if (property_exists($this->owner,$key))
+                {
+                    $this->owner->$key = $data;
+                }
+            }
+        }
+    }
+}

+ 60 - 0
vendor/smladeoye/yii2-paystack/paystack/Settlement.php

@@ -0,0 +1,60 @@
+<?php
+namespace smladeoye\paystack;
+
+use yii\base\Component;
+
+class Settlement extends Component
+{
+    /** @var array holds the default paystack component settlement operation configuration */
+    private $settlement = array(
+        'baseUrl'=>'/settlement',
+        'beforeSend'=>array(),
+        'afterSend'=>array()
+    );
+
+    /*Constructor method to setup paystack component, settlement operation configurations
+    * @param $paystack, Paystack instance
+     *@param config, Yii2 default object configuration array
+    */
+    public function __construct(Paystack $paystack, $config = [])
+    {
+        $this->attachBehavior('Resources',array('class'=> Resources::className()));
+
+        $this->setPaystack($paystack);
+
+        $this->settlement = array_replace($this->settlement,$paystack->settlement);
+        $this->setConfig($this->settlement);
+
+        parent::__construct($config);
+    }
+
+    /** fetch all settlements
+     * @param $from string
+     * @param $to string
+     * @return $this
+     */
+    public function fetchAll($from = null,$to = null, $subaccount = null)
+    {
+        $options = array();
+        if (is_array($from))
+        {
+            $this->setRequestOptions($from);
+        }
+        else
+        {
+            if ($from)
+                $options['from'] = $from;
+            if ($to)
+                $options['to'] = $to;
+            if ($subaccount)
+                $options['subaccount'] = $subaccount;
+
+            $this->setRequestOptions($options);
+        }
+
+        $this->sendRequest(Paystack::OP_SETTLEMENT_LIST);
+        $this->setResponseOptions();
+
+        return $this;
+    }
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов