支付宝支付开发实践总结-Java-支付宝当面付-支付宝小程序-二维码生成-支付宝退款-APP调起支付宝支付

楚鸿波
2023-12-01

虽然目前相对较火的支付方式是微信支付,但是本人更喜欢支付宝支付,有积分拿,还可以部分提现免手续费,每月还有信用卡还款免手续费额度,捐步数,蚂蚁森林等等,扯远了,总之,对我来说,微信用于沟通,支付宝是支付主力。

以下是支付宝集成到服务器的经验总结

准备工作

使用支付功能,首先是进行注册等等,然后获得相应的 pidappid 以及公钥私钥
支付宝小程序开发又有对应的 id 和 公钥私钥对;
申请支付宝开发的具体步骤略,懒得贴图,毕竟还要打码,哈哈
为了快速集成开发,引入了支付宝封装的jar包
alipay-sdk-java-{version}.jar
alipay-trade-sdk-{version}.jar
之后就可以开始 briskly 的 coding 啦

若是jar包问题,可以私发

初始化加载支付宝配置信息

// 支付宝支付工具类
public final class AlipayHelper {
	private AlipayHelper() { }
	private static AlipayTradeService tradeService;
	static {
		Configs.init("alipay-config.properties");
		/// 初始化
		tradeService = new AlipayTradeServiceImpl.ClientBuilder().build();
	}
	public static AlipayTradeService getTradeService() {
		return tradeService();
	}
}

支付宝小程序中获取用户ID

@Controller
@RequestMapping(value = { "/requestOf/alipay" })
public class AlipayController {
	/**
	 * /// 获取支付宝用户ID ///
	 * @param code 用户授权之后获取到的
	 */
	@ResponseBody
	@RequestMapping(value = { "/attain/user_id/{code}" })
	public Map<String,Object> attainUserId(@PathVariable String code) {
		// Map自动转换为Json
		Map<String,Object> dataMap = new HashMap<>(8,0.1f); 
		try {
			AlipayClient alipayClient = new DefaultAlipayClient(
				"https://openapi.alipay.com/gateway.do", "支付宝小程序APPID",
				"私钥字符串","json","UTF-8","公钥字符串","RSA2");
			// OAuthToken 验证请求封装类
			AlipaySystemOauthTokenRequest request 
				= new AlipaySystemOauthTokenRequest();
			request.setGrantType("authorization_code"); // 验证
			request.setCode(code);
			// OAuthToken 验证响应封装类 
			AlipaySystemOauthTokenResponse response 
				= alipayClient.execute(request);
			if(response.isSuccess()){
				String userId = response.getUserId();
				dataMap.put("user_id", userId);
			} else {
				dataMap.put("err_msg", response.getCode() 
					+ "-" + response.getMsg() + "-" 
					+ + response.getSubCode() + "-" 
					+ + response.getSubMsg());
			}
		} catch (AlipayApiException e) {
			dataMap.put("err_msg", e.getMessage());
		}
		return dataMap;
	}
}

自有APP调用支付宝支付

@Controller
@RequestMapping(value = { "/requestOf/alipay" })
public class AlipayController {
	/**
	 * 获取调起支付的参数
	 */
	@ResponseBody
	@RequestMapping(value = { "/pre/callFor/payment" })
	public Map<String,Object> preCallForPayment(String account,Double fee) {
		// Map自动转换为Json
		Map<String,Object> dataMap = new HashMap<>(8,0.1f); 
		try {
			AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
				model.setBody( "自定义字符串,如 缴费支付" );
				model.setSubject( "自定义字符串,如 202号楼6单元501室" );
				model.setOutTradeNo("自定义的交易单号,如 202101011010101234567890");
				model.setTimeoutExpress( "10m" );
				model.setTotalAmount( fee ); // 需支付金额:元
				model.setProductCode( "产品代码,如 ELECTRICITY_PAYMENT" );
			AlipayClient alipayClient = new DefaultAlipayClient(
				"https://openapi.alipay.com/gateway.do", "支付宝小程序APPID",
				"私钥字符串","json","UTF-8","公钥字符串","RSA2");
			// 当前调用接口名称:alipay.trade.app.pay
			AlipayTradeAppPayRequest alipayRequest 
				= new AlipayTradeAppPayRequest();
				alipayRequest.setBizModel( model );
				alipayRequest.setNotifyUrl( // 回调通知的URL,有调用策略,稍后会提
					"https://***.com/requestOf/alipay/pre/callFor/callback" );
	        // 这里使用的是sdkExecute
	        AlipayTradeAppPayResponse response 
	        	= alipayClient.sdkExecute(alipayRequest);
			/// 返回值 ///
			dataMap.put( "signed_string", new String[] { } );
			dataMap.put( "response_body", response.getBody() );
			dataMap.put( "sign_type", "RSA2" );
			dataMap.put( "body", "" );
			dataMap.put( "subject", "" );
			dataMap.put( "out_trade_no", "" );
			dataMap.put( "timeout_express", "10m" );
			dataMap.put( "total_amount", fee );
		} catch (AlipayApiException e) {
			dataMap.put("err_msg", e.getMessage());
		}
		return dataMap;
	}

	/**
	 * /// 支付宝给服务器发送的请求(回调) <br/>
	 * https://docs.open.alipay.com/194/103296/ <br/>
	 * 程序执行完后必须打印输出“success”(不包含引号)。<br/>
	 * 如果商户反馈给支付宝的字符不是success这7个字符,
	 * 支付宝服务器会不断重发通知,直到超过24小时22分钟。<br/>
	 * 一般情况下,25小时以内完成8次通知(
	 * 通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);<br/>
	 * <br/>
	 * <br/>
	 * 自有APP主动查询订单是否完成支付,也可以调用当前方法,可以
	 * 多传递参数来判断是支付宝回调还是APP查询状态
	 */
	@RequestMapping(value = { "/pre/callFor/callback" })
	public void preCallForCallback(HttpServletRequest request,
		HttpServletResponse response) throws Exception {
		// 订单号
		String out_trade_no = request.getParameter("out_trade_no");
		// 二次验证订单状态
		AlipayTradeQueryRequestBuilder builder 
			// Builder 典型的流式编程,setXxx() 返回 调用对象this 而非 void
			= new AlipayTradeQueryRequestBuilder().setOutTradeNo( out_trade_no );
		// 订单结果
		AlipayF2FQueryResult result 
			= AlipayHelper.getTradeService().queryTradeResult(builder);
		if (result.isTradeSuccess()) {
			/// 取出支付宝用户ID和交易号 ///
			String openid = result.getResponse().getBuyerUserId(); 
			String transaction_id = result.getResponse().getTradeNo(); 
			// TODO 更多逻辑操作等待
			// 告知支付宝我也处理成功,无需再次调用
			response.getWriter().write("success");
		} else {
			// TODO 未成功支付的相应操作
		}				
	}
}

支付宝小程序内调起支付

@Controller
@RequestMapping(value = { "/requestOf/alipay" })
public class AlipayController {
	/**
	 * 获取调起支付的参数
	 * @param aliuId ***获取到的支付宝用户ID,参考之前的示例***
	 */
	@ResponseBody
	@RequestMapping(value = { "/pre/callFor/miniProgram" })
	public Map<String,Object> preCallForMiniProgram(
		String account,Double fee,String aliuId) {
		// Map自动转换为Json
		Map<String,Object> dataMap = new HashMap<>(8,0.1f); 
			
		AlipayClient alipayClient = new DefaultAlipayClient(
			"https://openapi.alipay.com/gateway.do", "支付宝小程序APPID",
			"私钥字符串","json","UTF-8","公钥字符串","RSA2");
		AlipayTradePayModel model = new AlipayTradePayModel();
			model.setSubject( "电费充值" );
			model.setOutTradeNo( "自定义订单号" );
			model.setTimeoutExpress( "2m" );
			model.setTotalAmount( fee );
			model.setBuyerId( aliuId );
		AlipayTradeCreateRequest alipayRequest = new AlipayTradeCreateRequest();
			alipayRequest.setBizModel(model);
			// 支付完成回调,参考之前的示例
			alipayRequest.setNotifyUrl( "https://***.com/***" ); 
		AlipayTradeCreateResponse alipayResponse 
			= alipayClient.execute(alipayRequest);
		if (alipayResponse.isSuccess()) {
			dataMap.put("tradeNo", alipayResponse.getTradeNo());
			dataMap.put("outTradeNo", "自定义订单号");
			dataMap.put("totalAmount", fee);
			dataMap.put("subject", "电费充值");
		} else {
			dataMap.put("err_msg", "请重试");
		}	
		return dataMap;
	}
}

生成当面付二维码

@Controller
@RequestMapping(value = { "/requestOf/alipay" })
public class AlipayController {
	/**
	 * 支付宝当面付二维码
	 */
	@ResponseBody
	@RequestMapping(value = { "/face2face/payment" })
	public Map<String,Object> faceToFacePayment(
		String account,Double fee) {
		// Map自动转换为Json
		Map<String,Object> dataMap = new HashMap<>(8,0.1f); 
		// 预创建订单参数封装
		AlipayTradePrecreateRequestBuilder builder 
			= new AlipayTradePrecreateRequestBuilder()
        	.setOutTradeNo("自定义订单号")
        	.setSubject("话费充值")
            .setTotalAmount(fee )
            .setBody("话费充值")
            .setStoreId("CHARGE_STATION_"+fee)
            .setTimeoutExpress("2m")
            // 支付结果回调,见之前示例
            .setNotifyUrl("https://***.com/***");
        AlipayF2FPrecreateResult result 
        	= AlipayUtil.getTrade().tradePrecreate(builder);
		if ( result.isTradeSuccess() ) {
			// 二维码内容,生成二维码后,使用支付宝扫码即可支付
			dataMap.put("qrText", result.getResponse().getQrCode());
		} else {
			dataMap.put("err_msg", "请重试");
		}
		return dataMap;
	}
}

支付宝订单退款

@Controller
@RequestMapping(value = { "/requestOf/alipay" })
public class AlipayController {
	/**
	 * 对已支付的订单进行退款
	 */
	@ResponseBody
	@RequestMapping(value = { "/refundWith/{out_trade_no}" })
	public Map<String,Object> refund(@PathVariable String out_trade_no) {
		// Map自动转换为Json
		Map<String,Object> dataMap = new HashMap<>(8,0.1f); 
		try {
			/// 自定义方法,根据单号获取金额,或者请求参数中传递过来也可
			String yuan = invokeXxx(out_trade_no); 
			/// 
			AlipayTradeRefundRequestBuilder builder 
				= new AlipayTradeRefundRequestBuilder()
					.setOutTradeNo( out_trade_no )
					.setRefundAmount( yuan )
					.setOutRequestNo( out_trade_no )
					.setRefundReason( "退款" ); // 退款原因,支付宝订单详情是可以看到的
			AlipayF2FRefundResult result 
				= AlipayHelper.getTradeService().tradeRefund(builder);
			
			if ( result.isTradeSuccess() ) {
				// TODO 退款成功,请注意查收
				dataMap.put("suc_msg", "退款成功,请注意查收");
			} else {
				// TODO 请重试
				dataMap.put("err_msg", "请重试");
			}
		} catch (AlipayApiException e) {
			dataMap.put("err_msg", e.getMessage());
		}
		return dataMap;
	}
}

生成二维码

@Controller
@RequestMapping(value = { "/requestOf/alipay" })
public class AlipayController {
	/**
	 * 生成二维码图片流
	 */
	@ResponseBody
	@RequestMapping(value = { "/view/qr" })
	public Map<String,Object> viewQr(
		String qrText,HttpServletResponse response) {
		Map<EncodeHintType, Object> hints = new HashMap<>();
			hints.put(EncodeHintType.ERROR_CORRECTION,ErrorCorrectionLevel.L);
			hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
			hints.put(EncodeHintType.MARGIN, 1);
		BitMatrix bitMatrix 
			= new MultiFormatWriter()
			.encode(qrText,BarcodeFormat.QR_CODE, 300, 300, hints);
		OutputStream output = response.getOutputStream();
		MatrixToImageWriter.writeToStream(bitMatrix,"png",output);
		output.flush();
		output.close();
	}
}

引用类介绍

着急的小伙伴可以跳过路过这一部分

com.alipay.demo.trade.config.Configs配置文件加载类,借助接口org.apache.commons.configuration.Configuration的子类org.apache.commons.configuration.PropertiesConfiguration进行配置文件读取;

public class Configs {
    private static Log log = LogFactory.getLog(Configs.class);
    private static Configuration configs;
    // 根据文件名读取配置文件,文件后缀名必须为.properties
    public synchronized static void init(String filePath) {
        if (configs != null) {
            return;
        }
        try {
        	// **** 此处进行配置文件读取 ****
            configs = new PropertiesConfiguration(filePath);
        } catch (ConfigurationException e) {
            e.printStackTrace();
        }
        if (configs == null) {
            throw new IllegalStateException(
            	"can`t find file by path:" + filePath);
        }
		// **** 
        log.info("配置文件名: " + filePath);
        log.info(description());
    }
}
/**
 * This is the "classic" Properties loader which loads the values from
 * a single or multiple files (which can be chained with "include =".
 * 这是一个典型的属性加载器,从一个或多个文件中加载属性配置
 * 多文件的形式是:文件中存在 key=include 且 value=another.properties 
 * 即include作为键,另一个配置文件作为值
 * All given path references are either absolute or relative to the
 * file name supplied in the constructor.
 * 所有给定的路径引用要么是绝对路径 要么是 相对于构造器的提供的参数的相对路径
 * <p>
 * In this class, empty PropertyConfigurations can be built, properties
 * added and later saved. include statements are (obviously) not supported
 * if you don't construct a PropertyConfiguration from a file.
 * 这个类里面,空的属性配置对象也可以进行构建编译,允许属性稍后再进行设置。不过,显而易见的是,
 * 由于没有使用有参数的构造器,所以在当前配置中包含另一个配置文件是不被支持的,因为没有路径啊。
 *
 * <p>The properties file syntax is explained here, basically it follows
 * the syntax of the stream parsed by {@link java.util.Properties#load} and
 * adds several useful extensions:
 * 现在开始解释属性文件的语法,基本上是遵守 Properties.load 方法所规定的语法并添加了一些扩展
 *
 * <ul>
 *  <li>
 *   Each property has the syntax <code>key &lt;separator> value</code>. The
 *   separators accepted are {@code '='}, {@code ':'} and any white
 *   space character. Examples:
 * 每一个属性的书写均以 键 分隔符 值。可被接受的分隔符有 = : 和空白字符。示例如下:
 * <pre>
 *  key1 = value1
 *  key2 : value2
 *  key3   value3</pre>
 *  </li>
 *  <li>
 *   The <i>key</i> may use any character, separators must be escaped:
 * 属性的 键 可以使用任意字符,不过若包含指定的分隔符,则必须要转义设置下,防止被当作分隔符处理。
 * 如,若 键为 key:foo ,则需要在配置文件中使用 \: ,否则会存在异义。
 * <pre>
 *  key\:foo = bar</pre>
 *  </li>
 *  <li>
 *   <i>value</i> may be separated on different lines if a backslash
 *   is placed at the end of the line that continues below.
 * 这里需要注意的是,若在行的末尾处有一个 \ 则表示当前行被分隔为若干行,
 * 直到读取到行尾不包含 \ 的某处。
 *  </li>
 *  <li>
 *   <i>value</i> can contain <em>value delimiters</em> and will then be interpreted
 *   as a list of tokens. Default value delimiter is the comma ','. So the
 *   following property definition
 * 值对应的内容可以用分隔符,以此来表示值为集合型数据。默认的值分隔符是逗号。例如正面这个属性的定义
 * <pre>
 *  key = This property, has multiple, values
 * </pre>
 *   will result in a property with three values. 
 * 会导致这个属性具有三个值,分别为 "This property", "has multiple", "values"
 * 	 You can change the value
 *   delimiter using the {@link AbstractConfiguration#setListDelimiter(char)}
 *   method. Setting the delimiter to 0 will disable value splitting completely.
 * 可以通过调用父类方法 setListDelimiter(char) 设置值分隔符,设置为0则值不会被分隔。
 *  </li>
 *  <li>
 *   Commas in each token are escaped placing a backslash right before
 *   the comma.
 * 如果值的确需要包含逗号不作分隔之用途,则每一个逗号之前都需要加一个 \ 来转义。
 *  </li>
 *  <li>
 *   If a <i>key</i> is used more than once, the values are appended
 *   like if they were on the same line separated with commas. 
 * 如果一个 键 出现多次,则对就的值会被作为集合赋予 键 ,效用和 值分隔符 效用相同。
 * <em>Note</em>: 
 * 注意:
 *   When the configuration file is written back to disk the associated
 *   {@link PropertiesConfigurationLayout} object (see below) will
 *   try to preserve as much of the original format as possible, 
 * 当配置文件要写回到磁盘时,则使用的配置类对象一起工作的 PropertiesConfigurationLayout 对象
 * 会试着尽可能的保留原有格式。如:
 * i.e. properties
 *   with multiple values defined on a single line will also be written back on
 *   a single line, and multiple occurrences of a single key will be written on
 *   multiple lines. 
 * 一行多个值的仍旧会被保存为一行多个值,同样的,若一个key出现多行,则仍会被保存多行。
 * If the {@code addProperty()} method was called
 *   multiple times for adding multiple values to a property, these properties
 *   will per default be written on multiple lines in the output file, too.
 * 如果多次调用 addProperty() 方法给某属性增加多个值,则这些属性也会被保存为多行。
 *   Some options of the {@code PropertiesConfigurationLayout} class have
 *   influence on that behavior.
 * PropertiesConfigurationLayout类中的一些选项会影响到这个保存规则。
 *  </li>
 *  <li>
 *   Blank lines and lines starting with character '#' or '!' are skipped.
 * 任意行以 # 或 ! 开头的均会被跳过,不会被读取。
 *  </li>
 *  <li>
 *   If a property is named "include" (or whatever is defined by
 *   setInclude() and getInclude() and the value of that property is
 *   the full path to a file on disk, that file will be included into
 *   the configuration. 
 * 如果一个属性命名为 include 或者通过 setInclude() 方法设置的对应的值,并且对应的属性值
 * 是一个磁盘上的全路径,则这个文件会被加载读取,其中包含的属性会被包含进来。
 * You can also pull in files relative to the parent
 *   configuration file. So if you have something like the following:
 * 当然,也可以使用 相对于当前配置的文件 的相对路径,若配置如下,
 *
 *   include = additional.properties
 *
 *   Then "additional.properties" is expected to be in the same
 *   directory as the parent configuration file.
 * 则这个 additional.properties 应该存在于和 当前配置文件 相同的文件夹内。
 *
 *   The properties in the included file are added to the parent configuration,
 *   they do not replace existing properties with the same key.
 * 子配置文件中的属性会被添加到主配置项中,若有同名的key也不会替换主配置项的属性。
 * (对应的key变为集合型数据,类似于一个key多行的情形。此处有待验证)
 *  </li>
 * </ul>
 *
 * <p>Here is an example of a valid extended properties file:
 * 以下是一个符合规则的属性配置文件
 *
 * <p><pre>
 *      # lines starting with # are comments   
 *  “#”开头的被当作注释
 *
 *      # This is the simplest property
 *      key = value
 * 最简单的 键 = 值 形式的属性
 *
 *      # A long property may be separated on multiple lines
 *      longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
 *                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
 * 被分隔为多行的值,行尾以 \ 结束
 *
 *      # This is a property with many tokens
 *      tokens_on_a_line = first token, second token
 * 值分隔符的应用,值为集合型
 *
 *      # This sequence generates exactly the same result
 *      tokens_on_multiple_lines = first token
 *      tokens_on_multiple_lines = second token
 * 出现多行的同名key,同样被解析为集合型
 *
 *      # commas may be escaped in tokens
 *      commas.escaped = Hi\, what'up?
 * 在值分隔符前加入 \ 进行转义,免于被认为是多个值
 *
 *      # properties can reference other properties
 *      base.prop = /base
 *      first.prop = ${base.prop}/first
 *      second.prop = ${first.prop}/second
 * 属性之间相互引用 (后者引用前者)
 * </pre>
 *
 * <p>A {@code PropertiesConfiguration} object is associated with an
 * instance of the {@link PropertiesConfigurationLayout} class,
 * which is responsible for storing the layout of the parsed properties file
 * (i.e. empty lines, comments, and such things). 
 * 当前类对象会关联一个 PropertiesConfigurationLayout 对象,它负责存储配置文件,保留其格式
 * 
 * The {@code getLayout()}
 * method can be used to obtain this layout object. With {@code setLayout()}
 * a new layout object can be set. This should be done before a properties file
 * was loaded.
 * 可以通过 getLayout() 方法获取这个对象,也可以通 setLayout() 设置一个新的对象。
 * 设置这个对象的操作需在属性配置文件加载之前调用。
 * 
 * <p><em>Note:</em>Configuration objects of this type can be read concurrently
 * by multiple threads. However if one of these threads modifies the object,
 * synchronization has to be performed manually.
 * 注意,虽然这个配置对象可以被多个线程同步读取,但是
 * 若是其中一个线程修改的属性值,则需要代码调用来实现属性同步
 *
 */
public class PropertiesConfiguration extends AbstractFileConfiguration
{
    /** Constant for the supported comment characters.*/
	/** 支持的注释符常量 */
    static final String COMMENT_CHARS = "#!";
    /** Constant for the default properties separator.*/
    /** 默认的属性分隔符常量 */
    static final String DEFAULT_SEPARATOR = " = ";
    /** The list of possible key/value separators */
    /** 可能使用到的 键值 分隔符集合 */
    private static final char[] SEPARATORS = new char[] {'=', ':'};
    /** Stores the layout object.*/
    /** 模板对象,最终用于读取和写回到文件 */
    private PropertiesConfigurationLayout layout;
     /**
     * Creates and loads the extended properties from the specified file.
     * 从指定文件创建并加载扩展属性
     * The specified file can contain "include = " properties which then
     * are loaded and merged into the properties.
     * 指定的文件若包含 "include = another.properties" 的属性,
     * 则 another.properties 配置文件中的数据会被加载并放置到同一个属性集中
     */
    public PropertiesConfiguration(String fileName) 
    	throws ConfigurationException {
        super(fileName);  /**** 关注此方法 ****/
    }
}
/**
 * <p>Partial implementation of the {@code FileConfiguration} interface.
 * 此类部分实现了 FileConfiguration 接口
 * Developers of file based configuration may want to extend this class,
 * the two methods left to implement are {@link FileConfiguration#load(Reader)}
 * and {@link FileConfiguration#save(Writer)}.</p>
 * 使用基于文件配置的开发者可能会扩展这个类,若是如此,load(Reader)和save(Writer)方法需要去实现
 * <p>This base class already implements a couple of ways to specify the location
 * of the file this configuration is based on. 
 * 这个基类已经实现几种方式用于指定配置文件的路径
 * The following possibilities
 * exist:
 * 这些可行的方式有:
 * <ul><li>URLs: With the method {@code setURL()} a full URL to the
 * configuration source can be specified. This is the most flexible way. Note
 * that the {@code save()} methods support only <em>file:</em> URLs.</li>
 * 指定URL的方式,通过setURL()来指定配置文件全路径。这是一个可最大扩展的方式。
 * 需要注意的是,save()方法保存配置文件时,只支持file:开头的URL。
 * <li>Files: The {@code setFile()} method allows to specify the
 * configuration source as a file. This can be either a relative or an
 * absolute file. In the former case the file is resolved based on the current
 * directory.</li>
 * 指定File对象的方式,通过setFile()来指向配置文件。可以传一个相对路径或者绝对路径。
 * 如果是前一种的相对路径,则是基于当前路径来解析。
 * <li>As file paths in string form: With the {@code setPath()} method a
 * full path to a configuration file can be provided as a string.</li>
 * 还有一种是文件路径字符串,通过setPath()设置一个文件路径字符串。
 * <li>Separated as base path and file name: This is the native form in which
 * the location is stored. The base path is a string defining either a local
 * directory or a URL. It can be set using the {@code setBasePath()}
 * method. The file name, non surprisingly, defines the name of the configuration
 * file.</li>
 * 设置字符串路径,这个路径由基础路径和文件名组成,这也是本地文件存储的方式。
 * 基础路径是一个文件夹或者是URL,可以通过setBasePath()来设置。
 * 文件名的话,一点也不出乎意料,就是一个配置文件的名字。
 * </ul></p>
 * <p>The configuration source to be loaded can be specified using one of the
 * methods described above. Then the parameterless {@code load()} method can be
 * called. 
 * 被加载的配置源可以通过以上几种方式指定。之后load()方法就能被调用了。
 * Alternatively, one of the {@code load()} methods can be used which is
 * passed the source directly. 
 * 或者,可以直接使用load()的重载方法在调用时直接指定配置源。
 * These methods typically do not change the
 * internally stored file; however, if the configuration is not yet associated
 * with a configuration source, the first call to one of the {@code load()}
 * methods sets the base path and the source URL. 
 * 调用load()的重载方法不会改变文件内容,不过,
 * 如果调用load()时没有指定配置源,则第一次load()调用时,设置基础路径和源URL。
 * This fact has to be taken
 * into account when calling {@code load()} multiple times with different file
 * paths.</p>
 * 这是要考虑到使用不过文件路径调用load()的情形。
 * <p>Note that the {@code load()} methods do not wipe out the configuration's
 * content before the new configuration file is loaded. Thus it is very easy to
 * construct a union configuration by simply loading multiple configuration
 * files, e.g.</p>
 * 注意,在新的配置文件被装载之前,load()方法不会清除配置文件的内容(需要调用clear方法,正面有提)
 * 所以,可以通过简单的调用几次load()方法来加载多个配置文件,来组成一个配置文件联合体。
 * <p><pre>
 * config.load(configFile1);
 * config.load(configFile2);
 * </pre></p>
 * <p>After executing this code fragment, the resulting configuration will
 * contain both the properties of configFile1 and configFile2. 
 * 执行完上面两句代码,则最终的配置对象会包含configFile1和configFile2中的配置项。
 * On the other
 * hand, if the current configuration file is to be reloaded, {@code clear()}
 * should be called first. Otherwise the properties are doubled. This behavior
 * is analogous to the behavior of the {@code load(InputStream)} method
 * in {@code java.util.Properties}.</p>
 * 另一方面呢,如果要重新加载配置文件,需要先调用clear()方法,否则属性会有重复的,因为
 * 之前读取到的不会被删除(上面有提到)。这和Properties类的load(InputStream)方法类似。
 */
public abstract class AbstractFileConfiguration
extends BaseConfiguration
implements FileConfiguration, FileSystemBased
{
    /** Holds a reference to the reloading strategy.*/
    /** 配置文件重载策略 */
    protected ReloadingStrategy strategy;
    /** A lock object for protecting reload operations.*/
    /** 重载锁定 */
    protected Object reloadLock = new Lock("AbstractFileConfiguration");
    public AbstractFileConfiguration()
    {
        initReloadingStrategy(); /** 设置重载策略 */
        ......
    }
    private void initReloadingStrategy()
    {
    	// InvariantReloadingStrategy: A strategy that 
    	// never triggers a reloading.
    	// 一个永远不会触发重新加载配置的策略 ,这......
        setReloadingStrategy(new InvariantReloadingStrategy());
    }
    public AbstractFileConfiguration(String fileName) 
    	throws ConfigurationException
    {
        this();
        // store the file name
        setFileName(fileName);
        // load the file
        load(); /**** 这个是重点 
        	兜兜转转兜兜转转,最终调用的是 PropertiesConfiguration 里的 
        	PropertiesConfigurationLayout对象 的 load(Reader) 方法
		****/
    }
}

com.alipay.demo.trade.service.AlipayTradeService支付宝调用接口类,主要实现子类为com.alipay.demo.trade.service.impl.AlipayTradeServiceImpl,接口定义如下

public interface AlipayTradeService {

    // 当面付2.0流程支付
    public AlipayF2FPayResult tradePay(
    	AlipayTradePayRequestBuilder builder);

    // 当面付2.0消费查询
    public AlipayF2FQueryResult queryTradeResult(
    	AlipayTradeQueryRequestBuilder builder);

    // 当面付2.0消费退款
    public AlipayF2FRefundResult tradeRefund(
    	AlipayTradeRefundRequestBuilder builder);

    // 当面付2.0预下单(生成二维码)
    public AlipayF2FPrecreateResult tradePrecreate(
    	AlipayTradePrecreateRequestBuilder builder);
}

接口提供了四个方法,方便使用。观察参数 *Builder,典型的 建造者(Builder) 模式,
并运用流式编程中的 setXxx() 方法返回调用对象this 而不是常见的void。
AlipayTradePayRequestBuilder
AlipayTradeQueryRequestBuilder
AlipayTradeRefundRequestBuilder
AlipayTradePrecreateRequestBuilder

《未完待续》

Acknowledgement

以上是工作过程中应用过的,做了些许修改,如果书写错误或者其它想让本人帮忙调用的接口,可以留言私信。总之,大家一起交流提高吧

 类似资料: