All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jp.gopay.sdk.builders.batch_charge.AbstractBatchCreateCharge Maven / Gradle / Ivy

There is a newer version: 0.11.17
Show newest version
package jp.gopay.sdk.builders.batch_charge;

import io.reactivex.Flowable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import jp.gopay.sdk.builders.CreateChargeCompletionMonitor;
import jp.gopay.sdk.builders.charge.AbstractChargesBuilders;
import jp.gopay.sdk.models.common.IdempotencyKey;
import jp.gopay.sdk.models.errors.GoPayException;
import jp.gopay.sdk.models.response.charge.Charge;
import jp.gopay.sdk.utils.ExponentialBackoffSleeper;
import jp.gopay.sdk.utils.Sleeper;
import jp.gopay.sdk.utils.functions.EndoFunction;
import jp.gopay.sdk.utils.functions.GopayFunctions;
import jp.gopay.sdk.utils.streams.StreamOptions;
import retrofit2.Retrofit;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


/**
 * Request builder for creating charges in batch processing.
 *
 * How to use:
 *
 * 
 * {@code
 * BatchCreateCharge batch = gopay.batchCreateCharge();
 * for (Invoice i : targetInvoicesThisMonth) {
 *    batch.add(gopay.createCharge(i.getTransactionToken(), i.getTotalAmount(), "JPY"));
 * }
 *
 * CreateChargeResult[] results = batch.execute();
 * for (CreateChargeResult r : results) {
 *     if (r.createChargeException != null) {
 *         assert r.charge == null;
 *         // the charge creation request failed.
 *         ...
 *     } else if (r.statusCheckException != null) {
 *         assert r.charge != null;
 *         // Failed to check the charge status.
 *         ...
 *     } else {
 *         assert r.charge != null;
 *         assert r.charge.getStatus() != ChargeStatus.PENDING
 *         // Save charge result.
 *         ...
 *     }
 * }
 * }
 * 
*/ public abstract class AbstractBatchCreateCharge, G extends AbstractChargesBuilders.AbstractGetChargeRequestBuilder> implements CreateChargeCompletionMonitor { private final Retrofit retrofit; private final int createMaxRetry; private final int statusCheckTimeout; private StreamOptions streamOptions; private List builders = new ArrayList<>(); public AbstractBatchCreateCharge(Retrofit retrofit, int createMaxRetry, int statusCheckTimeout) { this.retrofit = retrofit; this.createMaxRetry = createMaxRetry; this.statusCheckTimeout = statusCheckTimeout; this.streamOptions = StreamOptions.newInstance(); } public void setStreamOptions(StreamOptions streamOptions) { this.streamOptions = streamOptions; } private Flowable getChargesFlow() { return Flowable.fromIterable(this.builders); } /** * Add create charge request builder to request queue. * * @param builder Create charge builder * @return */ public AbstractBatchCreateCharge add(B builder) { builders.add(builder); return this; } Sleeper createSleeper() { return new ExponentialBackoffSleeper(1_000, 30_000, 2.0, 0.5); } /** * Creates a flowable by dispatching each builder with idempotency. * * @return Array of batch processing result * @throws InterruptedException */ public Flowable> createFlowable() { return getChargesFlow() // Take a sliding window of a configurable number of items every X seconds .window( streamOptions.getWindowOptions().getLength(), streamOptions.getWindowOptions().getTimeUnit(), streamOptions.getWindowOptions().getWindowSize() ).flatMap(GopayFunctions.>identity()) // Process the following steps in parallel .parallel(streamOptions.getParallelism()) // Set an idempotency key for each builder where it hasn't been set .map(new EndoFunction() { @Override public B apply(B builder) throws Exception { if(builder.getIdempotencyKey() == null){ return builder.withIdempotencyKey(IdempotencyKey.randomFromUUID()); } return builder; } }) // Attempt to create the charges .map(new Function>() { @Override public CreateChargeResult apply(B builder) throws Exception { try { final M charge = builder.build().dispatch(createMaxRetry, createSleeper()); return new CreateChargeResult<>(charge); } catch (IOException | GoPayException e) { return new CreateChargeResult<>(e); } // Poll the status of each created charge } }).map(new EndoFunction>() { @Override public CreateChargeResult apply(CreateChargeResult result) throws Exception { if (result.charge != null) { try { final M charge = createChargeCompletionMonitor( retrofit, result.charge.getStoreId(), result.charge.getId() ).await(statusCheckTimeout); return new CreateChargeResult<>(charge); } catch (InterruptedException e) { throw e; } catch (Exception e) { result.statusCheckException = e; return result; } } else { return result; } } }) // Put the results in the different rails into one single sequence .sequential(); } /** * Create in parallel all the charges in the builders, polling the status of each charge. * This method is synchronous, meaning that it blocks the calling thread until all results are finished. * @return an array of CreateChargeResult * @see CreateChargeResult */ public CreateChargeResult[] execute() { return createFlowable().toList().blockingGet().toArray(new CreateChargeResult[0]); } /** * Create in parallel all the charges in the builders, polling the status of each charge. * The results get consumed on a separate thread, where the provided callback gets executed. * @param callback * @see CreateChargeResult */ public void execute(final BatchChargeCallback callback){ createFlowable() .subscribeOn(Schedulers.newThread()) .subscribe(new Consumer>() { @Override public void accept(CreateChargeResult createChargeResult) { callback.processResult(createChargeResult); } }); } }