├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist └── src └── Omnipay └── WeChat ├── ExpressGateway.php └── Message ├── BaseAbstractRequest.php ├── WechatCompletePurchaseRequest.php ├── WechatCompletePurchaseResponse.php ├── WechatPrePurchaseRequest.php ├── WechatPurchaseRequest.php └── WechatPurchaseResponse.php /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /vendor 3 | composer.lock 4 | phpunit.xml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | 9 | before_script: 10 | - composer self-update 11 | - composer install -n --dev --prefer-source 12 | 13 | script: vendor/bin/phpcs --standard=PSR2 src && vendor/bin/phpunit --coverage-clover=coverage.xml 14 | 15 | after_success: 16 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Omnipay-WeChat 2 | === 3 | 4 | **WeChat Payment driver for the Omnipay PHP payment processing library** 5 | 6 | [![Build Status](https://img.shields.io/badge/project-deprecated-red.svg)](https://github.com/labs7in0/omnipay-wechat) 7 | [![Build Status](https://img.shields.io/travis/labs7in0/omnipay-wechat.svg)](https://travis-ci.org/labs7in0/omnipay-wechat) 8 | [![Coverage Status](https://img.shields.io/codecov/c/github/labs7in0/omnipay-wechat.svg)](https://codecov.io/github/labs7in0/omnipay-wechat) 9 | [![Packagist Status](https://img.shields.io/packagist/v/labs7in0/omnipay-wechat.svg)](https://packagist.org/packages/labs7in0/omnipay-wechat) 10 | [![Packagist Downloads](https://img.shields.io/packagist/dt/labs7in0/omnipay-wechat.svg)](https://packagist.org/packages/labs7in0/omnipay-wechat) 11 | 12 | **Deprecated** We suggest you to use [@lokielse](https://github.com/lokielse)'s implementation of WeChatPay for Omnipay at [lokielse/omnipay-wechatpay](https://github.com/lokielse/omnipay-wechatpay). 13 | 14 | There's a pre-built Payment Gateway based on Omnipay at [labs7in0/E-cash](https://github.com/labs7in0/E-cash). 15 | 16 | ## Installation 17 | 18 | Omnipay is installed via [Composer](http://getcomposer.org/). To install, simply add it 19 | to your `composer.json` file: 20 | 21 | ```json 22 | { 23 | "require": { 24 | "labs7in0/omnipay-wechat": "dev-master" 25 | } 26 | } 27 | ``` 28 | 29 | And run composer to update your dependencies: 30 | 31 | $ curl -s http://getcomposer.org/installer | php 32 | $ php composer.phar update 33 | 34 | ## Basic Usage 35 | 36 | The following gateways are provided by this package: 37 | 38 | * WeChat Express (WeChat NATIVE) 39 | 40 | For general usage instructions, please see the main [Omnipay](https://github.com/thephpleague/omnipay) 41 | repository. 42 | 43 | ## Example 44 | 45 | ### Make a payment 46 | 47 | The WeChat NATIVE payment gateway return a URI which can be opened within WeChat In-App broswer, you can generate a QR code with the URI. 48 | 49 | ```php 50 | $omnipay = Omnipay::create('WeChat_Express'); 51 | 52 | $omnipay->setAppId('app_id'); // App ID of your WeChat MP account 53 | $omnipay->setAppKey('app_key'); // App Key of your WeChat MP account 54 | $omnipay->setMchId('partner_id'); // Partner ID of your WeChat merchandiser (WeChat Pay) account 55 | 56 | $params = array( 57 | 'out_trade_no' => time() . rand(100, 999), // billing id in your system 58 | 'notify_url' => $notify_url, // URL for asynchronous notify 59 | 'body' => $billing_desc, // A simple description 60 | 'total_fee' => 0.01, // Amount with less than 2 decimals places 61 | 'fee_type' => 'CNY', // Currency name from ISO4217, Optional, default as CNY 62 | ); 63 | 64 | $response = $omnipay->purchase($params)->send(); 65 | 66 | $qrCode = new Endroid\QrCode\QrCode(); // Use Endroid\QrCode to generate the QR code 67 | $qrCode 68 | ->setText($response->getRedirectUrl()) 69 | ->setSize(120) 70 | ->setPadding(0) 71 | ->render(); 72 | ``` 73 | 74 | ### Verify a payment (especially for asynchronous notify) 75 | 76 | `completePurchase` for Omnipay-WeChat does not require the same arguments as when you made the initial `purchase` call. The only required parameter is `out_trade_no` (the billing id in your system) or `transaction_id` (the trade number from WeChat). 77 | 78 | ```php 79 | $omnipay = Omnipay::create('WeChat_Express'); 80 | 81 | $omnipay->setAppId('app_id'); // App ID of your WeChat MP account 82 | $omnipay->setAppKey('app_key'); // App Key of your WeChat MP account 83 | $omnipay->setMchId('partner_id'); // Partner ID of your WeChat merchandiser (WeChat Pay) account 84 | 85 | $params = array( 86 | 'out_trade_no' => $billing_id, // billing id in your system 87 | //or you can use 'transaction_id', the trade number from WeChat 88 | ); 89 | 90 | $response = $omnipay->completePurchase($params)->send(); 91 | 92 | if ($response->isSuccessful() && $response->isTradeStatusOk()) { 93 | $responseData = $response->getData(); 94 | 95 | // Do something here 96 | } 97 | 98 | ``` 99 | 100 | ## Donate us 101 | 102 | [Donate us](https://7in0.me/#donate) 103 | 104 | ## License 105 | The MIT License (MIT) 106 | 107 | More info see [LICENSE](LICENSE) 108 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "labs7in0/omnipay-wechat", 3 | "description": "WeChat driver for the Omnipay PHP payment processing library", 4 | "keywords": [ 5 | "gateway", 6 | "merchant", 7 | "omnipay", 8 | "pay", 9 | "payment", 10 | "purchase", 11 | "wechat" 12 | ], 13 | "homepage": "https://github.com/labs7in0/omnipay-wechat", 14 | "type": "library", 15 | "autoload": { 16 | "psr-0": {"Omnipay\\WeChat\\": "src/"} 17 | }, 18 | "require": { 19 | "omnipay/common": "~2.0" 20 | }, 21 | "require-dev": { 22 | "omnipay/tests": "~2.0" 23 | }, 24 | "license": "MIT", 25 | "authors": [ 26 | { 27 | "name": "7IN0SAN9", 28 | "email": "me@7in0.me" 29 | } 30 | ], 31 | "minimum-stability": "beta" 32 | } 33 | 34 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ./src 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Omnipay/WeChat/ExpressGateway.php: -------------------------------------------------------------------------------- 1 | setParameter('app_id', $appId); 17 | } 18 | 19 | public function getAppId() 20 | { 21 | return $this->getParameter('app_id'); 22 | } 23 | 24 | public function setAppKey($appKey) 25 | { 26 | $this->setParameter('app_key', $appKey); 27 | } 28 | 29 | public function getAppKey() 30 | { 31 | return $this->getParameter('app_key'); 32 | } 33 | 34 | public function setMchId($mchId) 35 | { 36 | $this->setParameter('mch_id', $mchId); 37 | } 38 | 39 | public function getMchId() 40 | { 41 | return $this->getParameter('mch_id'); 42 | } 43 | 44 | public function setNotifyUrl($url) 45 | { 46 | $this->setParameter('notify_url', $url); 47 | } 48 | 49 | public function getNotifyUrl() 50 | { 51 | return $this->getParameter('notify_url'); 52 | } 53 | 54 | public function purchase($parameters = array()) 55 | { 56 | if (empty($parameters['code_url'])) { 57 | $res = $this->prePurchase($parameters)->send(); 58 | } 59 | 60 | return $this->createRequest('\Omnipay\WeChat\Message\WechatPurchaseRequest', $res); 61 | } 62 | 63 | public function prePurchase($parameters = array()) 64 | { 65 | $params = array( 66 | 'app_id' => $this->getAppId(), 67 | 'mch_id' => $this->getMchId(), 68 | 'device_info' => 'WEB', 69 | 'noncestr' => bin2hex(openssl_random_pseudo_bytes(8)), 70 | 'body' => $parameters['body'], 71 | 'out_trade_no' => $parameters['out_trade_no'], 72 | 'total_fee' => round($parameters['total_fee'] * 100), 73 | 'fee_type' => $parameters['fee_type'], 74 | 'spbill_create_ip' => $_SERVER['REMOTE_ADDR'], 75 | 'notify_url' => $parameters['notify_url'], 76 | 'trade_type' => 'NATIVE', 77 | ); 78 | 79 | return $this->createRequest('\Omnipay\WeChat\Message\WechatPrePurchaseRequest', $params); 80 | } 81 | 82 | public function completePurchase($parameters = array()) 83 | { 84 | return $this->createRequest('\Omnipay\WeChat\Message\WechatCompletePurchaseRequest', $parameters); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Omnipay/WeChat/Message/BaseAbstractRequest.php: -------------------------------------------------------------------------------- 1 | setParameter('app_id', $appId); 12 | } 13 | 14 | public function getAppId() 15 | { 16 | return $this->getParameter('app_id'); 17 | } 18 | 19 | public function setAppKey($appKey) 20 | { 21 | $this->setParameter('app_key', $appKey); 22 | } 23 | 24 | public function getAppKey() 25 | { 26 | return $this->getParameter('app_key'); 27 | } 28 | 29 | public function setMchId($mchId) 30 | { 31 | $this->setParameter('mch_id', $mchId); 32 | } 33 | 34 | public function getMchId() 35 | { 36 | return $this->getParameter('mch_id'); 37 | } 38 | 39 | protected function postStr($url, $data) 40 | { 41 | $ch = curl_init(); 42 | 43 | $options = array( 44 | CURLOPT_HEADER => false, 45 | CURLOPT_POST => true, 46 | CURLOPT_POSTFIELDS => $data, 47 | CURLOPT_RETURNTRANSFER => true, 48 | CURLOPT_SSL_VERIFYHOST => false, 49 | CURLOPT_SSL_VERIFYPEER => false, 50 | CURLOPT_TIMEOUT => 30, 51 | CURLOPT_URL => $url, 52 | ); 53 | curl_setopt_array($ch, $options); 54 | $result = curl_exec($ch); 55 | curl_close($ch); 56 | 57 | return $result; 58 | } 59 | 60 | protected function arrayToXml($arr) 61 | { 62 | $xml = ""; 63 | foreach ($arr as $key => $val) { 64 | if (is_numeric($val)) { 65 | $xml .= "<" . $key . ">" . $val . ""; 66 | 67 | } else { 68 | $xml .= "<" . $key . ">"; 69 | } 70 | 71 | } 72 | $xml .= ""; 73 | return $xml; 74 | } 75 | 76 | protected function xmlToArray($xml) 77 | { 78 | $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); 79 | return $array_data; 80 | } 81 | 82 | protected static function httpBuildQueryWithoutNull($params) 83 | { 84 | foreach ($params as $key => $value) { 85 | if (null == $value || 'null' == $value || 'sign' == $key) { 86 | unset($params[$key]); 87 | } 88 | } 89 | reset($params); 90 | ksort($params); 91 | 92 | return http_build_query($params); 93 | } 94 | 95 | protected static function httpBuildQuery($params) 96 | { 97 | ksort($params); 98 | 99 | $str = http_build_query($params); 100 | 101 | return $str; 102 | } 103 | 104 | protected function genSign($params) 105 | { 106 | $bizParameters = array(); 107 | foreach ($params as $k => $v) { 108 | $bizParameters[strtolower($k)] = $v; 109 | } 110 | $bizString = self::httpBuildQueryWithoutNull($bizParameters); 111 | $bizString .= '&key=' . $this->getAppKey(); 112 | 113 | return strtoupper(md5(urldecode($bizString))); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Omnipay/WeChat/Message/WechatCompletePurchaseRequest.php: -------------------------------------------------------------------------------- 1 | getParameter('transaction_id'); 12 | } 13 | 14 | public function setTransactionId($value) 15 | { 16 | $this->setParameter('transaction_id', $value); 17 | } 18 | 19 | public function getOutTradeNo() 20 | { 21 | $this->getParameter('out_trade_no'); 22 | } 23 | 24 | public function setOutTradeNo($value) 25 | { 26 | $this->setParameter('out_trade_no', $value); 27 | } 28 | 29 | public function getData() 30 | { 31 | $this->validate( 32 | 'app_id', 33 | 'mch_id' 34 | ); 35 | 36 | $params['appid'] = $this->parameters->get('app_id'); 37 | $params['mch_id'] = $this->parameters->get('mch_id'); 38 | $params['nonce_str'] = bin2hex(openssl_random_pseudo_bytes(8)); 39 | $params['transaction_id'] = $this->parameters->get('transaction_id'); 40 | $params['out_trade_no'] = $this->parameters->get('out_trade_no'); 41 | return $params; 42 | } 43 | 44 | public function sendData($data) 45 | { 46 | $data = array( 47 | 'appid' => $data['appid'], 48 | 'mch_id' => $data['mch_id'], 49 | 'nonce_str' => $data['nonce_str'], 50 | 'transaction_id' => $data['transaction_id'], 51 | 'out_trade_no' => $data['out_trade_no'], 52 | ); 53 | 54 | $data['sign'] = $this->genSign($data); 55 | 56 | $data = $this->arrayToXml($data); 57 | 58 | $data = $this->xmlToArray($this->postStr($this->endpoint, $data)); 59 | 60 | return new WechatCompletePurchaseResponse($this, $data); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Omnipay/WeChat/Message/WechatCompletePurchaseResponse.php: -------------------------------------------------------------------------------- 1 | getData(); 12 | if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') { 13 | return true; 14 | } else { 15 | return false; 16 | } 17 | } 18 | 19 | public function isRedirect() 20 | { 21 | return false; 22 | } 23 | 24 | public function isTradeStatusOk() 25 | { 26 | $result = $this->getData(); 27 | return $result['trade_state'] == 'SUCCESS'; 28 | } 29 | 30 | public function getMessage() 31 | { 32 | $result = $this->getData(); 33 | return $result['return_msg']; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Omnipay/WeChat/Message/WechatPrePurchaseRequest.php: -------------------------------------------------------------------------------- 1 | response) { 14 | throw new \RuntimeException('Request cannot be modified after it has been sent!'); 15 | } 16 | 17 | $this->parameters = new ParameterBag; 18 | foreach ($parameters as $k => $v) { 19 | $this->parameters->set($k, $v); 20 | } 21 | return $this; 22 | } 23 | 24 | public function getData() 25 | { 26 | $this->validate( 27 | 'app_id', 28 | 'mch_id', 29 | 'body', 30 | 'out_trade_no', 31 | 'total_fee', 32 | 'spbill_create_ip', 33 | 'notify_url', 34 | 'trade_type' 35 | ); 36 | 37 | $params['appid'] = $this->parameters->get('app_id'); 38 | $params['mch_id'] = $this->parameters->get('mch_id'); 39 | $params['nonce_str'] = bin2hex(openssl_random_pseudo_bytes(8)); 40 | $params['body'] = $this->parameters->get('body'); 41 | $params['out_trade_no'] = $this->parameters->get('out_trade_no'); 42 | $params['total_fee'] = $this->parameters->get('total_fee'); 43 | $params['spbill_create_ip'] = $this->parameters->get('spbill_create_ip'); 44 | $params['time_start'] = date('YmdHis'); 45 | $params['notify_url'] = $this->parameters->get('notify_url'); 46 | $params['trade_type'] = $this->parameters->get('trade_type'); 47 | $params['product_id'] = $this->parameters->get('out_trade_no'); 48 | return $params; 49 | } 50 | 51 | public function sendData($data) 52 | { 53 | $data = array( 54 | 'appid' => $data['appid'], 55 | 'mch_id' => $data['mch_id'], 56 | 'device_info' => 'WEB', 57 | 'nonce_str' => $data['nonce_str'], 58 | 'body' => $data['body'], 59 | 'out_trade_no' => $data['out_trade_no'], 60 | 'total_fee' => $data['total_fee'], 61 | 'spbill_create_ip' => $data['spbill_create_ip'], 62 | 'time_start' => $data['time_start'], 63 | 'notify_url' => $data['notify_url'], 64 | 'trade_type' => $data['trade_type'], 65 | 'product_id' => $data['product_id'], 66 | ); 67 | 68 | $data['sign'] = $this->genSign($data); 69 | 70 | $data = $this->arrayToXml($data); 71 | 72 | return $this->xmlToArray($this->postStr($this->endpoint, $data)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Omnipay/WeChat/Message/WechatPurchaseRequest.php: -------------------------------------------------------------------------------- 1 | response) { 12 | throw new \RuntimeException('Request cannot be modified after it has been sent!'); 13 | } 14 | 15 | $this->parameters = new ParameterBag; 16 | foreach ($parameters as $k => $v) { 17 | $this->parameters->set($k, $v); 18 | } 19 | return $this; 20 | } 21 | 22 | public function getData() 23 | { 24 | $this->validate('code_url', 'timestamp'); 25 | 26 | $params['code_url'] = $this->parameters->get('code_url'); 27 | $params['timestamp'] = $this->parameters->get('timestamp'); 28 | 29 | if (empty($params['code_url'])) { 30 | throw new \RuntimeException('The code_url that pre-purchase responded is empty, check your parameters!'); 31 | } 32 | 33 | return $params; 34 | } 35 | 36 | public function sendData($data) 37 | { 38 | return new WechatPurchaseResponse($this, $data); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Omnipay/WeChat/Message/WechatPurchaseResponse.php: -------------------------------------------------------------------------------- 1 | getData(); 11 | return !empty($data['code_url']); 12 | } 13 | 14 | public function isSuccessful() 15 | { 16 | return false; 17 | } 18 | 19 | public function getRedirectUrl() 20 | { 21 | $data = $this->getData(); 22 | return $data['code_url']; 23 | } 24 | 25 | public function getRedirectMethod() 26 | { 27 | return 'GET'; 28 | } 29 | 30 | public function getRedirectData() 31 | { 32 | return null; 33 | } 34 | 35 | public function redirect() 36 | { 37 | return $this->getRedirectUrl(); 38 | } 39 | } 40 | --------------------------------------------------------------------------------