diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e731025 --- /dev/null +++ b/.gitignore @@ -0,0 +1,462 @@ +##### Windows +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +##### Linux +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +##### MacOS +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +##### Backup +*.bak +*.gho +*.ori +*.orig +*.tmp + +##### GPG +secring.* + +##### Dropbox +# Dropbox settings and caches +.dropbox +.dropbox.attr +.dropbox.cache + +##### SynopsysVCS +# Waveform formats +*.vcd +*.vpd +*.evcd +*.fsdb + +# Default name of the simulation executable. A different name can be +# specified with this switch (the associated daidir database name is +# also taken from here): -o / +simv + +# Generated for Verilog and VHDL top configs +simv.daidir/ +simv.db.dir/ + +# Infrastructure necessary to co-simulate SystemC models with +# Verilog/VHDL models. An alternate directory may be specified with this +# switch: -Mdir= +csrc/ + +# Log file - the following switch allows to specify the file that will be +# used to write all messages from simulation: -l +*.log + +# Coverage results (generated with urg) and database location. The +# following switch can also be used: urg -dir .vdb +simv.vdb/ +urgReport/ + +# DVE and UCLI related files. +DVEfiles/ +ucli.key + +# When the design is elaborated for DirectC, the following file is created +# with declarations for C/C++ functions. +vc_hdrs.h + +##### SVN +.svn/ + +##### Mercurial +.hg/ +.hgignore +.hgsigs +.hgsub +.hgsubstate +.hgtags + +##### Bazaar +.bzr/ +.bzrignore + +##### CVS +/CVS/* +**/CVS/* +.cvsignore +*/.cvsignore + +##### TortoiseGit +# Project-level settings +/.tgitconfig + +##### PuTTY +# Private key +*.ppk + +##### Vim +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +##### Emacs +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data + +##### SublimeText +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings + +##### Notepad++ +# Notepad++ backups # +*.bak + +##### TextMate +*.tmproj +*.tmproject +tmtags + +##### VisualStudioCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +##### NetBeans +**/nbproject/private/ +**/nbproject/Makefile-*.mk +**/nbproject/Package-*.bash +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +##### JetBrains +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +##### Eclipse +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +##### Dreamweaver +# DW Dreamweaver added files +_notes +_compareTemp +configs/ +dwsync.xml +dw_php_codehinting.config +*.mno + +##### CodeKit +# General CodeKit files to ignore +config.codekit +config.codekit3 +/min + +##### Gradle +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +##### Composer +composer.phar +/vendor/ + +# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control +# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file +composer.lock + +##### PHP CodeSniffer +# gitignore for the PHP Codesniffer framework +# website: https://github.com/squizlabs/PHP_CodeSniffer +# +# Recommended template: PHP.gitignore + +/wpcs/* + +##### SASS +.sass-cache/ +*.css.map +*.sass.map +*.scss.map diff --git a/README.md b/README.md new file mode 100644 index 0000000..a422ea8 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# 苹果内购 + +## 安装 + +composer + +```bash +$ php composer.phar require minz/apple-pay +``` + + +## 使用 +```php +// $receipt apple支付凭据 +$receipt = request()->get('receipt'); +// 内购商品id +$productId = request()->get('productId'); +// notice 如果商品为续订类产品,password需要输入 提供的共享密码,否则无需传递参数 +$applePay = new ApplePay($receipt, $password); +if ($applePay->verifyReceipt(true)) { + $result = $applePay->query($productId, function ($tradeNo, $returnData) use ($productId) { + // 检查此交易号是否被使用 + $transaction = Transaction::find($tradeNo); + if ($transaction) { + throw new Exception("此笔交易号已经被使用"); + } + + //进行本身业务操作 + DB::transaction(function () use ($tradeNo, $productId) { + //..... + //db操作 + }); + return true; + }); + if (!$result) { + throw new Exception("app上报productId与apple返回数据不统一"); + } + // 验证成功... + // return response +} else { + throw new Exception("applePay验证异常,请关注"); +} +``` \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100755 index 0000000..5885c80 --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "minz/apple-pay", + "description": "apple pay verify", + "keywords": [ + "apple pay" + ], + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "minz", + "email": "zhaomin2719@gmail.com" + } + ], + "minimum-stability": "dev", + "require": { + "php": ">=5.4.0", + "curl/curl": "*" + }, + "autoload": { + "psr-4": { + "Minz\\Apple\\Pay\\": "src" + } + } +} diff --git a/src/ApplePay.php b/src/ApplePay.php new file mode 100755 index 0000000..287455c --- /dev/null +++ b/src/ApplePay.php @@ -0,0 +1,221 @@ +receipt = $receipt; + $this->password = $password; + } + + /** + * 验证凭证 + * @param bool $sendBox 是否使用沙箱环境 + * @return bool + */ + private function verify($sendBox = false) + { + if (strlen($this->receipt) < 10) { + $this->error = '凭证数据长度太短,请确定数据正确!'; + return false; + } + $return = $this->postData($this->receipt, $this->password, $sendBox ? $this->testUrl : $this->url); + if ($return) { + $this->returnData = json_decode($return, true); + if ($this->returnData['status'] != 0) { + $this->setStatusError($this->returnData['status']); + return false; + } + return $this->returnData; + } else { + $this->error = '与苹果服务器通讯失败!'; + return false; + } + } + + /** + * 验证凭证 + * @param bool $verifySendbox 是否验证沙盒环境 + * @return bool + */ + public function verifyReceipt($verifySendbox = false) + { + // 验证正式 + if ($result = $this->verify(false)) { + return $result; + } + + // 验证沙盒 + if ($verifySendbox) { + if ($result = $this->verify(true)) { + return $result; + } + } + + return false; + } + + /** + * 设置状态错误消息 + * @param $status + */ + private function setStatusError($status) + { + switch (intval($status)) { + case 21000: + $error = 'AppleStore不能读取你提供的JSON对象'; + break; + case 21002: + $error = 'receipt-data域的数据有问题'; + break; + case 21003: + $error = 'receipt无法通过验证'; + break; + case 21004: + $error = '提供的shared secret不匹配你账号中的shared secret'; + break; + case 21005: + $error = 'receipt服务器当前不可用'; + break; + case 21006: + $error = 'receipt合法,但是订阅已过期'; + break; + case 21007: + $error = 'receipt是沙盒凭证,但却发送至生产环境的验证服务'; + break; + case 21008: + $error = 'receipt是生产凭证,但却发送至沙盒环境的验证服务'; + break; + default: + $error = '未知错误'; + } + $this->error = $error; + } + + /** + * 返回交易id + * @return mixed + */ + public function getTransactionId() + { + return $this->returnData['receipt']['in_app'][0]['transaction_id']; + } + + /** + * 查询数据是否有效 + * @param $productId + * @param \Closure $successCallBack + * @return bool + */ + public function query($productId, \Closure $successCallBack) + { + if ($this->returnData) { + if ($this->returnData['status'] == 0) { + if ($productId == $this->returnData['receipt']['in_app'][0]['product_id']) { + return call_user_func_array($successCallBack, [ + $this->getTransactionId(), + $this->returnData + ]); + } else { + $this->error = '非法的苹果商店product_id,这个凭证有可能是伪造的!'; + return false; + } + } else { + $this->error = '苹果服务器返回订单状态不正确!'; + return false; + } + } else { + $this->error = '无效的苹果服务器返回数据!'; + return false; + } + } + + /** + * 返回错误信息 + * @return string + */ + public function getError() + { + return $this->error; + } + + /** + * curl提交数据 + * @param $receipt_data + * @param string $password + * @param $url + * @return mixed + */ + private function postData($receipt_data, $password, $url) + { + if (isset($this->password) && $this->password) { + $postData = [ + "receipt-data" => $receipt_data, + 'password' => $password + ]; + } else { + $postData = [ + "receipt-data" => $receipt_data, + ]; + } + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData)); + $result = curl_exec($ch); + curl_close($ch); + return $result; + } +} \ No newline at end of file