Can I set variable monthly payment amounts through PayPal REST API's billing plan/agreement? On existing agreements? - recurring-billing

I entered this as an issue on the PayPal-PHP-SDK github but it's rather time sensitive at this point.
Our goal is a subscription service that monthly charges users a value they select for each content update during that month. A $1 subscriber would pay $8 if there were 8 updates, for example.
Our implementation so far (which is already live with a number of subscribers) is using the PayPal-PHP-SDK / REST API to create a Billing Plan for each subscriber with a payment definition set to an amount calculated to be the maximum potential monthly charge. I then expected to be able to use Agreement->setBalance() to lower the value to the actual intended charge and then Agreement->billBalance() to process the payment.
Unfortunately due to time pressures we launched without verifying this was a viable implementation, and I've discovered that those functions are only for unpaid/delinquent balances. The start_date on our plans is April 1, and we'll have had 4 content releases but we're set to charge our subscribers for the maximum possible 9 updates.
I've tried a variety of Agreement->update() and Plan->update() calls to change the monthly value, along the lines of:
$Patch = new PayPal\Api\Patch();
$Patch->setOp("replace")
->setPath("/payment_definitions/0/amount/value")
->setValue($patch_value);
$PatchRequest = new PayPal\Api\PatchRequest();
$PatchRequest->addPatch($Patch);
$Plan = PayPal\Api\Plan::get($plan_id, $apiContext);
$Plan->update($PatchRequest, $apiContext);
which returns an exception with "validation_error: Invalid Path provided". Attempts to json encode the data path, up to and including the entire Plan object with $Patch->setPath("/") instead give an exception with "MALFORMED_REQUEST - Incoming JSON request does not map to API request". I suspect most values cannot be updated on an executed agreement or activated plan.
However, I see through the merchant account's website that the monthly value can be manually updated, so I hold out hope that I'm simply going about my API requests the wrong way.
I've also tried creating a Payment object with Transaction->setPurchaseUnitReferenceId($AgreementId) since I'd read something about reference transactions being a potential solution, but I'm getting the same malformed request error:
$Item = new PayPal\Api\Item();
$Item->setCategory('DIGITAL')
->setPrice($pledge_value)
->setDescription($update_name);
$ItemList = new PayPal\Api\ItemList();
$ItemList->addItem($Item);
$Amount = new PayPal\Api\Amount();
$Amount->setCurrency('USD')
->setTotal($pledge_value);
$Transaction = new PayPal\Api\Transaction();
$Transaction->setPurchaseUnitReferenceId($AgreementId)
->setDescription($update_name)
->setAmount($Amount)
->setItemList($ItemList)
->setNotifyUrl($notify_url);
$Payer = new PayPal\Api\Payer();
$Payer->setPaymentMethod('paypal');
$RedirectUrls = new PayPal\Api\RedirectUrls();
$RedirectUrls->setReturnUrl($return_url)
->setCancelUrl($cancel_url);
$Payment = new PayPal\Api\Payment();
$Payment->setIntent('sale')
->setPayer($Payer)
->setRedirectUrls($RedirectUrls)
->addTransaction($Transaction);
$Payment->create($apiContext);
So is there a way to fix our current implementation and plans/agreements? Failing that, is there a REST API solution that I should have used / can try to migrate to? I'd like to avoid Classic API if possible.

Related

How to push Salesforce Order to an external REST API?

I have experience in Salesforce administration, but not in Salesforce development.
My task is to push a Order in Salesforce to an external REST API, if the order is in the custom status "Processing" and the Order Start Date (EffectiveDate) is in 10 days.
The order will be than processed in the down-stream system.
If the order was successfully pushed to the REST API the status should be changed to "Activated".
Can anybody give me some example code to get started?
There's very cool guide for picking right mechanism, I've been studying from this PDF for one of SF certifications: https://developer.salesforce.com/docs/atlas.en-us.integration_patterns_and_practices.meta/integration_patterns_and_practices/integ_pat_intro_overview.htm
A lot depends on whether the endpoint is accessible from Salesforce (if it isn't - you might have to pull data instead of pushing), what authentication it needs.
For push out of Salesforce you could use
Outbound Message - it'd be an XML document sent when (time-based in your case?) workflow fires, not REST but it's just clicks, no code. The downside is that it's just 1 object in message. So you can send Order header but no line items.
External Service would be code-free and you could build a flow with it.
You could always push data with Apex code (something like this). We'd split the solution into 2 bits.
The part that gets actual work done: At high level you'd write function that takes list of Order ids as parameter, queries them, calls req.setBody(JSON.serialize([SELECT Id, OrderNumber FROM Order WHERE Id IN :ids]));... If the API needs some special authentication - you'd look into "Named Credentials". Hard to say what you'll need without knowing more about your target.
And the part that would call this Apex when the time comes. Could be more code (a nightly scheduled job that makes these callouts 1 minute after midnight?) https://salesforce.stackexchange.com/questions/226403/how-to-schedule-an-apex-batch-with-callout
Could be a flow / process builder (again, you probably want time-based flows) that calls this piece of Apex. The "worker" code would have to "implement interface" (a fancy way of saying that the code promises there will be function "suchAndSuchName" that takes "suchAndSuch" parameters). Check Process.Plugin out.
For pulling data... well, target application could login to SF (SOAP, REST) and query the table of orders once a day. Lots of integration tools have Salesforce plugins, do you already use Azure Data Factory? Informatica? BizTalk? Mulesoft?
There's also something called "long polling" where client app subscribes to notifications and SF pushes info to them. You might have heard about CometD? In SF-speak read up about Platform Events, Streaming API, Change Data Capture (although that last one fires on change and sends only the changed fields, not great for pushing a complete order + line items). You can send platform events from flows too.
So... don't dive straight to coding the solution. Plan a bit, the maintenance will be easier. This is untested, written in Notepad, I don't have org with orders handy... But in theory you should be able to schedule it to run at 1 AM for example. Or from dev console you can trigger it with Database.executeBatch(new OrderSyncBatch(), 1);
public class OrderSyncBatch implements Database.Batchable, Database.AllowsCallouts {
public Database.QueryLocator start(Database.BatchableContext bc) {
Date cutoff = System.today().addDays(10);
return Database.getQueryLocator([SELECT Id, Name, Account.Name, GrandTotalAmount, OrderNumber, OrderReferenceNumber,
(SELECT Id, UnitPrice, Quantity, OrderId FROM OrderItems)
FROM Order
WHERE Status = 'Processing' AND EffectiveDate = :cutoff]);
}
public void execute(Database.BatchableContext bc, List<sObject> scope) {
Http h = new Http();
List<Order> toUpdate = new List<Order>();
// Assuming you want 1 order at a time, not a list of orders?
for (Order o : (List<Order>)scope) {
HttpRequest req = new HttpRequest();
HttpResponse res;
req.setEndpoint('https://example.com'); // your API endpoint here, or maybe something that starts with "callout:" if you'd be using Named Credentials
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serializePretty(o));
res = h.send(req);
if (res.getStatusCode() == 200) {
o.Status = 'Activated';
toUpdate.add(o);
}
else {
// Error handling? Maybe just debug it, maybe make a Task for the user or look into
// Database.RaisesPlatformEvents
System.debug(res);
}
}
update toUpdate;
}
public void finish(Database.BatchableContext bc) {}
public void execute(SchedulableContext sc){
Database.executeBatch(new OrderSyncBatch(), Limits.getLimitCallouts()); // there's limit of 10 callouts per single transaction
// and by default batches process 200 records at a time so we want smaller chunks
// https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_limits.htm
// You might want to tweak the parameter even down to 1 order at a time if processing takes a while at the other end.
}
}

Setting BTC Pay Server invoice expiration time to never?

This is a follow up question to this post but my question is more programming related so I'm hoping this is the right place to post it.
I too am trying to use BTC Pay Server as a wallet. Thera are two problems:
As described in the article, you have to specify an amount when creating an invoice.
It has a security feature that basically results in you not being able to re-use deposit addresses.
The workaround for problem 1 is to set the invoice amount to 0.000001 BTC. So low that the client will always overpay. That works for me to.
But my problem is that address must not expire ever. I checked the code:
Here you can see the Invoice object.
Here you can see the code in use.
It looks like I might be able to use this:
public function setExpirationTime($expirationTime)
{
if (is_a($expirationTime, 'DateTime')) {
$this->expirationTime = $expirationTime;
} else if (is_numeric($expirationTime)) {
$expirationDateTime = new \DateTime('', new \DateTimeZone("UTC"));
$expirationDateTime->setTimestamp($expirationTime);
$this->expirationTime = $expirationDateTime;
}
return $this;
}
And set the expiration time to the year 3000. So my questions are:
Will BTC Pay server ditch my address if I attempt to use this to make it never expire?
Will I still receive the funds if a user sends to an expired address/
Or is there perhaps a better way to get BTC Pay server to act as a wallet as I want it to?
Thanks!
Will BTC Pay server ditch my address if I attempt to use this to
make it never expire?
Actually, you may encounter the year 2038 problem if the type for expirationTime is DateTime. If that really is the case, it will be set a negative value when you try to pass a value larger than 2038. It is unclear what will happen next.
If the system the code is running on is 64bit, then the Y2038 problem does not apply.
Will I still receive the funds if a user sends
to an expired address
https://docs.btcpayserver.org/FAQ/FAQ-Stores/#payment-invalid-if-transactions-fails-to-confirm-minutes-after-invoice-expiration
If the customer pays the invoice, but it fails to get the defined
number of confirmations within the set period, it is marked as
"invalid." The merchant can then decide whether to accept the invoice
afterward manually or decline it and require additional payment from
the customer. This is an additional protection mechanism against the
volatility
So not exactly - some work is required on your part to accept it, if it expires.
Or is there perhaps a better way to get BTC
Pay server to act as a wallet as I want it to?
Instead of setting it to the year 3000, why don't you just set the invoice a year ahead at a time ?

How to send Lumens to an uninitialised stellar address pragmatically

I am new to stellar so please bear with my question if it sounds too basic.
So, using the stellar laboratory, I created two accounts lets name 1 and 2. I funded the 1st account with test-net coins using friend-bot and left the 2nd account empty. Now as I understand that an account to be active on stellar network, it should have a minimum balance of about 1XLM. So using the transaction builder, I tried to perform a Payment Operation by trying to transfer 2XLM to the 2nd account. However I recieved the following response :
{
"type": "https://stellar.org/horizon-errors/transaction_failed",
"title": "Transaction Failed",
"status": 400,
"detail": "The transaction failed when submitted to the stellar network. The `extras.result_codes` field on this response contains further details. Descriptions of each code can be found at: https://www.stellar.org/developers/learn/concepts/list-of-operations.html",
"extras": {
"envelope_xdr": "AAAAAKNyr+6/r2REKzMV3sOL4jztg1HSdqlQhmthUU41BjPdAAAAZAAEmkQAAAADAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAmWhqbEZTUrZWFtvR1HU7VUW0pp3BwN4E9h4iQwvMr9kAAAAAAAAAAAExLQAAAAAAAAAAATUGM90AAABAHvtdpnjhq3usHFphQ/4naDHbKVhu+QqD8UFSavo/qlGo7Yiz/dLI3lQ0fmfa37uvwXWsYAn8mObDkrTjofc3Aw==",
"result_codes": {
"transaction": "tx_failed",
"operations": [
"op_no_destination"
]
},
"result_xdr": "AAAAAAAAAGT/////AAAAAQAAAAAAAAAB////+wAAAAA="
}
}
So can someone tell me which operation I need to use to send XLM to an un-initialised address so I can activate it, not by using friendbot.
First, you need to execute Create Account from the Transaction Builder.
Only then you can transfer funds to this address.
I think it's because the Stellar laboratory is not set up for this exact use case. It is more for getting a general feel of the basics. In order to create an account this way using an SDK and communicating with horizon you would have to both create the account and fund it in a single transaction, and therefore you would have to input the source account's secret key.
In the Stellar lab's account creation tab there is no way to input a source address its secret key (or at least I didn't see one).
So in your example, your first account is created and funded by the testbot. However, when you create the second account and try to send a payment to it from the first account, the reason it fails is because the second account is not yet a valid account as it has not been funded yet. Kind of a chicken and egg problem.
The good news is you can definitely do this using the SDK, but I haven't found a way to do it using the lab.
This from stellar.org about building transactions:
https://www.stellar.org/developers/js-stellar-base/reference/building-transactions.html
TransactionBuilder
The TransactionBuilder class is used to construct
new transactions. TransactionBuilder is given an account that is used
as transaction’s “source account”. The transaction will use the
current sequence number of the given Account object as its sequence
number and increments the given account’s sequence number when build()
is called on the TransactionBuilder.
Operations can be added to the transaction calling
addOperation(operation) for each operation you wish to add to the
transaction. See operation.js for a list of possible operations you
can add. addOperation(operation) returns the current
TransactionBuilder object so you can chain multiple calls.
After adding the desired operations, call the build() method on the
TransactionBuilder. This will return a fully constructed Transaction.
The returned transaction will contain the sequence number of the
source account. This transaction is unsigned. You must sign it before
it will be accepted by the Stellar network.
# This is the relevant code
StellarSdk.Network.useTestNetwork();
// StellarBase.Network.usePublicNetwork(); if this transaction is for the public network
// Create an Account object from an address and sequence number.
var account=new StellarBase.Account("GD6WU64OEP5C4LRBH6NK3MHYIA2ADN6K6II6EXPNVUR3ERBXT4AN4ACD","2319149195853854");
var transaction = new StellarBase.TransactionBuilder(account, {
fee: StellarBase.BASE_FEE
})
// add a payment operation to the transaction
.addOperation(StellarBase.Operation.payment({
destination: "GASOCNHNNLYFNMDJYQ3XFMI7BYHIOCFW3GJEOWRPEGK2TDPGTG2E5EDW",
asset: StellarBase.Asset.native(),
amount: "100.50" // 100.50 XLM
}))
// add a set options operation to the transaction
.addOperation(StellarBase.Operation.setOptions({
signer: {
ed25519PublicKey: secondAccountAddress,
weight: 1
}
}))
// mark this transaction as valid only for the next 30 seconds
.setTimeout(30)
.build();
# Note that it is adding different operations to a single transaction.

itemSummary.getItemData() returns null occasionally

I'm using the java API version 10.2. Here's how I obtain the ItemSummary:
DataExtent dataExtent = new DataExtent();
dataExtent.setStartLevel(0);
dataExtent.setEndLevel(Integer.MAX_VALUE);
ItemSummary itemSummary =
Util.getDataService().getItemSummaryForItem1(myContext,
new Long(myItemId), dataExtent);
itemSummary is returned with a 0 status and the correct containerType of BANK. But itemSummary.getItemData() is sometimes null. I say sometimes because if I use the same itemId after a period of time, getItemData() is no longer null (which it should't be). There seem to be an issue if I perform a removeService, addService and getItemData() in quick succession. Any thoughts?
When you do addService yodlee has to go to the bank's website and grab the data. Hence until yodlee has the data you will not have anything in ItemData.
You can call the isItemRefreshing API which will tell you if the item added has finished refreshing and once it does, then if you get the itemSummay and if the item was successfully refreshed then itemData should not be null

I am trying to use Yodlee/executeUserSearchRequest as a RESTful request and need an answer on how to call

I am working with the Yodlee services in c# and using the RESTful api. So far I have successfully connected and logged in with my CobrandSession and UserSessionToken in the development environment. I used the sample apps provided in c# and with some advice from shreyans i got an app working. What I got working was
1) Get YodleeAuthentication
2) Get UserAuthentication
3) Get ItemSummaries
I am now trying to get the full transaction details for each of the Items (i.e. collections of accounts that are an Item)
reading the Docs here https://developer.yodlee.com/Indy_FinApp/Aggregation_Services_Guide/REST_API_Reference/executeUserSearchRequest it states that I need to call executeUserSearchRequest and then paginate through the results using the getUserTransactions. So I am stuck at this point. I dont really want a search which has parameters I just want ALL transactions for this account that I can see.
However, I am using the variables as defined in that page :-
var request = new RestRequest("/jsonsdk/TransactionSearchService/executeUserSearchRequest", Method.POST);
request.AddParameter("cobSessionToken", param.CobSessionToken);
request.AddParameter("userSessionToken", param.UserSessionToken);
request.AddParameter("transactionSearchRequest.containerType", param.ContainerType);
request.AddParameter("transactionSearchRequest.higherFetchLimit", param.HigherFetchLimit);
request.AddParameter("transactionSearchRequest.lowerFetchLimit", param.LowerFetchLimit);
request.AddParameter("transactionSearchRequest.resultRange.endNumber", param.EndNumber);
request.AddParameter("transactionSearchRequest.resultRange.startNumber", param.StartNumber);
request.AddParameter("transactionSearchRequest.searchFilter.currencyCode", param.CurrencyCode);
request.AddParameter("transactionSearchRequest.searchFilter.postDateRange.fromDate", param.FromDate);
request.AddParameter("transactionSearchRequest.searchFilter.postDateRange.toDate", param.ToDate);
request.AddParameter("transactionSearchRequest.searchFilter.transactionSplitType.splitType", param.SplitType);
request.AddParameter("transactionSearchRequest.ignoreUserInput", param.IgnoreUserInput);
request.AddParameter("transactionSearchRequest.searchFilter.itemAcctId", param.ItemAcctId);
var response = RestClientUtil.GetBase().Execute(request);
var content = response.Content;
return new YodleeServiceResultDto(content);
As per the response from shreyans in this posting Getting Error "Any one of [**] of transactionSearchFilter cannot be NULL OR Invalid Values I am not putting in the ClientId and the ClientName
The documentation doesn't specify the format of the dates but the example seems to tell me that its american date format. And specifies a parameter saying IgnoreUserinput, but doesnt have a parameter for user input so this is confusing
When I make a call using this format I get an error response
var getSearchResult = yodleeExecuteUserSearchRequest.Go(yodleeExecuteUserSearchRequestDto);
getSearchResult.Result="
{"errorOccured":"true","exceptionType":"Exception Occured","refrenceCode":"_60ecb1d7-a4c4-4914-b3cd-49182518ca5d"}"
But I get no error message in this and I have no idea what I have done wrong or where to look up this error, can somebody who has used Yodlee REST Api point me in the right direction as I need to get this researched quickly....
thanks your your help, advice, corrections and pointers....
Here is the list of parameters which you can try
1) For a specific ItemAccountId all transactions
transactionSearchRequest.containerType=all
transactionSearchRequest.higherFetchLimit=500
transactionSearchRequest.lowerFetchLimit=1
transactionSearchRequest.resultRange.startNumber=1
transactionSearchRequest.resultRange.endNumber=500
transactionSearchRequest.searchClients.clientId=1
transactionSearchRequest.searchClients.clientName=DataSearchService
transactionSearchRequest.searchFilter.currencyCode=USD
transactionSearchRequest.searchClients=DEFAULT_SERVICE_CLIENT
transactionSearchRequest.ignoreUserInput=true
transactionSearchRequest.ignoreManualTransactions=false
transactionSearchRequest.searchFilter.transactionSplitType=ALL_TRANSACTION
transactionSearchRequest.searchFilter.itemAccountId.identifier=10000353
2) For a Specific account (itemAccountId) with start and end dates
transactionSearchRequest.containerType=all
transactionSearchRequest.higherFetchLimit=500
transactionSearchRequest.lowerFetchLimit=1
transactionSearchRequest.resultRange.startNumber=1
transactionSearchRequest.resultRange.endNumber=500
transactionSearchRequest.searchClients.clientId=1
transactionSearchRequest.searchClients.clientName=DataSearchService
transactionSearchRequest.searchFilter.currencyCode=USD
transactionSearchRequest.searchClients=DEFAULT_SERVICE_CLIENT
transactionSearchRequest.ignoreUserInput=true
transactionSearchRequest.ignoreManualTransactions=false
transactionSearchRequest.searchFilter.transactionSplitType=ALL_TRANSACTION
transactionSearchRequest.searchFilter.itemAccountId.identifier=10000353
transactionSearchRequest.searchFilter.postDateRange.fromDate=08-01-2013
transactionSearchRequest.searchFilter.postDateRange.toDate=10-31-2013