IOS支付,java后端代码

IOS支付,java后端代码

提示: 近期公司业务中APP新增了苹果内购支付,首次对接IOS支付,在此做记录。简单研究后发现,IOS支付和国内的微信支付宝支付流程有点不一样,IOS支付成功后也是依赖IOS APP客户端回调后台服务器,回调时携带IOS支付成功后的支付凭证等信息给服务器,跟微信支付宝不同的是微信和支付宝是通过他们的服务器通过REST API的方式回调,这可能和苹果是全球跨国企业,但有的国家对隐私做的比较严,不想暴露服务器的地址,所以才采用的这种方式,下面直接上代码

文章目录

  • IOS支付,java后端代码
  • 一、IOS需要在苹果系统中添加内购项目
  • 二、IOS支付成功之后客户端回调服务端代码
    • 1.Controller代码
    • 2.service代码
    • 3.IPayNotifyPo封装类
    • 4.IosVerifyUtil工具类
    • 5.Result返回客户端封装类
    • 6.pom文件

一、IOS需要在苹果系统中添加内购项目

注意:ios支付的金额是固定金额,跟支付宝微信不同的是,微信支付宝可以随意的输入金额。ios开发人员需要在苹果内购系统中配置好内购项目。
后端也要在数据库中配置跟ios内购项目相同的东西,以便在开发过程中能跟ios统一,结果如下:

	{
        "id": "1",
        "product_id": "001",
        "rmb_price": 1,
      },
      {
        "id": "2",
        "product_id": "002",
        "rmb_price": 6,
      },
      {
        "id": "3",
        "product_id": "003",
        "rmb_price": 12,
      }

二、IOS支付成功之后客户端回调服务端代码

注释:里边可能会涉及一些业务 需要自己来更改

1.Controller代码

/**
	 * IOS支付成功后通过APP回调服务器
	 *
	 * @param iPayNotifyPo
	 * @return
	 */
	@ApiOperation("ios支付成功后验证结果")
	@RequestMapping(value = "iPayNotify/", method = RequestMethod.POST)
	@ResponseBody
	public Result<Object> iPayNotify(@RequestBody IPayNotifyPo iPayNotifyPo) {
		logger.info("ios支付成功后验证结果[前端传递的ios支付参数:{}]", iPayNotifyPo.toString());
		String receipt = iPayNotifyPo.getTransactionReceipt();

		// 拿到收据的MD5
		String receiptMd5 = SecureUtil.md5(receipt);
		// 查询数据库,看是否是己经验证过的该支付收据
		boolean existsIOSReceipt = paymentService.isExistsIOSReceipt(receiptMd5);
		if (existsIOSReceipt) {
			return Result.error("该充值已完成");
		}

		// 1.先线上测试    发送平台验证
		String verifyResult = IosVerifyUtil.buyAppVerify(receipt, 1);
		logger.info("1,苹果返回的参数:{}]", verifyResult);
		if (verifyResult == null) {
			// 苹果服务器没有返回验证结果
			logger.info("苹果服务器没有返回验证结果");
			return Result.error("订单没有找到");
		} else {
			// 苹果验证有返回结果
			JSONObject job = JSONUtil.parseObj(verifyResult);
			logger.info("2,[苹果验证返回的json串:{}]", job.toString());
			String states = job.getStr("status");

			if ("21007".equals(states)) {
				logger.debug("是沙盒环境,应沙盒测试,否则执行下面");
				// 是沙盒环境,应沙盒测试,否则执行下面
				// 2.再沙盒测试  发送平台验证
				verifyResult = IosVerifyUtil.buyAppVerify(receipt, 0);
				job = JSONUtil.parseObj(verifyResult);
				logger.debug("3,沙盒环境验证返回的json字符串=" + job.toString());
				states = job.getStr("status");
			}
			if ("0".equals(states)) { // 前端所提供的收据是有效的    验证成功
				logger.debug("前端所提供的收据是有效的    验证成功");
				String r_receipt = job.getStr("receipt");
				JSONObject returnJson = JSONUtil.parseObj(r_receipt);
				String in_app = returnJson.getStr("in_app");

				/**
				 * in_app说明:
				 * 验证票据返回的receipt里面的in_app字段,这个字段包含了所有你未完成交易的票据信息。也就是在上面说到的APP完成交易之后,这个票据信息,就会从in_app中消失。
				 * 如果APP不完成交易,这个票据信息就会在in_app中一直保留。(这个情况可能仅限于你的商品类型为消耗型)
				 *
				 * 知道了事件的原委,就很好优化解决了,方案有2个
				 * 1.对票据返回的in_app数据全部进行处理,没有充值的全部进行充值
				 * 2.仅对最新的充值信息进行处理(我们采取的方案)
				 *
				 * 因为采用一方案:
				 * 如果用户仅进行了一次充值,该充值未到账,他不再进行充值了,那么会无法导致。
				 * 如果他通过客服的途径已经进行了补充充值,那么他在下一次充值的时候依旧会把之前的产品票据带回,这时候有可能出现重复充值的情况
				 *
				 * 以上说明是我在网上找到的,可以查看原文
				 * https://www.cnblogs.com/widgetbox/p/8241333.html
				 */

				JSONArray jsonArray = JSONUtil.parseArray(in_app);
				if (jsonArray.size() > 0) {
					int index = 0;
					JSONObject o = JSONUtil.parseObj(jsonArray.get(index));
					String transaction_id = o.getStr("transaction_id"); // 订单号
					String product_id = o.getStr("product_id"); // 产品id,也就是支付金额
					String purchase_date_ms = o.getStr("purchase_date_ms"); // 支付时间


					/**
					 * 此处为业务代码,可以根据自己的业务来进行开发
					 * 
					 */
					// 添加支付金额
					Result<Object> iosChargeSuccess = paymentService.iosChargeSuccess(transaction_id, product_id, purchase_date_ms, receiptMd5, iPayNotifyPo.getMoneyId(), iPayNotifyPo.getUserId());
					return iosChargeSuccess;
				}
			} else {
				return Result.error("收到数据有误");
			}
		}
		return Result.ok();
	}

2.service代码

里边涉及到的业务,可以根据自己的需求来进行开发

/**
     * 看是否是己经验证过的该支付收据
     * @param receiptMd5
     * @return
     */
    public boolean isExistsIOSReceipt(String receiptMd5);
/**
     * IOS支付
     * @param transaction_id 订单号
     * @param product_id 产品id,也就是支付金额
     * @param purchase_date_ms 支付时间
     * @param receiptMd5 拿到收据的MD5
     * @param moneyId 金额id
     */
    public Result<Object> iosChargeSuccess(String transaction_id, String product_id, String purchase_date_ms, String receiptMd5,String moneyId,String userId);

3.IPayNotifyPo封装类

public class IPayNotifyPo {
	@ApiModelProperty("苹果支付凭证")
    private String transactionReceipt;

    @ApiModelProperty("苹果支付单号")
    private String payId;

    @ApiModelProperty("用户id")
    private String userId;
    
    @ApiModelProperty("金额id")
    private String moneyId;
}

4.IosVerifyUtil工具类

package com.tools.payment.ios;

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: wyq
 * @date: 2022/07/25 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; //线上测试
        }
        //String url = EnvUtils.isOnline() ?url_verify : url_sandbox;

        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("Content-Type", "application/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;
    }
}

5.Result返回客户端封装类

package com.tools.result;

import java.util.List;
import java.util.Map;

public class Result<T> {

	/**
	 * 成功标志
	 */
	private boolean success = true;

	/**
	 * 返回代码
	 */
	private Integer code = 0;
	
	/**
	 * 返回处理消息
	 */
	private String message = "操作成功!";
	
	/**
	 * 返回数据对象 result
	 */
	private T result;
	
	/**
	 * 时间戳
	 */
	private long timestamp = System.currentTimeMillis();
	public Result() {
		
	}
	
	
	public boolean isSuccess() {
		return success;
	}


	public void setSuccess(boolean success) {
		this.success = success;
	}


	public Integer getCode() {
		return code;
	}


	public void setCode(Integer code) {
		this.code = code;
	}


	public String getMessage() {
		return message;
	}


	public void setMessage(String message) {
		this.message = message;
	}


	public T getResult() {
		return result;
	}


	public void setResult(T result) {
		this.result = result;
	}


	public long getTimestamp() {
		return timestamp;
	}


	public void setTimestamp(long timestamp) {
		this.timestamp = timestamp;
	}


	public Result<T> error500(String message) {
		this.setMessage(message);
		this.setCode(500);
		this.setSuccess(false);
		return this;
	}
	
	public Result<T> success(String message) {
		this.setMessage(message);
		this.setCode(200);
		this.setSuccess(true);
		return this;
	}
	
	
	public static Result<Object> ok() {
		Result<Object> r = new Result<Object>();
		r.setMessage("操作成功");
		r.setCode(200);
		r.setSuccess(true);
		return r;
	}
	
	public static Result<Object> ok(String msg) {
		Result<Object> r = new Result<Object>();
		r.setMessage(msg);
		r.setCode(200);
		r.setSuccess(true);
		r.setResult(null);
		return r;
	}
	
	public static Result<Object> ok(Map data) {
		Result<Object> r = new Result<Object>();
		r.setCode(200);
		r.setMessage("操作成功");
		r.setSuccess(true);
		r.setResult(data);
		return r;
	}
	
	public static Result<Object> ok(Map data,String msg) {
		Result<Object> r = new Result<Object>();
		r.setCode(200);
		r.setMessage(msg);
		r.setSuccess(true);
		r.setResult(data);
		return r;
	}
	
	public static Result<Object> ok(List data) {
		Result<Object> r = new Result<Object>();
		r.setCode(200);
		r.setSuccess(true);
		r.setResult(data);
		return r;
	}
	
	public static Result<List<?>> okl(List<?> data) {
		Result<List<?>> r = new Result<List<?>>();
		r.setCode(200);
		r.setSuccess(true);
		r.setResult(data);
		return r;
	}
	
	public static Result<Object> ok(Object data) {
		Result<Object> r = new Result<Object>();
		r.setCode(200);
		r.setSuccess(true);
		r.setResult(data);
		return r;
	}
	
	public static Result<Object> error(String msg) {
		return error(403, msg);
	}
	
	public static Result<Object> error(int code, String msg) {
		Result<Object> r = new Result<Object>();
		r.setCode(code);
		r.setSuccess(false);
		r.setMessage(msg);
		return r;
	}
	
	public static Result<Object> error(Map data,String msg) {
		Result<Object> r = new Result<Object>();
		r.setCode(403);
		r.setMessage(msg);
		r.setSuccess(false);
		r.setResult(data);
		return r;
	}
	
	/**
	 * 无权限访问返回结果
	 */
	public static Result<Object> noauth(String msg) {
		return error(401, msg);
	}
	
	/**
	 * 无权限访问返回结果
	 */
	public static Result<List<?>> noauth() {
		Result<List<?>> r = new Result<List<?>>();
		r.setCode(401);
		r.setSuccess(false);
		r.setMessage("您没有该接口的权限!");
		return r;
	}
}

6.pom文件

<!-- 工具包:https://hutool.cn/docs/ -->
		<dependency>
		    <groupId>cn.hutool</groupId>
		    <artifactId>hutool-all</artifactId>
		    <version>4.6.1</version>
		</dependency>

你可能感兴趣的:(支付,ios)