侧边栏壁纸
博主头像
蔡关荣博客 博主等级

行动起来,活在当下

  • 累计撰写 19 篇文章
  • 累计创建 9 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录
PHP

PHP服务端处理 ios内购(IPA)

Administrator
2020-07-31 / 0 评论 / 0 点赞 / 7 阅读 / 0 字

支付功能在平时开发肯定都是家常便饭,我们在网站或者app中接入最多的就是支付宝和微信支付。ios App 端苹果内购什么情况下必须接入呢。

假入你的付费内容属于虚拟商品就必须接入苹果内购、否则审核就不能通过。那么哪些内容属于虚拟商品呢。举个栗子。像视频课程、网站会员、网站内的金币这些不需要配送实物的商品就属于虚拟商品。苹果官方规定必须使用苹果IAP应用内支付,给苹果分成30%。还是比较坑的。

苹果内购流程是通过客户端接入iOS的IAP模块后,由客户端发起支付,然后再把充值数据(receipt)发给服务端,最后由服务端远程调用AppStore服务器验证。这个过程要处理好订单问题,不然可能会出现丢单的情况。

上代码,这里我是在uniapp 里接入苹果内购。以下接口可在这里查看 这里使用vue的mixins 先定义一个applePay.js 文件

export default {

  data() {

    return {

      productIds: [

        'com.xxx.xxx.xxx',//在苹果开发者平台填写的产品ID,每一个都是唯一的

      ],

      iapChannel: null,

      object_id: "", 

      productId: '', //商品的标识

      appusername: '', //购买用户名称

      quantity: 1, //商品数量

      payedProductList: [], //从苹果服务器获取的已购买商品订单

    }

  },

  onHide() {

    plus.nativeUI.closeWaiting();

  },

  methods: {

    get_channel() {

      return new Promise((resolve, reject) => {

        plus.payment.getChannels((channels) => {

          console.log("获取到channel" + JSON.stringify(channels))

          for (var i in channels) {

            var channel = channels[i];

            if (channel.id === 'appleiap') {

              this.iapChannel = channel;

              resolve(channel)

            }

          }

          if (!this.iapChannel) {

            reject('暂不支持苹果 iap 支付')

          }

        }, (e) => {

          reject('获取支付通道列表失败:' + e.message)

        });

      });

    },

    // 获取已购买商品(非消耗性商品和订阅商品)

    restoreComplatePay() {

      this.payedProductList = [];

      this.iapChannel.restoreComplateRequest({}, function(response) {

        console.log(response);

      });

    },

    //从苹果服务器请求支付商品列表

    requestOrder() {

      return new Promise((resolve, reject) => {

        plus.nativeUI.showWaiting('检测支付环境...');

        this.iapChannel.requestOrder(this.productIds, function(e) { //IAP支付在调用plus.payment.request方法支付前须先向服务器请求获取商品的详细信息,否则会支付失败

          plus.nativeUI.closeWaiting();

          resolve(e)

        }, function(e) {

          plus.nativeUI.closeWaiting();

          plus.nativeUI.confirm("请求失败", function(e) {

            if (e.index == 0) {

              requestOrder();

            }

          }, '重新请求支付', ['确定', '取消']);

        });

      });

    },

    //带上当前需要支付的product_id 支付

    applePay() {

      let vm = this

      return new Promise((resolve, reject) => {

        plus.nativeUI.showWaiting('', {

          style: "black",

          background: "rgba(0,0,0,0)"

        });

        plus.payment.request(vm.iapChannel, {

          "productid": vm.productId,

          "username": vm.appusername,

          "quantity": vm.quantity,

        }, function(response) {

          plus.nativeUI.closeWaiting();

          //去服务端验证是否支付成功

          vm.$request({

            url: 'xxx',//后台验证是否支付成功

            data: {

              callback_data: JSON.stringify(response),

              object_id: vm.object_id

            },

            method: 'post',

            success: function(res) {

              console.log(res);//支付成功后的逻辑

            },

            error: function(e) {

              reject("支付失败");

            }

          })

        }, function(e) {

          plus.nativeUI.closeWaiting();

          reject("支付失败");

        });

      });

    }

  }

}

服务端

简单分为以下几步

- 接收ios客户端发过来的购买凭证。

- 判断凭证是否已经存在或验证过,然后存储该凭证。

- 将该凭证发送到苹果的服务器验证,并将验证结果返回给客户端。客户端处理后续相关业务

苹果的验证接口文档:传送门。简单来说就是将该购买凭证POST发送给苹果的验证服务器,苹果将验证结果以JSON形式返回!

ApplePay.php 单例类

<?php

/**

 * 苹果内购订单查询

 * Class ApplyPay

 */

class ApplePay

{

    static protected $instance;

    //沙箱环境请求url

    static protected $sandbox_url = "https://sandbox.itunes.apple.com/verifyReceipt";

    //正式环境请求url

    static protected $production_url = "https://buy.itunes.apple.com/verifyReceipt";

    public static function instance($options = [])

    {

        if (!self::$instance instanceof self) {

            self::$instance = new self($options);

        }

        return self::$instance;

    }

    /**

     * 验证AppStore 内付

     * @param $receipt_data ios客户端生成的验签token 

     * @return bool|mixed|string

     */

    public function validate_apple_pay($receipt_data, $is_debug = false)

    {

		//        21000    App Store 不能读取你提供的JSON对象

		//        21002    receipt-data 域的数据有问题

		//        21003    receipt 无法通过验证

		//        21004    提供的 shared secret 不匹配你账号中的 shared secret

		//        21005    receipt 服务器当前不可用

		//        21006    receipt 合法, 但是订阅已过期. 服务器接收到这个状态码时, receipt 数据仍然会解码并一起发送

		//        21007    receipt 是 Sandbox receipt, 但却发送至生产系统的验证服务

		//        21008    receipt 是生产 receipt, 但却发送至 Sandbox 环境的验证服务

        $request_data = '{"receipt-data":"' . $receipt_data . '"}';

        // 沙箱环境

        if ($is_debug) $response = $this->httpRequest(self::$sandbox_url, $request_data);

        // 正式环境

        else $response = $this->httpRequest(self::$production_url, $request_data);

        if (!$response || !is_array($response) || !isset($response['status'])) return false;

        if ($response['status'] != 0) return false;

        return $response;

    }

    /**

     * curl 请求

     * @param $url

     * @param array $postData

     * @param bool $json

     * @return bool|mixed|string

     */

    protected function httpRequest($url, $postData = array(), $json = true)

    {

        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        if ($postData) {

            curl_setopt($ch, CURLOPT_POST, 1);

            curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);

        }

        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);

        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);

        $info = curl_getinfo($ch);

        $data = curl_exec($ch);

        curl_close($ch);

        if ($json) {

            return json_decode($data, true);

        } else {

            return $data;

        }

    }

}

AppStore 服务器有两个,对应测试环境(沙盒测试)和正式环境:

测试环境: https://sandbox.itunes.apple.com/verifyReceipt

正式环境: https://buy.itunes.apple.com/verifyReceipt

我们开发的时候使用沙盒测试,在测试机上登陆沙箱账号即可

订单查询结果示例

Array

      (

      [receipt] => Array

     (

     [original_purchase_date_pst] => 2020-05-27 02:17:25 America/Los_Angeles //    原始购买日期(pst)

     [purchase_date_ms] => 1590573569426 //原始购买日期(ms)

     [unique_identifier] => fdced6f10c130c0dd430f9433ab3fcc29ac57fd5

     [original_transaction_id] => 1000000670630742 // 原始交易号

     [bvrs] => 100

     [transaction_id] => 1000000670666202 //    交易号

     [quantity] => 1 //购买数量

     [unique_vendor_identifier] => 3E2D4E5E-0875-45EC-82A5-F3BF37FD59FB

     [item_id] => 1515440278

     [version_external_identifier] => 0

     [bid] => com.alhelp.wordApp

     [is_in_intro_offer_period] => false

     [product_id] => collection_video //    商品标识符

     [purchase_date] => 2020-05-27 09:59:29 Etc/GMT //购买日期

     [is_trial_period] => false

     [purchase_date_pst] => 2020-05-27 02:59:29 America/Los_Angeles //购买日期(pst)

     [original_purchase_date] => 2020-05-27 09:17:25 Etc/GMT //    原始购买日期

     [original_purchase_date_ms] => 1590571045000

)

      [status] => 0

)

验证订单是否成功,主要看这几个数据:

1、`status`为 0 表示成功;其他均为失败

2、根据 receipt.in_app 字段判断iOS版本,验证方法也不同

iOS7及以上有in_app字段,验证 receipt.bundle_id 是否为你 App 的 bundle id,根据 in_app 处理充值的每一笔订单, 根据 product_id 判断用户充值了哪个档位,同时取出 transaction_id

iOS7以下没有in_app字段,验证 receipt.bid 是否为你 App 的 bundle id,根据 product_id 判断用户充值了哪个档位,同时取出 transaction_id

3、根据 transaction_id 对比数据库历史订单判断是否已处理过,没有则认为本次充值是有效的。

上面说到可能会出现掉单,比如网络异常,如果验证失败了,应该进行重试。

0

评论区