使用ios内购,需在项目数据库建立虚拟币相关表(虚拟币余额表、充值面额表、充值订单表等)上代码
苹果IAP内购验证工具类 IosVerifyUtil

import javax.net.ssl.*;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Locale;/*** @desc: 苹果IAP内购验证工具类* @author: hwm* @date: 2019/9/3 17:11*/
public class IosVerifyUtil {private static class TrustAnyTrustManager implements X509TrustManager {public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[]{};}}private static class TrustAnyHostnameVerifier implements HostnameVerifier {public boolean verify(String hostname, SSLSession session) {return true;}}// 沙盒private static final String url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";// 线上private static final String url_verify = "https://buy.itunes.apple.com/verifyReceipt";/*** 苹果服务器验证** @param receipt 账单* @return null 或返回结果 沙盒 https://sandbox.itunes.apple.com/verifyReceipt* @url 要验证的地址*/public static String buyAppVerify(String receipt, int type) {//环境判断 线上/开发环境用不同的请求链接String url = "";if (type == 0) {url = url_sandbox; //沙盒测试} else {url = url_verify; //线上测试}try {SSLContext sc = SSLContext.getInstance("SSL");sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());URL console = new URL(url);HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();conn.setSSLSocketFactory(sc.getSocketFactory());conn.setHostnameVerifier(new TrustAnyHostnameVerifier());conn.setRequestMethod("POST");conn.setRequestProperty("content-type", "text/json");conn.setRequestProperty("Proxy-Connection", "Keep-Alive");conn.setDoInput(true);conn.setDoOutput(true);BufferedOutputStream hurlBufOus = new BufferedOutputStream(conn.getOutputStream());String str = String.format(Locale.CHINA, "{\"receipt-data\":\"" + receipt + "\"}");//拼成固定的格式传给平台hurlBufOus.write(str.getBytes());hurlBufOus.flush();InputStream is = conn.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(is));String line = null;StringBuffer sb = new StringBuffer();while ((line = reader.readLine()) != null) {sb.append(line);}return sb.toString();} catch (Exception ex) {System.out.println("苹果服务器异常");ex.printStackTrace();}return null;}/*** 用BASE64加密** @param str* @return*/public static String getBASE64(String str) {byte[] b = str.getBytes();String s = null;if (b != null) {s = new sun.misc.BASE64Encoder().encode(b);}return s;}
}

定义api入参


import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;@Data
@ApiModel("apple支付表单信息")
public class IPayNotifyFrom {/*** 票据*/@ApiModelProperty("票据")private String payload;/*** 订单id 充值订单表*/@ApiModelProperty("订单id")private String payOrderId;
}

定义接口


import com.szylt.kidsays.project.manager.entity.AppNotifications;
import com.szylt.kidsays.project.vo.ResultVo;
import com.szylt.kidsays.project.form.apple.IPayNotifyFrom;
import com.szylt.kidsays.project.vo.apple.JsonResult;
import org.springframework.http.ResponseEntity;/*** ios支付接口*/
public interface IosPayService {/*** ios充值支付成功后验证结果* @param iPayNotifyVO* @param uId* @return*/ResponseEntity<JsonResult> iosPay(IPayNotifyFrom iPayNotifyVO,String uId);/*** ios购买结账* @param uId* @param orderId* @return*/ResultVo iosTheInvoicing(String uId, String orderId);/*** ios服务通知* @param notifications* @return*/void notification(AppNotifications notifications);
}

实现接口


import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.szylt.kidsays.common.config.utils.ResultVoUtil;
import com.szylt.kidsays.common.constant.ResultCode;
import com.szylt.kidsays.project.entity.HOrder;
import com.szylt.kidsays.project.manager.entity.AppNotifications;
import com.szylt.kidsays.project.manager.entity.HPayOrder;
import com.szylt.kidsays.project.manager.entity.Latest_Receipt_Info;
import com.szylt.kidsays.project.manager.service.IHPayOrderService;
import com.szylt.kidsays.project.manager.service.IHVirtualCoinService;
import com.szylt.kidsays.project.manager.service.IosPayService;
import com.szylt.kidsays.project.manager.config.util.IosVerifyUtil;
import com.szylt.kidsays.project.manager.vo.InvoicingVo;
import com.szylt.kidsays.project.service.IHOrderService;
import com.szylt.kidsays.project.vo.ResultVo;
import com.szylt.kidsays.project.form.apple.IPayNotifyFrom;
import com.szylt.kidsays.project.vo.apple.JsonResult;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.math.BigDecimal;
import java.util.*;/*** 支付业务接口实现*/
@Slf4j
@Service
@AllArgsConstructor
public class IosPayServiceImpl implements IosPayService {private static final Logger logger = LoggerFactory.getLogger("sys-pay");private IHPayOrderService payOrderService;private IHOrderService orderService;private IHVirtualCoinService virtualCoinService;/*** ios充值支付成功后验证结果* @param iPayNotifyVO* @param uId* @return*/@Overridepublic ResponseEntity<JsonResult> iosPay(IPayNotifyFrom iPayNotifyVO, String uId) {JsonResult r = new JsonResult();if (uId == null) {r.setCode("0");r.setMessage("未登录");return ResponseEntity.ok(r);}//线上环境验证   type值  为0的时候是 沙盒环境、1为 线上环境String verifyResult = IosVerifyUtil.buyAppVerify(iPayNotifyVO.getPayload(), 1);if (verifyResult == null) {r.setCode("0");r.setMessage("苹果验证失败,返回数据为空");} else {logger.info("线上,苹果平台返回JSON:" + verifyResult);JSONObject appleReturn = JSONObject.parseObject(verifyResult);String states = appleReturn.getString("status");//无数据则沙箱环境验证if ("21007".equals(states)) {verifyResult = IosVerifyUtil.buyAppVerify(iPayNotifyVO.getPayload(), 0);logger.info("沙盒环境,苹果平台返回JSON:" + verifyResult);appleReturn = JSONObject.parseObject(verifyResult);states = appleReturn.getString("status");}logger.info("苹果平台返回值:appleReturn" + appleReturn);// 前端所提供的收据是有效的    验证成功if (states.equals("0")) {String receipt = appleReturn.getString("receipt");JSONObject returnJson = JSONObject.parseObject(receipt);String inApp = returnJson.getString("in_app");String productId = null;String transactionId = null;List<HashMap> inApps = JSONObject.parseArray(inApp, HashMap.class);if (!CollectionUtils.isEmpty(inApps)) {ArrayList<String> transactionIds = new ArrayList<String>();for (HashMap app : inApps) {transactionIds.add((String) app.get("transaction_id"));transactionId = (String) app.get("transaction_id");productId = (String) app.get("product_id");}//交易列表包含当前交易,则 认为交易成功if (transactionIds.contains(transactionId)) {/*** 处理业务 修改充值订单数据 并增加用户余额*/
//                        boolean result = payOrderService.upPayOrder(uId,transactionId,productId,iPayNotifyVO.getPayOrderId());
//                        if (result){
//                            logger.info("交易成功,新增并处理订单:{}transactionId{}: " + transactionId);
//                            r.setCode("200");
//                            r.setMessage("充值成功");
//                            return ResponseEntity.ok(r);
//                        }r.setCode("0");r.setMessage("支付失败");logger.info("支付失败,错误码:" + states);return ResponseEntity.ok(r);}r.setCode("0");r.setMessage("当前交易不在交易列表中");return ResponseEntity.ok(r);}r.setCode("0");r.setMessage("未能获取获取到交易列表");} else {r.setCode("0");r.setMessage("支付失败");logger.info("支付失败,错误码:" + states);}}return ResponseEntity.ok(r);}/*** ios/购买结账   减用户余额* @param uId* @param orderId* @return*/@Transactional(rollbackFor = {Exception.class})@Overridepublic ResultVo iosTheInvoicing(String uId, String orderId) {InvoicingVo invoicingVo = new InvoicingVo();boolean upOrderResult = orderService.updateOrder(orderId, "Kind Says币结算", 2);//HPayOrder payOrder = payOrderService.queryPayOrder(orderId);QueryWrapper<HOrder> wrapper = new QueryWrapper<>();wrapper.eq("o_id",orderId);HOrder order = orderService.getOne(wrapper);BigDecimal balance = virtualCoinService.findVirtualCoin(uId);if (balance.compareTo(order.getOrderPriceUsd()) == -1){return ResultVoUtil.error(ResultCode.VIRTUAL_INSUFFICIENT_BALANCE,"Insufficient balance");}boolean result = virtualCoinService.subtraction(uId, String.valueOf(order.getOrderPriceUsd()));if (result){orderService.updateOrderPayment(orderId, null);invoicingVo.setState("SUCCESS");BigDecimal price = virtualCoinService.findVirtualCoin(uId);invoicingVo.setBalance(price);return ResultVoUtil.success(invoicingVo);}return ResultVoUtil.error(ResultCode.VIRTUAL_FAIL,"FAIL");}/*** ios服务通知 修改充值订单 增加用户余额* @param notifications* @return*/@Overridepublic void notification(AppNotifications notifications) {if (notifications.getNotification_type().equals("REFUND")){for (Latest_Receipt_Info lr:notifications.getUnified_receipt().getLatest_receipt_info()) {QueryWrapper<HPayOrder> wrapper = new QueryWrapper<>();wrapper.eq("transaction_id",lr.getTransaction_id());wrapper.eq("status",1);HPayOrder order = payOrderService.getOne(wrapper);if (Objects.isNull(order)){logger.info("apple已进行过退款");break;}boolean result = virtualCoinService.addition(order.getUId(), String.valueOf(order.getUId()));if (!result){logger.info("apple退款失败");}logger.info("apple退款成功");}}}}

定义api入口


import com.szylt.kidsays.login.authorization.BaseController;
import com.szylt.kidsays.project.manager.entity.AppNotifications;
import com.szylt.kidsays.project.manager.service.IosPayService;
import com.szylt.kidsays.project.vo.ResultVo;
import com.szylt.kidsays.project.form.apple.IPayNotifyFrom;
import com.szylt.kidsays.project.vo.apple.JsonResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;@RestController
@Api(tags = "Apple支付")
@Slf4j
@RequestMapping(value = "/apple")
@AllArgsConstructor
public class ApplePayController extends BaseController {private static final Logger logger = LoggerFactory.getLogger("sys-pay");private IosPayService iosPayService;@RequiresAuthentication@ApiOperation("ios充值支付成功后验证结果")@PostMapping("/iospay")public synchronized ResponseEntity<JsonResult> iosPay(@RequestBody IPayNotifyFrom iPayNotifyVO, HttpServletRequest request) {String uId = getTokenUid(request);logger.info("ApplePayController.iosPay>>>>>>>>>>>>>>>>>>{iospay}apple充值支验证:IPayNotifyFrom=",iPayNotifyVO);return iosPayService.iosPay(iPayNotifyVO,uId);}@RequiresAuthentication@ApiOperation("ios购买结账")@PostMapping("/checkout")public ResultVo iosTheInvoicing(@ApiParam(name = "orderId",value = "订单号") @RequestParam(value = "orderId",required = false)String orderId,HttpServletRequest request) {String uId = getTokenUid(request);return iosPayService.iosTheInvoicing(uId,orderId);}/*** ios服务通知*/@ApiOperation(value = "ios服务通知")@PostMapping("/notification")@ResponseBodypublic void notification(AppNotifications notifications) {logger.info("ApplePayController.notification>>>>>>>>>>>>>>>>>>{notification}退款通知处理:notifications=",notifications);iosPayService.notification(notifications);}}

需要注意 退款通知参数需要自己在项目定义 大家也可以自己参照ios开发文档自己编写(代码如下)

AppNotifications 类

import lombok.Data;@Data
public class AppNotifications {/*** App Store Connect 生成的标识符,App Store 使用该标识符来唯一标识用户订阅续订的自动续订订阅。将此值视为 64 位整数*/private String auto_renew_adam_id;/*** 用户订阅续订的自动续订订阅的产品标识符。*/private String  auto_renew_product_id;/*** 自动续订订阅产品的当前续订状态。请注意,这些值与收据中的值不同。auto_renew_status* 可能的值: true, false*/private String auto_renew_status;/*** 用户打开或关闭自动续订订阅的续订状态的时间,采用类似于 ISO 8601 标准的日期时间格式*/private String auto_renew_status_change_date;/*** 用户打开或关闭自动续订订阅的续订状态的时间,采用 UNIX 纪元时间格式,以毫秒为单位。使用此时间格式来处理日期*/private String auto_renew_status_change_date_ms;/*** 用户打开或关闭自动续订订阅的续订状态的时间,以太平洋标准时间表示*/private String auto_renew_status_change_date_pst;/*** 包含应用程序包 ID 的字符串*/private String bid;/*** 包含应用程序包版本的字符串*/private String bvrs;/*** App Store 生成收据的环境。* 可能的值: Sandbox, PROD*/private String environment;/*** 订阅过期的原因。此字段仅适用于过期的自动续订订阅*/private String expiration_intent;/*** 触发通知的订阅事件*/private String notification_type;/*** App Store 服务器通知为其发送的原始交易标识符。此字段仅出现时是CONSUMPTION_REQUESTnotification_typeCONSUMPTION_REQUEST*/private long original_transaction_id;/*** 与您在验证收据时在password字段中提交的共享机密相同的值。requestBody*/private String password;/*** 一个对象,其中包含有关应用程序最近的应用程序内购买交易的信息*/private Unified_receipt unified_receipt;}
Unified_receipt类

import lombok.Data;import java.util.List;@Data
public class Unified_receipt {/*** App Store 生成收据的环境。* 可能的值: Sandbox, Production*/private String environment;/*** 最新的 Base64 编码应用收据*/private byte latest_receipt;/***包含解码值的最新 100 笔应用内购买交易的数组。此数组不包括您的应用标记为已完成的消费品的交易。* 此数组的内容与用于接收验证的verifyReceipt端点响应中的内容相同。* latest_receiptresponseBody.Latest_receipt_info*/private List<Latest_Receipt_Info> latest_receipt_info;/*** 一个数组,其中每个元素都包含 中标识的每个自动续订订阅的挂起续订信息。* 此数组的内容与用于接收验证的verifyReceipt端点响应中的内容相同。* product_idresponseBody.Pending_renewal_info*/private List<Pending_Renewal_Info> pending_renewal_info;}
Latest_Receipt_Info 类

@Data
public class Latest_Receipt_Info {/*** 在与此交易相关联。仅当您的应用程序在用户购买时提供了该字段时,才会出现此字段;它只存在于沙盒环境中。appAccountTokenappAccountToken(_:)*/private String app_account_token;/*** App Store 以类似于 ISO 8601 的日期时间格式退款或撤销交易的时间。此字段仅适用于退款或撤销的交易*/private String cancellation_date;/*** App Store 退还交易或从家庭共享中撤销交易的时间,以 UNIX 纪元时间格式,以毫秒为单位。此字段仅适用于已退款或已撤销的交易。* 使用此时间格式处理日期。有关更多信息,请参阅 。cancellation_date_ms*/private String cancellation_date_ms;/*** App Store 退款或取消家庭共享的时间,以太平洋标准时间为准。此字段仅适用于已退款或已撤销的交易*/private String cancellation_date_pst;/*** 退款或撤销交易的原因。值“1” 表示客户由于您的应用程序中的实际或感知问题取消了他们的交易。值“0” 表示交易因其他原因被取消;例如,如果客户不小心进行了购买。* 可能的值: 1, 0*/private String cancellation_reason;/*** 订阅到期或续订的时间,以 UNIX 纪元时间格式,以毫秒为单位。使用此时间格式处理日期。请注意,此字段在收据中调用。expires_date_ms*/private String expires_date;/*** 订阅到期或续订的时间,以 UNIX 纪元时间格式,以毫秒为单位。使用此时间格式处理日期。有关更多信息,请参阅。expires_date_ms*/private String expires_date_ms;/*** 订阅到期或续订的时间,以太平洋标准时间表*/private String expires_date_pst;/*** 一个值,指示用户是产品的购买者,还是可以通过家庭共享访问产品的家庭成员。有关更多信息,请参阅。in_app_ownership_type* 可能的值: FAMILY_SHARED, PURCHASED*/private String in_app_ownership_type;/*** 自动续订订阅是否处于介绍价格期的指标。有关更多信息,请参阅。is_in_intro_offer_period* 可能的值: true, false*/private String is_in_intro_offer_period;/*** 订阅是否处于免费试用期的指标。有关更多信息,请参阅。is_trial_period* 可能的值: true, false*/private String is_trial_period;/*** 由于用户升级,系统取消订阅的指示符。此字段仅适用于升级事务。* 价值: true*/private String is_upgraded;/*** 您在 App Store Connect 中配置的订阅优惠的参考名称。当客户兑换订阅优惠代码时,会出现此字段。有关更多信息,请参阅。offer_code_ref_name*/private String offer_code_ref_name;/*** 原始应用购买的时间,采用类似于 ISO 8601 标准的日期时间格式*/private String original_purchase_date;/*** 原始应用购买的时间,以 UNIX 纪元时间格式,以毫秒为单位。使用此时间格式处理日期。此值表示订阅的初始购买日期。* 原始购买日期适用于所有产品类型,并在同一产品 ID 的所有交易中保持不变。该值对应于 StoreKit 中原始事务的属性。transactionDate*/private String original_purchase_date_ms;/*** 原始应用购买的时间,采用太平洋标准时间*/private String original_purchase_date_pst;/*** 原始购买的交易标识符。有关更多信息,请参阅。original_transaction_id*/private String original_transaction_id;/*** 用户兑换的订阅优惠的标识符。有关更多信息,请参阅。promotional_offer_id*/private String promotional_offer_id;/*** 所购买产品的唯一标识符。您在 App Store Connect 中创建产品时提供此值,它对应于存储在事务属性中的对象的属性。productIdentifierSKPaymentpayment*/private String product_id;/*** App Store 以类似于 ISO 8601 标准的日期时间格式向用户帐户收取订阅购买或续订费用的时间*/private String purchase_date;/*** App Store 向用户帐户收取订阅购买或过期后续费的时间,采用 UNIX 纪元时间格式,以毫秒为单位。使用此时间格式处理日期*/private String purchase_date_ms;/*** App Store 向用户帐户收取订阅购买或过期后续订费用的时间,以太平洋标准时间计算*/private String purchase_date_pst;/*** 购买的消耗品数量。该值对应于存储在事务quantity属性中的SKPayment对象的payment属性。“1”除非使用可变付款修改,否则该值通常为。最大值为“10”*/private String quantity;/*** 订阅所属的订阅组标识。此字段的值与 中的属性相同。subscriptionGroupIdentifierSKProduct*/private String subscription_group_identifier;/*** 交易的唯一标识符,例如购买、恢复或续订。有关更多信息,请参阅。transaction_id*/private String transaction_id;/*** 跨设备购买事件的唯一标识符,包括订阅续订事件。该值是识别订阅购买的主键*/private String web_order_line_item_id;}
Pending_Renewal_Info 类

import lombok.Data;@Data
public class Pending_Renewal_Info {/*** 自动续订订阅的当前续订首选项。此键的值对应于客户订阅续订的产品的属性。productIdentifier*/private String auto_renew_product_id;/*** 自动续订订阅的当前续订状态。有关更多信息,请参阅。auto_renew_status* 可能的值: 1, 0*/private String auto_renew_status;/*** 订阅过期的原因。此字段仅适用于包含过期、自动更新订阅的收据。有关更多信息,请参阅。expiration_intent* 可能的值: 1, 2, 3, 4, 5*/private String expiration_intent;/*** 订阅续订宽限期到期的时间,采用类似于 ISO 8601 的日期时间格式*/private String grace_period_expires_date;/*** 订阅续订宽限期到期的时间,采用 UNIX 纪元时间格式,以毫秒为单位。此密钥仅适用于启用了计费宽限期的应用程序以及用户在续订时遇到计费错误时。使用此时间格式处理日期*/private String grace_period_expires_date_ms;/*** 订阅续订宽限期到期的时间,在太平洋时区*/private String grace_period_expires_date_pst;/*** 指示 Apple 正在尝试自动续订过期订阅的标志。此字段仅在自动续订订阅处于计费重试状态时出现。有关更多信息,请参阅。is_in_billing_retry_period* 可能的值: 1, 0*/private String is_in_billing_retry_period;/*** 您在 App Store Connect 中配置的订阅优惠的参考名称。当客户兑换订阅优惠代码时,会出现此字段。有关更多信息,请参阅。offer_code_ref_name*/private String offer_code_ref_name;/*** 原始购买的交易标识符*/private String original_transaction_id;/*** 订阅价格上涨的价格同意状态。仅当 App Store 通知客户价格上涨时,才会出现此字段。如果客户同意,默认值为"0"和 更改为"1" 。* 可能的值: 1, 0*/private String price_consent_status;/*** 所购买产品的唯一标识符。您在 App Store Connect 中创建产品时提供此值,它对应于存储在事务属性中的对象的属性。productIdentifierSKPaymentpayment*/private String product_id;/*** 用户兑换的自动续订订阅的促销优惠的标识符。在 App Store Connect 中创建促销优惠时,您在促销优惠标识符字段中提供此值。有关更多信息,请参阅。promotional_offer_id*/private String promotional_offer_id;}

java集成ios内购\与ios退款通知处理相关推荐

  1. IOS内购验证 (Java版)

    此处给各位贴出apple官方文档 App 内购买项目配置流程 apple 收据文档 apple 收据responseBody字段释义 IOS内购逻辑图 IOS内购验证相关代码 package xxxx ...

  2. Unity接入iOS内购

    1.内购种类 consumable:可消费的,如游戏中的金币,用完还可以再购买. non-consumable:不可销毁的,一次购买,永久生效.比如去广告,解锁游戏关卡,这种商品只能购买一次. sub ...

  3. iOS内购流程文档-Lion

    iOS内购流程: iOS内购 什么时候用到呢? 虚拟产品就需要用到iOS内购; 购买的商品,是在本app中使用和消耗的,就一定要用内购,否则会被拒绝上线,例如:游戏币,在线书籍,app中使用的道具等. ...

  4. 苹果 iOS 内购三步曲:App 内退款、历史订单查询、绑定用户防掉单

    ????????关注后回复 "进群" ,拉你进程序员交流群???????? 转自:掘金 37手游iOS技术运营团队 https://juejin.cn/post/697473339 ...

  5. 苹果iOS内购三步曲:App内退款、历史订单查询、绑定用户防掉单!--- WWDC21

    一.前言 如果大家的 App 有使用 IAP 功能,那么可能会遇到用户反馈苹果充值成功,但是服务没有到账的情况,用户一般会提供这样的苹果收据: 用户反馈时提供的苹果收据中,有一个字段中 ORDER I ...

  6. iOS内购--java后台

    最近公司iOS发布了新版本,被拒,原因就是没有添加内购,并被严重警告,为此,不得已要加上iOS内购功能,以下就是我为了iOS内购所写的后台代码,首先看下支付的时序图吧: 简单说下,时序图的意思吧: 第 ...

  7. iOS内购-防越狱破解刷单

    ---------------------------2018.10.16更新--------------------------- 最近我们公司丢单率上涨,尤其是10月份比9月份来说丢单率翻了3倍, ...

  8. IOS内购经常遇到的一些问题,和一些容易混淆的点。

    Q1:内购和Apple Pay的区别? A1:内购是内购,Apple Pay是Apple Pay.我不知道有多少人第一次接触时,会把这俩概念混淆掉,这里你可以简单这么理解,虚拟的物品就是用内购,实际的 ...

  9. iOS 内购(In-App Purchase)详解

    iOS 内购(In-App Purchase)详解 概述 IAP 全称:In-App Purchase,是指苹果 App Store 的应用内购买,是苹果为 App 内购买虚拟商品或服务提供的一套交易 ...

最新文章

  1. centos 7 安装 Vue
  2. flash,flex,actionscript的关系
  3. 提高你开发效率的十五个 Visual Studio 使用技巧
  4. k8s 使用helm部署dashboard
  5. python中反斜杠_Python中的正斜杠/与反斜杠\
  6. MAT(Memory Analyzer Tool)工具入门介绍
  7. html密码验证 并跳转页面,vuejs 实现前后端分离登录验证和页面自动跳转
  8. 树莓派3vnc分辨率设置
  9. uva 10474 - Where is the Marble?
  10. 汇编 LED驱动 烧写bin文件到SD卡
  11. 坚持不懈2 android游戏,坚持不懈的赛跑者
  12. 信息流广告文案与创意设计
  13. Excel VBA:设置行高与列宽
  14. android 从服务端获取的图片怎么适配不同分屏幕的手机,移动端的适配|切图|标注...
  15. 如何彻底禁止易升更新Win10自动更新
  16. 新加坡设自动巡逻机器人,助力城市精细化治理
  17. bootstrap editable有默认值
  18. 修改完bug GIT的提交流程 及NVM的常用指令
  19. Jerry Wang的SAP工作日志 - 2016年1月
  20. 深度学习目标检测在实际场景中的应用(附源代码)

热门文章

  1. embed预览pdf_#网页中动态嵌入PDF文件/在线预览PDF内容#
  2. 惠普服务器修改raid,惠普笔记本电脑 - 将笔记本电脑存储控制器配置从 RAID 改为 IDE,然后再改回 RAID 会导致蓝屏事件...
  3. 电子工程专业评副高总结_副高职称工作总结
  4. 应届生Java面试经验总结
  5. C# 大帧头数据转化 备忘
  6. AI艺术的背后:详解文本生成图像模型【基于GAN】
  7. Excel表格在编辑状态时文字完整显示,但是预览和打印时表格内容显示不全问题,如何解决
  8. 再见 RPM/DEB/TAR!下一代全平台安装程序来了!
  9. php 界面库,常见MFC UI界面库
  10. FIFA23反作弊报错ea anticheat launcher is running解决办法